import { ChangeEventHandler, FocusEventHandler, FormEventHandler, KeyboardEventHandler, useCallback, useEffect, useRef, useState, } from 'react' import { Disclosure, DisclosurePanel } from '@reach/disclosure' import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur' import { observer } from 'mobx-react-lite' import { classNames } from '@standardnotes/utils' import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants' import LinkedItemSearchResults from './LinkedItemSearchResults' import { LinkingController } from '@/Controllers/LinkingController' import { KeyboardKey } from '@standardnotes/ui-services' import { ElementIds } from '@/Constants/ElementIDs' import Menu from '../Menu/Menu' import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults' import { useApplication } from '../ApplicationProvider' type Props = { linkingController: LinkingController focusPreviousItem: () => void focusedId: string | undefined setFocusedId: (id: string) => void hoverLabel?: string } const ItemLinkAutocompleteInput = ({ linkingController, focusPreviousItem, focusedId, setFocusedId, hoverLabel, }: Props) => { const application = useApplication() const { tags, linkItemToSelectedItem, createAndAddNewTag, isEntitledToNoteLinking, activeItem } = linkingController const [searchQuery, setSearchQuery] = useState('') const { unlinkedItems, shouldShowCreateTag } = getLinkingSearchResults(searchQuery, application, activeItem) const [dropdownVisible, setDropdownVisible] = useState(false) const [dropdownMaxHeight, setDropdownMaxHeight] = useState('auto') const containerRef = useRef(null) const inputRef = useRef(null) const searchResultsMenuRef = useRef(null) const [closeOnBlur] = useCloseOnBlur(containerRef, (visible: boolean) => { setDropdownVisible(visible) setSearchQuery('') }) const showDropdown = () => { const { clientHeight } = document.documentElement const inputRect = inputRef.current?.getBoundingClientRect() if (inputRect) { setDropdownMaxHeight(clientHeight - inputRect.bottom - 32 * 2) setDropdownVisible(true) } } const onSearchQueryChange: ChangeEventHandler = (event) => { setSearchQuery(event.currentTarget.value) } const onFormSubmit: FormEventHandler = async (event) => { event.preventDefault() if (searchQuery !== '') { await createAndAddNewTag(searchQuery) } } const handleFocus = () => { if (focusedId !== ElementIds.ItemLinkAutocompleteInput) { setFocusedId(ElementIds.ItemLinkAutocompleteInput) } showDropdown() } const onBlur: FocusEventHandler = (event) => { closeOnBlur(event) } const onKeyDown: KeyboardEventHandler = (event) => { switch (event.key) { case KeyboardKey.Left: if (searchQuery.length === 0) { focusPreviousItem() } break case KeyboardKey.Down: if (searchQuery.length > 0) { event.preventDefault() searchResultsMenuRef.current?.focus() } break } } useEffect(() => { if (focusedId === ElementIds.ItemLinkAutocompleteInput) { inputRef.current?.focus() } }, [focusedId]) const areSearchResultsVisible = dropdownVisible && (unlinkedItems.length > 0 || shouldShowCreateTag) const handleMenuKeyDown: KeyboardEventHandler = useCallback((event) => { if (event.key === KeyboardKey.Escape) { inputRef.current?.focus() } }, []) return (
0 ? 'w-80' : 'mr-10 w-70'}`, 'bg-transparent text-sm text-text focus:border-b-2 focus:border-solid focus:border-info lg:text-xs', 'no-border h-7 focus:shadow-none focus:outline-none', )} value={searchQuery} onChange={onSearchQueryChange} type="text" placeholder="Link tags, notes, files..." onBlur={onBlur} onFocus={handleFocus} onKeyDown={onKeyDown} id={ElementIds.ItemLinkAutocompleteInput} autoComplete="off" title={hoverLabel} aria-label={hoverLabel} /> {areSearchResultsVisible && ( 0 ? 'w-80' : 'mr-10 w-70', 'absolute z-dropdown-menu flex flex-col overflow-y-auto rounded bg-default py-2 shadow-main', )} style={{ maxHeight: dropdownMaxHeight, }} onBlur={closeOnBlur} tabIndex={FOCUSABLE_BUT_NOT_TABBABLE} > setSearchQuery('')} isEntitledToNoteLinking={isEntitledToNoteLinking} /> )}
) } export default observer(ItemLinkAutocompleteInput)