@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.
100 lines (97 loc) • 4.04 kB
JavaScript
'use client';
import * as React from 'react';
import { useStore } from '@base-ui-components/utils/store';
import { FloatingFocusManager } from "../../floating-ui-react/index.js";
import { useRenderElement } from "../../utils/useRenderElement.js";
import { useComboboxFloatingContext, useComboboxRootContext, useComboboxDerivedItemsContext } from "../root/ComboboxRootContext.js";
import { selectors } from "../store.js";
import { popupStateMapping } from "../../utils/popupStateMapping.js";
import { useComboboxPositionerContext } from "../positioner/ComboboxPositionerContext.js";
import { useOpenChangeComplete } from "../../utils/useOpenChangeComplete.js";
import { transitionStatusMapping } from "../../utils/stateAttributesMapping.js";
import { contains, getTarget } from "../../floating-ui-react/utils.js";
import { getDisabledMountTransitionStyles } from "../../utils/getDisabledMountTransitionStyles.js";
import { jsx as _jsx } from "react/jsx-runtime";
const stateAttributesMapping = {
...popupStateMapping,
...transitionStatusMapping
};
/**
* A container for the list.
* Renders a `<div>` element.
*/
export const ComboboxPopup = /*#__PURE__*/React.forwardRef(function ComboboxPopup(componentProps, forwardedRef) {
const {
render,
className,
initialFocus,
finalFocus,
...elementProps
} = componentProps;
const store = useComboboxRootContext();
const positioning = useComboboxPositionerContext();
const floatingRootContext = useComboboxFloatingContext();
const {
filteredItems
} = useComboboxDerivedItemsContext();
const mounted = useStore(store, selectors.mounted);
const open = useStore(store, selectors.open);
const openMethod = useStore(store, selectors.openMethod);
const transitionStatus = useStore(store, selectors.transitionStatus);
const inputInsidePopup = useStore(store, selectors.inputInsidePopup);
const inputElement = useStore(store, selectors.inputElement);
const empty = filteredItems.length === 0;
useOpenChangeComplete({
open,
ref: store.state.popupRef,
onComplete() {
if (open) {
store.state.onOpenChangeComplete(true);
}
}
});
const state = React.useMemo(() => ({
open,
side: positioning.side,
align: positioning.align,
anchorHidden: positioning.anchorHidden,
transitionStatus,
empty
}), [open, positioning.side, positioning.align, positioning.anchorHidden, transitionStatus, empty]);
const element = useRenderElement('div', componentProps, {
state,
ref: [forwardedRef, store.state.popupRef],
props: [{
role: inputInsidePopup ? 'dialog' : 'presentation',
tabIndex: -1,
onFocus(event) {
const target = getTarget(event.nativeEvent);
if (openMethod !== 'touch' && (contains(store.state.listElement, target) || target === event.currentTarget)) {
store.state.inputRef.current?.focus();
}
}
}, getDisabledMountTransitionStyles(transitionStatus), elementProps],
stateAttributesMapping
});
// Default initial focus logic:
// If opened by touch, focus the popup element to prevent the virtual keyboard from opening
// (this is required for Android specifically as iOS handles this automatically).
const computedDefaultInitialFocus = inputInsidePopup ? interactionType => interactionType === 'touch' ? store.state.popupRef.current : inputElement : false;
const resolvedInitialFocus = initialFocus === undefined ? computedDefaultInitialFocus : initialFocus;
let resolvedFinalFocus;
if (finalFocus != null) {
resolvedFinalFocus = finalFocus;
} else {
resolvedFinalFocus = inputInsidePopup ? undefined : false;
}
return /*#__PURE__*/_jsx(FloatingFocusManager, {
context: floatingRootContext,
disabled: !mounted,
modal: !inputInsidePopup,
openInteractionType: openMethod,
initialFocus: resolvedInitialFocus,
returnFocus: resolvedFinalFocus,
children: element
});
});
if (process.env.NODE_ENV !== "production") ComboboxPopup.displayName = "ComboboxPopup";