UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

149 lines (143 loc) 4.3 kB
import { useRef, useReducer, useCallback, useMemo, useDeferredValue } from 'react'; import { AutocompleteContext, AutocompleteInputContext, AutocompleteDeferredInputContext } from './AutocompleteContext.js'; import AutocompleteInput from './AutocompleteInput.js'; import AutocompleteMenu from './AutocompleteMenu.js'; import AutocompleteOverlay from './AutocompleteOverlay.js'; import { useId } from '../hooks/useId.js'; import { jsx } from 'react/jsx-runtime'; const initialState = { inputValue: '', showMenu: false, isMenuDirectlyActivated: false, autocompleteSuggestion: '', selectedItemLength: 0 }; const reducer = (state, action) => { const { type, payload } = action; switch (type) { case 'inputValue': return { ...state, inputValue: payload }; case 'showMenu': return { ...state, showMenu: payload }; case 'isMenuDirectlyActivated': return { ...state, isMenuDirectlyActivated: payload }; case 'autocompleteSuggestion': return { ...state, autocompleteSuggestion: payload }; case 'selectedItemLength': return { ...state, selectedItemLength: payload }; default: return state; } }; const Autocomplete = ({ children, id: idProp }) => { const activeDescendantRef = useRef(null); const scrollContainerRef = useRef(null); const inputRef = useRef(null); const [state, dispatch] = useReducer(reducer, initialState); const { inputValue, showMenu, autocompleteSuggestion, isMenuDirectlyActivated, selectedItemLength } = state; const setInputValue = useCallback(value => { dispatch({ type: 'inputValue', payload: value }); }, []); const setShowMenu = useCallback(value => { dispatch({ type: 'showMenu', payload: value }); }, []); const setAutocompleteSuggestion = useCallback(value => { dispatch({ type: 'autocompleteSuggestion', payload: value }); }, []); const setIsMenuDirectlyActivated = useCallback(value => { dispatch({ type: 'isMenuDirectlyActivated', payload: value }); }, []); const setSelectedItemLength = useCallback(value => { dispatch({ type: 'selectedItemLength', payload: value }); }, []); const id = useId(idProp); // Base context: refs, IDs, menu visibility, and callbacks // Changes when menu opens/closes or selection changes, but NOT on every keystroke const autocompleteContextValue = useMemo(() => ({ activeDescendantRef, id, inputRef, scrollContainerRef, selectedItemLength, setAutocompleteSuggestion, setInputValue, setIsMenuDirectlyActivated, setShowMenu, setSelectedItemLength, showMenu }), [id, selectedItemLength, setAutocompleteSuggestion, setInputValue, setIsMenuDirectlyActivated, setShowMenu, setSelectedItemLength, showMenu]); // Input state context: values that change on every keystroke // Split to prevent Overlay from re-rendering during typing const autocompleteInputContextValue = useMemo(() => ({ autocompleteSuggestion, inputValue, isMenuDirectlyActivated }), [autocompleteSuggestion, inputValue, isMenuDirectlyActivated]); // Deferred input value for expensive operations like filtering // Menu subscribes to this instead of inputValue to avoid re-rendering on every keystroke const deferredInputValue = useDeferredValue(inputValue); const autocompleteDeferredInputContextValue = useMemo(() => ({ deferredInputValue }), [deferredInputValue]); return /*#__PURE__*/jsx(AutocompleteContext.Provider, { value: autocompleteContextValue, children: /*#__PURE__*/jsx(AutocompleteInputContext.Provider, { value: autocompleteInputContextValue, children: /*#__PURE__*/jsx(AutocompleteDeferredInputContext.Provider, { value: autocompleteDeferredInputContextValue, children: children }) }) }); }; Autocomplete.displayName = "Autocomplete"; var Autocomplete$1 = Object.assign(Autocomplete, { __SLOT__: Symbol('Autocomplete'), Context: AutocompleteContext, Input: AutocompleteInput, Menu: AutocompleteMenu, Overlay: AutocompleteOverlay }); export { Autocomplete$1 as default };