UNPKG

@base-ui-components/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

105 lines (100 loc) 3.56 kB
'use client'; import * as React from 'react'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { AriaCombobox } from "../../combobox/root/AriaCombobox.js"; import { useCoreFilter } from "../../combobox/root/utils/useFilter.js"; import { stringifyAsLabel } from "../../utils/resolveValueLabel.js"; import { REASONS } from "../../utils/reasons.js"; /** * Groups all parts of the autocomplete. * Doesn't render its own HTML element. * * Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete) */ import { jsx as _jsx } from "react/jsx-runtime"; export function AutocompleteRoot(props) { const { openOnInputClick = false, value, defaultValue, onValueChange, mode = 'list', itemToStringValue, ...other } = props; const enableInline = mode === 'inline' || mode === 'both'; const staticItems = mode === 'inline' || mode === 'none'; // Mirror the typed value for uncontrolled usage so we can compose the temporary // inline input value. const isControlled = value !== undefined; const [internalValue, setInternalValue] = React.useState(defaultValue ?? ''); const [inlineInputValue, setInlineInputValue] = React.useState(''); React.useEffect(() => { if (isControlled) { setInlineInputValue(''); } }, [value, isControlled]); // Compose the input value shown to the user: inline value takes precedence when present. let resolvedInputValue; if (enableInline && inlineInputValue !== '') { resolvedInputValue = inlineInputValue; } else if (isControlled) { resolvedInputValue = value ?? ''; } else { resolvedInputValue = internalValue; } const handleValueChange = useStableCallback((nextValue, eventDetails) => { setInlineInputValue(''); if (!isControlled) { setInternalValue(nextValue); } onValueChange?.(nextValue, eventDetails); }); const collator = useCoreFilter(); const baseFilter = React.useMemo(() => { if (other.filter) { return other.filter; } return (item, query, toString) => { return collator.contains(stringifyAsLabel(item, toString), query); }; }, [other, collator]); const resolvedQuery = String(isControlled ? value : internalValue).trim(); // In "both", wrap filtering to use only the typed value, ignoring the inline value. const resolvedFilter = React.useMemo(() => { if (mode !== 'both') { return staticItems ? null : other.filter; } return (item, _query, toString) => { return baseFilter(item, resolvedQuery, toString); }; }, [baseFilter, mode, other.filter, resolvedQuery, staticItems]); const handleItemHighlighted = useStableCallback((highlightedValue, eventDetails) => { props.onItemHighlighted?.(highlightedValue, eventDetails); if (eventDetails.reason === REASONS.pointer) { return; } if (enableInline) { if (highlightedValue == null) { setInlineInputValue(''); } else { setInlineInputValue(stringifyAsLabel(highlightedValue, itemToStringValue)); } } else { setInlineInputValue(''); } }); return /*#__PURE__*/_jsx(AriaCombobox, { ...other, itemToStringLabel: itemToStringValue, openOnInputClick: openOnInputClick, selectionMode: "none", fillInputOnItemPress: true, filter: resolvedFilter, autoComplete: mode, inputValue: resolvedInputValue, defaultInputValue: defaultValue, onInputValueChange: handleValueChange, onItemHighlighted: handleItemHighlighted }); }