@heroui/select
Version:
A select displays a collapsible list of options and allows a user to select one of them.
525 lines (523 loc) • 19.3 kB
JavaScript
"use client";
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/use-select.ts
var use_select_exports = {};
__export(use_select_exports, {
selectData: () => selectData,
useSelect: () => useSelect
});
module.exports = __toCommonJS(use_select_exports);
var import_system = require("@heroui/system");
var import_theme = require("@heroui/theme");
var import_react_utils = require("@heroui/react-utils");
var import_react = require("react");
var import_use_aria_button = require("@heroui/use-aria-button");
var import_focus = require("@react-aria/focus");
var import_shared_utils = require("@heroui/shared-utils");
var import_utils = require("@react-aria/utils");
var import_interactions = require("@react-aria/interactions");
var import_use_aria_multiselect = require("@heroui/use-aria-multiselect");
var import_use_safe_layout_effect = require("@heroui/use-safe-layout-effect");
var import_aria_utils = require("@heroui/aria-utils");
var import_form = require("@heroui/form");
var import_overlays = require("@react-aria/overlays");
var selectData = /* @__PURE__ */ new WeakMap();
function useSelect(originalProps) {
var _a, _b, _c, _d, _e, _f;
const globalContext = (0, import_system.useProviderContext)();
const { validationBehavior: formValidationBehavior } = (0, import_form.useSlottedContext)(import_form.FormContext) || {};
const [props, variantProps] = (0, import_system.mapPropsVariants)(originalProps, import_theme.select.variantKeys);
const disableAnimation = (_b = (_a = originalProps.disableAnimation) != null ? _a : globalContext == null ? void 0 : globalContext.disableAnimation) != null ? _b : false;
const {
ref,
as,
label,
name,
isLoading,
selectorIcon,
isOpen,
defaultOpen,
onOpenChange,
startContent,
endContent,
description,
renderValue,
onSelectionChange,
placeholder,
isVirtualized,
itemHeight = 36,
maxListboxHeight = 256,
children,
disallowEmptySelection = false,
selectionMode = "single",
spinnerRef,
scrollRef: scrollRefProp,
popoverProps = {},
scrollShadowProps = {},
listboxProps = {},
spinnerProps = {},
validationState,
onChange,
onClose,
className,
classNames,
validationBehavior = (_c = formValidationBehavior != null ? formValidationBehavior : globalContext == null ? void 0 : globalContext.validationBehavior) != null ? _c : "native",
hideEmptyContent = false,
...otherProps
} = props;
const scrollShadowRef = (0, import_react_utils.useDOMRef)(scrollRefProp);
const slotsProps = {
popoverProps: (0, import_utils.mergeProps)(
{
placement: "bottom",
triggerScaleOnOpen: false,
offset: 5,
disableAnimation
},
popoverProps
),
scrollShadowProps: (0, import_utils.mergeProps)(
{
ref: scrollShadowRef,
isEnabled: (_d = originalProps.showScrollIndicators) != null ? _d : true,
hideScrollBar: true,
offset: 15
},
scrollShadowProps
),
listboxProps: (0, import_utils.mergeProps)(
{
disableAnimation
},
listboxProps
)
};
const Component = as || "button";
const shouldFilterDOMProps = typeof Component === "string";
const domRef = (0, import_react_utils.useDOMRef)(ref);
const triggerRef = (0, import_react.useRef)(null);
const listBoxRef = (0, import_react.useRef)(null);
const popoverRef = (0, import_react.useRef)(null);
let state = (0, import_use_aria_multiselect.useMultiSelectState)({
...props,
isOpen,
selectionMode,
disallowEmptySelection,
validationBehavior,
children,
isRequired: originalProps.isRequired,
isDisabled: originalProps.isDisabled,
isInvalid: originalProps.isInvalid,
defaultOpen,
hideEmptyContent,
onOpenChange: (open) => {
onOpenChange == null ? void 0 : onOpenChange(open);
if (!open) {
onClose == null ? void 0 : onClose();
}
},
onSelectionChange: (keys) => {
onSelectionChange == null ? void 0 : onSelectionChange(keys);
if (onChange && typeof onChange === "function") {
onChange({
target: {
...domRef.current && {
...domRef.current,
name: domRef.current.name
},
value: Array.from(keys).join(",")
}
});
}
state.commitValidation();
}
});
state = {
...state,
...originalProps.isDisabled && {
disabledKeys: /* @__PURE__ */ new Set([...state.collection.getKeys()])
}
};
(0, import_use_safe_layout_effect.useSafeLayoutEffect)(() => {
var _a2;
if (!((_a2 = domRef.current) == null ? void 0 : _a2.value)) return;
state.setSelectedKeys(/* @__PURE__ */ new Set([...state.selectedKeys, domRef.current.value]));
}, [domRef.current]);
const {
labelProps,
triggerProps,
valueProps,
menuProps,
descriptionProps,
errorMessageProps,
isInvalid: isAriaInvalid,
validationErrors,
validationDetails
} = (0, import_use_aria_multiselect.useMultiSelect)(
{ ...props, disallowEmptySelection, isDisabled: originalProps.isDisabled },
state,
triggerRef
);
const isInvalid = originalProps.isInvalid || validationState === "invalid" || isAriaInvalid;
const { isPressed, buttonProps } = (0, import_use_aria_button.useAriaButton)(triggerProps, triggerRef);
const { focusProps, isFocused, isFocusVisible } = (0, import_focus.useFocusRing)();
const { isHovered, hoverProps } = (0, import_interactions.useHover)({ isDisabled: originalProps.isDisabled });
const labelPlacement = (0, import_system.useLabelPlacement)({
labelPlacement: originalProps.labelPlacement,
label
});
const hasPlaceholder = !!placeholder;
const shouldLabelBeOutside = labelPlacement === "outside-left" || labelPlacement === "outside";
const shouldLabelBeInside = labelPlacement === "inside";
const isOutsideLeft = labelPlacement === "outside-left";
const isFilled = state.isOpen || hasPlaceholder || !!((_e = state.selectedItems) == null ? void 0 : _e.length) || !!startContent || !!endContent || !!originalProps.isMultiline;
const hasValue = !!((_f = state.selectedItems) == null ? void 0 : _f.length);
const hasLabel = !!label;
const hasLabelOutside = hasLabel && (isOutsideLeft || shouldLabelBeOutside && hasPlaceholder);
const baseStyles = (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.base, className);
const slots = (0, import_react.useMemo)(
() => (0, import_theme.select)({
...variantProps,
isInvalid,
labelPlacement,
disableAnimation
}),
[(0, import_shared_utils.objectToDeps)(variantProps), isInvalid, labelPlacement, disableAnimation]
);
(0, import_overlays.usePreventScroll)({
isDisabled: !state.isOpen
});
const errorMessage = typeof props.errorMessage === "function" ? props.errorMessage({ isInvalid, validationErrors, validationDetails }) : props.errorMessage || (validationErrors == null ? void 0 : validationErrors.join(" "));
const hasHelper = !!description || !!errorMessage;
(0, import_react.useEffect)(() => {
if (state.isOpen && popoverRef.current && triggerRef.current) {
let selectRect = triggerRef.current.getBoundingClientRect();
let popover = popoverRef.current;
popover.style.width = selectRect.width + "px";
}
}, [state.isOpen]);
const getBaseProps = (0, import_react.useCallback)(
(props2 = {}) => ({
"data-slot": "base",
"data-filled": (0, import_shared_utils.dataAttr)(isFilled),
"data-has-value": (0, import_shared_utils.dataAttr)(hasValue),
"data-has-label": (0, import_shared_utils.dataAttr)(hasLabel),
"data-has-helper": (0, import_shared_utils.dataAttr)(hasHelper),
"data-invalid": (0, import_shared_utils.dataAttr)(isInvalid),
"data-has-label-outside": (0, import_shared_utils.dataAttr)(hasLabelOutside),
className: slots.base({
class: (0, import_shared_utils.clsx)(baseStyles, props2.className)
}),
...props2
}),
[slots, hasHelper, hasValue, hasLabel, hasLabelOutside, isFilled, baseStyles]
);
const getTriggerProps = (0, import_react.useCallback)(
(props2 = {}) => {
return {
ref: triggerRef,
"data-slot": "trigger",
"data-open": (0, import_shared_utils.dataAttr)(state.isOpen),
"data-disabled": (0, import_shared_utils.dataAttr)(originalProps == null ? void 0 : originalProps.isDisabled),
"data-focus": (0, import_shared_utils.dataAttr)(isFocused),
"data-pressed": (0, import_shared_utils.dataAttr)(isPressed),
"data-focus-visible": (0, import_shared_utils.dataAttr)(isFocusVisible),
"data-hover": (0, import_shared_utils.dataAttr)(isHovered),
className: slots.trigger({ class: classNames == null ? void 0 : classNames.trigger }),
...(0, import_utils.mergeProps)(
buttonProps,
focusProps,
hoverProps,
(0, import_react_utils.filterDOMProps)(otherProps, {
enabled: shouldFilterDOMProps
}),
(0, import_react_utils.filterDOMProps)(props2)
)
};
},
[
slots,
triggerRef,
state.isOpen,
classNames == null ? void 0 : classNames.trigger,
originalProps == null ? void 0 : originalProps.isDisabled,
isFocused,
isPressed,
isFocusVisible,
isHovered,
buttonProps,
focusProps,
hoverProps,
otherProps,
shouldFilterDOMProps
]
);
const getHiddenSelectProps = (0, import_react.useCallback)(
(props2 = {}) => ({
state,
triggerRef,
selectRef: domRef,
selectionMode,
label: originalProps == null ? void 0 : originalProps.label,
name: originalProps == null ? void 0 : originalProps.name,
isRequired: originalProps == null ? void 0 : originalProps.isRequired,
autoComplete: originalProps == null ? void 0 : originalProps.autoComplete,
isDisabled: originalProps == null ? void 0 : originalProps.isDisabled,
form: originalProps == null ? void 0 : originalProps.form,
onChange,
...props2
}),
[
state,
selectionMode,
originalProps == null ? void 0 : originalProps.label,
originalProps == null ? void 0 : originalProps.autoComplete,
originalProps == null ? void 0 : originalProps.name,
originalProps == null ? void 0 : originalProps.isDisabled,
triggerRef
]
);
const getLabelProps = (0, import_react.useCallback)(
(props2 = {}) => ({
"data-slot": "label",
className: slots.label({
class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.label, props2.className)
}),
...labelProps,
...props2
}),
[slots, classNames == null ? void 0 : classNames.label, labelProps]
);
const getValueProps = (0, import_react.useCallback)(
(props2 = {}) => ({
"data-slot": "value",
className: slots.value({
class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.value, props2.className)
}),
...valueProps,
...props2
}),
[slots, classNames == null ? void 0 : classNames.value, valueProps]
);
const getListboxWrapperProps = (0, import_react.useCallback)(
(props2 = {}) => ({
"data-slot": "listboxWrapper",
className: slots.listboxWrapper({
class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.listboxWrapper, props2 == null ? void 0 : props2.className)
}),
style: {
maxHeight: maxListboxHeight != null ? maxListboxHeight : 256,
...props2.style
},
...(0, import_utils.mergeProps)(slotsProps.scrollShadowProps, props2)
}),
[
slots.listboxWrapper,
classNames == null ? void 0 : classNames.listboxWrapper,
slotsProps.scrollShadowProps,
maxListboxHeight
]
);
const getListboxProps = (props2 = {}) => {
const shouldVirtualize = isVirtualized != null ? isVirtualized : state.collection.size > 50;
return {
state,
ref: listBoxRef,
isVirtualized: shouldVirtualize,
virtualization: shouldVirtualize ? {
maxListboxHeight,
itemHeight
} : void 0,
"data-slot": "listbox",
className: slots.listbox({
class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.listbox, props2 == null ? void 0 : props2.className)
}),
scrollShadowProps: slotsProps.scrollShadowProps,
...(0, import_utils.mergeProps)(slotsProps.listboxProps, props2, menuProps)
};
};
const getPopoverProps = (0, import_react.useCallback)(
(props2 = {}) => {
var _a2, _b2;
const popoverProps2 = (0, import_utils.mergeProps)(slotsProps.popoverProps, props2);
return {
state,
triggerRef,
ref: popoverRef,
"data-slot": "popover",
scrollRef: listBoxRef,
triggerType: "listbox",
classNames: {
content: slots.popoverContent({
class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.popoverContent, props2.className)
})
},
...popoverProps2,
offset: state.selectedItems && state.selectedItems.length > 0 ? (
// forces the popover to update its position when the selected items change
state.selectedItems.length * 1e-8 + (((_a2 = slotsProps.popoverProps) == null ? void 0 : _a2.offset) || 0)
) : (_b2 = slotsProps.popoverProps) == null ? void 0 : _b2.offset,
shouldCloseOnInteractOutside: (popoverProps2 == null ? void 0 : popoverProps2.shouldCloseOnInteractOutside) ? popoverProps2.shouldCloseOnInteractOutside : (element) => (0, import_aria_utils.ariaShouldCloseOnInteractOutside)(element, domRef, state)
};
},
[
slots,
classNames == null ? void 0 : classNames.popoverContent,
slotsProps.popoverProps,
triggerRef,
state,
state.selectedItems
]
);
const getSelectorIconProps = (0, import_react.useCallback)(
() => ({
"data-slot": "selectorIcon",
"aria-hidden": (0, import_shared_utils.dataAttr)(true),
"data-open": (0, import_shared_utils.dataAttr)(state.isOpen),
className: slots.selectorIcon({ class: classNames == null ? void 0 : classNames.selectorIcon })
}),
[slots, classNames == null ? void 0 : classNames.selectorIcon, state.isOpen]
);
const getInnerWrapperProps = (0, import_react.useCallback)(
(props2 = {}) => {
return {
...props2,
"data-slot": "innerWrapper",
className: slots.innerWrapper({
class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.innerWrapper, props2 == null ? void 0 : props2.className)
})
};
},
[slots, classNames == null ? void 0 : classNames.innerWrapper]
);
const getHelperWrapperProps = (0, import_react.useCallback)(
(props2 = {}) => {
return {
...props2,
"data-slot": "helperWrapper",
className: slots.helperWrapper({
class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.helperWrapper, props2 == null ? void 0 : props2.className)
})
};
},
[slots, classNames == null ? void 0 : classNames.helperWrapper]
);
const getDescriptionProps = (0, import_react.useCallback)(
(props2 = {}) => {
return {
...props2,
...descriptionProps,
"data-slot": "description",
className: slots.description({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.description, props2 == null ? void 0 : props2.className) })
};
},
[slots, classNames == null ? void 0 : classNames.description]
);
const getMainWrapperProps = (0, import_react.useCallback)(
(props2 = {}) => {
return {
...props2,
"data-slot": "mainWrapper",
className: slots.mainWrapper({
class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.mainWrapper, props2 == null ? void 0 : props2.className)
})
};
},
[slots, classNames == null ? void 0 : classNames.mainWrapper]
);
const getErrorMessageProps = (0, import_react.useCallback)(
(props2 = {}) => {
return {
...props2,
...errorMessageProps,
"data-slot": "error-message",
className: slots.errorMessage({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.errorMessage, props2 == null ? void 0 : props2.className) })
};
},
[slots, errorMessageProps, classNames == null ? void 0 : classNames.errorMessage]
);
const getSpinnerProps = (0, import_react.useCallback)(
(props2 = {}) => {
return {
"aria-hidden": (0, import_shared_utils.dataAttr)(true),
"data-slot": "spinner",
color: "current",
size: "sm",
...spinnerProps,
...props2,
ref: spinnerRef,
className: slots.spinner({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.spinner, props2 == null ? void 0 : props2.className) })
};
},
[slots, spinnerRef, spinnerProps, classNames == null ? void 0 : classNames.spinner]
);
selectData.set(state, {
isDisabled: originalProps == null ? void 0 : originalProps.isDisabled,
isRequired: originalProps == null ? void 0 : originalProps.isRequired,
name: originalProps == null ? void 0 : originalProps.name,
isInvalid,
validationBehavior
});
return {
Component,
domRef,
state,
label,
name,
triggerRef,
isLoading,
placeholder,
startContent,
endContent,
description,
selectorIcon,
hasHelper,
labelPlacement,
hasPlaceholder,
renderValue,
selectionMode,
disableAnimation,
isOutsideLeft,
shouldLabelBeOutside,
shouldLabelBeInside,
isInvalid,
errorMessage,
getBaseProps,
getTriggerProps,
getLabelProps,
getValueProps,
getListboxProps,
getPopoverProps,
getSpinnerProps,
getMainWrapperProps,
getListboxWrapperProps,
getHiddenSelectProps,
getInnerWrapperProps,
getHelperWrapperProps,
getDescriptionProps,
getErrorMessageProps,
getSelectorIconProps
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
selectData,
useSelect
});