@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.
215 lines (213 loc) • 7.86 kB
JavaScript
;
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SelectItem = void 0;
var React = _interopRequireWildcard(require("react"));
var _useIsoLayoutEffect = require("@base-ui-components/utils/useIsoLayoutEffect");
var _useValueAsRef = require("@base-ui-components/utils/useValueAsRef");
var _isMouseWithinBounds = require("@base-ui-components/utils/isMouseWithinBounds");
var _useTimeout = require("@base-ui-components/utils/useTimeout");
var _store = require("@base-ui-components/utils/store");
var _SelectRootContext = require("../root/SelectRootContext");
var _useCompositeListItem = require("../../composite/list/useCompositeListItem");
var _useRenderElement = require("../../utils/useRenderElement");
var _SelectItemContext = require("./SelectItemContext");
var _store2 = require("../store");
var _useButton = require("../../use-button");
var _createBaseUIEventDetails = require("../../utils/createBaseUIEventDetails");
var _reasons = require("../../utils/reasons");
var _itemEquality = require("../../utils/itemEquality");
var _jsxRuntime = require("react/jsx-runtime");
/**
* An individual option in the select popup.
* Renders a `<div>` element.
*
* Documentation: [Base UI Select](https://base-ui.com/react/components/select)
*/
const SelectItem = exports.SelectItem = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(function SelectItem(componentProps, forwardedRef) {
const {
render,
className,
value = null,
label,
disabled = false,
nativeButton = false,
...elementProps
} = componentProps;
const textRef = React.useRef(null);
const listItem = (0, _useCompositeListItem.useCompositeListItem)({
label,
textRef,
indexGuessBehavior: _useCompositeListItem.IndexGuessBehavior.GuessFromOrder
});
const {
store,
getItemProps,
setOpen,
setValue,
selectionRef,
typingRef,
valuesRef,
keyboardActiveRef,
multiple
} = (0, _SelectRootContext.useSelectRootContext)();
const highlightTimeout = (0, _useTimeout.useTimeout)();
const highlighted = (0, _store.useStore)(store, _store2.selectors.isActive, listItem.index);
const selected = (0, _store.useStore)(store, _store2.selectors.isSelected, listItem.index, value);
const selectedByFocus = (0, _store.useStore)(store, _store2.selectors.isSelectedByFocus, listItem.index);
const isItemEqualToValue = (0, _store.useStore)(store, _store2.selectors.isItemEqualToValue);
const index = listItem.index;
const hasRegistered = index !== -1;
const itemRef = React.useRef(null);
const indexRef = (0, _useValueAsRef.useValueAsRef)(index);
(0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => {
if (!hasRegistered) {
return undefined;
}
const values = valuesRef.current;
values[index] = value;
return () => {
delete values[index];
};
}, [hasRegistered, index, value, valuesRef]);
(0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => {
if (!hasRegistered) {
return undefined;
}
const selectedValue = store.state.value;
let candidate = selectedValue;
if (multiple && Array.isArray(selectedValue) && selectedValue.length > 0) {
candidate = selectedValue[selectedValue.length - 1];
}
if (candidate !== undefined && (0, _itemEquality.compareItemEquality)(candidate, value, isItemEqualToValue)) {
store.set('selectedIndex', index);
}
return undefined;
}, [hasRegistered, index, multiple, isItemEqualToValue, store, value]);
const state = React.useMemo(() => ({
disabled,
selected,
highlighted
}), [disabled, selected, highlighted]);
const rootProps = getItemProps({
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.
rootProps.onFocus = undefined;
rootProps.id = undefined;
const lastKeyRef = React.useRef(null);
const pointerTypeRef = React.useRef('mouse');
const didPointerDownRef = React.useRef(false);
const {
getButtonProps,
buttonRef
} = (0, _useButton.useButton)({
disabled,
focusableWhenDisabled: true,
native: nativeButton
});
function commitSelection(event) {
const selectedValue = store.state.value;
if (multiple) {
const currentValue = Array.isArray(selectedValue) ? selectedValue : [];
const nextValue = selected ? (0, _itemEquality.removeItem)(currentValue, value, isItemEqualToValue) : [...currentValue, value];
setValue(nextValue, (0, _createBaseUIEventDetails.createChangeEventDetails)(_reasons.REASONS.itemPress, event));
} else {
setValue(value, (0, _createBaseUIEventDetails.createChangeEventDetails)(_reasons.REASONS.itemPress, event));
setOpen(false, (0, _createBaseUIEventDetails.createChangeEventDetails)(_reasons.REASONS.itemPress, event));
}
}
const defaultProps = {
role: 'option',
'aria-selected': selected,
'aria-disabled': disabled || undefined,
tabIndex: highlighted ? 0 : -1,
onFocus() {
store.set('activeIndex', index);
},
onMouseEnter() {
if (!keyboardActiveRef.current && store.state.selectedIndex === null) {
store.set('activeIndex', index);
}
},
onMouseMove() {
store.set('activeIndex', index);
},
onMouseLeave(event) {
if (keyboardActiveRef.current || (0, _isMouseWithinBounds.isMouseWithinBounds)(event)) {
return;
}
highlightTimeout.start(0, () => {
if (store.state.activeIndex === index) {
store.set('activeIndex', null);
}
});
},
onTouchStart() {
selectionRef.current = {
allowSelectedMouseUp: false,
allowUnselectedMouseUp: false
};
},
onKeyDown(event) {
lastKeyRef.current = event.key;
store.set('activeIndex', index);
},
onClick(event) {
didPointerDownRef.current = false;
// Prevent double commit on {Enter}
if (event.type === 'keydown' && lastKeyRef.current === null) {
return;
}
if (disabled || lastKeyRef.current === ' ' && typingRef.current || pointerTypeRef.current !== 'touch' && !highlighted) {
return;
}
lastKeyRef.current = null;
commitSelection(event.nativeEvent);
},
onPointerEnter(event) {
pointerTypeRef.current = event.pointerType;
},
onPointerDown(event) {
pointerTypeRef.current = event.pointerType;
didPointerDownRef.current = true;
},
onMouseUp(event) {
if (disabled) {
return;
}
if (didPointerDownRef.current) {
didPointerDownRef.current = false;
return;
}
const disallowSelectedMouseUp = !selectionRef.current.allowSelectedMouseUp && selected;
const disallowUnselectedMouseUp = !selectionRef.current.allowUnselectedMouseUp && !selected;
if (disallowSelectedMouseUp || disallowUnselectedMouseUp || pointerTypeRef.current !== 'touch' && !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,
indexRef,
textRef,
selectedByFocus,
hasRegistered
}), [selected, indexRef, textRef, selectedByFocus, hasRegistered]);
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_SelectItemContext.SelectItemContext.Provider, {
value: contextValue,
children: element
});
}));
if (process.env.NODE_ENV !== "production") SelectItem.displayName = "SelectItem";