@octanejs/base-ui 0.1.50 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/MIGRATION.md +47 -0
- package/README.md +62 -5
- package/UPSTREAM.md +164 -64
- package/package.json +458 -27
- package/src/accordion/header/AccordionHeader.tsrx +42 -0
- package/src/accordion/header/AccordionHeaderDataAttributes.ts +13 -0
- package/src/accordion/index.parts.ts +5 -0
- package/src/accordion/index.ts +16 -0
- package/src/accordion/item/AccordionItem.tsrx +195 -0
- package/src/accordion/item/AccordionItemContext.ts +26 -0
- package/src/accordion/item/AccordionItemDataAttributes.ts +13 -0
- package/src/accordion/item/stateAttributesMapping.ts +12 -0
- package/src/accordion/panel/AccordionPanel.tsrx +163 -0
- package/src/accordion/panel/AccordionPanelCssVars.ts +10 -0
- package/src/accordion/panel/AccordionPanelDataAttributes.ts +27 -0
- package/src/accordion/root/AccordionRoot.tsrx +240 -0
- package/src/accordion/root/AccordionRootContext.ts +31 -0
- package/src/accordion/root/AccordionRootDataAttributes.ts +8 -0
- package/src/accordion/trigger/AccordionTrigger.tsrx +81 -0
- package/src/accordion/trigger/AccordionTriggerDataAttributes.ts +8 -0
- package/src/alert-dialog/handle.ts +22 -0
- package/src/alert-dialog/index.parts.ts +10 -0
- package/src/alert-dialog/index.ts +39 -0
- package/src/alert-dialog/root/AlertDialogRoot.tsrx +58 -0
- package/src/alert-dialog/trigger/AlertDialogTrigger.tsrx +38 -0
- package/src/alert-dialog/trigger/AlertDialogTriggerDataAttributes.ts +10 -0
- package/src/autocomplete/clear/AutocompleteClearDataAttributes.ts +1 -0
- package/src/autocomplete/index.parts.ts +25 -0
- package/src/autocomplete/index.ts +78 -0
- package/src/autocomplete/input-group/AutocompleteInputGroup.tsrx +53 -0
- package/src/autocomplete/input-group/AutocompleteInputGroupDataAttributes.ts +49 -0
- package/src/autocomplete/item/AutocompleteItem.tsrx +56 -0
- package/src/autocomplete/item/AutocompleteItemDataAttributes.ts +10 -0
- package/src/autocomplete/root/AutocompleteRoot.tsrx +296 -0
- package/src/autocomplete/separator/AutocompleteSeparator.tsrx +37 -0
- package/src/autocomplete/trigger/AutocompleteTrigger.tsrx +57 -0
- package/src/autocomplete/trigger/AutocompleteTriggerDataAttributes.ts +55 -0
- package/src/autocomplete/value/AutocompleteValue.tsrx +38 -0
- package/src/avatar/fallback/AvatarFallback.tsrx +68 -0
- package/src/avatar/image/AvatarImage.tsrx +210 -0
- package/src/avatar/image/AvatarImageDataAttributes.ts +18 -0
- package/src/avatar/image/useImageLoadingStatus.ts +72 -0
- package/src/avatar/index.parts.ts +3 -0
- package/src/avatar/index.ts +5 -0
- package/src/avatar/root/AvatarRoot.tsrx +60 -0
- package/src/avatar/root/AvatarRootContext.ts +21 -0
- package/src/avatar/root/stateAttributesMapping.ts +3 -0
- package/src/button/Button.tsrx +63 -0
- package/src/button/ButtonDataAttributes.tsrx +4 -0
- package/src/button/index.ts +3 -0
- package/src/checkbox/index.parts.ts +2 -0
- package/src/checkbox/index.ts +4 -0
- package/src/checkbox/indicator/CheckboxIndicator.tsrx +96 -0
- package/src/checkbox/indicator/CheckboxIndicatorDataAttributes.ts +58 -0
- package/src/checkbox/root/CheckboxRoot.tsrx +516 -0
- package/src/checkbox/root/CheckboxRootContext.ts +19 -0
- package/src/checkbox/root/CheckboxRootDataAttributes.ts +48 -0
- package/src/checkbox/utils/getCheckboxStateAttributesMapping.ts +24 -0
- package/src/checkbox-group/CheckboxGroup.tsrx +229 -0
- package/src/checkbox-group/CheckboxGroupContext.ts +33 -0
- package/src/checkbox-group/CheckboxGroupDataAttributes.ts +4 -0
- package/src/checkbox-group/index.ts +2 -0
- package/src/checkbox-group/useCheckboxGroupParent.ts +176 -0
- package/src/collapsible/index.parts.ts +3 -0
- package/src/collapsible/index.ts +12 -0
- package/src/collapsible/panel/CollapsiblePanel.tsrx +168 -0
- package/src/collapsible/panel/CollapsiblePanelCssVars.ts +10 -0
- package/src/collapsible/panel/CollapsiblePanelDataAttributes.ts +18 -0
- package/src/collapsible/panel/useCollapsiblePanel.ts +577 -0
- package/src/collapsible/root/CollapsibleRoot.tsrx +116 -0
- package/src/collapsible/root/CollapsibleRootContext.ts +25 -0
- package/src/collapsible/root/CollapsibleRootDataAttributes.ts +18 -0
- package/src/collapsible/root/stateAttributesMapping.ts +9 -0
- package/src/collapsible/root/useCollapsibleRoot.ts +121 -0
- package/src/collapsible/trigger/CollapsibleTrigger.tsrx +78 -0
- package/src/collapsible/trigger/CollapsibleTriggerDataAttributes.ts +4 -0
- package/src/combobox/arrow/ComboboxArrow.tsrx +72 -0
- package/src/combobox/arrow/ComboboxArrowDataAttributes.ts +24 -0
- package/src/combobox/backdrop/ComboboxBackdrop.tsrx +75 -0
- package/src/combobox/backdrop/ComboboxBackdropDataAttributes.ts +18 -0
- package/src/combobox/chip/ComboboxChip.tsrx +150 -0
- package/src/combobox/chip/ComboboxChipContext.ts +19 -0
- package/src/combobox/chip-remove/ComboboxChipRemove.tsrx +138 -0
- package/src/combobox/chips/ComboboxChips.tsrx +78 -0
- package/src/combobox/chips/ComboboxChipsContext.ts +17 -0
- package/src/combobox/clear/ComboboxClear.tsrx +178 -0
- package/src/combobox/clear/ComboboxClearDataAttributes.ts +25 -0
- package/src/combobox/collection/ComboboxCollection.tsrx +34 -0
- package/src/combobox/collection/GroupCollectionContext.tsrx +31 -0
- package/src/combobox/empty/ComboboxEmpty.tsrx +58 -0
- package/src/combobox/group/ComboboxGroup.tsrx +74 -0
- package/src/combobox/group/ComboboxGroupContext.ts +27 -0
- package/src/combobox/group-label/ComboboxGroupLabel.tsrx +52 -0
- package/src/combobox/icon/ComboboxIcon.tsrx +41 -0
- package/src/combobox/index.parts.ts +31 -0
- package/src/combobox/index.ts +36 -0
- package/src/combobox/input/ComboboxInput.tsrx +507 -0
- package/src/combobox/input/ComboboxInputDataAttributes.ts +55 -0
- package/src/combobox/input-group/ComboboxInputGroup.tsrx +110 -0
- package/src/combobox/input-group/ComboboxInputGroupDataAttributes.ts +53 -0
- package/src/combobox/item/ComboboxItem.tsrx +328 -0
- package/src/combobox/item/ComboboxItemContext.ts +20 -0
- package/src/combobox/item/ComboboxItemDataAttributes.ts +12 -0
- package/src/combobox/item-indicator/ComboboxItemIndicator.tsrx +109 -0
- package/src/combobox/item-indicator/ComboboxItemIndicatorDataAttributes.ts +10 -0
- package/src/combobox/items/createItems.ts +168 -0
- package/src/combobox/items/itemCollection.ts +67 -0
- package/src/combobox/label/ComboboxLabel.tsrx +88 -0
- package/src/combobox/list/ComboboxList.tsrx +152 -0
- package/src/combobox/popup/ComboboxPopup.tsrx +214 -0
- package/src/combobox/popup/ComboboxPopupDataAttributes.ts +36 -0
- package/src/combobox/portal/ComboboxPortal.tsrx +56 -0
- package/src/combobox/portal/ComboboxPortalContext.tsrx +12 -0
- package/src/combobox/positioner/ComboboxPositioner.tsrx +172 -0
- package/src/combobox/positioner/ComboboxPositionerContext.tsrx +30 -0
- package/src/combobox/positioner/ComboboxPositionerCssVars.ts +25 -0
- package/src/combobox/positioner/ComboboxPositionerDataAttributes.ts +28 -0
- package/src/combobox/root/AriaCombobox.tsrx +1970 -0
- package/src/combobox/root/ComboboxRoot.tsrx +183 -0
- package/src/combobox/root/ComboboxRootContext.tsrx +66 -0
- package/src/combobox/root/utils/constants.ts +5 -0
- package/src/combobox/root/utils/index.ts +68 -0
- package/src/combobox/root/utils/useFilter.ts +50 -0
- package/src/combobox/root/utils/useFilteredItems.ts +11 -0
- package/src/combobox/row/ComboboxRow.tsrx +37 -0
- package/src/combobox/row/ComboboxRowContext.ts +9 -0
- package/src/combobox/separator/ComboboxSeparator.tsrx +37 -0
- package/src/combobox/status/ComboboxStatus.tsrx +49 -0
- package/src/combobox/store.ts +206 -0
- package/src/combobox/trigger/ComboboxTrigger.tsrx +302 -0
- package/src/combobox/trigger/ComboboxTriggerDataAttributes.ts +59 -0
- package/src/combobox/utils/ComboboxInternalDismissButton.tsrx +47 -0
- package/src/combobox/utils/handleInputPress.ts +38 -0
- package/src/combobox/utils/parts.ts +59 -0
- package/src/combobox/utils/stateAttributesMapping.ts +19 -0
- package/src/combobox/utils/useInitialLiveRegionTextMutation.ts +66 -0
- package/src/combobox/value/ComboboxValue.tsrx +65 -0
- package/src/context-menu/index.parts.ts +21 -0
- package/src/context-menu/index.ts +66 -0
- package/src/context-menu/positioner/ContextMenuPositioner.tsrx +119 -0
- package/src/context-menu/positioner/ContextMenuPositionerCssVars.ts +1 -0
- package/src/context-menu/positioner/ContextMenuPositionerDataAttributes.ts +1 -0
- package/src/context-menu/root/ContextMenuRoot.tsrx +98 -0
- package/src/context-menu/root/ContextMenuRootContext.ts +34 -0
- package/src/context-menu/trigger/ContextMenuTrigger.tsrx +237 -0
- package/src/context-menu/trigger/ContextMenuTriggerDataAttributes.ts +10 -0
- package/src/csp-provider/CSPProvider.tsrx +43 -0
- package/src/csp-provider/index.parts.ts +1 -0
- package/src/csp-provider/index.ts +3 -0
- package/src/dialog/backdrop/DialogBackdrop.tsrx +76 -0
- package/src/dialog/backdrop/DialogBackdropDataAttributes.ts +18 -0
- package/src/dialog/close/DialogClose.tsrx +67 -0
- package/src/dialog/close/DialogCloseDataAttributes.ts +4 -0
- package/src/dialog/description/DialogDescription.tsrx +41 -0
- package/src/dialog/index.parts.ts +10 -0
- package/src/dialog/index.ts +14 -0
- package/src/dialog/popup/DialogPopup.tsrx +170 -0
- package/src/dialog/popup/DialogPopupCssVars.ts +5 -0
- package/src/dialog/popup/DialogPopupDataAttributes.ts +26 -0
- package/src/dialog/portal/DialogPortal.tsrx +63 -0
- package/src/dialog/portal/DialogPortalContext.ts +13 -0
- package/src/dialog/root/DialogRoot.tsrx +117 -0
- package/src/dialog/root/DialogRootContext.ts +20 -0
- package/src/dialog/root/useDialogRoot.ts +128 -0
- package/src/dialog/root/useRenderDialogRoot.tsrx +108 -0
- package/src/dialog/store/DialogHandle.ts +83 -0
- package/src/dialog/store/DialogStore.ts +149 -0
- package/src/dialog/title/DialogTitle.tsrx +41 -0
- package/src/dialog/trigger/DialogTrigger.tsrx +142 -0
- package/src/dialog/trigger/DialogTriggerDataAttributes.ts +10 -0
- package/src/dialog/utils/stateAttributesMapping.ts +22 -0
- package/src/dialog/viewport/DialogViewport.tsrx +89 -0
- package/src/dialog/viewport/DialogViewportDataAttributes.ts +26 -0
- package/src/direction-provider/DirectionProvider.tsrx +37 -0
- package/src/direction-provider/index.parts.ts +5 -0
- package/src/direction-provider/index.ts +2 -0
- package/src/drawer/backdrop/DrawerBackdrop.tsrx +81 -0
- package/src/drawer/backdrop/DrawerBackdropCssVars.ts +5 -0
- package/src/drawer/backdrop/DrawerBackdropDataAttributes.ts +18 -0
- package/src/drawer/close/DrawerClose.tsrx +31 -0
- package/src/drawer/content/DrawerContent.tsrx +37 -0
- package/src/drawer/content/drawerContentAttribute.ts +1 -0
- package/src/drawer/description/DrawerDescription.tsrx +25 -0
- package/src/drawer/handle.ts +22 -0
- package/src/drawer/indent/DrawerIndent.tsrx +104 -0
- package/src/drawer/indent-background/DrawerIndentBackground.tsrx +61 -0
- package/src/drawer/index.parts.ts +16 -0
- package/src/drawer/index.ts +17 -0
- package/src/drawer/popup/DrawerPopup.tsrx +485 -0
- package/src/drawer/popup/DrawerPopupCssVars.ts +35 -0
- package/src/drawer/popup/DrawerPopupDataAttributes.ts +43 -0
- package/src/drawer/portal/DrawerPortal.tsrx +39 -0
- package/src/drawer/provider/DrawerProvider.tsrx +117 -0
- package/src/drawer/provider/DrawerProviderContext.ts +29 -0
- package/src/drawer/root/DrawerRoot.tsrx +478 -0
- package/src/drawer/root/DrawerRootContext.ts +115 -0
- package/src/drawer/root/useDrawerSnapPoints.ts +198 -0
- package/src/drawer/swipe-area/DrawerSwipeArea.tsrx +528 -0
- package/src/drawer/swipe-area/DrawerSwipeAreaDataAttributes.ts +23 -0
- package/src/drawer/title/DrawerTitle.tsrx +25 -0
- package/src/drawer/trigger/DrawerTrigger.tsrx +53 -0
- package/src/drawer/viewport/DrawerViewport.tsrx +1237 -0
- package/src/drawer/viewport/DrawerViewportContext.tsrx +15 -0
- package/src/drawer/viewport/DrawerViewportCssVars.ts +6 -0
- package/src/drawer/viewport/DrawerViewportDataAttributes.ts +22 -0
- package/src/drawer/virtual-keyboard-provider/DrawerVirtualKeyboardContext.tsrx +19 -0
- package/src/drawer/virtual-keyboard-provider/DrawerVirtualKeyboardProvider.tsrx +858 -0
- package/src/field/control/FieldControl.tsrx +241 -0
- package/src/field/control/FieldControlDataAttributes.ts +28 -0
- package/src/field/description/FieldDescription.tsrx +67 -0
- package/src/field/description/FieldDescriptionDataAttributes.ts +28 -0
- package/src/field/error/FieldError.tsrx +160 -0
- package/src/field/error/FieldErrorDataAttributes.ts +38 -0
- package/src/field/index.parts.ts +9 -0
- package/src/field/index.ts +9 -0
- package/src/field/item/FieldItem.tsrx +66 -0
- package/src/field/item/FieldItemContext.ts +15 -0
- package/src/field/item/FieldItemDataAttributes.ts +28 -0
- package/src/field/label/FieldLabel.tsrx +107 -0
- package/src/field/label/FieldLabelDataAttributes.ts +28 -0
- package/src/field/root/FieldRoot.tsrx +336 -0
- package/src/field/root/FieldRootDataAttributes.ts +28 -0
- package/src/field/root/useFieldValidation.ts +418 -0
- package/src/field/utils/getCombinedFieldValidityData.ts +18 -0
- package/src/field/validity/FieldValidity.tsrx +69 -0
- package/src/fieldset/index.parts.ts +2 -0
- package/src/fieldset/index.ts +4 -0
- package/src/fieldset/legend/FieldsetLegend.tsrx +51 -0
- package/src/fieldset/root/FieldsetRoot.tsrx +74 -0
- package/src/fieldset/root/FieldsetRootContext.ts +23 -0
- package/src/floating-ui-react/components/FloatingDelayGroup.tsrx +281 -0
- package/src/floating-ui-react/components/FloatingFocusManager.tsrx +1004 -0
- package/src/floating-ui-react/components/FloatingPortal.tsrx +319 -0
- package/src/floating-ui-react/components/FloatingRootStore.ts +136 -0
- package/src/floating-ui-react/components/FloatingTree.tsrx +95 -0
- package/src/floating-ui-react/components/FloatingTreeStore.ts +24 -0
- package/src/floating-ui-react/hooks/gridNavigation.ts +50 -0
- package/src/floating-ui-react/hooks/useClick.ts +229 -0
- package/src/floating-ui-react/hooks/useClientPoint.ts +261 -0
- package/src/floating-ui-react/hooks/useDismiss.ts +808 -0
- package/src/floating-ui-react/hooks/useFloating.ts +205 -0
- package/src/floating-ui-react/hooks/useFloatingRootContext.ts +84 -0
- package/src/floating-ui-react/hooks/useFocus.ts +253 -0
- package/src/floating-ui-react/hooks/useHover.ts +468 -0
- package/src/floating-ui-react/hooks/useHoverFloatingInteraction.ts +274 -0
- package/src/floating-ui-react/hooks/useHoverInteractionSharedState.ts +132 -0
- package/src/floating-ui-react/hooks/useHoverReferenceInteraction.ts +523 -0
- package/src/floating-ui-react/hooks/useHoverShared.ts +72 -0
- package/src/floating-ui-react/hooks/useListNavigation.ts +941 -0
- package/src/floating-ui-react/hooks/useSyncedFloatingRootContext.ts +115 -0
- package/src/floating-ui-react/hooks/useTypeahead.ts +248 -0
- package/src/floating-ui-react/index.ts +48 -0
- package/src/floating-ui-react/middleware/arrow.ts +128 -0
- package/src/floating-ui-react/middleware/positioning.ts +28 -0
- package/src/floating-ui-react/safePolygon.ts +451 -0
- package/src/floating-ui-react/types.ts +206 -0
- package/src/floating-ui-react/utils/composite.ts +528 -0
- package/src/floating-ui-react/utils/constants.ts +10 -0
- package/src/floating-ui-react/utils/createAttribute.ts +3 -0
- package/src/floating-ui-react/utils/createEventEmitter.ts +19 -0
- package/src/floating-ui-react/utils/element.ts +96 -0
- package/src/floating-ui-react/utils/enqueueFocus.ts +37 -0
- package/src/floating-ui-react/utils/event.ts +60 -0
- package/src/floating-ui-react/utils/getEmptyRootContext.ts +17 -0
- package/src/floating-ui-react/utils/markOthers.ts +219 -0
- package/src/floating-ui-react/utils/nodes.ts +54 -0
- package/src/floating-ui-react/utils/tabbable.ts +283 -0
- package/src/floating-ui-react/utils.ts +5 -0
- package/src/form/Form.tsrx +248 -0
- package/src/form/index.ts +3 -0
- package/src/global.d.ts +9 -0
- package/src/index.ts +45 -39
- package/src/input/Input.tsrx +44 -0
- package/src/input/InputDataAttributes.ts +28 -0
- package/src/input/index.ts +3 -0
- package/src/internals/PrehydrationScript.tsrx +59 -0
- package/src/internals/RequestQueue.ts +126 -0
- package/src/internals/TimeoutManager.ts +29 -0
- package/src/internals/TransitionStatusDataAttributes.ts +9 -0
- package/src/internals/composite/composite.ts +174 -0
- package/src/internals/composite/constants.ts +1 -0
- package/src/internals/composite/index.ts +19 -0
- package/src/internals/composite/item/CompositeItem.tsrx +57 -0
- package/src/internals/composite/item/useCompositeItem.ts +50 -0
- package/src/internals/composite/list/CompositeList.tsrx +325 -0
- package/src/internals/composite/list/CompositeListContext.ts +28 -0
- package/src/internals/composite/list/useCompositeListItem.ts +100 -0
- package/src/internals/composite/root/CompositeRoot.tsrx +138 -0
- package/src/internals/composite/root/CompositeRootContext.ts +33 -0
- package/src/internals/composite/root/gridNavigation.ts +127 -0
- package/src/internals/composite/root/useCompositeRoot.ts +378 -0
- package/src/internals/constants.ts +41 -0
- package/src/internals/createBaseUIEventDetails.ts +166 -0
- package/src/internals/csp-context/CSPContext.tsrx +17 -0
- package/src/internals/csp-context/index.ts +2 -0
- package/src/internals/direction-context/DirectionContext.tsrx +16 -0
- package/src/internals/direction-context/index.ts +5 -0
- package/src/internals/field-constants/constants.ts +44 -0
- package/src/internals/field-constants/index.ts +6 -0
- package/src/internals/field-register-control/index.ts +6 -0
- package/src/internals/field-register-control/useFieldControlRegistration.ts +185 -0
- package/src/internals/field-register-control/useRegisterFieldControl.ts +47 -0
- package/src/internals/field-root-context/FieldRootContext.ts +79 -0
- package/src/internals/field-root-context/index.ts +2 -0
- package/src/internals/filter.ts +82 -0
- package/src/internals/form-context/FormContext.ts +51 -0
- package/src/internals/form-context/index.ts +2 -0
- package/src/internals/getDisabledMountTransitionStyles.ts +10 -0
- package/src/internals/getStateAttributesProps.ts +32 -0
- package/src/internals/itemEquality.ts +134 -0
- package/src/internals/labelable-provider/LabelableContext.ts +49 -0
- package/src/internals/labelable-provider/LabelableProvider.tsrx +120 -0
- package/src/internals/labelable-provider/index.ts +6 -0
- package/src/internals/labelable-provider/useAriaLabelledBy.ts +73 -0
- package/src/internals/labelable-provider/useLabel.ts +113 -0
- package/src/internals/labelable-provider/useLabelableId.ts +101 -0
- package/src/internals/noop.ts +1 -0
- package/src/internals/prehydrationScript.stub.ts +6 -0
- package/src/internals/reason-parts.ts +42 -0
- package/src/internals/reasons.ts +5 -0
- package/src/internals/resolveValueLabel.tsrx +158 -0
- package/src/internals/serializeValue.ts +13 -0
- package/src/internals/stateAttributesMapping.ts +20 -0
- package/src/internals/temporal/index.ts +2 -0
- package/src/internals/temporal/temporal-adapter.ts +382 -0
- package/src/internals/temporal/temporal.ts +49 -0
- package/src/internals/temporal-adapter-date-fns/TemporalAdapterDateFns.ts +474 -0
- package/src/internals/temporal-adapter-date-fns/index.ts +1 -0
- package/src/internals/temporal-adapter-luxon/TemporalAdapterLuxon.ts +380 -0
- package/src/internals/temporal-adapter-luxon/index.ts +1 -0
- package/src/internals/types.ts +97 -0
- package/src/internals/use-button/index.ts +1 -0
- package/src/internals/use-button/useButton.ts +309 -0
- package/src/internals/useAnchorPositioning.ts +828 -0
- package/src/internals/useAnimationsFinished.ts +161 -0
- package/src/internals/useBaseUiId.ts +12 -0
- package/src/internals/useOpenChangeComplete.tsrx +56 -0
- package/src/internals/usePressAndHold.ts +287 -0
- package/src/internals/useRenderElement.tsrx +284 -0
- package/src/internals/useTransitionStatus.ts +98 -0
- package/src/internals/useValueChanged.ts +18 -0
- package/src/menu/arrow/MenuArrow.tsrx +71 -0
- package/src/menu/arrow/MenuArrowDataAttributes.ts +24 -0
- package/src/menu/backdrop/MenuBackdrop.tsrx +75 -0
- package/src/menu/backdrop/MenuBackdropDataAttributes.ts +18 -0
- package/src/menu/checkbox-item/MenuCheckboxItem.tsrx +184 -0
- package/src/menu/checkbox-item/MenuCheckboxItemContext.ts +24 -0
- package/src/menu/checkbox-item/MenuCheckboxItemDataAttributes.ts +16 -0
- package/src/menu/checkbox-item-indicator/MenuCheckboxItemIndicator.tsrx +96 -0
- package/src/menu/checkbox-item-indicator/MenuCheckboxItemIndicatorDataAttributes.ts +22 -0
- package/src/menu/group/MenuGroup.tsrx +47 -0
- package/src/menu/group/MenuGroupContext.ts +18 -0
- package/src/menu/group-label/MenuGroupLabel.tsrx +51 -0
- package/src/menu/index.parts.ts +21 -0
- package/src/menu/index.ts +24 -0
- package/src/menu/item/MenuItem.tsrx +107 -0
- package/src/menu/item/MenuItemDataAttributes.ts +8 -0
- package/src/menu/item/useMenuItem.ts +142 -0
- package/src/menu/item/useMenuItemCommonProps.ts +139 -0
- package/src/menu/link-item/MenuLinkItem.tsrx +106 -0
- package/src/menu/link-item/MenuLinkItemDataAttributes.ts +4 -0
- package/src/menu/popup/MenuPopup.tsrx +199 -0
- package/src/menu/popup/MenuPopupDataAttributes.ts +33 -0
- package/src/menu/portal/MenuPortal.tsrx +60 -0
- package/src/menu/portal/MenuPortalContext.ts +13 -0
- package/src/menu/positioner/MenuPositioner.tsrx +378 -0
- package/src/menu/positioner/MenuPositionerContext.ts +25 -0
- package/src/menu/positioner/MenuPositionerCssVars.ts +37 -0
- package/src/menu/positioner/MenuPositionerDataAttributes.ts +24 -0
- package/src/menu/radio-group/MenuRadioGroup.tsrx +129 -0
- package/src/menu/radio-group/MenuRadioGroupContext.ts +25 -0
- package/src/menu/radio-item/MenuRadioItem.tsrx +156 -0
- package/src/menu/radio-item/MenuRadioItemContext.ts +24 -0
- package/src/menu/radio-item/MenuRadioItemDataAttributes.ts +16 -0
- package/src/menu/radio-item-indicator/MenuRadioItemIndicator.tsrx +96 -0
- package/src/menu/radio-item-indicator/MenuRadioItemIndicatorDataAttributes.ts +22 -0
- package/src/menu/root/MenuRoot.tsrx +818 -0
- package/src/menu/root/MenuRootContext.ts +25 -0
- package/src/menu/store/MenuHandle.ts +54 -0
- package/src/menu/store/MenuStore.ts +234 -0
- package/src/menu/submenu-root/MenuSubmenuRoot.tsrx +64 -0
- package/src/menu/submenu-root/MenuSubmenuRootContext.ts +16 -0
- package/src/menu/submenu-trigger/MenuSubmenuTrigger.tsrx +278 -0
- package/src/menu/submenu-trigger/MenuSubmenuTriggerDataAttributes.ts +14 -0
- package/src/menu/trigger/MenuTrigger.tsrx +395 -0
- package/src/menu/trigger/MenuTriggerDataAttributes.ts +14 -0
- package/src/menu/utils/findRootOwnerId.ts +13 -0
- package/src/menu/utils/stateAttributesMapping.ts +17 -0
- package/src/menu/utils/types.ts +8 -0
- package/src/menu/viewport/MenuViewport.tsrx +75 -0
- package/src/menu/viewport/MenuViewportCssVars.ts +12 -0
- package/src/menu/viewport/MenuViewportDataAttributes.ts +24 -0
- package/src/menubar/Menubar.tsrx +173 -0
- package/src/menubar/MenubarContext.ts +31 -0
- package/src/menubar/MenubarDataAttributes.ts +13 -0
- package/src/menubar/index.ts +3 -0
- package/src/merge-props/index.ts +1 -0
- package/src/merge-props/mergeProps.ts +302 -0
- package/src/meter/index.parts.ts +5 -0
- package/src/meter/index.ts +7 -0
- package/src/meter/indicator/MeterIndicator.tsrx +46 -0
- package/src/meter/label/MeterLabel.tsrx +46 -0
- package/src/meter/root/MeterRoot.tsrx +129 -0
- package/src/meter/root/MeterRootContext.ts +26 -0
- package/src/meter/track/MeterTrack.tsrx +34 -0
- package/src/meter/value/MeterValue.tsrx +51 -0
- package/src/navigation-menu/arrow/NavigationMenuArrow.tsrx +77 -0
- package/src/navigation-menu/arrow/NavigationMenuArrowDataAttributes.ts +24 -0
- package/src/navigation-menu/backdrop/NavigationMenuBackdrop.tsrx +69 -0
- package/src/navigation-menu/backdrop/NavigationMenuBackdropDataAttributes.ts +18 -0
- package/src/navigation-menu/content/NavigationMenuContent.tsrx +208 -0
- package/src/navigation-menu/content/NavigationMenuContentDataAttributes.ts +23 -0
- package/src/navigation-menu/icon/NavigationMenuIcon.tsrx +56 -0
- package/src/navigation-menu/icon/NavigationMenuIconDataAttributes.ts +6 -0
- package/src/navigation-menu/index.parts.ts +13 -0
- package/src/navigation-menu/index.ts +15 -0
- package/src/navigation-menu/item/NavigationMenuItem.tsrx +59 -0
- package/src/navigation-menu/item/NavigationMenuItemContext.ts +21 -0
- package/src/navigation-menu/link/NavigationMenuLink.tsrx +108 -0
- package/src/navigation-menu/link/NavigationMenuLinkDataAttributes.ts +4 -0
- package/src/navigation-menu/list/NavigationMenuDismissContext.ts +12 -0
- package/src/navigation-menu/list/NavigationMenuList.tsrx +144 -0
- package/src/navigation-menu/popup/NavigationMenuPopup.tsrx +106 -0
- package/src/navigation-menu/popup/NavigationMenuPopupCssVars.ts +10 -0
- package/src/navigation-menu/popup/NavigationMenuPopupDataAttributes.ts +32 -0
- package/src/navigation-menu/portal/NavigationMenuPortal.tsrx +58 -0
- package/src/navigation-menu/portal/NavigationMenuPortalContext.ts +13 -0
- package/src/navigation-menu/positioner/NavigationMenuPositioner.tsrx +219 -0
- package/src/navigation-menu/positioner/NavigationMenuPositionerContext.ts +26 -0
- package/src/navigation-menu/positioner/NavigationMenuPositionerCssVars.ts +35 -0
- package/src/navigation-menu/positioner/NavigationMenuPositionerDataAttributes.ts +28 -0
- package/src/navigation-menu/root/NavigationMenuRoot.tsrx +439 -0
- package/src/navigation-menu/root/NavigationMenuRootContext.ts +84 -0
- package/src/navigation-menu/trigger/NavigationMenuTrigger.tsrx +846 -0
- package/src/navigation-menu/trigger/NavigationMenuTriggerDataAttributes.ts +14 -0
- package/src/navigation-menu/utils/constants.ts +3 -0
- package/src/navigation-menu/utils/isOutsideMenuEvent.ts +36 -0
- package/src/navigation-menu/utils/setSharedFixedSize.ts +20 -0
- package/src/navigation-menu/utils/useNavigationMenuAnchorPositioning.ts +18 -0
- package/src/navigation-menu/viewport/NavigationMenuViewport.tsrx +146 -0
- package/src/number-field/decrement/NumberFieldDecrement.tsrx +30 -0
- package/src/number-field/decrement/NumberFieldDecrementDataAttributes.ts +40 -0
- package/src/number-field/group/NumberFieldGroup.tsrx +42 -0
- package/src/number-field/group/NumberFieldGroupDataAttributes.ts +40 -0
- package/src/number-field/increment/NumberFieldIncrement.tsrx +30 -0
- package/src/number-field/increment/NumberFieldIncrementDataAttributes.ts +40 -0
- package/src/number-field/index.parts.ts +7 -0
- package/src/number-field/index.ts +9 -0
- package/src/number-field/input/NumberFieldInput.tsrx +488 -0
- package/src/number-field/input/NumberFieldInputDataAttributes.ts +40 -0
- package/src/number-field/root/NumberFieldRoot.tsrx +735 -0
- package/src/number-field/root/NumberFieldRootContext.ts +52 -0
- package/src/number-field/root/NumberFieldRootDataAttributes.ts +40 -0
- package/src/number-field/root/useNumberFieldStepperButton.ts +191 -0
- package/src/number-field/scrub-area/NumberFieldScrubArea.tsrx +395 -0
- package/src/number-field/scrub-area/NumberFieldScrubAreaContext.ts +24 -0
- package/src/number-field/scrub-area/NumberFieldScrubAreaDataAttributes.ts +40 -0
- package/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.tsrx +73 -0
- package/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursorDataAttributes.ts +40 -0
- package/src/number-field/utils/constants.ts +2 -0
- package/src/number-field/utils/getViewportRect.ts +34 -0
- package/src/number-field/utils/parse.ts +221 -0
- package/src/number-field/utils/stateAttributesMapping.ts +9 -0
- package/src/number-field/utils/types.ts +27 -0
- package/src/number-field/utils/validate.ts +136 -0
- package/src/otp-field/index.parts.ts +3 -0
- package/src/otp-field/index.ts +4 -0
- package/src/otp-field/input/OTPFieldInput.tsrx +356 -0
- package/src/otp-field/input/OTPFieldInputDataAttributes.ts +40 -0
- package/src/otp-field/root/OTPFieldRoot.tsrx +679 -0
- package/src/otp-field/root/OTPFieldRootContext.ts +58 -0
- package/src/otp-field/root/OTPFieldRootDataAttributes.ts +40 -0
- package/src/otp-field/utils/otp.ts +118 -0
- package/src/otp-field/utils/stateAttributesMapping.ts +16 -0
- package/src/popover/arrow/PopoverArrow.tsrx +69 -0
- package/src/popover/arrow/PopoverArrowDataAttributes.ts +24 -0
- package/src/popover/backdrop/PopoverBackdrop.tsrx +71 -0
- package/src/popover/backdrop/PopoverBackdropDataAttributes.ts +18 -0
- package/src/popover/close/PopoverClose.tsrx +65 -0
- package/src/popover/description/PopoverDescription.tsrx +46 -0
- package/src/popover/index.parts.ts +15 -0
- package/src/popover/index.ts +16 -0
- package/src/popover/popup/PopoverPopup.tsrx +188 -0
- package/src/popover/popup/PopoverPopupCssVars.ts +8 -0
- package/src/popover/popup/PopoverPopupDataAttributes.ts +33 -0
- package/src/popover/portal/PopoverPortal.tsrx +54 -0
- package/src/popover/portal/PopoverPortalContext.ts +13 -0
- package/src/popover/positioner/PopoverPositioner.tsrx +196 -0
- package/src/popover/positioner/PopoverPositionerContext.ts +28 -0
- package/src/popover/positioner/PopoverPositionerCssVars.ts +37 -0
- package/src/popover/positioner/PopoverPositionerDataAttributes.ts +24 -0
- package/src/popover/root/PopoverRoot.tsrx +262 -0
- package/src/popover/root/PopoverRootContext.ts +20 -0
- package/src/popover/store/PopoverHandle.ts +54 -0
- package/src/popover/store/PopoverStore.ts +229 -0
- package/src/popover/title/PopoverTitle.tsrx +46 -0
- package/src/popover/trigger/PopoverTrigger.tsrx +233 -0
- package/src/popover/trigger/PopoverTriggerDataAttributes.ts +14 -0
- package/src/popover/utils/constants.ts +1 -0
- package/src/popover/viewport/PopoverViewport.tsrx +75 -0
- package/src/popover/viewport/PopoverViewportCssVars.ts +12 -0
- package/src/popover/viewport/PopoverViewportDataAttributes.ts +24 -0
- package/src/preview-card/arrow/PreviewCardArrow.tsrx +70 -0
- package/src/preview-card/arrow/PreviewCardArrowDataAttributes.ts +24 -0
- package/src/preview-card/backdrop/PreviewCardBackdrop.tsrx +73 -0
- package/src/preview-card/backdrop/PreviewCardBackdropDataAttributes.ts +18 -0
- package/src/preview-card/index.parts.ts +12 -0
- package/src/preview-card/index.ts +16 -0
- package/src/preview-card/popup/PreviewCardPopup.tsrx +104 -0
- package/src/preview-card/popup/PreviewCardPopupDataAttributes.ts +28 -0
- package/src/preview-card/portal/PreviewCardPortal.tsrx +59 -0
- package/src/preview-card/portal/PreviewCardPortalContext.ts +13 -0
- package/src/preview-card/positioner/PreviewCardPositioner.tsrx +145 -0
- package/src/preview-card/positioner/PreviewCardPositionerContext.ts +24 -0
- package/src/preview-card/positioner/PreviewCardPositionerCssVars.ts +37 -0
- package/src/preview-card/positioner/PreviewCardPositionerDataAttributes.ts +24 -0
- package/src/preview-card/root/PreviewCardContext.ts +23 -0
- package/src/preview-card/root/PreviewCardRoot.tsrx +210 -0
- package/src/preview-card/store/PreviewCardHandle.ts +58 -0
- package/src/preview-card/store/PreviewCardStore.ts +140 -0
- package/src/preview-card/trigger/PreviewCardTrigger.tsrx +148 -0
- package/src/preview-card/trigger/PreviewCardTriggerDataAttributes.ts +6 -0
- package/src/preview-card/utils/constants.ts +2 -0
- package/src/preview-card/viewport/PreviewCardViewport.tsrx +78 -0
- package/src/preview-card/viewport/PreviewCardViewportCssVars.ts +12 -0
- package/src/preview-card/viewport/PreviewCardViewportDataAttributes.ts +24 -0
- package/src/progress/index.parts.ts +7 -0
- package/src/progress/index.ts +7 -0
- package/src/progress/indicator/ProgressIndicator.tsrx +59 -0
- package/src/progress/indicator/ProgressIndicatorDataAttributes.ts +12 -0
- package/src/progress/label/ProgressLabel.tsrx +51 -0
- package/src/progress/label/ProgressLabelDataAttributes.ts +12 -0
- package/src/progress/root/ProgressRoot.tsrx +154 -0
- package/src/progress/root/ProgressRootContext.tsrx +37 -0
- package/src/progress/root/ProgressRootDataAttributes.ts +12 -0
- package/src/progress/root/stateAttributesMapping.ts +18 -0
- package/src/progress/track/ProgressTrack.tsrx +42 -0
- package/src/progress/track/ProgressTrackDataAttributes.ts +12 -0
- package/src/progress/value/ProgressValue.tsrx +62 -0
- package/src/progress/value/ProgressValueDataAttributes.ts +12 -0
- package/src/radio/index.parts.ts +2 -0
- package/src/radio/index.ts +4 -0
- package/src/radio/indicator/RadioIndicator.tsrx +84 -0
- package/src/radio/indicator/RadioIndicatorDataAttributes.ts +54 -0
- package/src/radio/root/RadioRoot.tsrx +337 -0
- package/src/radio/root/RadioRootContext.ts +19 -0
- package/src/radio/root/RadioRootDataAttributes.ts +44 -0
- package/src/radio/utils/stateAttributesMapping.ts +20 -0
- package/src/radio-group/RadioGroup.tsrx +337 -0
- package/src/radio-group/RadioGroupContext.ts +29 -0
- package/src/radio-group/RadioGroupDataAttributes.ts +4 -0
- package/src/radio-group/index.ts +3 -0
- package/src/scroll-area/constants.ts +2 -0
- package/src/scroll-area/content/ScrollAreaContent.tsrx +90 -0
- package/src/scroll-area/content/ScrollAreaContentDataAttributes.ts +28 -0
- package/src/scroll-area/corner/ScrollAreaCorner.tsrx +54 -0
- package/src/scroll-area/index.parts.ts +6 -0
- package/src/scroll-area/index.ts +8 -0
- package/src/scroll-area/root/ScrollAreaRoot.tsrx +424 -0
- package/src/scroll-area/root/ScrollAreaRootContext.ts +61 -0
- package/src/scroll-area/root/ScrollAreaRootCssVars.ts +10 -0
- package/src/scroll-area/root/ScrollAreaRootDataAttributes.ts +28 -0
- package/src/scroll-area/root/stateAttributes.ts +15 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbar.tsrx +275 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbarContext.ts +19 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbarCssVars.ts +10 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbarDataAttributes.ts +37 -0
- package/src/scroll-area/thumb/ScrollAreaThumb.tsrx +80 -0
- package/src/scroll-area/thumb/ScrollAreaThumbDataAttributes.ts +9 -0
- package/src/scroll-area/utils/getOffset.ts +21 -0
- package/src/scroll-area/viewport/ScrollAreaViewport.tsrx +492 -0
- package/src/scroll-area/viewport/ScrollAreaViewportContext.ts +21 -0
- package/src/scroll-area/viewport/ScrollAreaViewportCssVars.ts +20 -0
- package/src/scroll-area/viewport/ScrollAreaViewportDataAttributes.ts +28 -0
- package/src/select/arrow/SelectArrow.tsrx +75 -0
- package/src/select/arrow/SelectArrowDataAttributes.ts +24 -0
- package/src/select/backdrop/SelectBackdrop.tsrx +77 -0
- package/src/select/backdrop/SelectBackdropDataAttributes.ts +18 -0
- package/src/select/group/SelectGroup.tsrx +52 -0
- package/src/select/group/SelectGroupContext.ts +20 -0
- package/src/select/group-label/SelectGroupLabel.tsrx +49 -0
- package/src/select/icon/SelectIcon.tsrx +51 -0
- package/src/select/icon/SelectIconDataAttributes.ts +6 -0
- package/src/select/index.parts.ts +19 -0
- package/src/select/index.ts +21 -0
- package/src/select/item/SelectItem.tsrx +291 -0
- package/src/select/item/SelectItemContext.ts +22 -0
- package/src/select/item/SelectItemDataAttributes.ts +12 -0
- package/src/select/item-indicator/SelectItemIndicator.tsrx +108 -0
- package/src/select/item-indicator/SelectItemIndicatorDataAttributes.ts +10 -0
- package/src/select/item-text/SelectItemText.tsrx +56 -0
- package/src/select/label/SelectLabel.tsrx +64 -0
- package/src/select/list/SelectList.tsrx +62 -0
- package/src/select/popup/SelectPopup.tsrx +596 -0
- package/src/select/popup/SelectPopupDataAttributes.ts +28 -0
- package/src/select/popup/utils.ts +13 -0
- package/src/select/portal/SelectPortal.tsrx +45 -0
- package/src/select/positioner/SelectPositioner.tsrx +268 -0
- package/src/select/positioner/SelectPositionerContext.ts +26 -0
- package/src/select/positioner/SelectPositionerCssVars.ts +25 -0
- package/src/select/positioner/SelectPositionerDataAttributes.ts +24 -0
- package/src/select/root/SelectRoot.tsrx +752 -0
- package/src/select/root/SelectRootContext.ts +57 -0
- package/src/select/scroll-arrow/SelectScrollArrow.tsrx +236 -0
- package/src/select/scroll-down-arrow/SelectScrollDownArrow.tsrx +37 -0
- package/src/select/scroll-down-arrow/SelectScrollDownArrowDataAttributes.ts +24 -0
- package/src/select/scroll-up-arrow/SelectScrollUpArrow.tsrx +37 -0
- package/src/select/scroll-up-arrow/SelectScrollUpArrowDataAttributes.ts +24 -0
- package/src/select/separator/SelectSeparator.tsrx +34 -0
- package/src/select/store.ts +155 -0
- package/src/select/trigger/SelectTrigger.tsrx +270 -0
- package/src/select/trigger/SelectTriggerDataAttributes.ts +55 -0
- package/src/select/value/SelectValue.tsrx +112 -0
- package/src/select/value/SelectValueDataAttributes.ts +4 -0
- package/src/separator/Separator.tsrx +49 -0
- package/src/separator/SeparatorDataAttributes.ts +5 -0
- package/src/separator/index.ts +3 -0
- package/src/slider/control/SliderControl.tsrx +540 -0
- package/src/slider/control/SliderControlDataAttributes.ts +33 -0
- package/src/slider/index.parts.ts +7 -0
- package/src/slider/index.ts +9 -0
- package/src/slider/indicator/SliderIndicator.tsrx +102 -0
- package/src/slider/indicator/SliderIndicatorDataAttributes.ts +33 -0
- package/src/slider/label/SliderLabel.tsrx +71 -0
- package/src/slider/root/SliderRoot.tsrx +610 -0
- package/src/slider/root/SliderRootContext.ts +115 -0
- package/src/slider/root/SliderRootDataAttributes.ts +33 -0
- package/src/slider/root/stateAttributesMapping.ts +15 -0
- package/src/slider/thumb/SliderThumb.tsrx +567 -0
- package/src/slider/thumb/SliderThumbDataAttributes.ts +37 -0
- package/src/slider/thumb/prehydrationScript.min.ts +5 -0
- package/src/slider/track/SliderTrack.tsrx +49 -0
- package/src/slider/track/SliderTrackDataAttributes.ts +33 -0
- package/src/slider/utils/asc.ts +3 -0
- package/src/slider/utils/getMidpoint.ts +4 -0
- package/src/slider/utils/getPushedThumbValues.ts +60 -0
- package/src/slider/utils/getSliderValue.ts +22 -0
- package/src/slider/utils/resolveThumbCollision.ts +154 -0
- package/src/slider/utils/roundValueToStep.ts +21 -0
- package/src/slider/utils/test-utils.ts +18 -0
- package/src/slider/utils/validateMinimumDistance.ts +17 -0
- package/src/slider/value/SliderValue.tsrx +82 -0
- package/src/slider/value/SliderValueDataAttributes.ts +33 -0
- package/src/switch/index.parts.ts +2 -0
- package/src/switch/index.ts +4 -0
- package/src/switch/root/SwitchRoot.tsrx +335 -0
- package/src/switch/root/SwitchRootContext.ts +19 -0
- package/src/switch/root/SwitchRootDataAttributes.ts +44 -0
- package/src/switch/stateAttributesMapping.ts +15 -0
- package/src/switch/thumb/SwitchThumb.tsrx +40 -0
- package/src/switch/thumb/SwitchThumbDataAttributes.ts +44 -0
- package/src/tabs/index.parts.ts +5 -0
- package/src/tabs/index.ts +21 -0
- package/src/tabs/indicator/TabsIndicator.tsrx +292 -0
- package/src/tabs/indicator/TabsIndicatorCssVars.ts +30 -0
- package/src/tabs/indicator/TabsIndicatorDataAttributes.ts +10 -0
- package/src/tabs/indicator/prehydrationScript.min.ts +5 -0
- package/src/tabs/list/TabsList.tsrx +153 -0
- package/src/tabs/list/TabsListContext.ts +23 -0
- package/src/tabs/list/TabsListDataAttributes.ts +10 -0
- package/src/tabs/panel/TabsPanel.tsrx +145 -0
- package/src/tabs/panel/TabsPanelDataAttributes.ts +28 -0
- package/src/tabs/root/TabsRoot.tsrx +477 -0
- package/src/tabs/root/TabsRootContext.ts +55 -0
- package/src/tabs/root/TabsRootDataAttributes.ts +10 -0
- package/src/tabs/root/stateAttributesMapping.ts +9 -0
- package/src/tabs/tab/TabsTab.tsrx +280 -0
- package/src/tabs/tab/TabsTabDataAttributes.ts +18 -0
- package/src/toast/action/ToastAction.tsrx +72 -0
- package/src/toast/action/ToastActionDataAttributes.ts +5 -0
- package/src/toast/arrow/ToastArrow.tsrx +57 -0
- package/src/toast/arrow/ToastArrowDataAttributes.ts +16 -0
- package/src/toast/close/ToastClose.tsrx +79 -0
- package/src/toast/close/ToastCloseDataAttributes.ts +5 -0
- package/src/toast/content/ToastContent.tsrx +76 -0
- package/src/toast/content/ToastContentDataAttributes.ts +10 -0
- package/src/toast/createToastManager.ts +104 -0
- package/src/toast/description/ToastDescription.tsrx +54 -0
- package/src/toast/description/ToastDescriptionDataAttributes.ts +5 -0
- package/src/toast/index.parts.ts +14 -0
- package/src/toast/index.ts +18 -0
- package/src/toast/portal/ToastPortal.tsrx +33 -0
- package/src/toast/positioner/ToastPositioner.tsrx +154 -0
- package/src/toast/positioner/ToastPositionerContext.ts +23 -0
- package/src/toast/positioner/ToastPositionerCssVars.ts +25 -0
- package/src/toast/positioner/ToastPositionerDataAttributes.ts +16 -0
- package/src/toast/provider/ToastProvider.tsrx +109 -0
- package/src/toast/provider/ToastProviderContext.ts +16 -0
- package/src/toast/root/ToastRoot.tsrx +563 -0
- package/src/toast/root/ToastRootContext.ts +25 -0
- package/src/toast/root/ToastRootCssVars.ts +25 -0
- package/src/toast/root/ToastRootDataAttributes.ts +35 -0
- package/src/toast/store.ts +527 -0
- package/src/toast/title/ToastTitle.tsrx +53 -0
- package/src/toast/title/ToastTitleDataAttributes.ts +5 -0
- package/src/toast/useToastManager.ts +150 -0
- package/src/toast/utils/focusVisible.ts +1 -0
- package/src/toast/utils/isRenderableNode.ts +18 -0
- package/src/toast/utils/resolvePromiseOptions.ts +22 -0
- package/src/toast/utils/test-utils.tsrx +39 -0
- package/src/toast/utils/useToastLabelPart.ts +53 -0
- package/src/toast/viewport/ToastViewport.tsrx +293 -0
- package/src/toast/viewport/ToastViewportCssVars.ts +5 -0
- package/src/toast/viewport/ToastViewportDataAttributes.ts +5 -0
- package/src/toggle/Toggle.tsrx +198 -0
- package/src/toggle/ToggleDataAttributes.ts +8 -0
- package/src/toggle/index.ts +3 -0
- package/src/toggle-group/ToggleGroup.tsrx +201 -0
- package/src/toggle-group/ToggleGroupContext.ts +28 -0
- package/src/toggle-group/ToggleGroupDataAttributes.ts +13 -0
- package/src/toggle-group/index.ts +3 -0
- package/src/toolbar/button/ToolbarButton.tsrx +108 -0
- package/src/toolbar/button/ToolbarButtonDataAttributes.ts +13 -0
- package/src/toolbar/group/ToolbarGroup.tsrx +69 -0
- package/src/toolbar/group/ToolbarGroupContext.ts +13 -0
- package/src/toolbar/group/ToolbarGroupDataAttributes.ts +9 -0
- package/src/toolbar/index.parts.ts +8 -0
- package/src/toolbar/index.ts +9 -0
- package/src/toolbar/input/ToolbarInput.tsrx +108 -0
- package/src/toolbar/input/ToolbarInputDataAttributes.ts +13 -0
- package/src/toolbar/link/ToolbarLink.tsrx +64 -0
- package/src/toolbar/link/ToolbarLinkDataAttributes.ts +5 -0
- package/src/toolbar/root/ToolbarRoot.tsrx +121 -0
- package/src/toolbar/root/ToolbarRootContext.ts +24 -0
- package/src/toolbar/root/ToolbarRootDataAttributes.ts +9 -0
- package/src/toolbar/separator/ToolbarSeparator.tsrx +37 -0
- package/src/toolbar/separator/ToolbarSeparatorDataAttributes.ts +5 -0
- package/src/tooltip/arrow/TooltipArrow.tsrx +76 -0
- package/src/tooltip/arrow/TooltipArrowDataAttributes.ts +29 -0
- package/src/tooltip/index.parts.ts +12 -0
- package/src/tooltip/index.ts +13 -0
- package/src/tooltip/popup/TooltipPopup.tsrx +108 -0
- package/src/tooltip/popup/TooltipPopupDataAttributes.ts +33 -0
- package/src/tooltip/portal/TooltipPortal.tsrx +54 -0
- package/src/tooltip/portal/TooltipPortalContext.ts +13 -0
- package/src/tooltip/positioner/TooltipPositioner.tsrx +150 -0
- package/src/tooltip/positioner/TooltipPositionerContext.ts +23 -0
- package/src/tooltip/positioner/TooltipPositionerCssVars.ts +37 -0
- package/src/tooltip/positioner/TooltipPositionerDataAttributes.ts +24 -0
- package/src/tooltip/provider/TooltipProvider.tsrx +49 -0
- package/src/tooltip/provider/TooltipProviderContext.ts +12 -0
- package/src/tooltip/root/TooltipRoot.tsrx +282 -0
- package/src/tooltip/root/TooltipRootContext.ts +21 -0
- package/src/tooltip/store/TooltipHandle.ts +54 -0
- package/src/tooltip/store/TooltipStore.ts +148 -0
- package/src/tooltip/trigger/TooltipTrigger.tsrx +351 -0
- package/src/tooltip/trigger/TooltipTriggerDataAttributes.ts +10 -0
- package/src/tooltip/utils/constants.ts +1 -0
- package/src/tooltip/viewport/TooltipViewport.tsrx +75 -0
- package/src/tooltip/viewport/TooltipViewportCssVars.ts +12 -0
- package/src/tooltip/viewport/TooltipViewportDataAttributes.ts +24 -0
- package/src/types/index.ts +29 -0
- package/src/unstable-use-media-query/index.ts +94 -0
- package/src/use-render/index.ts +2 -0
- package/src/use-render/useRender.ts +113 -0
- package/src/utils/CommonPopupCssVars.ts +12 -0
- package/src/utils/CommonPopupDataAttributes.ts +38 -0
- package/src/utils/CommonPositionerCssVars.ts +37 -0
- package/src/utils/CommonTriggerDataAttributes.ts +9 -0
- package/src/utils/CommonViewportDataAttributes.ts +7 -0
- package/src/utils/FloatingPortalLite.tsrx +51 -0
- package/src/utils/FocusGuard.tsrx +44 -0
- package/src/utils/InternalBackdrop.tsrx +50 -0
- package/src/utils/NullStore.ts +26 -0
- package/src/utils/adaptiveOriginConstants.ts +12 -0
- package/src/utils/adaptiveOriginMiddleware.ts +5 -14
- package/src/utils/closePart.tsrx +37 -0
- package/src/utils/collapsibleOpenStateMapping.ts +28 -23
- package/src/utils/dispatchClickWithModifiers.ts +36 -0
- package/src/utils/getCssDimensions.ts +11 -15
- package/src/utils/getElementAtPoint.ts +12 -0
- package/src/utils/getElementTransform.ts +33 -0
- package/src/utils/getPseudoElementBounds.ts +22 -7
- package/src/utils/hideMiddleware.ts +15 -12
- package/src/utils/listbox-separator/ListboxSeparator.tsrx +47 -0
- package/src/utils/mergeStyles.ts +15 -0
- package/src/utils/popupStateMapping.ts +57 -35
- package/src/utils/popups/index.ts +4 -8
- package/src/utils/popups/inlineRect.ts +19 -16
- package/src/utils/popups/popupHandle.ts +291 -0
- package/src/utils/popups/popupStoreUtils.ts +482 -320
- package/src/utils/popups/popupTriggerMap.ts +102 -28
- package/src/utils/popups/store.ts +146 -66
- package/src/utils/popups/usePopupHandleStore.ts +37 -0
- package/src/utils/popups/useTriggerFocusGuards.ts +36 -21
- package/src/utils/resolveAriaLabelledBy.ts +2 -1
- package/src/utils/resolveClassName.ts +8 -3
- package/src/utils/resolveRef.ts +8 -4
- package/src/utils/resolveStyle.ts +10 -6
- package/src/utils/scrollEdges.ts +33 -0
- package/src/utils/scrollable.ts +77 -0
- package/src/utils/styles.tsrx +15 -0
- package/src/utils/useAnchoredPopupScrollLock.ts +31 -37
- package/src/utils/useFocusableWhenDisabled.ts +81 -65
- package/src/utils/useIsHydrating.ts +12 -15
- package/src/utils/useMixedToggleClickHandler.ts +45 -60
- package/src/utils/useOpenInteractionType.ts +43 -45
- package/src/utils/usePopupAutoResize.ts +198 -207
- package/src/utils/usePopupViewport.tsrx +396 -0
- package/src/utils/usePositioner.tsrx +50 -0
- package/src/utils/useRegisteredLabelId.ts +16 -27
- package/src/utils/useSwipeDismiss.ts +1161 -0
- package/src/utils/valueToPercent.ts +1 -2
- package/dist/cjs/accordion.cjs +0 -497
- package/dist/cjs/alert-dialog.cjs +0 -65
- package/dist/cjs/avatar.cjs +0 -234
- package/dist/cjs/button.cjs +0 -54
- package/dist/cjs/checkbox-group.cjs +0 -165
- package/dist/cjs/checkbox.cjs +0 -475
- package/dist/cjs/collapsible.cjs +0 -700
- package/dist/cjs/context-menu.cjs +0 -257
- package/dist/cjs/csp-provider.cjs +0 -40
- package/dist/cjs/dialog.cjs +0 -741
- package/dist/cjs/direction-provider.cjs +0 -35
- package/dist/cjs/field.cjs +0 -559
- package/dist/cjs/fieldset.cjs +0 -101
- package/dist/cjs/form.cjs +0 -177
- package/dist/cjs/index.cjs +0 -133
- package/dist/cjs/input.cjs +0 -32
- package/dist/cjs/internal.cjs +0 -43
- package/dist/cjs/menu.cjs +0 -2420
- package/dist/cjs/menubar.cjs +0 -132
- package/dist/cjs/merge-props.cjs +0 -34
- package/dist/cjs/meter.cjs +0 -169
- package/dist/cjs/number-field.cjs +0 -812
- package/dist/cjs/popover.cjs +0 -966
- package/dist/cjs/preview-card.cjs +0 -632
- package/dist/cjs/progress.cjs +0 -206
- package/dist/cjs/radio-group.cjs +0 -264
- package/dist/cjs/radio.cjs +0 -327
- package/dist/cjs/separator.cjs +0 -44
- package/dist/cjs/slider.cjs +0 -1384
- package/dist/cjs/switch.cjs +0 -306
- package/dist/cjs/tabs.cjs +0 -727
- package/dist/cjs/toast.cjs +0 -1766
- package/dist/cjs/toggle-group.cjs +0 -118
- package/dist/cjs/toggle.cjs +0 -121
- package/dist/cjs/tooltip.cjs +0 -835
- package/dist/cjs/unstable-use-media-query.cjs +0 -79
- package/dist/cjs/use-render.cjs +0 -40
- package/dist/cjs/utils/CSPContext.cjs +0 -38
- package/dist/cjs/utils/CheckboxGroupContext.cjs +0 -40
- package/dist/cjs/utils/CompositeRootContext.cjs +0 -40
- package/dist/cjs/utils/ContextMenuRootContext.cjs +0 -42
- package/dist/cjs/utils/DirectionContext.cjs +0 -34
- package/dist/cjs/utils/FloatingPortalLite.cjs +0 -47
- package/dist/cjs/utils/InternalBackdrop.cjs +0 -49
- package/dist/cjs/utils/MenubarContext.cjs +0 -40
- package/dist/cjs/utils/RadioGroupContext.cjs +0 -34
- package/dist/cjs/utils/RequestQueue.cjs +0 -111
- package/dist/cjs/utils/TimeoutManager.cjs +0 -50
- package/dist/cjs/utils/ToggleGroupContext.cjs +0 -40
- package/dist/cjs/utils/adaptiveOriginMiddleware.cjs +0 -94
- package/dist/cjs/utils/addEventListener.cjs +0 -33
- package/dist/cjs/utils/areArraysEqual.cjs +0 -30
- package/dist/cjs/utils/clamp.cjs +0 -30
- package/dist/cjs/utils/closePart.cjs +0 -70
- package/dist/cjs/utils/collapsibleOpenStateMapping.cjs +0 -42
- package/dist/cjs/utils/composeRefs.cjs +0 -68
- package/dist/cjs/utils/composite/CompositeItem.cjs +0 -59
- package/dist/cjs/utils/composite/CompositeList.cjs +0 -177
- package/dist/cjs/utils/composite/CompositeListContext.cjs +0 -43
- package/dist/cjs/utils/composite/CompositeRoot.cjs +0 -118
- package/dist/cjs/utils/composite/grid-utils.cjs +0 -343
- package/dist/cjs/utils/composite/gridNavigation.cjs +0 -92
- package/dist/cjs/utils/composite/keys.cjs +0 -174
- package/dist/cjs/utils/composite/list-utils.cjs +0 -90
- package/dist/cjs/utils/composite/useCompositeItem.cjs +0 -63
- package/dist/cjs/utils/composite/useCompositeListItem.cjs +0 -104
- package/dist/cjs/utils/composite/useCompositeRoot.cjs +0 -263
- package/dist/cjs/utils/constants.cjs +0 -56
- package/dist/cjs/utils/contains.cjs +0 -46
- package/dist/cjs/utils/createChangeEventDetails.cjs +0 -92
- package/dist/cjs/utils/dom.cjs +0 -35
- package/dist/cjs/utils/empty.cjs +0 -34
- package/dist/cjs/utils/field/FieldItemContext.cjs +0 -34
- package/dist/cjs/utils/field/FieldRootContext.cjs +0 -80
- package/dist/cjs/utils/field/FormContext.cjs +0 -41
- package/dist/cjs/utils/field/LabelableContext.cjs +0 -43
- package/dist/cjs/utils/field/LabelableProvider.cjs +0 -115
- package/dist/cjs/utils/field/constants.cjs +0 -68
- package/dist/cjs/utils/field/getCombinedFieldValidityData.cjs +0 -36
- package/dist/cjs/utils/field/useAriaLabelledBy.cjs +0 -87
- package/dist/cjs/utils/field/useFieldControlRegistration.cjs +0 -165
- package/dist/cjs/utils/field/useFieldValidation.cjs +0 -285
- package/dist/cjs/utils/field/useLabel.cjs +0 -99
- package/dist/cjs/utils/field/useLabelableId.cjs +0 -105
- package/dist/cjs/utils/field/useRegisterFieldControl.cjs +0 -60
- package/dist/cjs/utils/findRootOwnerId.cjs +0 -37
- package/dist/cjs/utils/floating/FloatingDelayGroup.cjs +0 -219
- package/dist/cjs/utils/floating/FloatingFocusManager.cjs +0 -685
- package/dist/cjs/utils/floating/FloatingPortal.cjs +0 -230
- package/dist/cjs/utils/floating/FloatingRootStore.cjs +0 -87
- package/dist/cjs/utils/floating/FloatingTree.cjs +0 -86
- package/dist/cjs/utils/floating/FloatingTreeStore.cjs +0 -41
- package/dist/cjs/utils/floating/FocusGuard.cjs +0 -54
- package/dist/cjs/utils/floating/composite.cjs +0 -42
- package/dist/cjs/utils/floating/constants.cjs +0 -34
- package/dist/cjs/utils/floating/createAttribute.cjs +0 -30
- package/dist/cjs/utils/floating/createEventEmitter.cjs +0 -44
- package/dist/cjs/utils/floating/element.cjs +0 -103
- package/dist/cjs/utils/floating/enqueueFocus.cjs +0 -51
- package/dist/cjs/utils/floating/event.cjs +0 -65
- package/dist/cjs/utils/floating/getEmptyRootContext.cjs +0 -42
- package/dist/cjs/utils/floating/gridNavigation.cjs +0 -45
- package/dist/cjs/utils/floating/markOthers.cjs +0 -181
- package/dist/cjs/utils/floating/nodes.cjs +0 -48
- package/dist/cjs/utils/floating/safePolygon.cjs +0 -301
- package/dist/cjs/utils/floating/tabbable.cjs +0 -242
- package/dist/cjs/utils/floating/types.cjs +0 -16
- package/dist/cjs/utils/floating/useClick.cjs +0 -153
- package/dist/cjs/utils/floating/useClientPoint.cjs +0 -225
- package/dist/cjs/utils/floating/useDismiss.cjs +0 -506
- package/dist/cjs/utils/floating/useFloating.cjs +0 -186
- package/dist/cjs/utils/floating/useFloatingRootContext.cjs +0 -75
- package/dist/cjs/utils/floating/useFocus.cjs +0 -184
- package/dist/cjs/utils/floating/useHoverFloatingInteraction.cjs +0 -231
- package/dist/cjs/utils/floating/useHoverInteractionSharedState.cjs +0 -113
- package/dist/cjs/utils/floating/useHoverReferenceInteraction.cjs +0 -380
- package/dist/cjs/utils/floating/useHoverShared.cjs +0 -65
- package/dist/cjs/utils/floating/useListNavigation.cjs +0 -665
- package/dist/cjs/utils/floating/useSyncedFloatingRootContext.cjs +0 -91
- package/dist/cjs/utils/floating/useTypeahead.cjs +0 -187
- package/dist/cjs/utils/formatNumber.cjs +0 -60
- package/dist/cjs/utils/generateId.cjs +0 -32
- package/dist/cjs/utils/getCssDimensions.cjs +0 -42
- package/dist/cjs/utils/getDefaultFormSubmitter.cjs +0 -42
- package/dist/cjs/utils/getDisabledMountTransitionStyles.cjs +0 -32
- package/dist/cjs/utils/getElementRef.cjs +0 -35
- package/dist/cjs/utils/getPseudoElementBounds.cjs +0 -56
- package/dist/cjs/utils/getStateAttributesProps.cjs +0 -46
- package/dist/cjs/utils/hideMiddleware.cjs +0 -42
- package/dist/cjs/utils/inertValue.cjs +0 -30
- package/dist/cjs/utils/matchesFocusVisible.cjs +0 -38
- package/dist/cjs/utils/mergeCleanups.cjs +0 -37
- package/dist/cjs/utils/mergeObjects.cjs +0 -39
- package/dist/cjs/utils/mergeProps.cjs +0 -182
- package/dist/cjs/utils/noop.cjs +0 -29
- package/dist/cjs/utils/number/constants.cjs +0 -34
- package/dist/cjs/utils/number/parse.cjs +0 -258
- package/dist/cjs/utils/number/types.cjs +0 -16
- package/dist/cjs/utils/number/validate.cjs +0 -104
- package/dist/cjs/utils/owner.cjs +0 -35
- package/dist/cjs/utils/platform.cjs +0 -83
- package/dist/cjs/utils/popupStateMapping.cjs +0 -74
- package/dist/cjs/utils/popups/index.cjs +0 -39
- package/dist/cjs/utils/popups/inlineRect.cjs +0 -218
- package/dist/cjs/utils/popups/popupStoreUtils.cjs +0 -379
- package/dist/cjs/utils/popups/popupTriggerMap.cjs +0 -76
- package/dist/cjs/utils/popups/store.cjs +0 -118
- package/dist/cjs/utils/popups/useTriggerFocusGuards.cjs +0 -72
- package/dist/cjs/utils/resolveAriaLabelledBy.cjs +0 -35
- package/dist/cjs/utils/resolveClassName.cjs +0 -30
- package/dist/cjs/utils/resolveRef.cjs +0 -33
- package/dist/cjs/utils/resolveStyle.cjs +0 -30
- package/dist/cjs/utils/serializeValue.cjs +0 -40
- package/dist/cjs/utils/slider/asc.cjs +0 -30
- package/dist/cjs/utils/slider/getMidpoint.cjs +0 -34
- package/dist/cjs/utils/slider/getPushedThumbValues.cjs +0 -73
- package/dist/cjs/utils/slider/getSliderValue.cjs +0 -42
- package/dist/cjs/utils/slider/replaceArrayItemAtIndex.cjs +0 -33
- package/dist/cjs/utils/slider/resolveThumbCollision.cjs +0 -155
- package/dist/cjs/utils/slider/roundValueToStep.cjs +0 -45
- package/dist/cjs/utils/slider/validateMinimumDistance.cjs +0 -40
- package/dist/cjs/utils/slider/valueArrayToPercentages.cjs +0 -36
- package/dist/cjs/utils/store/ReactStore.cjs +0 -135
- package/dist/cjs/utils/store/Store.cjs +0 -83
- package/dist/cjs/utils/store/createSelector.cjs +0 -74
- package/dist/cjs/utils/store/useStore.cjs +0 -51
- package/dist/cjs/utils/stringifyLocale.cjs +0 -36
- package/dist/cjs/utils/useAnchorPositioning.cjs +0 -421
- package/dist/cjs/utils/useAnchoredPopupScrollLock.cjs +0 -59
- package/dist/cjs/utils/useAnimationFrame.cjs +0 -93
- package/dist/cjs/utils/useAnimationsFinished.cjs +0 -98
- package/dist/cjs/utils/useBaseUiId.cjs +0 -36
- package/dist/cjs/utils/useButton.cjs +0 -177
- package/dist/cjs/utils/useCheckboxGroupParent.cjs +0 -114
- package/dist/cjs/utils/useControlled.cjs +0 -47
- package/dist/cjs/utils/useEnhancedClickHandler.cjs +0 -60
- package/dist/cjs/utils/useFocusableWhenDisabled.cjs +0 -77
- package/dist/cjs/utils/useForcedRerendering.cjs +0 -35
- package/dist/cjs/utils/useIsHydrating.cjs +0 -44
- package/dist/cjs/utils/useMixedToggleClickHandler.cjs +0 -66
- package/dist/cjs/utils/useOnFirstRender.cjs +0 -35
- package/dist/cjs/utils/useOnMount.cjs +0 -36
- package/dist/cjs/utils/useOpenChangeComplete.cjs +0 -57
- package/dist/cjs/utils/useOpenInteractionType.cjs +0 -69
- package/dist/cjs/utils/usePopupAutoResize.cjs +0 -191
- package/dist/cjs/utils/usePopupViewport.cjs +0 -283
- package/dist/cjs/utils/usePositioner.cjs +0 -53
- package/dist/cjs/utils/usePressAndHold.cjs +0 -51
- package/dist/cjs/utils/usePreviousValue.cjs +0 -47
- package/dist/cjs/utils/useRefWithInit.cjs +0 -41
- package/dist/cjs/utils/useRegisteredLabelId.cjs +0 -48
- package/dist/cjs/utils/useRenderElement.cjs +0 -106
- package/dist/cjs/utils/useScrollLock.cjs +0 -232
- package/dist/cjs/utils/useStableCallback.cjs +0 -41
- package/dist/cjs/utils/useTimeout.cjs +0 -66
- package/dist/cjs/utils/useTransitionStatus.cjs +0 -116
- package/dist/cjs/utils/useValueAsRef.cjs +0 -43
- package/dist/cjs/utils/useValueChanged.cjs +0 -55
- package/dist/cjs/utils/valueToPercent.cjs +0 -30
- package/dist/cjs/utils/visuallyHidden.cjs +0 -49
- package/src/accordion.ts +0 -615
- package/src/alert-dialog.ts +0 -49
- package/src/avatar.ts +0 -308
- package/src/button.ts +0 -63
- package/src/checkbox-group.ts +0 -183
- package/src/checkbox.ts +0 -576
- package/src/collapsible.ts +0 -916
- package/src/context-menu.ts +0 -317
- package/src/csp-provider.ts +0 -37
- package/src/dialog.ts +0 -930
- package/src/direction-provider.ts +0 -32
- package/src/field.ts +0 -650
- package/src/fieldset.ts +0 -150
- package/src/form.ts +0 -183
- package/src/input.ts +0 -9
- package/src/internal.ts +0 -29
- package/src/menu.ts +0 -3387
- package/src/menubar.ts +0 -160
- package/src/merge-props.ts +0 -2
- package/src/meter.ts +0 -211
- package/src/number-field.ts +0 -951
- package/src/popover.ts +0 -1282
- package/src/preview-card.ts +0 -812
- package/src/progress.ts +0 -267
- package/src/radio-group.ts +0 -285
- package/src/radio.ts +0 -393
- package/src/separator.ts +0 -43
- package/src/slider.ts +0 -1624
- package/src/switch.ts +0 -363
- package/src/tabs.ts +0 -947
- package/src/toast.ts +0 -2462
- package/src/toggle-group.ts +0 -136
- package/src/toggle.ts +0 -145
- package/src/tooltip.ts +0 -1053
- package/src/unstable-use-media-query.ts +0 -101
- package/src/use-render.ts +0 -41
- package/src/utils/CSPContext.ts +0 -21
- package/src/utils/CheckboxGroupContext.ts +0 -30
- package/src/utils/CompositeRootContext.ts +0 -27
- package/src/utils/ContextMenuRootContext.ts +0 -44
- package/src/utils/DirectionContext.ts +0 -15
- package/src/utils/FloatingPortalLite.ts +0 -34
- package/src/utils/InternalBackdrop.ts +0 -28
- package/src/utils/MenubarContext.ts +0 -38
- package/src/utils/RadioGroupContext.ts +0 -27
- package/src/utils/RequestQueue.ts +0 -132
- package/src/utils/TimeoutManager.ts +0 -32
- package/src/utils/ToggleGroupContext.ts +0 -33
- package/src/utils/addEventListener.ts +0 -13
- package/src/utils/areArraysEqual.ts +0 -11
- package/src/utils/clamp.ts +0 -8
- package/src/utils/closePart.ts +0 -61
- package/src/utils/composeRefs.ts +0 -53
- package/src/utils/composite/CompositeItem.ts +0 -56
- package/src/utils/composite/CompositeList.ts +0 -190
- package/src/utils/composite/CompositeListContext.ts +0 -23
- package/src/utils/composite/CompositeRoot.ts +0 -132
- package/src/utils/composite/grid-utils.ts +0 -437
- package/src/utils/composite/gridNavigation.ts +0 -127
- package/src/utils/composite/keys.ts +0 -179
- package/src/utils/composite/list-utils.ts +0 -106
- package/src/utils/composite/useCompositeItem.ts +0 -58
- package/src/utils/composite/useCompositeListItem.ts +0 -108
- package/src/utils/composite/useCompositeRoot.ts +0 -344
- package/src/utils/constants.ts +0 -41
- package/src/utils/contains.ts +0 -22
- package/src/utils/createChangeEventDetails.ts +0 -95
- package/src/utils/dom.ts +0 -9
- package/src/utils/empty.ts +0 -7
- package/src/utils/field/FieldItemContext.ts +0 -13
- package/src/utils/field/FieldRootContext.ts +0 -98
- package/src/utils/field/FormContext.ts +0 -37
- package/src/utils/field/LabelableContext.ts +0 -32
- package/src/utils/field/LabelableProvider.ts +0 -108
- package/src/utils/field/constants.ts +0 -80
- package/src/utils/field/getCombinedFieldValidityData.ts +0 -16
- package/src/utils/field/useAriaLabelledBy.ts +0 -86
- package/src/utils/field/useFieldControlRegistration.ts +0 -179
- package/src/utils/field/useFieldValidation.ts +0 -311
- package/src/utils/field/useLabel.ts +0 -95
- package/src/utils/field/useLabelableId.ts +0 -99
- package/src/utils/field/useRegisterFieldControl.ts +0 -41
- package/src/utils/findRootOwnerId.ts +0 -19
- package/src/utils/floating/FloatingDelayGroup.ts +0 -280
- package/src/utils/floating/FloatingFocusManager.ts +0 -837
- package/src/utils/floating/FloatingPortal.ts +0 -280
- package/src/utils/floating/FloatingRootStore.ts +0 -127
- package/src/utils/floating/FloatingTree.ts +0 -70
- package/src/utils/floating/FloatingTreeStore.ts +0 -22
- package/src/utils/floating/FocusGuard.ts +0 -34
- package/src/utils/floating/composite.ts +0 -20
- package/src/utils/floating/constants.ts +0 -8
- package/src/utils/floating/createAttribute.ts +0 -4
- package/src/utils/floating/createEventEmitter.ts +0 -20
- package/src/utils/floating/element.ts +0 -88
- package/src/utils/floating/enqueueFocus.ts +0 -38
- package/src/utils/floating/event.ts +0 -53
- package/src/utils/floating/getEmptyRootContext.ts +0 -19
- package/src/utils/floating/gridNavigation.ts +0 -54
- package/src/utils/floating/markOthers.ts +0 -197
- package/src/utils/floating/nodes.ts +0 -31
- package/src/utils/floating/safePolygon.ts +0 -448
- package/src/utils/floating/tabbable.ts +0 -260
- package/src/utils/floating/types.ts +0 -57
- package/src/utils/floating/useClick.ts +0 -174
- package/src/utils/floating/useClientPoint.ts +0 -276
- package/src/utils/floating/useDismiss.ts +0 -639
- package/src/utils/floating/useFloating.ts +0 -198
- package/src/utils/floating/useFloatingRootContext.ts +0 -81
- package/src/utils/floating/useFocus.ts +0 -248
- package/src/utils/floating/useHoverFloatingInteraction.ts +0 -302
- package/src/utils/floating/useHoverInteractionSharedState.ts +0 -139
- package/src/utils/floating/useHoverReferenceInteraction.ts +0 -510
- package/src/utils/floating/useHoverShared.ts +0 -78
- package/src/utils/floating/useListNavigation.ts +0 -1018
- package/src/utils/floating/useSyncedFloatingRootContext.ts +0 -93
- package/src/utils/floating/useTypeahead.ts +0 -287
- package/src/utils/formatNumber.ts +0 -44
- package/src/utils/generateId.ts +0 -10
- package/src/utils/getDefaultFormSubmitter.ts +0 -19
- package/src/utils/getDisabledMountTransitionStyles.ts +0 -12
- package/src/utils/getElementRef.ts +0 -13
- package/src/utils/getStateAttributesProps.ts +0 -34
- package/src/utils/inertValue.ts +0 -5
- package/src/utils/matchesFocusVisible.ts +0 -16
- package/src/utils/mergeCleanups.ts +0 -13
- package/src/utils/mergeObjects.ts +0 -18
- package/src/utils/mergeProps.ts +0 -206
- package/src/utils/noop.ts +0 -4
- package/src/utils/number/constants.ts +0 -4
- package/src/utils/number/parse.ts +0 -227
- package/src/utils/number/types.ts +0 -21
- package/src/utils/number/validate.ts +0 -129
- package/src/utils/owner.ts +0 -9
- package/src/utils/platform.ts +0 -69
- package/src/utils/serializeValue.ts +0 -15
- package/src/utils/slider/asc.ts +0 -4
- package/src/utils/slider/getMidpoint.ts +0 -14
- package/src/utils/slider/getPushedThumbValues.ts +0 -73
- package/src/utils/slider/getSliderValue.ts +0 -27
- package/src/utils/slider/replaceArrayItemAtIndex.ts +0 -8
- package/src/utils/slider/resolveThumbCollision.ts +0 -178
- package/src/utils/slider/roundValueToStep.ts +0 -22
- package/src/utils/slider/validateMinimumDistance.ts +0 -22
- package/src/utils/slider/valueArrayToPercentages.ts +0 -11
- package/src/utils/store/ReactStore.ts +0 -168
- package/src/utils/store/Store.ts +0 -84
- package/src/utils/store/createSelector.ts +0 -66
- package/src/utils/store/useStore.ts +0 -49
- package/src/utils/stringifyLocale.ts +0 -10
- package/src/utils/useAnchorPositioning.ts +0 -581
- package/src/utils/useAnimationFrame.ts +0 -82
- package/src/utils/useAnimationsFinished.ts +0 -100
- package/src/utils/useBaseUiId.ts +0 -19
- package/src/utils/useButton.ts +0 -217
- package/src/utils/useCheckboxGroupParent.ts +0 -113
- package/src/utils/useControlled.ts +0 -42
- package/src/utils/useEnhancedClickHandler.ts +0 -47
- package/src/utils/useForcedRerendering.ts +0 -11
- package/src/utils/useOnFirstRender.ts +0 -11
- package/src/utils/useOnMount.ts +0 -17
- package/src/utils/useOpenChangeComplete.ts +0 -46
- package/src/utils/usePopupViewport.ts +0 -375
- package/src/utils/usePositioner.ts +0 -48
- package/src/utils/usePressAndHold.ts +0 -35
- package/src/utils/usePreviousValue.ts +0 -33
- package/src/utils/useRefWithInit.ts +0 -23
- package/src/utils/useRenderElement.ts +0 -164
- package/src/utils/useScrollLock.ts +0 -253
- package/src/utils/useStableCallback.ts +0 -26
- package/src/utils/useTimeout.ts +0 -51
- package/src/utils/useTransitionStatus.ts +0 -115
- package/src/utils/useValueAsRef.ts +0 -25
- package/src/utils/useValueChanged.ts +0 -37
- package/src/utils/visuallyHidden.ts +0 -24
|
@@ -0,0 +1,1970 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'octane';
|
|
3
|
+
import { useControlled } from '@octanejs/base-ui-utils/useControlled';
|
|
4
|
+
import { useIsoLayoutEffect } from '@octanejs/base-ui-utils/useIsoLayoutEffect';
|
|
5
|
+
import { useOnFirstRender } from '@octanejs/base-ui-utils/useOnFirstRender';
|
|
6
|
+
import { useStableCallback } from '@octanejs/base-ui-utils/useStableCallback';
|
|
7
|
+
import { useMergedRefs } from '@octanejs/base-ui-utils/useMergedRefs';
|
|
8
|
+
import { useValueAsRef } from '@octanejs/base-ui-utils/useValueAsRef';
|
|
9
|
+
import { visuallyHidden, visuallyHiddenInput } from '@octanejs/base-ui-utils/visuallyHidden';
|
|
10
|
+
import { useRefWithInit } from '@octanejs/base-ui-utils/useRefWithInit';
|
|
11
|
+
import { ReactStore } from '@octanejs/base-ui-utils/store';
|
|
12
|
+
import { EMPTY_ARRAY, EMPTY_OBJECT } from '@octanejs/base-ui-utils/empty';
|
|
13
|
+
import { isHTMLElement } from '@floating-ui/utils/dom';
|
|
14
|
+
import {
|
|
15
|
+
type ElementProps,
|
|
16
|
+
getOverflowAncestors,
|
|
17
|
+
useDismiss,
|
|
18
|
+
useFloatingRootContext,
|
|
19
|
+
useListNavigation,
|
|
20
|
+
useClick,
|
|
21
|
+
} from '../../floating-ui-react';
|
|
22
|
+
import { gridNavigation } from '../../floating-ui-react/hooks/gridNavigation';
|
|
23
|
+
import { contains, getTarget } from '../../floating-ui-react/utils';
|
|
24
|
+
import {
|
|
25
|
+
createChangeEventDetails,
|
|
26
|
+
createGenericEventDetails,
|
|
27
|
+
type BaseUIChangeEventDetails,
|
|
28
|
+
type BaseUIGenericEventDetails,
|
|
29
|
+
} from '../../internals/createBaseUIEventDetails';
|
|
30
|
+
import { REASONS } from '../../internals/reasons';
|
|
31
|
+
import {
|
|
32
|
+
ComboboxFloatingContext,
|
|
33
|
+
ComboboxDerivedItemsContext,
|
|
34
|
+
ComboboxHasItemsContext,
|
|
35
|
+
ComboboxRootContext,
|
|
36
|
+
ComboboxInputValueContext,
|
|
37
|
+
} from './ComboboxRootContext.tsrx';
|
|
38
|
+
import { selectors, type ComboboxStoreContext, type State as StoreState } from '../store';
|
|
39
|
+
import { useOpenChangeComplete } from '../../internals/useOpenChangeComplete.tsrx';
|
|
40
|
+
import { useFieldRootContext } from '../../internals/field-root-context/FieldRootContext';
|
|
41
|
+
import { useRegisterFieldControl } from '../../internals/field-register-control/useRegisterFieldControl';
|
|
42
|
+
import { useFormContext } from '../../internals/form-context/FormContext';
|
|
43
|
+
import { useLabelableId } from '../../internals/labelable-provider/useLabelableId';
|
|
44
|
+
import { createCollatorItemFilter, type FilterItemToString } from './utils';
|
|
45
|
+
import { useCoreFilter } from './utils/useFilter';
|
|
46
|
+
import { useTransitionStatus } from '../../internals/useTransitionStatus';
|
|
47
|
+
import { useOpenInteractionType } from '../../utils/useOpenInteractionType';
|
|
48
|
+
import { isScrollableY } from '../../utils/scrollable';
|
|
49
|
+
import type { BaseUIEvent, HTMLProps } from '../../internals/types';
|
|
50
|
+
import { useValueChanged } from '../../internals/useValueChanged';
|
|
51
|
+
import { NOOP } from '../../internals/noop';
|
|
52
|
+
import { FOCUSABLE_POPUP_PROPS } from '../../utils/popups';
|
|
53
|
+
import { mergeProps } from '../../merge-props';
|
|
54
|
+
import {
|
|
55
|
+
stringifyAsLabel,
|
|
56
|
+
stringifyAsValue,
|
|
57
|
+
type Group,
|
|
58
|
+
flattenLeafItems,
|
|
59
|
+
isGroupedItems,
|
|
60
|
+
} from '../../internals/resolveValueLabel.tsrx';
|
|
61
|
+
import {
|
|
62
|
+
compareItemEquality,
|
|
63
|
+
defaultItemEquality,
|
|
64
|
+
findItemIndex,
|
|
65
|
+
findSelectionIndex,
|
|
66
|
+
isSelectedValueDirty,
|
|
67
|
+
removeItem,
|
|
68
|
+
selectedValueIncludes,
|
|
69
|
+
} from '../../internals/itemEquality';
|
|
70
|
+
import { INITIAL_LAST_HIGHLIGHT, NO_ACTIVE_VALUE } from './utils/constants';
|
|
71
|
+
import { useDirection } from '../../internals/direction-context/DirectionContext.tsrx';
|
|
72
|
+
import {
|
|
73
|
+
findCollectionItem,
|
|
74
|
+
type ComboboxItemCollection,
|
|
75
|
+
type ItemCollection,
|
|
76
|
+
} from '../items/itemCollection';
|
|
77
|
+
|
|
78
|
+
type InternalAriaComboboxProps<Value, Mode extends SelectionMode, Item = Value> = AriaComboboxProps<
|
|
79
|
+
Value,
|
|
80
|
+
Mode,
|
|
81
|
+
Item
|
|
82
|
+
> & {
|
|
83
|
+
filterQuery?: string | undefined;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @internal
|
|
88
|
+
*/
|
|
89
|
+
export function AriaCombobox<Value, Mode extends SelectionMode = 'none', Item = Value>(
|
|
90
|
+
props: Omit<InternalAriaComboboxProps<Value, Mode, Item>, 'items'> & {
|
|
91
|
+
items: readonly Group<any>[];
|
|
92
|
+
},
|
|
93
|
+
): React.ReactElement;
|
|
94
|
+
export function AriaCombobox<Value, Mode extends SelectionMode = 'none', Item = Value>(
|
|
95
|
+
props: Omit<InternalAriaComboboxProps<Value, Mode, Item>, 'items'> & {
|
|
96
|
+
items?: readonly any[] | ComboboxItemCollection<Item, any> | undefined;
|
|
97
|
+
},
|
|
98
|
+
): React.ReactElement;
|
|
99
|
+
export function AriaCombobox<Value = any, Mode extends SelectionMode = 'none', Item = Value>(
|
|
100
|
+
props: InternalAriaComboboxProps<Value, Mode, Item>,
|
|
101
|
+
): React.ReactElement {
|
|
102
|
+
const {
|
|
103
|
+
id: idProp,
|
|
104
|
+
onOpenChangeComplete: onOpenChangeCompleteProp,
|
|
105
|
+
defaultSelectedValue = null,
|
|
106
|
+
selectedValue: selectedValueProp,
|
|
107
|
+
onSelectedValueChange,
|
|
108
|
+
defaultInputValue,
|
|
109
|
+
inputValue: inputValueProp,
|
|
110
|
+
open: openProp,
|
|
111
|
+
defaultOpen = false,
|
|
112
|
+
selectionMode,
|
|
113
|
+
onItemHighlighted: onItemHighlightedProp,
|
|
114
|
+
name: nameProp,
|
|
115
|
+
form,
|
|
116
|
+
disabled: disabledProp = false,
|
|
117
|
+
readOnly = false,
|
|
118
|
+
required = false,
|
|
119
|
+
inputRef: inputRefProp,
|
|
120
|
+
grid = false,
|
|
121
|
+
items: itemsProp,
|
|
122
|
+
filteredItems: filteredItemsProp,
|
|
123
|
+
filter: filterProp,
|
|
124
|
+
filterQuery: filterQueryProp,
|
|
125
|
+
openOnInputClick = true,
|
|
126
|
+
autoHighlight = false,
|
|
127
|
+
keepHighlight = false,
|
|
128
|
+
highlightItemOnHover = true,
|
|
129
|
+
loopFocus = true,
|
|
130
|
+
itemToStringLabel: itemToStringLabelProp,
|
|
131
|
+
itemToStringValue,
|
|
132
|
+
isItemEqualToValue = defaultItemEquality,
|
|
133
|
+
virtualized = false,
|
|
134
|
+
inline: inlineProp = false,
|
|
135
|
+
fillInputOnItemPress = true,
|
|
136
|
+
modal = false,
|
|
137
|
+
limit = -1,
|
|
138
|
+
autoComplete = 'list',
|
|
139
|
+
formAutoComplete,
|
|
140
|
+
locale,
|
|
141
|
+
submitOnItemClick = false,
|
|
142
|
+
} = props;
|
|
143
|
+
|
|
144
|
+
const { clearErrors } = useFormContext();
|
|
145
|
+
const {
|
|
146
|
+
setDirty,
|
|
147
|
+
validityData,
|
|
148
|
+
setFilled,
|
|
149
|
+
name: fieldName,
|
|
150
|
+
disabled: fieldDisabled,
|
|
151
|
+
setTouched,
|
|
152
|
+
setFocused,
|
|
153
|
+
validationMode,
|
|
154
|
+
validation,
|
|
155
|
+
} = useFieldRootContext();
|
|
156
|
+
|
|
157
|
+
const direction = useDirection();
|
|
158
|
+
const id = useLabelableId({ id: idProp });
|
|
159
|
+
const collatorFilter = useCoreFilter({ locale });
|
|
160
|
+
|
|
161
|
+
// Plain items are arrays; normalized `createItems()` collections are objects.
|
|
162
|
+
const collection = Array.isArray(itemsProp)
|
|
163
|
+
? null
|
|
164
|
+
: (itemsProp as unknown as ItemCollection<Item, Value> | undefined);
|
|
165
|
+
|
|
166
|
+
if (collection && typeof collection.label !== 'function') {
|
|
167
|
+
throw new Error(
|
|
168
|
+
'Base UI: the `items` prop received an object that is not a collection, ' +
|
|
169
|
+
'so its items cannot be read. Pass an array of items, an array of groups with items, ' +
|
|
170
|
+
'or the result of `createItems()`. ' +
|
|
171
|
+
'See https://base-ui.com/react/components/combobox#createitems',
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const items = (collection ? collection.data : itemsProp) as
|
|
176
|
+
readonly Item[] | readonly Group<Item>[] | undefined;
|
|
177
|
+
const itemToValue = collection?.value;
|
|
178
|
+
|
|
179
|
+
// A projected collection's items live in the source domain, not the selection-value domain the
|
|
180
|
+
// store matches against, so they are withheld from the store.
|
|
181
|
+
const storeItems = itemToValue ? undefined : items;
|
|
182
|
+
|
|
183
|
+
// The externally filtered items projected to their selection values, with a lookup back to the
|
|
184
|
+
// source items. Declared before `itemToStringLabel`, which resolves labels from it on the
|
|
185
|
+
// first render (initial input value).
|
|
186
|
+
const externalWindow = React.useMemo(() => {
|
|
187
|
+
if (!filteredItemsProp || !itemToValue) {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
const flat = flattenLeafItems(filteredItemsProp);
|
|
191
|
+
const values = flat.map(itemToValue);
|
|
192
|
+
let valueToItem: Map<any, any> | undefined;
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
values,
|
|
196
|
+
findItem(itemValue: any, isEqual: (item: any, value: any) => boolean) {
|
|
197
|
+
if (!valueToItem) {
|
|
198
|
+
valueToItem = new Map();
|
|
199
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
200
|
+
if (!valueToItem.has(values[i])) {
|
|
201
|
+
valueToItem.set(values[i], flat[i]);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return findCollectionItem(valueToItem, itemValue, isEqual);
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}, [filteredItemsProp, itemToValue]);
|
|
210
|
+
|
|
211
|
+
// Labels selection values from current props only: collection data first, then the current
|
|
212
|
+
// external window, then the prop. Nothing from a past window is remembered — keeping a value
|
|
213
|
+
// resolvable over time means keeping its item in the collection's data.
|
|
214
|
+
const itemToStringLabel = React.useMemo(() => {
|
|
215
|
+
if (!collection) {
|
|
216
|
+
return itemToStringLabelProp;
|
|
217
|
+
}
|
|
218
|
+
return (itemValue: Value) => {
|
|
219
|
+
return collection.label(itemValue, isItemEqualToValue, (unresolvedValue: any) => {
|
|
220
|
+
const externalItem = externalWindow?.findItem(unresolvedValue, isItemEqualToValue);
|
|
221
|
+
if (externalItem != null) {
|
|
222
|
+
return collection.itemLabel(externalItem);
|
|
223
|
+
}
|
|
224
|
+
return stringifyAsLabel(unresolvedValue, itemToStringLabelProp);
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
}, [collection, itemToStringLabelProp, externalWindow, isItemEqualToValue]);
|
|
228
|
+
|
|
229
|
+
const filterItemToString = React.useMemo<FilterItemToString | undefined>(() => {
|
|
230
|
+
if (!collection) {
|
|
231
|
+
return itemToStringLabelProp;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return Object.assign((item: any) => collection.itemLabel(item), {
|
|
235
|
+
selected: (value: any) => stringifyAsLabel(value, itemToStringLabel),
|
|
236
|
+
});
|
|
237
|
+
}, [collection, itemToStringLabel, itemToStringLabelProp]);
|
|
238
|
+
|
|
239
|
+
function stringifyValueLabel(item: any) {
|
|
240
|
+
return stringifyAsLabel(item, itemToStringLabel);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const [queryChangedAfterOpen, setQueryChangedAfterOpen] = React.useState(false);
|
|
244
|
+
const [closeQuery, setCloseQuery] = React.useState<string | null>(null);
|
|
245
|
+
const previousCloseQueryRef = React.useRef(closeQuery);
|
|
246
|
+
|
|
247
|
+
const listRef = React.useRef<Array<HTMLElement | null>>([]);
|
|
248
|
+
const labelsRef = React.useRef<Array<string | null>>([]);
|
|
249
|
+
const popupRef = React.useRef<HTMLDivElement | null>(null);
|
|
250
|
+
const inputRef = React.useRef<HTMLInputElement | null>(null);
|
|
251
|
+
const startDismissRef = React.useRef<HTMLSpanElement | null>(null);
|
|
252
|
+
const endDismissRef = React.useRef<HTMLSpanElement | null>(null);
|
|
253
|
+
const emptyRef = React.useRef<HTMLDivElement | null>(null);
|
|
254
|
+
const keyboardActiveRef = React.useRef(true);
|
|
255
|
+
const hadInputClearRef = React.useRef(false);
|
|
256
|
+
const chipsContainerRef = React.useRef<HTMLDivElement | null>(null);
|
|
257
|
+
const clearRef = React.useRef<HTMLButtonElement | null>(null);
|
|
258
|
+
const selectionEventRef = React.useRef<MouseEvent | PointerEvent | KeyboardEvent | null>(null);
|
|
259
|
+
const lastHighlightRef = React.useRef(INITIAL_LAST_HIGHLIGHT);
|
|
260
|
+
const pendingQueryHighlightRef = React.useRef<null | {
|
|
261
|
+
hasQuery: boolean;
|
|
262
|
+
selection?: boolean | undefined;
|
|
263
|
+
// The value a selection-driven clear just added, so the restore can keep it
|
|
264
|
+
// highlighted instead of returning to the open anchor.
|
|
265
|
+
toggledValue?: any;
|
|
266
|
+
}>(null);
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Contains the currently visible list of item values post-filtering.
|
|
270
|
+
*/
|
|
271
|
+
const valuesRef = React.useRef<any[]>([]);
|
|
272
|
+
/**
|
|
273
|
+
* The item element that received the last `pointerdown`, used to detect whether a
|
|
274
|
+
* `mouseup` on an item belongs to a drag-select gesture that started elsewhere.
|
|
275
|
+
*/
|
|
276
|
+
const pointerDownItemRef = React.useRef<Element | null>(null);
|
|
277
|
+
|
|
278
|
+
const disabled = fieldDisabled || disabledProp;
|
|
279
|
+
const name = fieldName ?? nameProp;
|
|
280
|
+
const multiple = selectionMode === 'multiple';
|
|
281
|
+
const single = selectionMode === 'single';
|
|
282
|
+
const hasInputValue = inputValueProp !== undefined || defaultInputValue !== undefined;
|
|
283
|
+
const hasItems = items !== undefined;
|
|
284
|
+
const hasFilteredItemsProp = filteredItemsProp !== undefined;
|
|
285
|
+
|
|
286
|
+
let autoHighlightMode: false | 'input-change' | 'always';
|
|
287
|
+
if (autoHighlight === 'always') {
|
|
288
|
+
autoHighlightMode = 'always';
|
|
289
|
+
} else {
|
|
290
|
+
autoHighlightMode = autoHighlight ? 'input-change' : false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const [selectedValue, setSelectedValueUnwrapped] = useControlled<any>({
|
|
294
|
+
controlled: selectedValueProp,
|
|
295
|
+
default: multiple ? (defaultSelectedValue ?? EMPTY_ARRAY) : defaultSelectedValue,
|
|
296
|
+
name: 'Combobox',
|
|
297
|
+
state: 'selectedValue',
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const filter = React.useMemo(() => {
|
|
301
|
+
if (filterProp === null) {
|
|
302
|
+
return () => true;
|
|
303
|
+
}
|
|
304
|
+
if (filterProp !== undefined) {
|
|
305
|
+
return filterProp;
|
|
306
|
+
}
|
|
307
|
+
// `shouldBypassFiltering` already empties the query whenever a single selection's label
|
|
308
|
+
// matches it exactly, so the filter never needs a selection-aware variant here.
|
|
309
|
+
return createCollatorItemFilter(collatorFilter, filterItemToString);
|
|
310
|
+
}, [filterProp, collatorFilter, filterItemToString]);
|
|
311
|
+
|
|
312
|
+
// If neither inputValue nor defaultInputValue are provided, derive it from the
|
|
313
|
+
// selected value for single mode so the input reflects the selection on mount.
|
|
314
|
+
const initialDefaultInputValue = useRefWithInit(() => {
|
|
315
|
+
if (hasInputValue) {
|
|
316
|
+
return defaultInputValue ?? '';
|
|
317
|
+
}
|
|
318
|
+
if (single) {
|
|
319
|
+
return stringifyValueLabel(selectedValue);
|
|
320
|
+
}
|
|
321
|
+
return '';
|
|
322
|
+
}).current;
|
|
323
|
+
|
|
324
|
+
const [inputValue, setInputValueUnwrapped] = useControlled({
|
|
325
|
+
controlled: inputValueProp,
|
|
326
|
+
default: initialDefaultInputValue,
|
|
327
|
+
name: 'Combobox',
|
|
328
|
+
state: 'inputValue',
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const [open, setOpenUnwrapped] = useControlled({
|
|
332
|
+
controlled: openProp,
|
|
333
|
+
default: defaultOpen,
|
|
334
|
+
name: 'Combobox',
|
|
335
|
+
state: 'open',
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const isGrouped = isGroupedItems(items);
|
|
339
|
+
const query = !open && closeQuery !== null ? closeQuery : String(inputValue).trim();
|
|
340
|
+
|
|
341
|
+
const selectedLabelString = single ? stringifyValueLabel(selectedValue) : '';
|
|
342
|
+
|
|
343
|
+
const shouldBypassFiltering =
|
|
344
|
+
single &&
|
|
345
|
+
!queryChangedAfterOpen &&
|
|
346
|
+
query !== '' &&
|
|
347
|
+
selectedLabelString.length === query.length &&
|
|
348
|
+
collatorFilter.contains(selectedLabelString, query);
|
|
349
|
+
|
|
350
|
+
const filterQuery = shouldBypassFiltering ? '' : (filterQueryProp ?? query);
|
|
351
|
+
const shouldIgnoreExternalFiltering =
|
|
352
|
+
hasItems &&
|
|
353
|
+
hasFilteredItemsProp &&
|
|
354
|
+
shouldBypassFiltering &&
|
|
355
|
+
(!collection || collection.hasValue(selectedValue, isItemEqualToValue));
|
|
356
|
+
|
|
357
|
+
const flatItems: readonly Item[] = React.useMemo(
|
|
358
|
+
() => (items ? flattenLeafItems<Item>(items) : EMPTY_ARRAY),
|
|
359
|
+
[items],
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const filteredItems: Item[] | Group<Item>[] = React.useMemo(() => {
|
|
363
|
+
if (filteredItemsProp && !shouldIgnoreExternalFiltering) {
|
|
364
|
+
return filteredItemsProp as Item[] | Group<Item>[];
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (!items) {
|
|
368
|
+
return EMPTY_ARRAY;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (isGrouped) {
|
|
372
|
+
const groupedItems = items;
|
|
373
|
+
const resultingGroups: Group<Item>[] = [];
|
|
374
|
+
let currentCount = 0;
|
|
375
|
+
|
|
376
|
+
for (const group of groupedItems) {
|
|
377
|
+
if (limit > -1 && currentCount >= limit) {
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const remainingLimit = limit > -1 ? limit - currentCount : Infinity;
|
|
382
|
+
const itemsToTake = filterQuery === '' ? group.items.slice(0, remainingLimit) : [];
|
|
383
|
+
|
|
384
|
+
if (filterQuery !== '') {
|
|
385
|
+
for (const item of group.items) {
|
|
386
|
+
if (itemsToTake.length >= remainingLimit) {
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
if (filter(item, filterQuery, filterItemToString)) {
|
|
390
|
+
itemsToTake.push(item);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (itemsToTake.length > 0) {
|
|
396
|
+
const newGroup = { ...group, items: itemsToTake };
|
|
397
|
+
resultingGroups.push(newGroup);
|
|
398
|
+
currentCount += itemsToTake.length;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return resultingGroups;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (filterQuery === '') {
|
|
406
|
+
return limit > -1
|
|
407
|
+
? flatItems.slice(0, limit)
|
|
408
|
+
: // The cast here is done as `flatItems` is readonly.
|
|
409
|
+
// valuesRef.current, a mutable ref, can be set to `flatFilteredValues`, which may
|
|
410
|
+
// reference this exact readonly value, creating a mutation risk.
|
|
411
|
+
// However, <Combobox.Item> can never mutate this value as the mutating effect
|
|
412
|
+
// bails early when `items` is provided, and this is only ever returned
|
|
413
|
+
// when `items` is provided due to the early return at the top of this hook.
|
|
414
|
+
(flatItems as Item[]);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const limitedItems: Item[] = [];
|
|
418
|
+
for (const item of flatItems) {
|
|
419
|
+
if (limit > -1 && limitedItems.length >= limit) {
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
if (filter(item, filterQuery, filterItemToString)) {
|
|
423
|
+
limitedItems.push(item);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return limitedItems;
|
|
428
|
+
}, [
|
|
429
|
+
filteredItemsProp,
|
|
430
|
+
shouldIgnoreExternalFiltering,
|
|
431
|
+
items,
|
|
432
|
+
isGrouped,
|
|
433
|
+
filterQuery,
|
|
434
|
+
limit,
|
|
435
|
+
filter,
|
|
436
|
+
filterItemToString,
|
|
437
|
+
flatItems,
|
|
438
|
+
]);
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* The filtered items flattened across groups and projected to their selection values.
|
|
442
|
+
*/
|
|
443
|
+
const flatFilteredValues: any[] = React.useMemo(() => {
|
|
444
|
+
if (externalWindow && filteredItems === filteredItemsProp) {
|
|
445
|
+
return externalWindow.values;
|
|
446
|
+
}
|
|
447
|
+
// Explicit type argument: inferring it from a union of both shapes resolves `Item` to
|
|
448
|
+
// `Group<Item>`, which tsc rejects and tsgo does not.
|
|
449
|
+
const flat = flattenLeafItems<Item>(filteredItems);
|
|
450
|
+
return itemToValue ? flat.map((item) => itemToValue(item)) : (flat as any[]);
|
|
451
|
+
}, [filteredItems, filteredItemsProp, externalWindow, itemToValue]);
|
|
452
|
+
|
|
453
|
+
const store = useRefWithInit(() => {
|
|
454
|
+
// An inline list open on the first render never gets a closed pass of the closed-state
|
|
455
|
+
// sync effect below, and `items`-prop lists don't self-register their index the way
|
|
456
|
+
// individually rendered `<Combobox.Item>`s do, so the selected item was never highlighted.
|
|
457
|
+
// Seeding the index here lets list navigation highlight and scroll to the selection on
|
|
458
|
+
// mount. Computed once by construction, so a selection or list that resolves after mount
|
|
459
|
+
// doesn't move an existing highlight or scroll the list away.
|
|
460
|
+
let initialSelectedIndex: number | null = null;
|
|
461
|
+
if (inlineProp && open && hasItems && selectionMode !== 'none') {
|
|
462
|
+
initialSelectedIndex = findSelectionIndex(
|
|
463
|
+
flatFilteredValues,
|
|
464
|
+
selectedValue,
|
|
465
|
+
isItemEqualToValue,
|
|
466
|
+
multiple,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return new ReactStore<StoreState, ComboboxStoreContext, typeof selectors>(
|
|
471
|
+
{
|
|
472
|
+
id,
|
|
473
|
+
labelId: undefined,
|
|
474
|
+
selectedValue,
|
|
475
|
+
open,
|
|
476
|
+
items: storeItems,
|
|
477
|
+
selectionMode,
|
|
478
|
+
name,
|
|
479
|
+
form,
|
|
480
|
+
disabled,
|
|
481
|
+
readOnly,
|
|
482
|
+
required,
|
|
483
|
+
grid,
|
|
484
|
+
virtualized,
|
|
485
|
+
openOnInputClick,
|
|
486
|
+
itemToStringLabel,
|
|
487
|
+
isItemEqualToValue,
|
|
488
|
+
modal,
|
|
489
|
+
autoHighlight: autoHighlightMode,
|
|
490
|
+
submitOnItemClick,
|
|
491
|
+
hasInputValue,
|
|
492
|
+
mounted: false,
|
|
493
|
+
forceMounted: false,
|
|
494
|
+
transitionStatus: 'idle',
|
|
495
|
+
inline: inlineProp,
|
|
496
|
+
activeIndex: null,
|
|
497
|
+
selectedIndex: initialSelectedIndex,
|
|
498
|
+
popupProps: {},
|
|
499
|
+
listProps: {},
|
|
500
|
+
inputProps: {},
|
|
501
|
+
triggerProps: {},
|
|
502
|
+
itemProps: EMPTY_OBJECT,
|
|
503
|
+
positionerElement: null,
|
|
504
|
+
listElement: null,
|
|
505
|
+
popupId: undefined,
|
|
506
|
+
triggerElement: null,
|
|
507
|
+
inputElement: null,
|
|
508
|
+
inputGroupElement: null,
|
|
509
|
+
popupSide: null,
|
|
510
|
+
openMethod: null,
|
|
511
|
+
inputInsidePopup: true,
|
|
512
|
+
// Avoid duplicate names in the server HTML. Popup inputs aren't rendered
|
|
513
|
+
// until after hydration, so the hidden input takes over then if needed.
|
|
514
|
+
inputOwnsFormValue: selectionMode === 'none',
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
// Placeholder callbacks replaced on first render
|
|
518
|
+
onOpenChangeComplete: NOOP,
|
|
519
|
+
setOpen: NOOP,
|
|
520
|
+
setInputValue: NOOP,
|
|
521
|
+
setSelectedValue: NOOP,
|
|
522
|
+
setIndices: NOOP,
|
|
523
|
+
handleSelection: NOOP,
|
|
524
|
+
forceMount: NOOP,
|
|
525
|
+
requestSubmit: NOOP,
|
|
526
|
+
listRef,
|
|
527
|
+
labelsRef,
|
|
528
|
+
popupRef,
|
|
529
|
+
emptyRef,
|
|
530
|
+
inputRef,
|
|
531
|
+
startDismissRef,
|
|
532
|
+
endDismissRef,
|
|
533
|
+
keyboardActiveRef,
|
|
534
|
+
chipsContainerRef,
|
|
535
|
+
clearRef,
|
|
536
|
+
valuesRef,
|
|
537
|
+
pointerDownItemRef,
|
|
538
|
+
selectionEventRef,
|
|
539
|
+
},
|
|
540
|
+
selectors,
|
|
541
|
+
);
|
|
542
|
+
}).current;
|
|
543
|
+
|
|
544
|
+
const fieldRawValue = selectionMode === 'none' ? inputValue : selectedValue;
|
|
545
|
+
const fieldStringValue = React.useMemo(() => {
|
|
546
|
+
if (selectionMode === 'none') {
|
|
547
|
+
return fieldRawValue;
|
|
548
|
+
}
|
|
549
|
+
if (Array.isArray(selectedValue)) {
|
|
550
|
+
return selectedValue.map((value) => stringifyAsValue(value, itemToStringValue));
|
|
551
|
+
}
|
|
552
|
+
return stringifyAsValue(selectedValue, itemToStringValue);
|
|
553
|
+
}, [fieldRawValue, itemToStringValue, selectionMode, selectedValue]);
|
|
554
|
+
|
|
555
|
+
const onItemHighlighted = useStableCallback(onItemHighlightedProp);
|
|
556
|
+
const onOpenChangeComplete = useStableCallback(onOpenChangeCompleteProp);
|
|
557
|
+
|
|
558
|
+
const activeIndex = store.useState('activeIndex');
|
|
559
|
+
const selectedIndex = store.useState('selectedIndex');
|
|
560
|
+
const positionerElement = store.useState('positionerElement');
|
|
561
|
+
const listElement = store.useState('listElement');
|
|
562
|
+
const triggerElement = store.useState('triggerElement');
|
|
563
|
+
const inputElement = store.useState('inputElement');
|
|
564
|
+
const inputGroupElement = store.useState('inputGroupElement');
|
|
565
|
+
const inline = store.useState('inline');
|
|
566
|
+
const inputInsidePopup = store.useState('inputInsidePopup');
|
|
567
|
+
const inputOwnsFormValue = store.useState('inputOwnsFormValue');
|
|
568
|
+
const inputMatchesSelectedValue =
|
|
569
|
+
single && !inputInsidePopup && inputValue === selectedLabelString;
|
|
570
|
+
|
|
571
|
+
const triggerRef = useValueAsRef(triggerElement);
|
|
572
|
+
|
|
573
|
+
const { mounted, setMounted, transitionStatus } = useTransitionStatus(open);
|
|
574
|
+
const { openMethod, triggerProps } = useOpenInteractionType(open);
|
|
575
|
+
|
|
576
|
+
const getStringifiedValueForForm = useStableCallback(() => fieldStringValue);
|
|
577
|
+
|
|
578
|
+
useRegisterFieldControl(
|
|
579
|
+
inputInsidePopup ? triggerRef : inputRef,
|
|
580
|
+
id,
|
|
581
|
+
fieldRawValue,
|
|
582
|
+
getStringifiedValueForForm,
|
|
583
|
+
!disabled,
|
|
584
|
+
nameProp,
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
const forceMount = useStableCallback(() => {
|
|
588
|
+
if (items) {
|
|
589
|
+
// Ensure typeahead works on a closed list.
|
|
590
|
+
labelsRef.current = flatFilteredValues.map(stringifyValueLabel);
|
|
591
|
+
} else {
|
|
592
|
+
store.set('forceMounted', true);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Emits `onItemHighlighted` for the item at `index`, or clears the highlight when `index` is `-1`
|
|
598
|
+
* (a no-op if nothing was highlighted). Keeps `lastHighlightRef` in sync with what was emitted.
|
|
599
|
+
*/
|
|
600
|
+
const emitHighlight = useStableCallback(
|
|
601
|
+
(value: any, index: number, type: AriaCombobox.HighlightEventReason) => {
|
|
602
|
+
if (index === -1) {
|
|
603
|
+
if (lastHighlightRef.current === INITIAL_LAST_HIGHLIGHT) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;
|
|
607
|
+
} else {
|
|
608
|
+
lastHighlightRef.current = { value, index };
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
onItemHighlighted(value, createGenericEventDetails(type, undefined, { index }));
|
|
612
|
+
},
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
const setIndices = useStableCallback(
|
|
616
|
+
(options: {
|
|
617
|
+
activeIndex?: number | null | undefined;
|
|
618
|
+
selectedIndex?: number | null | undefined;
|
|
619
|
+
type?: AriaCombobox.HighlightEventReason | undefined;
|
|
620
|
+
}) => {
|
|
621
|
+
const update = {} as Pick<StoreState, 'activeIndex' | 'selectedIndex'>;
|
|
622
|
+
|
|
623
|
+
if (options.activeIndex !== undefined) {
|
|
624
|
+
update.activeIndex = options.activeIndex;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (options.selectedIndex !== undefined) {
|
|
628
|
+
update.selectedIndex = options.selectedIndex;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
store.update(update);
|
|
632
|
+
|
|
633
|
+
const activeIndexOption = options.activeIndex;
|
|
634
|
+
if (activeIndexOption === undefined) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const type: AriaCombobox.HighlightEventReason = options.type || REASONS.none;
|
|
639
|
+
|
|
640
|
+
if (activeIndexOption === null) {
|
|
641
|
+
emitHighlight(undefined, -1, type);
|
|
642
|
+
} else {
|
|
643
|
+
emitHighlight(valuesRef.current[activeIndexOption], activeIndexOption, type);
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
const setInputValue = useStableCallback(
|
|
649
|
+
(next: string, eventDetails: AriaCombobox.ChangeEventDetails) => {
|
|
650
|
+
props.onInputValueChange?.(next, eventDetails);
|
|
651
|
+
|
|
652
|
+
if (eventDetails.isCanceled) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// A canceled selection clear must not suppress close-completion cleanup.
|
|
657
|
+
hadInputClearRef.current = eventDetails.reason === REASONS.inputClear;
|
|
658
|
+
|
|
659
|
+
// If user is typing, ensure we don't auto-highlight on open due to a race
|
|
660
|
+
// with the post-open effect that sets this flag.
|
|
661
|
+
if (eventDetails.reason === REASONS.inputChange) {
|
|
662
|
+
// A controlled popup may ignore a close request. Resuming input proves the popup
|
|
663
|
+
// is remaining open, so release the query captured for an exit animation.
|
|
664
|
+
if (open && closeQuery !== null) {
|
|
665
|
+
setCloseQuery(null);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const event = eventDetails.event as Event;
|
|
669
|
+
const inputType = (event as InputEvent).inputType;
|
|
670
|
+
// Treat composition commits as typed input; autofill may omit `inputType` or
|
|
671
|
+
// report `insertReplacementText`.
|
|
672
|
+
const isTypedInput =
|
|
673
|
+
event.type === 'compositionend' ||
|
|
674
|
+
(inputType != null && inputType !== '' && inputType !== 'insertReplacementText');
|
|
675
|
+
if (isTypedInput) {
|
|
676
|
+
const hasQuery = next.trim() !== '';
|
|
677
|
+
if (hasQuery) {
|
|
678
|
+
setQueryChangedAfterOpen(true);
|
|
679
|
+
}
|
|
680
|
+
// Defer index updates until after the filtered items have been derived to ensure
|
|
681
|
+
// `onItemHighlighted` receives the latest item.
|
|
682
|
+
pendingQueryHighlightRef.current = { hasQuery };
|
|
683
|
+
|
|
684
|
+
// Virtualized lists own their scroller. Reset regular lists directly so a stale
|
|
685
|
+
// composite registry cannot select a reordered item and scrolling cannot escape
|
|
686
|
+
// the popup.
|
|
687
|
+
const list = store.state.listElement;
|
|
688
|
+
if (!store.state.virtualized && list) {
|
|
689
|
+
const popup = popupRef.current;
|
|
690
|
+
for (const ancestor of getOverflowAncestors(list.firstElementChild ?? list)) {
|
|
691
|
+
if (
|
|
692
|
+
!isHTMLElement(ancestor) ||
|
|
693
|
+
(popup ? !contains(popup, ancestor) : ancestor.getAttribute('role') === 'dialog')
|
|
694
|
+
) {
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (isScrollableY(ancestor)) {
|
|
699
|
+
ancestor.scrollTop = 0;
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (
|
|
706
|
+
hasQuery &&
|
|
707
|
+
autoHighlightMode &&
|
|
708
|
+
store.state.activeIndex == null &&
|
|
709
|
+
(open || inline)
|
|
710
|
+
) {
|
|
711
|
+
store.set('activeIndex', 0);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
} else if (
|
|
715
|
+
eventDetails.reason === REASONS.inputClear &&
|
|
716
|
+
next === '' &&
|
|
717
|
+
store.state.inputInsidePopup
|
|
718
|
+
) {
|
|
719
|
+
// A programmatic clear of an active query (e.g. after selecting an item with the
|
|
720
|
+
// input inside the popup): restore the highlight to the selected item.
|
|
721
|
+
pendingQueryHighlightRef.current = { hasQuery: false, selection: true };
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
setInputValueUnwrapped(next);
|
|
725
|
+
},
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
const handleInterruptedReopen = useStableCallback((isInputChange: boolean) => {
|
|
729
|
+
// Preserve values supplied with the reopen rather than owned by the interrupted close.
|
|
730
|
+
const clearsPendingInput =
|
|
731
|
+
!isInputChange &&
|
|
732
|
+
inputInsidePopup &&
|
|
733
|
+
!inline &&
|
|
734
|
+
inputValue !== '' &&
|
|
735
|
+
(String(inputValue).trim() === closeQuery || inputValue === selectedLabelString);
|
|
736
|
+
|
|
737
|
+
// Keep the flag while a visible filter survives so the `items` sync cannot overwrite it.
|
|
738
|
+
if (!isInputChange && (clearsPendingInput || inputValue === '' || inputMatchesSelectedValue)) {
|
|
739
|
+
setQueryChangedAfterOpen(false);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
setCloseQuery(null);
|
|
743
|
+
|
|
744
|
+
if (clearsPendingInput) {
|
|
745
|
+
// Cleanup clears omit the selection flag and reopening gesture.
|
|
746
|
+
setInputValue('', createChangeEventDetails(REASONS.inputClear));
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
const setOpen = useStableCallback(
|
|
751
|
+
(nextOpen: boolean, eventDetails: AriaCombobox.ChangeEventDetails) => {
|
|
752
|
+
if (open === nextOpen) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// If the `Empty` component is not used, the positioner or popup should be hidden
|
|
757
|
+
// with CSS. In this case, allow the Escape key to bubble to close a parent popup
|
|
758
|
+
// if there are no items to show.
|
|
759
|
+
if (
|
|
760
|
+
eventDetails.reason === REASONS.escapeKey &&
|
|
761
|
+
hasItems &&
|
|
762
|
+
flatFilteredValues.length === 0 &&
|
|
763
|
+
!emptyRef.current
|
|
764
|
+
) {
|
|
765
|
+
eventDetails.allowPropagation();
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
props.onOpenChange?.(nextOpen, eventDetails);
|
|
769
|
+
|
|
770
|
+
if (eventDetails.isCanceled) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (nextOpen && closeQuery !== null) {
|
|
775
|
+
// `ComboboxInput` calls `setInputValue` before `setOpen`, so on an input-change reopen
|
|
776
|
+
// `inputValue` is still the pre-keystroke value and the typed filter always survives.
|
|
777
|
+
handleInterruptedReopen(eventDetails.reason === REASONS.inputChange);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (!nextOpen && queryChangedAfterOpen) {
|
|
781
|
+
if (single) {
|
|
782
|
+
if (!inline) {
|
|
783
|
+
setCloseQuery(query);
|
|
784
|
+
}
|
|
785
|
+
// Avoid a flicker when closing the popup with an empty query.
|
|
786
|
+
if (query === '') {
|
|
787
|
+
setQueryChangedAfterOpen(false);
|
|
788
|
+
}
|
|
789
|
+
} else if (multiple) {
|
|
790
|
+
if (!inline) {
|
|
791
|
+
// Freeze the current query so filtering remains stable while exiting.
|
|
792
|
+
setCloseQuery(query);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (inputInsidePopup) {
|
|
796
|
+
setIndices({ activeIndex: null });
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Clear the input immediately on close while retaining filtering via closeQuery for exit animations
|
|
800
|
+
// if the input is outside the popup. When the input is inside the popup, defer the clear until
|
|
801
|
+
// unmount so the filtered list doesn't flash to unfiltered during the exit animation.
|
|
802
|
+
if (!inputInsidePopup || inline) {
|
|
803
|
+
setInputValue(
|
|
804
|
+
'',
|
|
805
|
+
createChangeEventDetails(REASONS.inputClear, eventDetails.event, undefined, {
|
|
806
|
+
isItemPress: eventDetails.reason === REASONS.itemPress,
|
|
807
|
+
}),
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
setOpenUnwrapped(nextOpen);
|
|
814
|
+
|
|
815
|
+
if (
|
|
816
|
+
!nextOpen &&
|
|
817
|
+
inputInsidePopup &&
|
|
818
|
+
(eventDetails.reason === REASONS.focusOut || eventDetails.reason === REASONS.outsidePress)
|
|
819
|
+
) {
|
|
820
|
+
setTouched(true);
|
|
821
|
+
setFocused(false);
|
|
822
|
+
|
|
823
|
+
if (validationMode === 'onBlur') {
|
|
824
|
+
const valueToValidate = selectionMode === 'none' ? inputValue : selectedValue;
|
|
825
|
+
validation.commit(valueToValidate);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
const setSelectedValue = useStableCallback(
|
|
832
|
+
(nextValue: Value | Value[] | null, eventDetails: AriaCombobox.ChangeEventDetails) => {
|
|
833
|
+
// Cast to `any` due to conditional value type (single vs. multiple).
|
|
834
|
+
// The runtime implementation already ensures the correct value shape.
|
|
835
|
+
onSelectedValueChange?.(nextValue as any, eventDetails);
|
|
836
|
+
|
|
837
|
+
if (eventDetails.isCanceled) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
setSelectedValueUnwrapped(nextValue);
|
|
842
|
+
|
|
843
|
+
const shouldFillInput =
|
|
844
|
+
(selectionMode === 'none' && popupRef.current && fillInputOnItemPress) ||
|
|
845
|
+
(single && !store.state.inputInsidePopup);
|
|
846
|
+
|
|
847
|
+
if (shouldFillInput) {
|
|
848
|
+
setInputValue(
|
|
849
|
+
stringifyValueLabel(nextValue),
|
|
850
|
+
createChangeEventDetails(eventDetails.reason, eventDetails.event),
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
},
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
const handleSelection = useStableCallback(
|
|
857
|
+
(event: MouseEvent | PointerEvent | KeyboardEvent, itemValue: any) => {
|
|
858
|
+
const targetEl = getTarget(event) as HTMLElement | null;
|
|
859
|
+
const overrideEvent = selectionEventRef.current ?? event;
|
|
860
|
+
selectionEventRef.current = null;
|
|
861
|
+
const eventDetails = createChangeEventDetails(REASONS.itemPress, overrideEvent);
|
|
862
|
+
|
|
863
|
+
// Let the link handle the click.
|
|
864
|
+
const href = targetEl?.closest('a')?.getAttribute('href');
|
|
865
|
+
if (href) {
|
|
866
|
+
if (href.startsWith('#')) {
|
|
867
|
+
setOpen(false, eventDetails);
|
|
868
|
+
}
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (multiple) {
|
|
873
|
+
const currentSelectedValue = Array.isArray(selectedValue) ? selectedValue : [];
|
|
874
|
+
const isCurrentlySelected = selectedValueIncludes(
|
|
875
|
+
currentSelectedValue,
|
|
876
|
+
itemValue,
|
|
877
|
+
isItemEqualToValue,
|
|
878
|
+
);
|
|
879
|
+
const nextValue = isCurrentlySelected
|
|
880
|
+
? removeItem(currentSelectedValue, itemValue, isItemEqualToValue)
|
|
881
|
+
: [...currentSelectedValue, itemValue];
|
|
882
|
+
|
|
883
|
+
setSelectedValue(nextValue, eventDetails);
|
|
884
|
+
|
|
885
|
+
if (eventDetails.isCanceled) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const wasFiltering = inputRef.current ? inputRef.current.value.trim() !== '' : false;
|
|
890
|
+
if (!wasFiltering) {
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (store.state.inputInsidePopup) {
|
|
895
|
+
setInputValue(
|
|
896
|
+
'',
|
|
897
|
+
createChangeEventDetails(REASONS.inputClear, eventDetails.event, undefined, {
|
|
898
|
+
isItemPress: true,
|
|
899
|
+
}),
|
|
900
|
+
);
|
|
901
|
+
// A newly selected item stays highlighted through the clear; a deselection
|
|
902
|
+
// falls back to the standard selection anchor.
|
|
903
|
+
const pendingHighlight = pendingQueryHighlightRef.current;
|
|
904
|
+
if (pendingHighlight && !isCurrentlySelected) {
|
|
905
|
+
pendingHighlight.toggledValue = itemValue;
|
|
906
|
+
}
|
|
907
|
+
} else {
|
|
908
|
+
setOpen(false, eventDetails);
|
|
909
|
+
}
|
|
910
|
+
} else {
|
|
911
|
+
setSelectedValue(itemValue, eventDetails);
|
|
912
|
+
|
|
913
|
+
if (eventDetails.isCanceled) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
setOpen(false, eventDetails);
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
const requestSubmit = useStableCallback(() => {
|
|
923
|
+
const formElement = validation.inputRef.current?.form ?? store.state.inputElement?.form;
|
|
924
|
+
if (formElement && typeof formElement.requestSubmit === 'function') {
|
|
925
|
+
formElement.requestSubmit();
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
const handleUnmount = useStableCallback(() => {
|
|
930
|
+
setMounted(false);
|
|
931
|
+
onOpenChangeComplete?.(false);
|
|
932
|
+
setQueryChangedAfterOpen(false);
|
|
933
|
+
setCloseQuery(null);
|
|
934
|
+
|
|
935
|
+
if (selectionMode === 'none') {
|
|
936
|
+
setIndices({ activeIndex: null, selectedIndex: null });
|
|
937
|
+
} else {
|
|
938
|
+
setIndices({ activeIndex: null });
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Multiple selection mode:
|
|
942
|
+
// If the user typed a filter and didn't select in multiple mode, clear the input
|
|
943
|
+
// after close completes to avoid mid-exit flicker and start fresh on next open.
|
|
944
|
+
if (
|
|
945
|
+
multiple &&
|
|
946
|
+
inputRef.current &&
|
|
947
|
+
inputRef.current.value !== '' &&
|
|
948
|
+
!hadInputClearRef.current
|
|
949
|
+
) {
|
|
950
|
+
setInputValue('', createChangeEventDetails(REASONS.inputClear));
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Single selection mode:
|
|
954
|
+
// - If input is rendered inside the popup, clear it so the next open is blank
|
|
955
|
+
// - If input is outside the popup, sync it to the selected value
|
|
956
|
+
if (single) {
|
|
957
|
+
if (store.state.inputInsidePopup) {
|
|
958
|
+
if (inputRef.current && inputRef.current.value !== '') {
|
|
959
|
+
setInputValue('', createChangeEventDetails(REASONS.inputClear));
|
|
960
|
+
}
|
|
961
|
+
} else {
|
|
962
|
+
const stringVal = stringifyValueLabel(selectedValue);
|
|
963
|
+
if (inputRef.current && inputRef.current.value !== stringVal) {
|
|
964
|
+
// If no selection was made, treat this as clearing the typed filter.
|
|
965
|
+
const reason = stringVal === '' ? REASONS.inputClear : REASONS.none;
|
|
966
|
+
setInputValue(stringVal, createChangeEventDetails(reason));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
// Support composing the Dialog component around an inline combobox.
|
|
973
|
+
// `[role="dialog"]` is more interoperable than using a context, e.g. it can work
|
|
974
|
+
// with third-party modal libraries, though the limitation is that the closest
|
|
975
|
+
// `role=dialog` part must be the animated element.
|
|
976
|
+
const resolvedPopupRef: React.RefObject<HTMLElement | null> = React.useMemo(() => {
|
|
977
|
+
if (inline && positionerElement) {
|
|
978
|
+
return { current: positionerElement.closest('[role="dialog"]') };
|
|
979
|
+
}
|
|
980
|
+
return popupRef;
|
|
981
|
+
}, [inline, positionerElement]);
|
|
982
|
+
|
|
983
|
+
useOpenChangeComplete({
|
|
984
|
+
enabled: !props.actionsRef,
|
|
985
|
+
open,
|
|
986
|
+
ref: resolvedPopupRef,
|
|
987
|
+
onComplete() {
|
|
988
|
+
if (!open) {
|
|
989
|
+
handleUnmount();
|
|
990
|
+
}
|
|
991
|
+
},
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
React.useImperativeHandle(props.actionsRef, () => ({ unmount: handleUnmount }), [handleUnmount]);
|
|
995
|
+
|
|
996
|
+
useIsoLayoutEffect(
|
|
997
|
+
function syncSelectedIndex() {
|
|
998
|
+
const closeQueryReleased = previousCloseQueryRef.current !== null && closeQuery === null;
|
|
999
|
+
previousCloseQueryRef.current = closeQuery;
|
|
1000
|
+
|
|
1001
|
+
// Closing indexes against the frozen filtered list. Reopening releases that query, so its
|
|
1002
|
+
// rendered coordinates must be synchronized again even though the popup is already open.
|
|
1003
|
+
if (open && (!closeQueryReleased || !hasItems)) {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// State-driven (not tied to the internal event path) so controlled closes
|
|
1008
|
+
// also clear a pointerdown that never received a matching item mouseup.
|
|
1009
|
+
if (!open) {
|
|
1010
|
+
pointerDownItemRef.current = null;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
if (selectionMode === 'none') {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// Without `items`, look the selection up in the live registry of mounted item
|
|
1018
|
+
// values (the list stays mounted while closed when closed-state features need
|
|
1019
|
+
// it — trigger interaction and rendered-label autofill force-mount it). Mounted
|
|
1020
|
+
// items re-assert the index themselves when their registration moves; when
|
|
1021
|
+
// nothing is mounted the lookup resolves to `null` and each item re-registers
|
|
1022
|
+
// the index on the next open.
|
|
1023
|
+
// Keep the selected index in the coordinates of the list that is actually rendered.
|
|
1024
|
+
const registry: readonly any[] = hasItems ? flatFilteredValues : valuesRef.current;
|
|
1025
|
+
|
|
1026
|
+
setIndices({
|
|
1027
|
+
selectedIndex: findSelectionIndex(registry, selectedValue, isItemEqualToValue, multiple),
|
|
1028
|
+
});
|
|
1029
|
+
},
|
|
1030
|
+
[
|
|
1031
|
+
open,
|
|
1032
|
+
closeQuery,
|
|
1033
|
+
selectedValue,
|
|
1034
|
+
selectionMode,
|
|
1035
|
+
multiple,
|
|
1036
|
+
hasItems,
|
|
1037
|
+
flatFilteredValues,
|
|
1038
|
+
isItemEqualToValue,
|
|
1039
|
+
setIndices,
|
|
1040
|
+
],
|
|
1041
|
+
);
|
|
1042
|
+
|
|
1043
|
+
useIsoLayoutEffect(() => {
|
|
1044
|
+
if (items) {
|
|
1045
|
+
valuesRef.current = flatFilteredValues;
|
|
1046
|
+
listRef.current.length = flatFilteredValues.length;
|
|
1047
|
+
}
|
|
1048
|
+
}, [items, flatFilteredValues]);
|
|
1049
|
+
|
|
1050
|
+
useIsoLayoutEffect(() => {
|
|
1051
|
+
const pendingHighlight = pendingQueryHighlightRef.current;
|
|
1052
|
+
if (pendingHighlight) {
|
|
1053
|
+
// A directly rendered list remains visible when the popup state is closed, while a
|
|
1054
|
+
// kept-mounted Positioner is hidden and should stay inert.
|
|
1055
|
+
const listIsNavigable = open || inline || store.state.positionerElement?.hidden === false;
|
|
1056
|
+
if (pendingHighlight.hasQuery) {
|
|
1057
|
+
if (autoHighlightMode && listIsNavigable) {
|
|
1058
|
+
store.set('activeIndex', 0);
|
|
1059
|
+
}
|
|
1060
|
+
pendingQueryHighlightRef.current = null;
|
|
1061
|
+
} else if (String(inputValue).trim() === '') {
|
|
1062
|
+
// Only handle the clear once it has committed (a controlled input may reject it),
|
|
1063
|
+
// so a restore cannot fire while a query is still active.
|
|
1064
|
+
pendingQueryHighlightRef.current = null;
|
|
1065
|
+
if (listIsNavigable) {
|
|
1066
|
+
const clearedBySelection = pendingHighlight.selection;
|
|
1067
|
+
if (
|
|
1068
|
+
autoHighlightMode === 'always' &&
|
|
1069
|
+
!clearedBySelection &&
|
|
1070
|
+
store.state.selectionMode === 'none'
|
|
1071
|
+
) {
|
|
1072
|
+
// There is no selection to restore in Autocomplete. Keep the first-item reset
|
|
1073
|
+
// synchronous so list navigation sees it before a directly rendered list closes.
|
|
1074
|
+
store.set('activeIndex', 0);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// Items re-mounted by the clear publish their composite indices in a follow-up
|
|
1078
|
+
// commit, so the item registries are mid-update here. Defer past React's cascade.
|
|
1079
|
+
queueMicrotask(() => {
|
|
1080
|
+
if (
|
|
1081
|
+
(!store.state.open && !store.state.inline) ||
|
|
1082
|
+
(inputRef.current && inputRef.current.value.trim() !== '')
|
|
1083
|
+
) {
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// Return the highlight to the selected item, the same anchor the popup uses
|
|
1088
|
+
// when it first opens. Read the selection through the store so consumers can
|
|
1089
|
+
// pass an inline `isItemEqualToValue` or a fresh `selectedValue` array without
|
|
1090
|
+
// re-running this effect on every render.
|
|
1091
|
+
const currentSelectedValue = store.state.selectedValue;
|
|
1092
|
+
const isMultiple = store.state.selectionMode === 'multiple';
|
|
1093
|
+
const hasSelection =
|
|
1094
|
+
isMultiple && Array.isArray(currentSelectedValue)
|
|
1095
|
+
? currentSelectedValue.length > 0
|
|
1096
|
+
: store.state.selectionMode !== 'none' && currentSelectedValue != null;
|
|
1097
|
+
|
|
1098
|
+
if (hasSelection) {
|
|
1099
|
+
const registry =
|
|
1100
|
+
hasItems || hasFilteredItemsProp ? flatFilteredValues : valuesRef.current;
|
|
1101
|
+
// A selection-driven clear keeps the just-selected item highlighted;
|
|
1102
|
+
// otherwise return to the open anchor. A selection that is no longer in
|
|
1103
|
+
// the list drops the highlight rather than leaving it on whichever item
|
|
1104
|
+
// now occupies that index.
|
|
1105
|
+
// `findItemIndex` resolves to -1 when no value was toggled.
|
|
1106
|
+
const toggledIndex = findItemIndex(
|
|
1107
|
+
registry,
|
|
1108
|
+
pendingHighlight.toggledValue,
|
|
1109
|
+
store.state.isItemEqualToValue,
|
|
1110
|
+
);
|
|
1111
|
+
store.set(
|
|
1112
|
+
'activeIndex',
|
|
1113
|
+
toggledIndex !== -1
|
|
1114
|
+
? toggledIndex
|
|
1115
|
+
: findSelectionIndex(
|
|
1116
|
+
registry,
|
|
1117
|
+
currentSelectedValue,
|
|
1118
|
+
store.state.isItemEqualToValue,
|
|
1119
|
+
isMultiple,
|
|
1120
|
+
),
|
|
1121
|
+
);
|
|
1122
|
+
} else if (clearedBySelection) {
|
|
1123
|
+
store.set('activeIndex', null);
|
|
1124
|
+
} else if (autoHighlightMode === 'always') {
|
|
1125
|
+
store.set('activeIndex', 0);
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
if (!open && !inline) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
const shouldUseFlatFilteredValues = hasItems || hasFilteredItemsProp;
|
|
1137
|
+
const candidateItems = shouldUseFlatFilteredValues ? flatFilteredValues : valuesRef.current;
|
|
1138
|
+
const storeActiveIndex = store.state.activeIndex;
|
|
1139
|
+
|
|
1140
|
+
if (storeActiveIndex == null) {
|
|
1141
|
+
if (autoHighlightMode === 'always' && candidateItems.length > 0) {
|
|
1142
|
+
store.set('activeIndex', 0);
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
emitHighlight(undefined, -1, REASONS.none);
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
if (storeActiveIndex >= candidateItems.length) {
|
|
1150
|
+
emitHighlight(undefined, -1, REASONS.none);
|
|
1151
|
+
store.set('activeIndex', null);
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
const itemValue = candidateItems[storeActiveIndex];
|
|
1156
|
+
const previouslyHighlightedItemValue = lastHighlightRef.current.value;
|
|
1157
|
+
const isSameItem =
|
|
1158
|
+
previouslyHighlightedItemValue !== NO_ACTIVE_VALUE &&
|
|
1159
|
+
compareItemEquality(
|
|
1160
|
+
itemValue,
|
|
1161
|
+
previouslyHighlightedItemValue,
|
|
1162
|
+
store.state.isItemEqualToValue,
|
|
1163
|
+
);
|
|
1164
|
+
|
|
1165
|
+
if (lastHighlightRef.current.index !== storeActiveIndex || !isSameItem) {
|
|
1166
|
+
emitHighlight(itemValue, storeActiveIndex, REASONS.none);
|
|
1167
|
+
}
|
|
1168
|
+
}, [
|
|
1169
|
+
activeIndex,
|
|
1170
|
+
autoHighlightMode,
|
|
1171
|
+
emitHighlight,
|
|
1172
|
+
hasFilteredItemsProp,
|
|
1173
|
+
hasItems,
|
|
1174
|
+
flatFilteredValues,
|
|
1175
|
+
inline,
|
|
1176
|
+
open,
|
|
1177
|
+
store,
|
|
1178
|
+
// Reruns the effect when the query changes without affecting the deps above, such as
|
|
1179
|
+
// clearing the input when no items are filtered out (individually rendered items).
|
|
1180
|
+
inputValue,
|
|
1181
|
+
]);
|
|
1182
|
+
|
|
1183
|
+
useIsoLayoutEffect(() => {
|
|
1184
|
+
if (selectionMode === 'none') {
|
|
1185
|
+
setFilled(String(inputValue) !== '');
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
setFilled(
|
|
1189
|
+
multiple ? Array.isArray(selectedValue) && selectedValue.length > 0 : selectedValue != null,
|
|
1190
|
+
);
|
|
1191
|
+
}, [setFilled, selectionMode, inputValue, selectedValue, multiple]);
|
|
1192
|
+
|
|
1193
|
+
// Ensures that the active index is not set to 0 when the list is empty.
|
|
1194
|
+
// This avoids needing to press ArrowDown twice under certain conditions.
|
|
1195
|
+
React.useEffect(() => {
|
|
1196
|
+
if (hasItems && autoHighlightMode && flatFilteredValues.length === 0) {
|
|
1197
|
+
setIndices({ activeIndex: null });
|
|
1198
|
+
}
|
|
1199
|
+
}, [hasItems, autoHighlightMode, flatFilteredValues.length, setIndices]);
|
|
1200
|
+
|
|
1201
|
+
function handleQueryChanged() {
|
|
1202
|
+
if (
|
|
1203
|
+
open &&
|
|
1204
|
+
query !== '' &&
|
|
1205
|
+
query !== String(initialDefaultInputValue) &&
|
|
1206
|
+
!inputMatchesSelectedValue
|
|
1207
|
+
) {
|
|
1208
|
+
setQueryChangedAfterOpen(true);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
function handleOpenChanged() {
|
|
1213
|
+
// A controlled `open` prop can interrupt the close without calling `setOpen`.
|
|
1214
|
+
if (open && closeQuery !== null) {
|
|
1215
|
+
handleInterruptedReopen(false);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// These sync triggers can run in the same commit while still seeing the pre-commit `inputValue`.
|
|
1220
|
+
// This render-scoped flag prevents duplicate callbacks and resets so canceled writes can retry.
|
|
1221
|
+
let syncedSelectedLabel = false;
|
|
1222
|
+
|
|
1223
|
+
function syncInputToSelectedLabel() {
|
|
1224
|
+
if (!syncedSelectedLabel && inputValue !== selectedLabelString) {
|
|
1225
|
+
syncedSelectedLabel = true;
|
|
1226
|
+
setInputValue(selectedLabelString, createChangeEventDetails(REASONS.none));
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
function handleSelectedValueChanged() {
|
|
1231
|
+
if (selectionMode === 'none') {
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
clearErrors(name);
|
|
1236
|
+
setDirty(isSelectedValueDirty(selectedValue, validityData.initialValue, isItemEqualToValue));
|
|
1237
|
+
|
|
1238
|
+
validation.change(selectedValue);
|
|
1239
|
+
|
|
1240
|
+
if (single && !hasInputValue && !inputInsidePopup) {
|
|
1241
|
+
syncInputToSelectedLabel();
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// The label catches accessor changes while the items identity restores the selected label after
|
|
1246
|
+
// a one-step input clear followed by a data reload. The shared sync prevents duplicate writes
|
|
1247
|
+
// when both change in the same commit.
|
|
1248
|
+
function syncInputAfterItemsOrLabelChange() {
|
|
1249
|
+
if (single && !hasInputValue && !inputInsidePopup && !queryChangedAfterOpen) {
|
|
1250
|
+
syncInputToSelectedLabel();
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
function handleInputValueChanged() {
|
|
1255
|
+
if (selectionMode !== 'none') {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
clearErrors(name);
|
|
1260
|
+
setDirty(inputValue !== validityData.initialValue);
|
|
1261
|
+
|
|
1262
|
+
validation.change(inputValue);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
useValueChanged(query, handleQueryChanged);
|
|
1266
|
+
useValueChanged(open, handleOpenChanged);
|
|
1267
|
+
useValueChanged(selectedValue, handleSelectedValueChanged);
|
|
1268
|
+
useValueChanged(selectedLabelString, syncInputAfterItemsOrLabelChange);
|
|
1269
|
+
useValueChanged(items, syncInputAfterItemsOrLabelChange);
|
|
1270
|
+
useValueChanged(inputValue, handleInputValueChanged);
|
|
1271
|
+
|
|
1272
|
+
const floatingRootContext = useFloatingRootContext({
|
|
1273
|
+
open: inline ? true : open,
|
|
1274
|
+
onOpenChange: setOpen,
|
|
1275
|
+
elements: {
|
|
1276
|
+
reference: inputInsidePopup ? triggerElement : inputElement,
|
|
1277
|
+
floating: positionerElement,
|
|
1278
|
+
},
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
const ariaHasPopup = grid ? 'grid' : 'listbox';
|
|
1282
|
+
// An inline list isn't gated on `open`: it renders for as long as it's in the tree, so the
|
|
1283
|
+
// combobox is permanently expanded even while the internal open state is `false`.
|
|
1284
|
+
const expanded = open || inline;
|
|
1285
|
+
const ariaExpanded = expanded ? 'true' : 'false';
|
|
1286
|
+
|
|
1287
|
+
const role: ElementProps = React.useMemo(() => {
|
|
1288
|
+
const isPlainInput = inputElement?.tagName === 'INPUT';
|
|
1289
|
+
// During SSR and initial hydration, the input ref is not available yet.
|
|
1290
|
+
// Assume an input-like control so combobox ARIA attributes are present.
|
|
1291
|
+
const shouldTreatAsInput = inputElement == null || isPlainInput;
|
|
1292
|
+
// A non-input control only takes on combobox semantics while the list is exposed, which for
|
|
1293
|
+
// an inline list is the whole time.
|
|
1294
|
+
const shouldApplyAria = shouldTreatAsInput || expanded;
|
|
1295
|
+
|
|
1296
|
+
const reference = shouldTreatAsInput
|
|
1297
|
+
? ({
|
|
1298
|
+
autoComplete: 'off',
|
|
1299
|
+
spellCheck: 'false',
|
|
1300
|
+
autoCorrect: 'off',
|
|
1301
|
+
autoCapitalize: 'none',
|
|
1302
|
+
} as HTMLProps<HTMLInputElement>)
|
|
1303
|
+
: {};
|
|
1304
|
+
|
|
1305
|
+
if (shouldApplyAria) {
|
|
1306
|
+
reference.role = 'combobox';
|
|
1307
|
+
reference['aria-expanded'] = ariaExpanded;
|
|
1308
|
+
reference['aria-haspopup'] = ariaHasPopup;
|
|
1309
|
+
reference['aria-controls'] = expanded ? listElement?.id : undefined;
|
|
1310
|
+
// `readOnly` accepts no input, so no completion of any kind is offered.
|
|
1311
|
+
reference['aria-autocomplete'] = readOnly ? 'none' : autoComplete;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
return {
|
|
1315
|
+
reference,
|
|
1316
|
+
floating: { role: 'presentation' },
|
|
1317
|
+
};
|
|
1318
|
+
}, [inputElement, expanded, ariaExpanded, ariaHasPopup, listElement?.id, autoComplete, readOnly]);
|
|
1319
|
+
|
|
1320
|
+
// `readOnly` locks the value, not the interaction: the popup opens and can be browsed.
|
|
1321
|
+
// Value changes stay blocked in `ComboboxItem`, `ComboboxInput`'s keydown, `ComboboxTrigger`'s
|
|
1322
|
+
// typeahead, the clear/remove parts, and the hidden input's autofill handler.
|
|
1323
|
+
const click = useClick(floatingRootContext, {
|
|
1324
|
+
enabled: !disabled && openOnInputClick,
|
|
1325
|
+
event: 'mousedown-only',
|
|
1326
|
+
toggle: false,
|
|
1327
|
+
// Apply a small delay for touch to let mobile viewport/keyboard positioning settle.
|
|
1328
|
+
// This avoids top-bottom flip flickers if the preferred position is "top" when first tapping.
|
|
1329
|
+
touchOpenDelay: inputInsidePopup ? 0 : 100,
|
|
1330
|
+
reason: REASONS.inputPress,
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
const dismiss = useDismiss(floatingRootContext, {
|
|
1334
|
+
enabled: !disabled && !inline,
|
|
1335
|
+
outsidePressEvent: {
|
|
1336
|
+
mouse: 'sloppy',
|
|
1337
|
+
// The visual viewport (affected by the mobile software keyboard) can be
|
|
1338
|
+
// somewhat small. The user may want to scroll the screen to see more of
|
|
1339
|
+
// the popup.
|
|
1340
|
+
touch: 'intentional',
|
|
1341
|
+
},
|
|
1342
|
+
// Without a popup, let the Escape key bubble the event up to other popups' handlers.
|
|
1343
|
+
bubbles: inline ? true : undefined,
|
|
1344
|
+
outsidePress(event) {
|
|
1345
|
+
const target = getTarget(event) as Element | null;
|
|
1346
|
+
return (
|
|
1347
|
+
!contains(triggerElement, target) &&
|
|
1348
|
+
!contains(clearRef.current, target) &&
|
|
1349
|
+
!contains(chipsContainerRef.current, target) &&
|
|
1350
|
+
!contains(inputGroupElement, target)
|
|
1351
|
+
);
|
|
1352
|
+
},
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
const listNavigation = useListNavigation(floatingRootContext, {
|
|
1356
|
+
enabled: !disabled,
|
|
1357
|
+
id,
|
|
1358
|
+
listRef,
|
|
1359
|
+
activeIndex,
|
|
1360
|
+
selectedIndex,
|
|
1361
|
+
virtual: true,
|
|
1362
|
+
loopFocus,
|
|
1363
|
+
allowEscape: loopFocus && !autoHighlightMode,
|
|
1364
|
+
focusItemOnOpen:
|
|
1365
|
+
queryChangedAfterOpen || (selectionMode === 'none' && !autoHighlightMode) ? false : 'auto',
|
|
1366
|
+
focusItemOnHover: highlightItemOnHover,
|
|
1367
|
+
resetOnPointerLeave: !keepHighlight,
|
|
1368
|
+
orientation: grid ? 'horizontal' : undefined,
|
|
1369
|
+
rtl: direction === 'rtl',
|
|
1370
|
+
disabledIndices: EMPTY_ARRAY,
|
|
1371
|
+
grid: grid ? gridNavigation : undefined,
|
|
1372
|
+
onNavigate(nextActiveIndex, event) {
|
|
1373
|
+
// Retain the highlight only while actually transitioning out or closed.
|
|
1374
|
+
if ((!event && !open) || transitionStatus === 'ending') {
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
if (!event) {
|
|
1379
|
+
setIndices({
|
|
1380
|
+
activeIndex: nextActiveIndex,
|
|
1381
|
+
});
|
|
1382
|
+
} else {
|
|
1383
|
+
setIndices({
|
|
1384
|
+
activeIndex: nextActiveIndex,
|
|
1385
|
+
type: keyboardActiveRef.current ? REASONS.keyboard : REASONS.pointer,
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
},
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
const inputProps = React.useMemo(
|
|
1392
|
+
() =>
|
|
1393
|
+
mergeProps(
|
|
1394
|
+
listNavigation.reference,
|
|
1395
|
+
{
|
|
1396
|
+
onKeyDown(event: BaseUIEvent<React.KeyboardEvent>) {
|
|
1397
|
+
// In grid mode the navigation hook treats ArrowLeft/ArrowRight as horizontal
|
|
1398
|
+
// grid movement. When the input has focus and no item is highlighted the user
|
|
1399
|
+
// is still editing the query, so let the input keep its native caret behavior.
|
|
1400
|
+
if (
|
|
1401
|
+
grid &&
|
|
1402
|
+
store.state.activeIndex == null &&
|
|
1403
|
+
(event.key === 'ArrowLeft' || event.key === 'ArrowRight')
|
|
1404
|
+
) {
|
|
1405
|
+
event.preventBaseUIHandler();
|
|
1406
|
+
}
|
|
1407
|
+
},
|
|
1408
|
+
},
|
|
1409
|
+
dismiss.reference,
|
|
1410
|
+
click.reference,
|
|
1411
|
+
role.reference,
|
|
1412
|
+
),
|
|
1413
|
+
[listNavigation.reference, dismiss.reference, click.reference, role.reference, grid, store],
|
|
1414
|
+
);
|
|
1415
|
+
|
|
1416
|
+
const popupProps = React.useMemo(
|
|
1417
|
+
() => mergeProps(FOCUSABLE_POPUP_PROPS, dismiss.floating),
|
|
1418
|
+
[dismiss.floating],
|
|
1419
|
+
);
|
|
1420
|
+
|
|
1421
|
+
const listProps = React.useMemo(
|
|
1422
|
+
() => mergeProps(listNavigation.floating, role.floating),
|
|
1423
|
+
[listNavigation.floating, role.floating],
|
|
1424
|
+
);
|
|
1425
|
+
|
|
1426
|
+
const itemProps = React.useMemo<HTMLProps>(() => {
|
|
1427
|
+
const listNavigationItemProps = listNavigation.item as HTMLProps | undefined;
|
|
1428
|
+
if (!listNavigationItemProps) {
|
|
1429
|
+
return EMPTY_OBJECT;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// Combobox keeps focus on the input; item focus would incorrectly sync
|
|
1433
|
+
// list navigation state from DOM focus.
|
|
1434
|
+
return { ...listNavigationItemProps, onFocus: undefined };
|
|
1435
|
+
}, [listNavigation.item]);
|
|
1436
|
+
|
|
1437
|
+
store.useContextCallback('setOpen', setOpen);
|
|
1438
|
+
store.useContextCallback('setInputValue', setInputValue);
|
|
1439
|
+
store.useContextCallback('setSelectedValue', setSelectedValue);
|
|
1440
|
+
store.useContextCallback('setIndices', setIndices);
|
|
1441
|
+
store.useContextCallback('handleSelection', handleSelection);
|
|
1442
|
+
store.useContextCallback('forceMount', forceMount);
|
|
1443
|
+
store.useContextCallback('requestSubmit', requestSubmit);
|
|
1444
|
+
store.useContextCallback('onOpenChangeComplete', onOpenChangeCompleteProp);
|
|
1445
|
+
|
|
1446
|
+
// The prop bags must be in the store before the parts render: they read them with `useStore`
|
|
1447
|
+
// during render, and a layout effect commits only after all children have rendered.
|
|
1448
|
+
useOnFirstRender(() => {
|
|
1449
|
+
store.update({
|
|
1450
|
+
inline: inlineProp,
|
|
1451
|
+
popupProps,
|
|
1452
|
+
listProps,
|
|
1453
|
+
inputProps,
|
|
1454
|
+
triggerProps,
|
|
1455
|
+
itemProps,
|
|
1456
|
+
});
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
const syncedValues = {
|
|
1460
|
+
id,
|
|
1461
|
+
selectedValue,
|
|
1462
|
+
open,
|
|
1463
|
+
mounted,
|
|
1464
|
+
transitionStatus,
|
|
1465
|
+
items: storeItems,
|
|
1466
|
+
inline: inlineProp,
|
|
1467
|
+
popupProps,
|
|
1468
|
+
listProps,
|
|
1469
|
+
inputProps,
|
|
1470
|
+
triggerProps,
|
|
1471
|
+
itemProps,
|
|
1472
|
+
openMethod,
|
|
1473
|
+
selectionMode,
|
|
1474
|
+
name,
|
|
1475
|
+
form,
|
|
1476
|
+
disabled,
|
|
1477
|
+
readOnly,
|
|
1478
|
+
required,
|
|
1479
|
+
grid,
|
|
1480
|
+
virtualized,
|
|
1481
|
+
openOnInputClick,
|
|
1482
|
+
itemToStringLabel,
|
|
1483
|
+
modal,
|
|
1484
|
+
autoHighlight: autoHighlightMode,
|
|
1485
|
+
isItemEqualToValue,
|
|
1486
|
+
submitOnItemClick,
|
|
1487
|
+
hasInputValue,
|
|
1488
|
+
};
|
|
1489
|
+
|
|
1490
|
+
useIsoLayoutEffect(() => {
|
|
1491
|
+
// `inputOwnsFormValue` is derived here rather than during render because `ComboboxInput`
|
|
1492
|
+
// writes it from a ref callback earlier in the same commit, and it has to land in this same
|
|
1493
|
+
// `update` so subscribers never observe an intermediate snapshot. That is also why
|
|
1494
|
+
// `store.useSyncedValues` can't be used yet: it would need a second write. The dependencies
|
|
1495
|
+
// are derived from `syncedValues` so a newly synchronized field can't be forgotten here.
|
|
1496
|
+
store.update({
|
|
1497
|
+
...syncedValues,
|
|
1498
|
+
inputOwnsFormValue: selectionMode === 'none' && (inlineProp || !store.state.inputInsidePopup),
|
|
1499
|
+
});
|
|
1500
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1501
|
+
}, [store, ...Object.values(syncedValues)]);
|
|
1502
|
+
|
|
1503
|
+
const hiddenInputRef = useMergedRefs(inputRefProp, validation.inputRef);
|
|
1504
|
+
|
|
1505
|
+
const itemsContextValue: ComboboxDerivedItemsContext = React.useMemo(
|
|
1506
|
+
() => ({
|
|
1507
|
+
query,
|
|
1508
|
+
hasItems,
|
|
1509
|
+
filteredItems,
|
|
1510
|
+
flatFilteredValues,
|
|
1511
|
+
}),
|
|
1512
|
+
[query, hasItems, filteredItems, flatFilteredValues],
|
|
1513
|
+
);
|
|
1514
|
+
|
|
1515
|
+
const serializedValue = React.useMemo(() => {
|
|
1516
|
+
if (Array.isArray(fieldRawValue)) {
|
|
1517
|
+
return '';
|
|
1518
|
+
}
|
|
1519
|
+
return stringifyAsValue(fieldRawValue, itemToStringValue);
|
|
1520
|
+
}, [fieldRawValue, itemToStringValue]);
|
|
1521
|
+
|
|
1522
|
+
const hasMultipleSelection = multiple && Array.isArray(selectedValue) && selectedValue.length > 0;
|
|
1523
|
+
const hiddenInputName =
|
|
1524
|
+
multiple || (selectionMode === 'none' && inputOwnsFormValue) ? undefined : name;
|
|
1525
|
+
|
|
1526
|
+
const hiddenInputs = React.useMemo(() => {
|
|
1527
|
+
if (!multiple || !Array.isArray(selectedValue) || !name) {
|
|
1528
|
+
return null;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
return selectedValue.map((value: Value) => {
|
|
1532
|
+
const currentSerializedValue = stringifyAsValue(value, itemToStringValue);
|
|
1533
|
+
return (
|
|
1534
|
+
<input
|
|
1535
|
+
key={currentSerializedValue}
|
|
1536
|
+
type="hidden"
|
|
1537
|
+
form={form}
|
|
1538
|
+
name={name}
|
|
1539
|
+
value={currentSerializedValue}
|
|
1540
|
+
disabled={disabled}
|
|
1541
|
+
/>
|
|
1542
|
+
);
|
|
1543
|
+
});
|
|
1544
|
+
}, [multiple, selectedValue, form, name, itemToStringValue, disabled]);
|
|
1545
|
+
|
|
1546
|
+
const children = (
|
|
1547
|
+
<React.Fragment>
|
|
1548
|
+
{props.children}
|
|
1549
|
+
<input
|
|
1550
|
+
{...validation.getValidationProps(disabled, {
|
|
1551
|
+
// Move focus when the hidden input is focused.
|
|
1552
|
+
onFocus() {
|
|
1553
|
+
if (inputInsidePopup) {
|
|
1554
|
+
triggerElement?.focus();
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
(inputRef.current || triggerElement)?.focus();
|
|
1559
|
+
},
|
|
1560
|
+
// Handle browser autofill.
|
|
1561
|
+
onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
|
1562
|
+
// Workaround for https://github.com/react/react/issues/9023
|
|
1563
|
+
if (event.defaultPrevented || disabled || readOnly) {
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
const nextValue = event.currentTarget.value;
|
|
1568
|
+
const nextValueLower = nextValue.toLowerCase();
|
|
1569
|
+
const details = createChangeEventDetails(REASONS.none, event);
|
|
1570
|
+
|
|
1571
|
+
const findSerializedMatchIndex = () =>
|
|
1572
|
+
valuesRef.current.findIndex(
|
|
1573
|
+
(candidate) =>
|
|
1574
|
+
stringifyAsValue(candidate, itemToStringValue).toLowerCase() === nextValueLower ||
|
|
1575
|
+
stringifyValueLabel(candidate).toLowerCase() === nextValueLower,
|
|
1576
|
+
);
|
|
1577
|
+
|
|
1578
|
+
function handleChange() {
|
|
1579
|
+
// Browser autofill only writes a single scalar value.
|
|
1580
|
+
if (multiple) {
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
if (selectionMode === 'none') {
|
|
1585
|
+
setInputValue(nextValue, details);
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// Preserve the original serialized matching, then fall back to rendered text,
|
|
1590
|
+
// which browsers can autofill for primitive values like `value="US">United States`.
|
|
1591
|
+
let matchingIndex = findSerializedMatchIndex();
|
|
1592
|
+
|
|
1593
|
+
if (matchingIndex === -1) {
|
|
1594
|
+
matchingIndex = valuesRef.current.findIndex((_, index) => {
|
|
1595
|
+
const renderedLabel = labelsRef.current[index];
|
|
1596
|
+
return renderedLabel != null && renderedLabel.toLowerCase() === nextValueLower;
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
const matchingValue =
|
|
1601
|
+
matchingIndex === -1 ? undefined : valuesRef.current[matchingIndex];
|
|
1602
|
+
if (matchingValue != null) {
|
|
1603
|
+
// `setSelectedValue` may be canceled by `onValueChange`; rely on
|
|
1604
|
+
// `useValueChanged` to mark the field dirty and run validation only
|
|
1605
|
+
// when the value actually changes.
|
|
1606
|
+
setSelectedValue?.(matchingValue, details);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// Only single-selection autofill matches against the registered values/labels.
|
|
1611
|
+
// `multiple` ignores autofill and `none` just writes the input value, so avoid the
|
|
1612
|
+
// sticky `forceMounted` mount (which never resets) for those modes.
|
|
1613
|
+
if (single) {
|
|
1614
|
+
forceMount();
|
|
1615
|
+
if (items && findSerializedMatchIndex() === -1) {
|
|
1616
|
+
// `forceMount` only refreshes the derived labels for the `items` prop. When
|
|
1617
|
+
// serialized matching misses, also mount the list so rendered labels (which can
|
|
1618
|
+
// differ from the serialized values) are registered for autofill matching.
|
|
1619
|
+
store.set('forceMounted', true);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
queueMicrotask(handleChange);
|
|
1623
|
+
},
|
|
1624
|
+
})}
|
|
1625
|
+
id={id && hiddenInputName == null ? `${id}-hidden-input` : undefined}
|
|
1626
|
+
form={form}
|
|
1627
|
+
name={hiddenInputName}
|
|
1628
|
+
autoComplete={formAutoComplete}
|
|
1629
|
+
disabled={disabled}
|
|
1630
|
+
required={required && !hasMultipleSelection}
|
|
1631
|
+
readOnly={readOnly}
|
|
1632
|
+
value={serializedValue}
|
|
1633
|
+
ref={hiddenInputRef}
|
|
1634
|
+
style={hiddenInputName ? visuallyHiddenInput : visuallyHidden}
|
|
1635
|
+
tabIndex={-1}
|
|
1636
|
+
aria-hidden
|
|
1637
|
+
suppressHydrationWarning
|
|
1638
|
+
/>
|
|
1639
|
+
{hiddenInputs}
|
|
1640
|
+
</React.Fragment>
|
|
1641
|
+
);
|
|
1642
|
+
|
|
1643
|
+
return (
|
|
1644
|
+
<ComboboxRootContext.Provider value={store}>
|
|
1645
|
+
<ComboboxFloatingContext.Provider value={floatingRootContext}>
|
|
1646
|
+
<ComboboxHasItemsContext.Provider value={hasItems}>
|
|
1647
|
+
<ComboboxDerivedItemsContext.Provider value={itemsContextValue}>
|
|
1648
|
+
<ComboboxInputValueContext.Provider value={inputValue}>
|
|
1649
|
+
{children}
|
|
1650
|
+
</ComboboxInputValueContext.Provider>
|
|
1651
|
+
</ComboboxDerivedItemsContext.Provider>
|
|
1652
|
+
</ComboboxHasItemsContext.Provider>
|
|
1653
|
+
</ComboboxFloatingContext.Provider>
|
|
1654
|
+
</ComboboxRootContext.Provider>
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
type SelectionMode = 'single' | 'multiple' | 'none';
|
|
1659
|
+
|
|
1660
|
+
type ComboboxItemValueType<ItemValue, Mode extends SelectionMode> = Mode extends 'multiple'
|
|
1661
|
+
? ItemValue[]
|
|
1662
|
+
: ItemValue;
|
|
1663
|
+
|
|
1664
|
+
interface ComboboxRootProps<ItemValue, Item = ItemValue> {
|
|
1665
|
+
children?: React.OctaneNode;
|
|
1666
|
+
/**
|
|
1667
|
+
* Identifies the field when a form is submitted.
|
|
1668
|
+
*/
|
|
1669
|
+
name?: string | undefined;
|
|
1670
|
+
/**
|
|
1671
|
+
* Identifies the form that owns the internal input.
|
|
1672
|
+
* Useful when the combobox is rendered outside the form.
|
|
1673
|
+
*/
|
|
1674
|
+
form?: string | undefined;
|
|
1675
|
+
/**
|
|
1676
|
+
* The id of the component.
|
|
1677
|
+
*/
|
|
1678
|
+
id?: string | undefined;
|
|
1679
|
+
/**
|
|
1680
|
+
* Whether the user must choose a value before submitting a form.
|
|
1681
|
+
* @default false
|
|
1682
|
+
*/
|
|
1683
|
+
required?: boolean | undefined;
|
|
1684
|
+
/**
|
|
1685
|
+
* Whether the user should be unable to choose a different option from the popup.
|
|
1686
|
+
* @default false
|
|
1687
|
+
*/
|
|
1688
|
+
readOnly?: boolean | undefined;
|
|
1689
|
+
/**
|
|
1690
|
+
* Whether the component should ignore user interaction.
|
|
1691
|
+
* @default false
|
|
1692
|
+
*/
|
|
1693
|
+
disabled?: boolean | undefined;
|
|
1694
|
+
/**
|
|
1695
|
+
* Whether the popup is initially open.
|
|
1696
|
+
*
|
|
1697
|
+
* To render a controlled popup, use the `open` prop instead.
|
|
1698
|
+
* @default false
|
|
1699
|
+
*/
|
|
1700
|
+
defaultOpen?: boolean | undefined;
|
|
1701
|
+
/**
|
|
1702
|
+
* Whether the popup is currently open. Use when controlled.
|
|
1703
|
+
*/
|
|
1704
|
+
open?: boolean | undefined;
|
|
1705
|
+
/**
|
|
1706
|
+
* Event handler called when the popup is opened or closed.
|
|
1707
|
+
*/
|
|
1708
|
+
onOpenChange?:
|
|
1709
|
+
((open: boolean, eventDetails: AriaCombobox.ChangeEventDetails) => void) | undefined;
|
|
1710
|
+
/**
|
|
1711
|
+
* Event handler called after any animations complete when the popup is opened or closed.
|
|
1712
|
+
*/
|
|
1713
|
+
onOpenChangeComplete?: ((open: boolean) => void) | undefined;
|
|
1714
|
+
/**
|
|
1715
|
+
* Whether the popup opens when clicking the input.
|
|
1716
|
+
* @default true
|
|
1717
|
+
*/
|
|
1718
|
+
openOnInputClick?: boolean | undefined;
|
|
1719
|
+
/**
|
|
1720
|
+
* Whether the first matching item is highlighted automatically.
|
|
1721
|
+
* - `false`: do not highlight automatically.
|
|
1722
|
+
* - `true`: highlight after the user types and keep the highlight while the query changes.
|
|
1723
|
+
* - `'always'`: highlight the first item as soon as the list opens.
|
|
1724
|
+
* @default false
|
|
1725
|
+
*/
|
|
1726
|
+
autoHighlight?: boolean | 'always' | undefined;
|
|
1727
|
+
/**
|
|
1728
|
+
* Whether the highlighted item should be preserved when the pointer leaves the list.
|
|
1729
|
+
* @default false
|
|
1730
|
+
*/
|
|
1731
|
+
keepHighlight?: boolean | undefined;
|
|
1732
|
+
/**
|
|
1733
|
+
* Whether moving the pointer over items should highlight them.
|
|
1734
|
+
* Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.
|
|
1735
|
+
* @default true
|
|
1736
|
+
*/
|
|
1737
|
+
highlightItemOnHover?: boolean | undefined;
|
|
1738
|
+
/**
|
|
1739
|
+
* Whether to loop keyboard focus back to the input when the end of the list is reached while using the arrow keys. The first item can then be reached by pressing <kbd>ArrowDown</kbd> again from the input, or the last item can be reached by pressing <kbd>ArrowUp</kbd> from the input.
|
|
1740
|
+
* The input is always included in the focus loop per [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/).
|
|
1741
|
+
* When disabled, focus does not move when on the last element and the user presses <kbd>ArrowDown</kbd>, or when on the first element and the user presses <kbd>ArrowUp</kbd>.
|
|
1742
|
+
* @default true
|
|
1743
|
+
*/
|
|
1744
|
+
loopFocus?: boolean | undefined;
|
|
1745
|
+
/**
|
|
1746
|
+
* The input value of the combobox. Use when controlled.
|
|
1747
|
+
*/
|
|
1748
|
+
inputValue?: React.ComponentProps<'input'>['value'] | undefined;
|
|
1749
|
+
/**
|
|
1750
|
+
* Callback fired when the input value of the combobox changes.
|
|
1751
|
+
*/
|
|
1752
|
+
onInputValueChange?:
|
|
1753
|
+
((value: string, eventDetails: AriaCombobox.ChangeEventDetails) => void) | undefined;
|
|
1754
|
+
/**
|
|
1755
|
+
* The uncontrolled input value when initially rendered.
|
|
1756
|
+
*
|
|
1757
|
+
* To render a controlled input, use the `inputValue` prop instead.
|
|
1758
|
+
*/
|
|
1759
|
+
defaultInputValue?: React.ComponentProps<'input'>['defaultValue'] | undefined;
|
|
1760
|
+
/**
|
|
1761
|
+
* A ref to imperative actions.
|
|
1762
|
+
* - `unmount`: Manually unmounts the combobox.
|
|
1763
|
+
* Call this after any externally controlled closing animation finishes.
|
|
1764
|
+
*/
|
|
1765
|
+
actionsRef?: React.RefObject<AriaCombobox.Actions | null> | undefined;
|
|
1766
|
+
/**
|
|
1767
|
+
* Callback fired when an item is highlighted or unhighlighted.
|
|
1768
|
+
* Receives the highlighted item value (or `undefined` if no item is highlighted) and event details with a `reason` property describing why the highlight changed.
|
|
1769
|
+
* The `reason` can be:
|
|
1770
|
+
* - `'keyboard'`: the highlight changed due to keyboard navigation.
|
|
1771
|
+
* - `'pointer'`: the highlight changed due to pointer hovering.
|
|
1772
|
+
* - `'none'`: the highlight changed programmatically.
|
|
1773
|
+
*/
|
|
1774
|
+
onItemHighlighted?:
|
|
1775
|
+
| ((itemValue: ItemValue | undefined, eventDetails: AriaCombobox.HighlightEventDetails) => void)
|
|
1776
|
+
| undefined;
|
|
1777
|
+
/**
|
|
1778
|
+
* A ref to the hidden input element.
|
|
1779
|
+
*/
|
|
1780
|
+
inputRef?: React.Ref<HTMLInputElement> | undefined;
|
|
1781
|
+
/**
|
|
1782
|
+
* Whether list items are presented in a grid layout.
|
|
1783
|
+
* When enabled, arrow keys navigate across rows and columns inferred from DOM rows.
|
|
1784
|
+
* @default false
|
|
1785
|
+
*/
|
|
1786
|
+
grid?: boolean | undefined;
|
|
1787
|
+
/**
|
|
1788
|
+
* The items to be displayed in the list.
|
|
1789
|
+
* Can be a flat array of items, an array of groups with items, or a collection created by
|
|
1790
|
+
* the `createItems()` function, which derives each item's selection value and label.
|
|
1791
|
+
* Nullish entries are not supported: remove them from the data before passing it.
|
|
1792
|
+
*/
|
|
1793
|
+
items?:
|
|
1794
|
+
readonly any[] | readonly Group<any>[] | ComboboxItemCollection<Item, ItemValue> | undefined;
|
|
1795
|
+
/**
|
|
1796
|
+
* Filtered items to display in the list.
|
|
1797
|
+
* When provided, the list uses these items instead of filtering the `items` prop internally.
|
|
1798
|
+
* When `items` is also provided, this array must preserve its flat or grouped structure.
|
|
1799
|
+
* With a `createItems()` collection, pass source items rather than derived values.
|
|
1800
|
+
* Nullish entries are not supported, as in `items`.
|
|
1801
|
+
* Use when you want to control filtering logic externally with the `useFilter()` hook.
|
|
1802
|
+
*/
|
|
1803
|
+
filteredItems?: readonly Item[] | readonly Group<Item>[] | undefined;
|
|
1804
|
+
/**
|
|
1805
|
+
* Filter function used to match items vs input query.
|
|
1806
|
+
* Receives the source item, which is the derived value's item when `items` is a `createItems()`
|
|
1807
|
+
* collection, and the item itself otherwise.
|
|
1808
|
+
*/
|
|
1809
|
+
filter?:
|
|
1810
|
+
| null
|
|
1811
|
+
| ((item: Item, query: string, itemToString?: (item: Item) => string) => boolean)
|
|
1812
|
+
| undefined;
|
|
1813
|
+
/**
|
|
1814
|
+
* When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for display in the input.
|
|
1815
|
+
* If the shape of the object is `{ value, label }`, the label will be used automatically without needing to specify this prop.
|
|
1816
|
+
* With a `createItems()` collection, this receives the derived value, and the collection's
|
|
1817
|
+
* `getLabel` takes precedence for values it can resolve.
|
|
1818
|
+
*/
|
|
1819
|
+
itemToStringLabel?: ((itemValue: ItemValue) => string) | undefined;
|
|
1820
|
+
/**
|
|
1821
|
+
* When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for form submission.
|
|
1822
|
+
* If the shape of the object is `{ value, label }`, the value will be used automatically without needing to specify this prop.
|
|
1823
|
+
* With a `createItems()` collection, this receives the derived value.
|
|
1824
|
+
*/
|
|
1825
|
+
itemToStringValue?: ((itemValue: ItemValue) => string) | undefined;
|
|
1826
|
+
/**
|
|
1827
|
+
* Custom comparison logic used to determine if a combobox item value matches the current selected value. Useful when item values are objects without matching referentially.
|
|
1828
|
+
* With a `createItems()` collection, both arguments are derived values.
|
|
1829
|
+
* Defaults to `Object.is` comparison.
|
|
1830
|
+
*/
|
|
1831
|
+
isItemEqualToValue?: ((itemValue: ItemValue, value: ItemValue) => boolean) | undefined;
|
|
1832
|
+
/**
|
|
1833
|
+
* Whether the items are being externally virtualized.
|
|
1834
|
+
* @default false
|
|
1835
|
+
*/
|
|
1836
|
+
virtualized?: boolean | undefined;
|
|
1837
|
+
/**
|
|
1838
|
+
* Whether the list is rendered inline without using the component's own popup.
|
|
1839
|
+
*
|
|
1840
|
+
* Specify `open` unconditionally in conjunction with this prop so the list is considered
|
|
1841
|
+
* visible: `<Combobox.Root inline open>`
|
|
1842
|
+
*
|
|
1843
|
+
* In a `Combobox.Root` > `Dialog.Root` composition, bind the Combobox's `open` and
|
|
1844
|
+
* `onOpenChange` props to the `Dialog`'s `open` and `onOpenChange` state instead so the
|
|
1845
|
+
* component resets its transient state (filter query, highlighted item, and input value) when
|
|
1846
|
+
* the dialog closes.
|
|
1847
|
+
* @default false
|
|
1848
|
+
*/
|
|
1849
|
+
inline?: boolean | undefined;
|
|
1850
|
+
/**
|
|
1851
|
+
* Determines if the popup enters a modal state when open.
|
|
1852
|
+
* - `true`: user interaction is limited to the popup: document page scroll is locked and pointer interactions on outside elements are disabled.
|
|
1853
|
+
* - `false`: user interaction with the rest of the document is allowed.
|
|
1854
|
+
*
|
|
1855
|
+
* On touch devices, a `true` modal blocks outside taps but leaves the page scrollable unless the popup spans nearly the full viewport width, matching native iOS behavior.
|
|
1856
|
+
* @default false
|
|
1857
|
+
*/
|
|
1858
|
+
modal?: boolean | undefined;
|
|
1859
|
+
/**
|
|
1860
|
+
* The maximum number of items to display in the list.
|
|
1861
|
+
* @default -1
|
|
1862
|
+
*/
|
|
1863
|
+
limit?: number | undefined;
|
|
1864
|
+
/**
|
|
1865
|
+
* Controls how the component behaves with respect to list filtering and inline autocompletion.
|
|
1866
|
+
* - `list` (default): items are dynamically filtered based on the input value. The input value does not change based on the active item.
|
|
1867
|
+
* - `both`: items are dynamically filtered based on the input value, which will temporarily change based on the active item (inline autocompletion).
|
|
1868
|
+
* - `inline`: items are static (not filtered), and the input value will temporarily change based on the active item (inline autocompletion).
|
|
1869
|
+
* - `none`: items are static (not filtered), and the input value will not change based on the active item.
|
|
1870
|
+
* @default 'list'
|
|
1871
|
+
*/
|
|
1872
|
+
autoComplete?: 'list' | 'both' | 'inline' | 'none' | undefined;
|
|
1873
|
+
/**
|
|
1874
|
+
* Provides a hint to the browser for autofill on the hidden input element.
|
|
1875
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/autocomplete
|
|
1876
|
+
*/
|
|
1877
|
+
formAutoComplete?: string | undefined;
|
|
1878
|
+
/**
|
|
1879
|
+
* The locale to use for string comparison.
|
|
1880
|
+
* Defaults to the user's runtime locale.
|
|
1881
|
+
*/
|
|
1882
|
+
locale?: Intl.LocalesArgument | undefined;
|
|
1883
|
+
/**
|
|
1884
|
+
* Whether clicking an item should submit the owning form.
|
|
1885
|
+
* @default false
|
|
1886
|
+
*/
|
|
1887
|
+
submitOnItemClick?: boolean | undefined;
|
|
1888
|
+
/**
|
|
1889
|
+
* INTERNAL: When `selectionMode` is `none`, controls whether selecting an item fills the input.
|
|
1890
|
+
*/
|
|
1891
|
+
fillInputOnItemPress?: boolean | undefined;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
export interface AriaComboboxState {}
|
|
1895
|
+
|
|
1896
|
+
export type AriaComboboxProps<
|
|
1897
|
+
Value,
|
|
1898
|
+
Mode extends SelectionMode = 'none',
|
|
1899
|
+
Item = Value,
|
|
1900
|
+
> = ComboboxRootProps<Value, Item> & {
|
|
1901
|
+
/**
|
|
1902
|
+
* How the combobox should remember the selected value.
|
|
1903
|
+
* - `single`: Remembers the last selected value.
|
|
1904
|
+
* - `multiple`: Remember all selected values.
|
|
1905
|
+
* - `none`: Do not remember the selected value.
|
|
1906
|
+
*/
|
|
1907
|
+
selectionMode: Mode;
|
|
1908
|
+
/**
|
|
1909
|
+
* The selected value of the combobox. Use when controlled.
|
|
1910
|
+
*/
|
|
1911
|
+
selectedValue?: ComboboxItemValueType<Value, Mode> | undefined;
|
|
1912
|
+
/**
|
|
1913
|
+
* The uncontrolled selected value of the combobox when it's initially rendered.
|
|
1914
|
+
*
|
|
1915
|
+
* To render a controlled combobox, use the `selectedValue` prop instead.
|
|
1916
|
+
*/
|
|
1917
|
+
defaultSelectedValue?: ComboboxItemValueType<Value, Mode> | null | undefined;
|
|
1918
|
+
/**
|
|
1919
|
+
* Callback fired when the selected value of the combobox changes.
|
|
1920
|
+
*/
|
|
1921
|
+
onSelectedValueChange?:
|
|
1922
|
+
| ((
|
|
1923
|
+
value: ComboboxItemValueType<Value, Mode>,
|
|
1924
|
+
eventDetails: AriaCombobox.ChangeEventDetails,
|
|
1925
|
+
) => void)
|
|
1926
|
+
| undefined;
|
|
1927
|
+
};
|
|
1928
|
+
|
|
1929
|
+
export namespace AriaCombobox {
|
|
1930
|
+
export type Props<Value, Mode extends SelectionMode = 'none', Item = Value> = AriaComboboxProps<
|
|
1931
|
+
Value,
|
|
1932
|
+
Mode,
|
|
1933
|
+
Item
|
|
1934
|
+
>;
|
|
1935
|
+
export type State = AriaComboboxState;
|
|
1936
|
+
|
|
1937
|
+
export interface Actions {
|
|
1938
|
+
unmount: () => void;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
export type HighlightEventReason =
|
|
1942
|
+
typeof REASONS.keyboard | typeof REASONS.pointer | typeof REASONS.none;
|
|
1943
|
+
export type HighlightEventDetails = BaseUIGenericEventDetails<
|
|
1944
|
+
HighlightEventReason,
|
|
1945
|
+
{ index: number }
|
|
1946
|
+
>;
|
|
1947
|
+
|
|
1948
|
+
export type ChangeEventReason =
|
|
1949
|
+
| typeof REASONS.triggerPress
|
|
1950
|
+
| typeof REASONS.inputPress
|
|
1951
|
+
| typeof REASONS.outsidePress
|
|
1952
|
+
| typeof REASONS.itemPress
|
|
1953
|
+
| typeof REASONS.closePress
|
|
1954
|
+
| typeof REASONS.escapeKey
|
|
1955
|
+
| typeof REASONS.listNavigation
|
|
1956
|
+
| typeof REASONS.focusOut
|
|
1957
|
+
| typeof REASONS.inputChange
|
|
1958
|
+
| typeof REASONS.inputClear
|
|
1959
|
+
| typeof REASONS.clearPress
|
|
1960
|
+
| typeof REASONS.chipRemovePress
|
|
1961
|
+
| typeof REASONS.cancelOpen
|
|
1962
|
+
| typeof REASONS.none;
|
|
1963
|
+
export type ChangeEventDetails = BaseUIChangeEventDetails<ChangeEventReason> & {
|
|
1964
|
+
/**
|
|
1965
|
+
* When `reason` is `input-clear` in multiple mode, indicates whether an item press caused the
|
|
1966
|
+
* clear. Automatic cleanup clears omit this property.
|
|
1967
|
+
*/
|
|
1968
|
+
isItemPress?: boolean | undefined;
|
|
1969
|
+
};
|
|
1970
|
+
}
|