import { Loader } from './common/Loader'; import { InputButtons } from './common/InputButtons'; import { Button, Input, Section, Stack } from '../components'; import { useBackend, useLocalState } from '../backend'; import { KEY_A, KEY_DOWN, KEY_ESCAPE, KEY_ENTER, KEY_UP, KEY_Z } from '../../common/keycodes'; import { Window } from '../layouts'; type ListInputData = { init_value: string; items: string[]; large_buttons: boolean; message: string; timeout: number; title: string; }; export const ListInputModal = (props, context) => { const { act, data } = useBackend(context); const { items = [], message = '', init_value, large_buttons, timeout, title } = data; const [selected, setSelected] = useLocalState(context, 'selected', items.indexOf(init_value)); const [searchBarVisible, setSearchBarVisible] = useLocalState(context, 'searchBarVisible', items.length > 9); const [searchQuery, setSearchQuery] = useLocalState(context, 'searchQuery', ''); // User presses up or down on keyboard // Simulates clicking an item const onArrowKey = (key: number) => { const len = filteredItems.length - 1; if (key === KEY_DOWN) { if (selected === null || selected === len) { setSelected(0); document!.getElementById('0')?.scrollIntoView(); } else { setSelected(selected + 1); document!.getElementById((selected + 1).toString())?.scrollIntoView(); } } else if (key === KEY_UP) { if (selected === null || selected === 0) { setSelected(len); document!.getElementById(len.toString())?.scrollIntoView(); } else { setSelected(selected - 1); document!.getElementById((selected - 1).toString())?.scrollIntoView(); } } }; // User selects an item with mouse const onClick = (index: number) => { if (index === selected) { return; } setSelected(index); }; // User presses a letter key and searchbar is visible const onFocusSearch = () => { setSearchBarVisible(false); setSearchBarVisible(true); }; // User presses a letter key with no searchbar visible const onLetterSearch = (key: number) => { const keyChar = String.fromCharCode(key); const foundItem = items.find((item) => { return item?.toLowerCase().startsWith(keyChar?.toLowerCase()); }); if (foundItem) { const foundIndex = items.indexOf(foundItem); setSelected(foundIndex); document!.getElementById(foundIndex.toString())?.scrollIntoView(); } }; // User types into search bar const onSearch = (query: string) => { if (query === searchQuery) { return; } setSearchQuery(query); setSelected(0); document!.getElementById('0')?.scrollIntoView(); }; // User presses the search button const onSearchBarToggle = () => { setSearchBarVisible(!searchBarVisible); setSearchQuery(''); }; const filteredItems = items.filter((item) => item?.toLowerCase().includes(searchQuery.toLowerCase())); // Dynamically changes the window height based on the message. const windowHeight = 325 + Math.ceil(message.length / 3) + (large_buttons ? 5 : 0); // Grabs the cursor when no search bar is visible. if (!searchBarVisible) { setTimeout(() => document!.getElementById(selected.toString())?.focus(), 1); } return ( {timeout && } { const keyCode = window.event ? event.which : event.keyCode; if (keyCode === KEY_DOWN || keyCode === KEY_UP) { event.preventDefault(); onArrowKey(keyCode); } if (keyCode === KEY_ENTER) { event.preventDefault(); act('submit', { entry: filteredItems[selected] }); } if (!searchBarVisible && keyCode >= KEY_A && keyCode <= KEY_Z) { event.preventDefault(); onLetterSearch(keyCode); } if (keyCode === KEY_ESCAPE) { event.preventDefault(); act('cancel'); } }}>
onSearchBarToggle()} /> } className="ListInput__Section" fill title={message}> {searchBarVisible && ( )}
); }; /** * Displays the list of selectable items. * If a search query is provided, filters the items. */ const ListDisplay = (props, context) => { const { act } = useBackend(context); const { filteredItems, onClick, onFocusSearch, searchBarVisible, selected } = props; return (
{filteredItems.map((item, index) => { return ( ); })}
); }; /** * Renders a search bar input. * Closing the bar defaults input to an empty string. */ const SearchBar = (props, context) => { const { act } = useBackend(context); const { filteredItems, onSearch, searchQuery, selected } = props; return ( { event.preventDefault(); act('submit', { entry: filteredItems[selected] }); }} onInput={(_, value) => onSearch(value)} placeholder="Search..." value={searchQuery} /> ); };