UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

146 lines (138 loc) 5.37 kB
import React, { useContext, useState, useCallback, useEffect } from 'react'; import { AutocompleteContext } from './AutocompleteContext.js'; import { useRefObjectAsForwardedRef } from '../hooks/useRefObjectAsForwardedRef.js'; import useSafeTimeout from '../hooks/useSafeTimeout.js'; import { jsx } from 'react/jsx-runtime'; import TextInput from '../TextInput/TextInput.js'; const ARROW_KEYS_NAV = new Set(['ArrowUp', 'ArrowDown']); const AutocompleteInput = /*#__PURE__*/React.forwardRef(({ as: Component = TextInput, onFocus, onBlur, onChange, onKeyDown, onKeyUp, onKeyPress, value, openOnFocus = false, ...props }, forwardedRef) => { const autocompleteContext = useContext(AutocompleteContext); if (autocompleteContext === null) { throw new Error('AutocompleteContext returned null values'); } const { activeDescendantRef, autocompleteSuggestion = '', id, inputRef, inputValue = '', isMenuDirectlyActivated, setInputValue, setShowMenu, showMenu } = autocompleteContext; useRefObjectAsForwardedRef(forwardedRef, inputRef); const [highlightRemainingText, setHighlightRemainingText] = useState(true); const { safeSetTimeout } = useSafeTimeout(); const handleInputFocus = event => { onFocus === null || onFocus === void 0 ? void 0 : onFocus(event); if (openOnFocus) { setShowMenu(true); } }; const handleInputBlur = useCallback(event => { onBlur && onBlur(event); // HACK: wait a tick and check the focused element before hiding the autocomplete menu // this prevents the menu from hiding when the user is clicking an option in the Autoselect.Menu, // but still hides the menu when the user blurs the input by tabbing out or clicking somewhere else on the page safeSetTimeout(() => { if (document.activeElement !== inputRef.current) { setShowMenu(false); } }, 0); }, [onBlur, setShowMenu, inputRef, safeSetTimeout]); const handleInputChange = event => { onChange && onChange(event); setInputValue(event.currentTarget.value); if (!showMenu) { setShowMenu(true); } }; const handleInputKeyDown = useCallback(event => { var _inputRef$current; onKeyDown && onKeyDown(event); if (event.key === 'Backspace') { setHighlightRemainingText(false); } if (event.key === 'Escape' && (_inputRef$current = inputRef.current) !== null && _inputRef$current !== void 0 && _inputRef$current.value) { setInputValue(''); inputRef.current.value = ''; } if (!showMenu && ARROW_KEYS_NAV.has(event.key) && !event.altKey) { setShowMenu(true); } }, [inputRef, setInputValue, setHighlightRemainingText, onKeyDown, showMenu, setShowMenu]); const handleInputKeyUp = useCallback(event => { onKeyUp && onKeyUp(event); if (event.key === 'Backspace') { setHighlightRemainingText(true); } }, [setHighlightRemainingText, onKeyUp]); const onInputKeyPress = useCallback(event => { onKeyPress && onKeyPress(event); if (showMenu && event.key === 'Enter' && activeDescendantRef.current) { event.preventDefault(); event.nativeEvent.stopImmediatePropagation(); // Forward Enter key press to active descendant so that item gets activated const activeDescendantEvent = new KeyboardEvent(event.type, event.nativeEvent); activeDescendantRef.current.dispatchEvent(activeDescendantEvent); } }, [activeDescendantRef, showMenu, onKeyPress]); useEffect(() => { if (!inputRef.current) { return; } // resets input value to being empty after a selection has been made if (!autocompleteSuggestion) { inputRef.current.value = inputValue; } // TODO: fix bug where this function prevents `onChange` from being triggered if the highlighted item text // is the same as what I'm typing // e.g.: typing 'tw' highlights 'two', but when I 'two', the text input change does not get triggered if (highlightRemainingText && autocompleteSuggestion && (inputValue || isMenuDirectlyActivated)) { inputRef.current.value = autocompleteSuggestion; if (autocompleteSuggestion.toLowerCase().indexOf(inputValue.toLowerCase()) === 0) { inputRef.current.setSelectionRange(inputValue.length, autocompleteSuggestion.length); } } // calling this useEffect when `highlightRemainingText` changes breaks backspace functionality // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps }, [autocompleteSuggestion, inputValue, inputRef, isMenuDirectlyActivated]); useEffect(() => { setInputValue(typeof value !== 'undefined' ? value.toString() : ''); }, [value, setInputValue]); return /*#__PURE__*/jsx(Component, { onFocus: handleInputFocus, onBlur: handleInputBlur, onChange: handleInputChange, onKeyDown: handleInputKeyDown, onKeyPress: onInputKeyPress, onKeyUp: handleInputKeyUp, ref: inputRef, "aria-controls": `${id}-listbox`, "aria-autocomplete": "both", role: "combobox", "aria-expanded": showMenu, "aria-haspopup": "listbox", "aria-owns": `${id}-listbox`, autoComplete: "off", id: id, ...props }); }); AutocompleteInput.displayName = 'AutocompleteInput'; export { AutocompleteInput as default };