@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.
277 lines (275 loc) • 8.09 kB
JavaScript
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
import { useSelectRootContext } from '../root/SelectRootContext.js';
import { useSelectIndexContext } from '../root/SelectIndexContext.js';
import { useCompositeListItem } from '../../composite/list/useCompositeListItem.js';
import { useForkRef } from '../../utils/useForkRef.js';
import { useComponentRenderer } from '../../utils/useComponentRenderer.js';
import { useSelectItem } from './useSelectItem.js';
import { useEnhancedEffect } from '../../utils/useEnhancedEffect.js';
import { useLatestRef } from '../../utils/useLatestRef.js';
import { SelectItemContext } from './SelectItemContext.js';
import { jsx as _jsx } from "react/jsx-runtime";
const InnerSelectItem = /*#__PURE__*/React.forwardRef(function InnerSelectItem(props, forwardedRef) {
const {
className,
disabled = false,
highlighted,
selected,
getRootItemProps,
render,
setOpen,
typingRef,
selectionRef,
open,
value,
setValue,
selectedIndexRef,
indexRef,
setActiveIndex,
popupRef,
...otherProps
} = props;
const state = React.useMemo(() => ({
disabled,
open,
highlighted,
selected
}), [disabled, open, highlighted, selected]);
const {
getItemProps,
rootRef
} = useSelectItem({
open,
setOpen,
disabled,
highlighted,
selected,
ref: forwardedRef,
typingRef,
handleSelect: () => setValue(value),
selectionRef,
selectedIndexRef,
indexRef,
setActiveIndex,
popupRef
});
const mergedRef = useForkRef(rootRef, forwardedRef);
const {
renderElement
} = useComponentRenderer({
propGetter(externalProps = {}) {
const rootProps = getRootItemProps({
...externalProps,
active: highlighted,
selected
});
// With our custom `focusItemOnHover` implementation, this interferes with the logic and can
// cause the index state to be stuck when leaving the select popup.
delete rootProps.onFocus;
return getItemProps(rootProps);
},
render: render ?? 'div',
ref: mergedRef,
className,
state,
extraProps: otherProps
});
const contextValue = React.useMemo(() => ({
selected,
indexRef
}), [selected, indexRef]);
return /*#__PURE__*/_jsx(SelectItemContext.Provider, {
value: contextValue,
children: renderElement()
});
});
process.env.NODE_ENV !== "production" ? InnerSelectItem.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
/**
* @ignore
*/
children: PropTypes.node,
/**
* CSS class applied to the element, or a function that
* returns a class based on the component’s state.
*/
className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/**
* Whether the component should ignore user interaction.
* @default false
*/
disabled: PropTypes.bool,
/**
* @ignore
*/
getRootItemProps: PropTypes.func.isRequired,
/**
* @ignore
*/
highlighted: PropTypes.bool.isRequired,
/**
* @ignore
*/
indexRef: PropTypes.shape({
current: PropTypes.number.isRequired
}).isRequired,
/**
* Overrides the text label to use on the trigger when this item is selected
* and when the item is matched during keyboard text navigation.
*/
label: PropTypes.string,
/**
* @ignore
*/
open: PropTypes.bool.isRequired,
/**
* @ignore
*/
popupRef: PropTypes.shape({
current: PropTypes.object
}).isRequired,
/**
* Allows you to replace the component’s HTML element
* with a different tag, or compose it with another component.
*
* Accepts a `ReactElement` or a function that returns the element to render.
*/
render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
/**
* @ignore
*/
selected: PropTypes.bool.isRequired,
/**
* @ignore
*/
selectedIndexRef: PropTypes.shape({
current: PropTypes.number
}).isRequired,
/**
* @ignore
*/
selectionRef: PropTypes.shape({
current: PropTypes.shape({
allowSelect: PropTypes.bool.isRequired,
allowSelectedMouseUp: PropTypes.bool.isRequired,
allowUnselectedMouseUp: PropTypes.bool.isRequired
}).isRequired
}).isRequired,
/**
* @ignore
*/
setActiveIndex: PropTypes.func.isRequired,
/**
* @ignore
*/
setOpen: PropTypes.func.isRequired,
/**
* @ignore
*/
setValue: PropTypes.func.isRequired,
/**
* @ignore
*/
typingRef: PropTypes.shape({
current: PropTypes.bool.isRequired
}).isRequired,
/**
* @ignore
*/
value: PropTypes.any.isRequired
} : void 0;
const MemoizedInnerSelectItem = /*#__PURE__*/React.memo(InnerSelectItem);
/**
* An individual option in the select menu.
* Renders a `<div>` element.
*
* Documentation: [Base UI Select](https://base-ui.com/react/components/select)
*/
const SelectItem = /*#__PURE__*/React.forwardRef(function SelectItem(props, forwardedRef) {
const {
value: valueProp = null,
label,
...otherProps
} = props;
const listItem = useCompositeListItem({
label
});
const {
activeIndex,
selectedIndex,
setActiveIndex
} = useSelectIndexContext();
const {
getItemProps,
setOpen,
setValue,
open,
selectionRef,
typingRef,
valuesRef,
popupRef
} = useSelectRootContext();
const selectedIndexRef = useLatestRef(selectedIndex);
const indexRef = useLatestRef(listItem.index);
const mergedRef = useForkRef(listItem.ref, forwardedRef);
useEnhancedEffect(() => {
if (listItem.index === -1) {
return undefined;
}
const values = valuesRef.current;
values[listItem.index] = valueProp;
return () => {
delete values[listItem.index];
};
}, [listItem.index, valueProp, valuesRef]);
const highlighted = activeIndex === listItem.index;
const selected = selectedIndex === listItem.index;
return /*#__PURE__*/_jsx(MemoizedInnerSelectItem, {
ref: mergedRef,
highlighted: highlighted,
selected: selected,
getRootItemProps: getItemProps,
setOpen: setOpen,
open: open,
selectionRef: selectionRef,
typingRef: typingRef,
value: valueProp,
setValue: setValue,
selectedIndexRef: selectedIndexRef,
indexRef: indexRef,
setActiveIndex: setActiveIndex,
popupRef: popupRef,
...otherProps
});
});
process.env.NODE_ENV !== "production" ? SelectItem.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
/**
* @ignore
*/
children: PropTypes.node,
/**
* Whether the component should ignore user interaction.
* @default false
*/
disabled: PropTypes.bool,
/**
* Overrides the text label to use on the trigger when this item is selected
* and when the item is matched during keyboard text navigation.
*/
label: PropTypes.string,
/**
* A unique value that identifies this select item.
* @default null
*/
value: PropTypes.any
} : void 0;
export { SelectItem };