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.

178 lines (176 loc) 6.86 kB
"use strict"; 'use client'; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.ComboboxItem = void 0; var React = _interopRequireWildcard(require("react")); var ReactDOM = _interopRequireWildcard(require("react-dom")); var _store = require("@base-ui-components/utils/store"); var _useIsoLayoutEffect = require("@base-ui-components/utils/useIsoLayoutEffect"); var _ComboboxRootContext = require("../root/ComboboxRootContext"); var _useCompositeListItem = require("../../composite/list/useCompositeListItem"); var _useRenderElement = require("../../utils/useRenderElement"); var _ComboboxItemContext = require("./ComboboxItemContext"); var _store2 = require("../store"); var _useButton = require("../../use-button"); var _ComboboxRowContext = require("../row/ComboboxRowContext"); var _itemEquality = require("../../utils/itemEquality"); var _jsxRuntime = require("react/jsx-runtime"); /** * An individual item in the list. * Renders a `<div>` element. */ const ComboboxItem = exports.ComboboxItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(function ComboboxItem(componentProps, forwardedRef) { const { render, className, value = null, index: indexProp, disabled = false, nativeButton = false, ...elementProps } = componentProps; const didPointerDownRef = React.useRef(false); const textRef = React.useRef(null); const listItem = (0, _useCompositeListItem.useCompositeListItem)({ index: indexProp, textRef, indexGuessBehavior: _useCompositeListItem.IndexGuessBehavior.GuessFromOrder }); const store = (0, _ComboboxRootContext.useComboboxRootContext)(); const isRow = (0, _ComboboxRowContext.useComboboxRowContext)(); const { flatFilteredItems } = (0, _ComboboxRootContext.useComboboxDerivedItemsContext)(); const open = (0, _store.useStore)(store, _store2.selectors.open); const selectionMode = (0, _store.useStore)(store, _store2.selectors.selectionMode); const readOnly = (0, _store.useStore)(store, _store2.selectors.readOnly); const virtualized = (0, _store.useStore)(store, _store2.selectors.virtualized); const isItemEqualToValue = (0, _store.useStore)(store, _store2.selectors.isItemEqualToValue); const selectable = selectionMode !== 'none'; const index = indexProp ?? (virtualized ? (0, _itemEquality.findItemIndex)(flatFilteredItems, value, isItemEqualToValue) : listItem.index); const hasRegistered = listItem.index !== -1; const rootId = (0, _store.useStore)(store, _store2.selectors.id); const highlighted = (0, _store.useStore)(store, _store2.selectors.isActive, index); const matchesSelectedValue = (0, _store.useStore)(store, _store2.selectors.isSelected, value); const items = (0, _store.useStore)(store, _store2.selectors.items); const getItemProps = (0, _store.useStore)(store, _store2.selectors.getItemProps); const itemRef = React.useRef(null); const id = rootId != null && hasRegistered ? `${rootId}-${index}` : undefined; const selected = matchesSelectedValue && selectable; (0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => { const shouldRun = hasRegistered && (virtualized || indexProp != null); if (!shouldRun) { return undefined; } const list = store.state.listRef.current; list[index] = itemRef.current; return () => { delete list[index]; }; }, [hasRegistered, virtualized, index, indexProp, store]); (0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => { if (!hasRegistered || items) { return undefined; } const visibleMap = store.state.valuesRef.current; visibleMap[index] = value; // Stable registry that doesn't depend on filtering. Assume that no // filtering had occurred at this point; otherwise, an `items` prop is // required. if (selectionMode !== 'none') { store.state.allValuesRef.current.push(value); } return () => { delete visibleMap[index]; }; }, [hasRegistered, items, index, value, store, selectionMode]); (0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => { if (!open) { didPointerDownRef.current = false; return; } if (!hasRegistered || items) { return; } const selectedValue = store.state.selectedValue; const lastSelectedValue = Array.isArray(selectedValue) ? selectedValue[selectedValue.length - 1] : selectedValue; if ((0, _itemEquality.compareItemEquality)(lastSelectedValue, value, isItemEqualToValue)) { store.set('selectedIndex', index); } }, [hasRegistered, items, open, store, index, value, isItemEqualToValue]); const state = React.useMemo(() => ({ disabled, selected, highlighted }), [disabled, selected, highlighted]); const rootProps = getItemProps({ active: highlighted, selected }); rootProps.id = undefined; rootProps.onFocus = undefined; const { getButtonProps, buttonRef } = (0, _useButton.useButton)({ disabled, focusableWhenDisabled: true, native: nativeButton }); function commitSelection(nativeEvent) { function selectItem() { store.state.handleSelection(nativeEvent, value); } if (store.state.submitOnItemClick) { ReactDOM.flushSync(selectItem); store.state.requestSubmit(); } else { selectItem(); } } const defaultProps = { id, role: isRow ? 'gridcell' : 'option', 'aria-disabled': disabled || undefined, 'aria-selected': selectable ? selected : undefined, // Focusable items steal focus from the input upon mouseup. // Warn if the user renders a natively focusable element like `<button>`, // as it should be a `<div>` instead. tabIndex: undefined, onPointerDownCapture(event) { didPointerDownRef.current = true; event.preventDefault(); }, onClick(event) { if (disabled || readOnly) { return; } commitSelection(event.nativeEvent); }, onMouseUp(event) { const pointerStartedOnItem = didPointerDownRef.current; didPointerDownRef.current = false; if (disabled || readOnly || event.button !== 0 || pointerStartedOnItem || !highlighted) { return; } commitSelection(event.nativeEvent); } }; const element = (0, _useRenderElement.useRenderElement)('div', componentProps, { ref: [buttonRef, forwardedRef, listItem.ref, itemRef], state, props: [rootProps, defaultProps, elementProps, getButtonProps] }); const contextValue = React.useMemo(() => ({ selected, textRef }), [selected, textRef]); return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ComboboxItemContext.ComboboxItemContext.Provider, { value: contextValue, children: element }); })); if (process.env.NODE_ENV !== "production") ComboboxItem.displayName = "ComboboxItem";