@cube-dev/ui-kit 0.80.2 → 0.82.0
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/CHANGELOG.md +18 -0
- package/es/_internal/hooks/index.js +1 -1
- package/es/_internal/hooks/use-chained-callback.js +1 -1
- package/es/_internal/hooks/use-debounced-value.js +1 -1
- package/es/_internal/hooks/use-deprecation-warning.js +1 -1
- package/es/_internal/hooks/use-effect-once.js +1 -1
- package/es/_internal/hooks/use-event.js +1 -1
- package/es/_internal/hooks/use-is-first-render.js +1 -1
- package/es/_internal/hooks/use-sync-ref.js +1 -1
- package/es/_internal/hooks/use-timer/index.js +1 -1
- package/es/_internal/hooks/use-timer/timer.js +1 -1
- package/es/_internal/hooks/use-timer/use-timer.js +1 -1
- package/es/_internal/hooks/use-update-effect.js +1 -1
- package/es/_internal/hooks/use-warn.js +1 -1
- package/es/_internal/index.js +1 -1
- package/es/components/Block.js +1 -1
- package/es/components/GlobalStyles.js +1 -1
- package/es/components/GridProvider.js +1 -1
- package/es/components/HiddenInput.js +1 -1
- package/es/components/Item.js +1 -1
- package/es/components/OpenTrasition.js +1 -1
- package/es/components/Root.js +1 -1
- package/es/components/actions/Action/Action.js +1 -1
- package/es/components/actions/Button/Button.js +1 -1
- package/es/components/actions/Button/index.js +1 -1
- package/es/components/actions/ButtonGroup/ButtonGroup.js +1 -1
- package/es/components/actions/CommandMenu/CommandMenu.js +1 -1
- package/es/components/actions/CommandMenu/index.js +1 -1
- package/es/components/actions/CommandMenu/styled.js +1 -1
- package/es/components/actions/ItemAction/ItemAction.js +7 -2
- package/es/components/actions/ItemAction/index.js +1 -1
- package/es/components/actions/ItemButton/ItemButton.js +1 -1
- package/es/components/actions/ItemButton/index.js +1 -1
- package/es/components/actions/Link/Link.js +1 -1
- package/es/components/actions/Menu/Menu.js +1 -1
- package/es/components/actions/Menu/MenuItem.js +1 -1
- package/es/components/actions/Menu/MenuSection.js +1 -1
- package/es/components/actions/Menu/MenuTrigger.js +1 -1
- package/es/components/actions/Menu/SubMenuTrigger.js +1 -1
- package/es/components/actions/Menu/SubmenuTriggerContext.js +1 -1
- package/es/components/actions/Menu/context.js +1 -1
- package/es/components/actions/Menu/index.js +1 -1
- package/es/components/actions/Menu/styled.js +1 -1
- package/es/components/actions/index.js +1 -1
- package/es/components/actions/use-action.js +1 -1
- package/es/components/actions/use-anchored-menu.js +1 -1
- package/es/components/actions/use-context-menu.js +1 -1
- package/es/components/content/ActiveZone/ActiveZone.js +1 -1
- package/es/components/content/Alert/Alert.js +1 -1
- package/es/components/content/Alert/index.js +1 -1
- package/es/components/content/Alert/types.js +1 -1
- package/es/components/content/Alert/use-alert.js +1 -1
- package/es/components/content/Avatar/Avatar.js +1 -1
- package/es/components/content/Badge/Badge.js +1 -1
- package/es/components/content/Card/Card.js +1 -1
- package/es/components/content/Content.js +1 -1
- package/es/components/content/CopyPasteBlock/CopyPasteBlock.js +1 -1
- package/es/components/content/CopyPasteBlock/index.js +1 -1
- package/es/components/content/CopySnippet/CopySnippet.js +1 -1
- package/es/components/content/CopySnippet/index.js +1 -1
- package/es/components/content/Divider.js +1 -1
- package/es/components/content/Footer.js +1 -1
- package/es/components/content/Header.js +1 -1
- package/es/components/content/HotKeys/HotKeys.js +1 -1
- package/es/components/content/HotKeys/index.js +1 -1
- package/es/components/content/ItemBase/ItemBase.js +1 -1
- package/es/components/content/ItemBase/index.js +1 -1
- package/es/components/content/List/SectionHeading.js +1 -1
- package/es/components/content/List/index.js +1 -1
- package/es/components/content/Paragraph.js +1 -1
- package/es/components/content/Placeholder/Placeholder.js +1 -1
- package/es/components/content/PrismCode/PrismCode.js +1 -1
- package/es/components/content/PrismCode/prismSetup.js +1 -1
- package/es/components/content/PrismDiffCode/PrismDiffCode.js +1 -1
- package/es/components/content/Result/Result.js +1 -1
- package/es/components/content/Skeleton/Skeleton.js +1 -1
- package/es/components/content/Tag/Tag.js +1 -1
- package/es/components/content/Text.js +1 -1
- package/es/components/content/Title.js +1 -1
- package/es/components/fields/Checkbox/Checkbox.js +1 -1
- package/es/components/fields/Checkbox/CheckboxGroup.js +1 -1
- package/es/components/fields/Checkbox/context.js +1 -1
- package/es/components/fields/Checkbox/index.js +1 -1
- package/es/components/fields/ComboBox/ComboBox.js +796 -180
- package/es/components/fields/ComboBox/index.js +2 -2
- package/es/components/fields/DatePicker/DateInput.js +1 -1
- package/es/components/fields/DatePicker/DateInputBase.js +1 -1
- package/es/components/fields/DatePicker/DatePicker.js +1 -1
- package/es/components/fields/DatePicker/DatePickerButton.js +1 -1
- package/es/components/fields/DatePicker/DatePickerElement.js +1 -1
- package/es/components/fields/DatePicker/DatePickerInput.js +1 -1
- package/es/components/fields/DatePicker/DatePickerSegment.js +1 -1
- package/es/components/fields/DatePicker/DateRangePicker.js +1 -1
- package/es/components/fields/DatePicker/DateRangeSeparatedPicker.js +1 -1
- package/es/components/fields/DatePicker/TimeInput.js +1 -1
- package/es/components/fields/DatePicker/index.js +1 -1
- package/es/components/fields/DatePicker/intl.js +1 -1
- package/es/components/fields/DatePicker/parseDate.js +1 -1
- package/es/components/fields/DatePicker/props.js +1 -1
- package/es/components/fields/DatePicker/types.js +1 -1
- package/es/components/fields/DatePicker/utils.js +1 -1
- package/es/components/fields/FileInput/FileInput.js +1 -1
- package/es/components/fields/FilterListBox/FilterListBox.js +47 -47
- package/es/components/fields/FilterListBox/index.js +1 -1
- package/es/components/fields/FilterPicker/FilterPicker.js +93 -315
- package/es/components/fields/FilterPicker/index.js +1 -1
- package/es/components/fields/Input/Input.js +1 -1
- package/es/components/fields/Input/index.js +1 -1
- package/es/components/fields/LegacyComboBox/LegacyComboBox.js +290 -0
- package/es/components/fields/LegacyComboBox/index.js +10 -0
- package/es/components/fields/ListBox/ListBox.js +35 -5
- package/es/components/fields/ListBox/index.js +1 -1
- package/es/components/fields/NumberInput/NumberInput.js +1 -1
- package/es/components/fields/NumberInput/StepButton.js +1 -1
- package/es/components/fields/PasswordInput/PasswordInput.js +1 -1
- package/es/components/fields/RadioGroup/Radio.js +1 -1
- package/es/components/fields/RadioGroup/RadioGroup.js +1 -1
- package/es/components/fields/RadioGroup/context.js +1 -1
- package/es/components/fields/RadioGroup/index.js +1 -1
- package/es/components/fields/SearchInput/SearchInput.js +1 -1
- package/es/components/fields/SearchInput/index.js +1 -1
- package/es/components/fields/Select/Select.js +72 -53
- package/es/components/fields/Select/index.js +1 -1
- package/es/components/fields/Slider/Gradation.js +1 -1
- package/es/components/fields/Slider/Header.js +1 -1
- package/es/components/fields/Slider/RangeSlider.js +1 -1
- package/es/components/fields/Slider/Slider.js +1 -1
- package/es/components/fields/Slider/SliderBase.js +1 -1
- package/es/components/fields/Slider/SliderInput.js +1 -1
- package/es/components/fields/Slider/SliderThumb.js +1 -1
- package/es/components/fields/Slider/SliderTrack.js +1 -1
- package/es/components/fields/Slider/elements.js +1 -1
- package/es/components/fields/Slider/index.js +1 -1
- package/es/components/fields/Slider/types.js +1 -1
- package/es/components/fields/Switch/Switch.js +35 -27
- package/es/components/fields/Switch/index.js +1 -1
- package/es/components/fields/TextArea/TextArea.js +1 -1
- package/es/components/fields/TextArea/index.js +1 -1
- package/es/components/fields/TextInput/TextInput.js +1 -1
- package/es/components/fields/TextInput/TextInputBase.js +1 -1
- package/es/components/fields/TextInput/index.js +1 -1
- package/es/components/fields/TextInputMapper/TextInputMapper.js +2 -2
- package/es/components/fields/TextInputMapper/index.js +1 -1
- package/es/components/fields/index.js +2 -1
- package/es/components/form/FieldWrapper/FieldWrapper.js +1 -1
- package/es/components/form/FieldWrapper/extract-field-wrapper-props.js +1 -1
- package/es/components/form/FieldWrapper/index.js +1 -1
- package/es/components/form/FieldWrapper/types.js +1 -1
- package/es/components/form/Form/Field.js +1 -1
- package/es/components/form/Form/Form.js +1 -1
- package/es/components/form/Form/ResetButton/ResetButton.js +1 -1
- package/es/components/form/Form/ResetButton/index.js +1 -1
- package/es/components/form/Form/SubmitButton/SubmitButton.js +1 -1
- package/es/components/form/Form/SubmitButton/index.js +1 -1
- package/es/components/form/Form/SubmitError.js +1 -1
- package/es/components/form/Form/index.js +1 -1
- package/es/components/form/Form/types.js +1 -1
- package/es/components/form/Form/use-field/index.js +1 -1
- package/es/components/form/Form/use-field/types.js +1 -1
- package/es/components/form/Form/use-field/use-field-props.js +1 -1
- package/es/components/form/Form/use-field/use-field.js +1 -1
- package/es/components/form/Form/use-form.js +1 -1
- package/es/components/form/Form/validation.js +1 -1
- package/es/components/form/Label.js +1 -1
- package/es/components/form/index.js +1 -1
- package/es/components/form/wrapper.js +1 -1
- package/es/components/helpers/DisplayTransition/DisplayTransition.js +242 -0
- package/es/components/helpers/index.js +10 -0
- package/es/components/layout/Flex.js +1 -1
- package/es/components/layout/Flow.js +1 -1
- package/es/components/layout/Grid.js +1 -1
- package/es/components/layout/Panel.js +1 -1
- package/es/components/layout/Prefix.js +1 -1
- package/es/components/layout/ResizablePanel.js +1 -1
- package/es/components/layout/Space.js +1 -1
- package/es/components/layout/Suffix.js +1 -1
- package/es/components/navigation/LegacyTabs/LegacyTabs.js +1 -1
- package/es/components/organisms/FileTabs/FileTabs.js +1 -1
- package/es/components/organisms/StatsCard/StatsCard.js +1 -1
- package/es/components/other/Base64Upload/Base64Upload.js +1 -1
- package/es/components/other/Calendar/Calendar.js +1 -1
- package/es/components/other/Calendar/CalendarCell.js +1 -1
- package/es/components/other/Calendar/CalendarGrid.js +1 -1
- package/es/components/other/Calendar/RangeCalendar.js +1 -1
- package/es/components/other/CloudLogo/CloudLogo.js +1 -1
- package/es/components/overlays/AlertDialog/AlertDialog.js +1 -1
- package/es/components/overlays/AlertDialog/AlertDialogApiProvider.js +1 -1
- package/es/components/overlays/AlertDialog/AlertDialogZone.js +1 -1
- package/es/components/overlays/AlertDialog/index.js +1 -1
- package/es/components/overlays/AlertDialog/types.js +1 -1
- package/es/components/overlays/Dialog/Dialog.js +1 -1
- package/es/components/overlays/Dialog/DialogContainer.js +1 -1
- package/es/components/overlays/Dialog/DialogForm.js +1 -1
- package/es/components/overlays/Dialog/DialogTrigger.js +1 -1
- package/es/components/overlays/Dialog/context.js +1 -1
- package/es/components/overlays/Dialog/index.js +1 -1
- package/es/components/overlays/Dialog/use-dialog-container.js +1 -1
- package/es/components/overlays/Modal/Modal.js +1 -1
- package/es/components/overlays/Modal/OpenTransition.js +1 -1
- package/es/components/overlays/Modal/Overlay.js +1 -1
- package/es/components/overlays/Modal/Popover.js +1 -1
- package/es/components/overlays/Modal/Tray.js +1 -1
- package/es/components/overlays/Modal/Underlay.js +1 -1
- package/es/components/overlays/Modal/index.js +1 -1
- package/es/components/overlays/Modal/types.js +1 -1
- package/es/components/overlays/NewNotifications/Bar/FloatingNotification.js +1 -1
- package/es/components/overlays/NewNotifications/Bar/NotificationsBar.js +1 -1
- package/es/components/overlays/NewNotifications/Bar/TransitionComponent.js +1 -1
- package/es/components/overlays/NewNotifications/Bar/index.js +1 -1
- package/es/components/overlays/NewNotifications/Dialog/NotificationsDialogContext.js +1 -1
- package/es/components/overlays/NewNotifications/Dialog/NotificationsDialogTrigger.js +1 -1
- package/es/components/overlays/NewNotifications/Dialog/index.js +1 -1
- package/es/components/overlays/NewNotifications/Notification.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationAction.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationCloseButton.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationDescription.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationFooter.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationHeader.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationIcon.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationProvider.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationView.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/index.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/types.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsContext/NotificationsContext.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsContext/NotificationsProvider.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsContext/index.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsContext/use-notifications.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsList/NotificationsList.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsList/NotificationsListItem.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsList/index.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsList/types.js +1 -1
- package/es/components/overlays/NewNotifications/hooks/index.js +1 -1
- package/es/components/overlays/NewNotifications/hooks/types.js +1 -1
- package/es/components/overlays/NewNotifications/hooks/use-notification-list-item.js +1 -1
- package/es/components/overlays/NewNotifications/hooks/use-notifications-api.js +1 -1
- package/es/components/overlays/NewNotifications/hooks/use-notifications-list.js +1 -1
- package/es/components/overlays/NewNotifications/hooks/use-notifications-observer.js +1 -1
- package/es/components/overlays/NewNotifications/index.js +1 -1
- package/es/components/overlays/NewNotifications/types.js +1 -1
- package/es/components/overlays/Notification/Notification.js +1 -1
- package/es/components/overlays/OverlayWrapper.js +1 -1
- package/es/components/overlays/Toasts/Toast.js +1 -1
- package/es/components/overlays/Toasts/index.js +1 -1
- package/es/components/overlays/Toasts/types.js +1 -1
- package/es/components/overlays/Toasts/use-toasts-api.js +1 -1
- package/es/components/overlays/Tooltip/Tooltip.js +30 -18
- package/es/components/overlays/Tooltip/TooltipProvider.js +2 -2
- package/es/components/overlays/Tooltip/TooltipTrigger.js +25 -7
- package/es/components/overlays/Tooltip/context.js +1 -1
- package/es/components/overlays/Tooltip/index.js +1 -1
- package/es/components/portal/Portal.js +5 -2
- package/es/components/portal/PortalProvider.js +1 -1
- package/es/components/portal/index.js +1 -1
- package/es/components/portal/types.js +1 -1
- package/es/components/portal/usePortal.js +1 -1
- package/es/components/shared/InvalidIcon.js +1 -1
- package/es/components/shared/ValidIcon.js +1 -1
- package/es/components/status/LoadingAnimation/LoadingAnimation.js +1 -1
- package/es/components/status/LoadingAnimation/index.js +1 -1
- package/es/components/status/Spin/Cube.js +1 -1
- package/es/components/status/Spin/InternalSpinner.js +1 -1
- package/es/components/status/Spin/Spin.js +1 -1
- package/es/components/status/Spin/SpinsContainer.js +1 -1
- package/es/components/status/Spin/index.js +1 -1
- package/es/components/status/Spin/types.js +1 -1
- package/es/components/status/index.js +1 -1
- package/es/data/item-themes.js +1 -1
- package/es/data/themes.js +1 -1
- package/es/icons/AdjustmentsHorizontalIcon.js +1 -1
- package/es/icons/AdjustmentsIcon.js +1 -1
- package/es/icons/AiIcon.js +1 -1
- package/es/icons/AreaChartIcon.js +1 -1
- package/es/icons/BackwardIcon.js +1 -1
- package/es/icons/BarChartIcon.js +1 -1
- package/es/icons/BellFilledIcon.js +1 -1
- package/es/icons/BellIcon.js +1 -1
- package/es/icons/BooleanIcon.js +1 -1
- package/es/icons/CalendarEditIcon.js +1 -1
- package/es/icons/CalendarIcon.js +1 -1
- package/es/icons/CaretDownIcon.js +1 -1
- package/es/icons/CaretUpIcon.js +1 -1
- package/es/icons/ChartAreaStackedIcon.js +1 -1
- package/es/icons/ChartAreaStackedPercentageIcon.js +1 -1
- package/es/icons/ChartBarGroupedHorizontalIcon.js +1 -1
- package/es/icons/ChartBarGroupedIcon.js +1 -1
- package/es/icons/ChartBarHorizontalIcon.js +1 -1
- package/es/icons/ChartBarLineIcon.js +1 -1
- package/es/icons/ChartBarStackedHorizontalIcon.js +1 -1
- package/es/icons/ChartBarStackedIcon.js +1 -1
- package/es/icons/ChartBarStackedPercentageHorizontalIcon.js +1 -1
- package/es/icons/ChartBarStackedPercentageIcon.js +1 -1
- package/es/icons/ChartBoxPlot2Icon.js +1 -1
- package/es/icons/ChartBoxPlotIcon.js +1 -1
- package/es/icons/ChartBubbleIcon.js +1 -1
- package/es/icons/ChartDonut2Icon.js +1 -1
- package/es/icons/ChartFunnelIcon.js +1 -1
- package/es/icons/ChartHeatmapIcon.js +1 -1
- package/es/icons/ChartKPIIcon.js +1 -1
- package/es/icons/ChartPie2Icon.js +1 -1
- package/es/icons/ChartScatterIcon.js +1 -1
- package/es/icons/CheckCircleFilledIcon.js +1 -1
- package/es/icons/CheckCircleIcon.js +1 -1
- package/es/icons/CheckIcon.js +1 -1
- package/es/icons/CircleFilledIcon.js +1 -1
- package/es/icons/ClearIcon.js +1 -1
- package/es/icons/CloseCircleFilledIcon.js +1 -1
- package/es/icons/CloseCircleIcon.js +1 -1
- package/es/icons/CloseIcon.js +1 -1
- package/es/icons/CodeIcon.js +1 -1
- package/es/icons/CopyIcon.js +1 -1
- package/es/icons/CountIcon.js +1 -1
- package/es/icons/CubeIcon.js +1 -1
- package/es/icons/CubePauseIcon.js +1 -1
- package/es/icons/CubePlayIcon.js +1 -1
- package/es/icons/CurrencyDollarIcon.js +1 -1
- package/es/icons/DangerIcon.js +1 -1
- package/es/icons/DashboardIcon.js +1 -1
- package/es/icons/DatabaseIcon.js +1 -1
- package/es/icons/DecimalDecreaseIcon.js +1 -1
- package/es/icons/DecimalIncreaseIcon.js +1 -1
- package/es/icons/DirectionIcon.js +1 -1
- package/es/icons/DonutIcon.js +1 -1
- package/es/icons/DownIcon.js +1 -1
- package/es/icons/EditIcon.js +1 -1
- package/es/icons/ExclamationCircleFilledIcon.js +1 -1
- package/es/icons/ExclamationCircleIcon.js +1 -1
- package/es/icons/ExclamationIcon.js +1 -1
- package/es/icons/EyeIcon.js +1 -1
- package/es/icons/EyeInvisibleIcon.js +1 -1
- package/es/icons/FilterIcon.js +1 -1
- package/es/icons/FolderFilledIcon.js +1 -1
- package/es/icons/FolderIcon.js +1 -1
- package/es/icons/FolderOpenFilledIcon.js +1 -1
- package/es/icons/FolderOpenIcon.js +1 -1
- package/es/icons/ForwardIcon.js +1 -1
- package/es/icons/HierarchyIcon.js +1 -1
- package/es/icons/Icon.js +1 -1
- package/es/icons/InfoCircleIcon.js +1 -1
- package/es/icons/InfoIcon.js +1 -1
- package/es/icons/KeyIcon.js +1 -1
- package/es/icons/LeftIcon.js +1 -1
- package/es/icons/LineChartIcon.js +1 -1
- package/es/icons/LoadingIcon.js +1 -1
- package/es/icons/LockFilledIcon.js +1 -1
- package/es/icons/LockIcon.js +1 -1
- package/es/icons/MoreIcon.js +1 -1
- package/es/icons/NotAllowedIcon.js +1 -1
- package/es/icons/Number123Icon.js +1 -1
- package/es/icons/NumberIcon.js +1 -1
- package/es/icons/PauseCircleFilledIcon.js +1 -1
- package/es/icons/PauseCircleIcon.js +1 -1
- package/es/icons/PauseIcon.js +1 -1
- package/es/icons/PercentageIcon.js +1 -1
- package/es/icons/PieChartIcon.js +1 -1
- package/es/icons/PlayCircleIcon.js +1 -1
- package/es/icons/PlayIcon.js +1 -1
- package/es/icons/PlusIcon.js +1 -1
- package/es/icons/ReloadIcon.js +1 -1
- package/es/icons/ReportIcon.js +1 -1
- package/es/icons/ReturnIcon.js +1 -1
- package/es/icons/RightIcon.js +1 -1
- package/es/icons/SchemeIcon.js +1 -1
- package/es/icons/SearchIcon.js +1 -1
- package/es/icons/SettingsIcon.js +1 -1
- package/es/icons/ShieldFilledIcon.js +1 -1
- package/es/icons/ShieldIcon.js +1 -1
- package/es/icons/SlashIcon.js +1 -1
- package/es/icons/SparklesIcon.js +1 -1
- package/es/icons/SqlIcon.js +1 -1
- package/es/icons/StatsIcon.js +1 -1
- package/es/icons/StopIcon.js +1 -1
- package/es/icons/StringIcon.js +1 -1
- package/es/icons/SwitchIcon.js +1 -1
- package/es/icons/TableIcon.js +1 -1
- package/es/icons/ThumbsDownIcon.js +1 -1
- package/es/icons/ThumbsUpIcon.js +1 -1
- package/es/icons/ThunderboltCrossedIcon.js +1 -1
- package/es/icons/ThunderboltFilledIcon.js +1 -1
- package/es/icons/ThunderboltIcon.js +1 -1
- package/es/icons/TimeIcon.js +1 -1
- package/es/icons/UnlockIcon.js +1 -1
- package/es/icons/UpIcon.js +1 -1
- package/es/icons/UserGroupIcon.js +1 -1
- package/es/icons/UserIcon.js +1 -1
- package/es/icons/UserLockIcon.js +1 -1
- package/es/icons/ViewIcon.js +1 -1
- package/es/icons/WarningFilledIcon.js +1 -1
- package/es/icons/WarningIcon.js +1 -1
- package/es/icons/add-new-icon.js +1 -1
- package/es/icons/index.js +1 -1
- package/es/icons/wrap-icon.js +1 -1
- package/es/index.js +2 -1
- package/es/provider.js +1 -1
- package/es/providers/TrackingProvider.js +1 -1
- package/es/providers/navigation.types.js +1 -1
- package/es/providers/navigationAdapter.default.js +1 -1
- package/es/services/notification.js +1 -1
- package/es/shared/form.js +1 -1
- package/es/shared/index.js +1 -1
- package/es/stories/Form.legacy-stories.js +1 -1
- package/es/stories/FormFieldArgs.js +1 -1
- package/es/stories/Layout.stories.js +1 -1
- package/es/stories/Tasty.stories.js +1 -1
- package/es/stories/components/ConfirmDeletionDialogForm.js +1 -1
- package/es/stories/components/DialogFormApp.js +1 -1
- package/es/stories/components/StyledButton.js +1 -1
- package/es/stories/lists/baseProps.js +1 -1
- package/es/tasty/debug.js +1 -1
- package/es/tasty/index.js +1 -1
- package/es/tasty/injector/index.js +1 -1
- package/es/tasty/injector/injector.js +1 -1
- package/es/tasty/injector/sheet-manager.js +1 -1
- package/es/tasty/injector/types.js +1 -1
- package/es/tasty/parser/classify.js +1 -1
- package/es/tasty/parser/const.js +1 -1
- package/es/tasty/parser/lru.js +1 -1
- package/es/tasty/parser/parser.js +1 -1
- package/es/tasty/parser/tokenizer.js +1 -1
- package/es/tasty/parser/types.js +1 -1
- package/es/tasty/providers/BreakpointsProvider.js +1 -1
- package/es/tasty/styles/align.js +1 -1
- package/es/tasty/styles/border.js +1 -1
- package/es/tasty/styles/boxShadow.combinator.js +1 -1
- package/es/tasty/styles/color.js +1 -1
- package/es/tasty/styles/createStyle.js +1 -1
- package/es/tasty/styles/dimension.js +1 -1
- package/es/tasty/styles/display.js +1 -1
- package/es/tasty/styles/fade.js +1 -1
- package/es/tasty/styles/fill.js +1 -1
- package/es/tasty/styles/flow.js +1 -1
- package/es/tasty/styles/font.js +1 -1
- package/es/tasty/styles/fontStyle.js +1 -1
- package/es/tasty/styles/gap.js +1 -1
- package/es/tasty/styles/groupRadius.js +1 -1
- package/es/tasty/styles/height.js +1 -1
- package/es/tasty/styles/index.js +1 -1
- package/es/tasty/styles/inset.js +1 -1
- package/es/tasty/styles/justify.js +1 -1
- package/es/tasty/styles/list.js +1 -1
- package/es/tasty/styles/margin.js +1 -1
- package/es/tasty/styles/outline.js +1 -1
- package/es/tasty/styles/padding.js +1 -1
- package/es/tasty/styles/place.js +1 -1
- package/es/tasty/styles/predefined.js +1 -1
- package/es/tasty/styles/preset.js +1 -1
- package/es/tasty/styles/radius.js +1 -1
- package/es/tasty/styles/reset.js +1 -1
- package/es/tasty/styles/scrollbar.js +1 -1
- package/es/tasty/styles/shadow.js +1 -1
- package/es/tasty/styles/styledScrollbar.js +1 -1
- package/es/tasty/styles/transition.js +1 -1
- package/es/tasty/styles/types.js +1 -1
- package/es/tasty/styles/width.js +1 -1
- package/es/tasty/tasty.js +1 -1
- package/es/tasty/types.js +1 -1
- package/es/tasty/utils/cache-wrapper.js +1 -1
- package/es/tasty/utils/case-converter.js +1 -1
- package/es/tasty/utils/colors.js +1 -1
- package/es/tasty/utils/dotize.js +1 -1
- package/es/tasty/utils/filterBaseProps.js +1 -1
- package/es/tasty/utils/getDisplayName.js +1 -1
- package/es/tasty/utils/getModCombinations.js +1 -1
- package/es/tasty/utils/isDevEnv.js +1 -1
- package/es/tasty/utils/mergeStyles.js +1 -1
- package/es/tasty/utils/modAttrs.js +1 -1
- package/es/tasty/utils/renderStyles.js +1 -1
- package/es/tasty/utils/responsive.js +1 -1
- package/es/tasty/utils/string.js +1 -1
- package/es/tasty/utils/styles.js +1 -1
- package/es/tasty/utils/warnings.js +1 -1
- package/es/tokens.js +1 -1
- package/es/type-checks.js +1 -1
- package/es/utils/ResizeSensor.js +1 -1
- package/es/utils/modules.js +1 -1
- package/es/utils/promise.js +1 -1
- package/es/utils/random.js +1 -1
- package/es/utils/range.js +1 -1
- package/es/utils/react/Slots.js +1 -1
- package/es/utils/react/chain.js +1 -1
- package/es/utils/react/forwardRefWithGenerics.js +1 -1
- package/es/utils/react/index.js +2 -2
- package/es/utils/react/interactions.js +1 -1
- package/es/utils/react/isTextOnly.js +1 -1
- package/es/utils/react/mapProps.js +1 -1
- package/es/utils/react/mergeProps.js +1 -1
- package/es/utils/react/nullableValue.js +1 -1
- package/es/utils/react/sharedStore.js +1 -1
- package/es/utils/react/useCombinedRefs.js +11 -6
- package/es/utils/react/useControlledFocusVisible.js +1 -1
- package/es/utils/react/useEventBus.js +1 -1
- package/es/utils/react/useId.js +1 -1
- package/es/utils/react/useIsDarwin.js +1 -1
- package/es/utils/react/useKeySymbols.js +1 -1
- package/es/utils/react/useLayoutEffect.js +1 -1
- package/es/utils/react/useQaProps.js +1 -1
- package/es/utils/react/useViewportSize.js +1 -1
- package/es/utils/react/wrapNodeIfPlain.js +1 -1
- package/es/utils/transitions.js +1 -1
- package/es/utils/tree.js +1 -1
- package/es/utils/warnings.js +1 -1
- package/es/version.js +2 -2
- package/package.json +1 -1
- package/types/components/GridProvider.d.ts +1 -1
- package/types/components/fields/ComboBox/ComboBox.d.ts +101 -30
- package/types/components/fields/ComboBox/index.d.ts +2 -1
- package/types/components/fields/FilterListBox/FilterListBox.d.ts +7 -0
- package/types/components/fields/FilterPicker/FilterPicker.d.ts +9 -2
- package/types/components/fields/LegacyComboBox/LegacyComboBox.d.ts +49 -0
- package/types/components/fields/LegacyComboBox/index.d.ts +1 -0
- package/types/components/fields/ListBox/ListBox.d.ts +5 -0
- package/types/components/fields/Select/Select.d.ts +3 -1
- package/types/components/fields/Switch/Switch.d.ts +2 -1
- package/types/components/fields/index.d.ts +1 -0
- package/types/components/helpers/DisplayTransition/DisplayTransition.d.ts +24 -0
- package/types/components/helpers/index.d.ts +1 -0
- package/types/components/layout/Prefix.d.ts +1 -1
- package/types/components/layout/Suffix.d.ts +1 -1
- package/types/components/overlays/Tooltip/TooltipTrigger.d.ts +5 -0
- package/types/components/overlays/Tooltip/context.d.ts +6 -1
- package/types/components/portal/Portal.d.ts +1 -1
- package/types/index.d.ts +2 -0
- package/types/utils/react/index.d.ts +1 -1
- package/types/utils/react/useCombinedRefs.d.ts +3 -2
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license MIT
|
|
3
3
|
* author: Cube Dev Team
|
|
4
|
-
* @cube-dev/ui-kit v0.
|
|
4
|
+
* @cube-dev/ui-kit v0.82.0
|
|
5
5
|
* Released under the MIT license.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
|
-
import { cloneElement, forwardRef, useEffect, useMemo, } from 'react';
|
|
10
|
-
import {
|
|
11
|
-
import { Section as BaseSection,
|
|
12
|
-
import { useEvent } from '../../../_internal
|
|
13
|
-
import { CloseIcon,
|
|
9
|
+
import React, { cloneElement, forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState, } from 'react';
|
|
10
|
+
import { useFilter, useKeyboard, useOverlay, useOverlayPosition, } from 'react-aria';
|
|
11
|
+
import { Section as BaseSection, Item } from 'react-stately';
|
|
12
|
+
import { useEvent } from '../../../_internal';
|
|
13
|
+
import { CloseIcon, DirectionIcon, LoadingIcon } from '../../../icons';
|
|
14
14
|
import { useProviderProps } from '../../../provider';
|
|
15
15
|
import { BASE_STYLES, COLOR_STYLES, extractStyles, OUTER_STYLES, tasty, } from '../../../tasty';
|
|
16
16
|
import { generateRandomId } from '../../../utils/random';
|
|
@@ -19,12 +19,12 @@ import { useFocus } from '../../../utils/react/interactions';
|
|
|
19
19
|
import { useEventBus } from '../../../utils/react/useEventBus';
|
|
20
20
|
import { ItemAction } from '../../actions';
|
|
21
21
|
import { useFieldProps, useFormProps, wrapWithField } from '../../form';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
22
|
+
import { DisplayTransition } from '../../helpers';
|
|
23
|
+
import { Portal } from '../../portal';
|
|
24
24
|
import { InvalidIcon } from '../../shared/InvalidIcon';
|
|
25
25
|
import { ValidIcon } from '../../shared/ValidIcon';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
26
|
+
import { ListBox } from '../ListBox/ListBox';
|
|
27
|
+
import { DEFAULT_INPUT_STYLES, INPUT_WRAPPER_STYLES, } from '../TextInput/TextInputBase';
|
|
28
28
|
const ComboBoxWrapperElement = tasty({
|
|
29
29
|
styles: INPUT_WRAPPER_STYLES,
|
|
30
30
|
});
|
|
@@ -32,91 +32,435 @@ const InputElement = tasty({
|
|
|
32
32
|
as: 'input',
|
|
33
33
|
styles: DEFAULT_INPUT_STYLES,
|
|
34
34
|
});
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
type: 'neutral',
|
|
35
|
+
const ComboBoxOverlayElement = tasty({
|
|
36
|
+
qa: 'ComboBoxOverlay',
|
|
38
37
|
styles: {
|
|
39
38
|
display: 'grid',
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
gridRows: '1sf',
|
|
40
|
+
gridColumns: '1sf',
|
|
41
|
+
width: '$min-width max-content 50vw',
|
|
42
|
+
height: 'initial max-content (50vh - $size)',
|
|
43
|
+
overflow: 'auto',
|
|
44
|
+
background: '#white',
|
|
45
|
+
radius: '1cr',
|
|
46
|
+
shadow: '0 .5x 2x #shadow',
|
|
44
47
|
padding: '0',
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
hovered: '#dark-02',
|
|
50
|
-
pressed: '#purple',
|
|
51
|
-
disabled: '#dark.30',
|
|
48
|
+
border: '#border',
|
|
49
|
+
hide: {
|
|
50
|
+
'': false,
|
|
51
|
+
hidden: true,
|
|
52
52
|
},
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
'': '
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
disabled: '#clear',
|
|
53
|
+
transition: 'translate $transition ease-out, scale $transition ease-out, theme $transition ease-out',
|
|
54
|
+
translate: {
|
|
55
|
+
'': '0 0',
|
|
56
|
+
'open & [data-placement="top"]': '0 0',
|
|
57
|
+
'!open & [data-placement="top"]': '0 10%',
|
|
58
|
+
'open & ([data-placement="bottom"] | ![data-placement]': '0 0',
|
|
59
|
+
'!open & ([data-placement="bottom"] | ![data-placement])': '0 -10%',
|
|
61
60
|
},
|
|
62
|
-
|
|
61
|
+
scale: {
|
|
62
|
+
'': '1 1',
|
|
63
|
+
'!open': '1 .9',
|
|
64
|
+
},
|
|
65
|
+
opacity: {
|
|
66
|
+
'': 1,
|
|
67
|
+
'!open': 0.001,
|
|
68
|
+
},
|
|
69
|
+
'$min-width': 'min 30x',
|
|
63
70
|
},
|
|
64
71
|
});
|
|
65
72
|
const PROP_STYLES = [...BASE_STYLES, ...OUTER_STYLES, ...COLOR_STYLES];
|
|
73
|
+
function useComboBoxState({ selectedKey, defaultSelectedKey, inputValue, defaultInputValue, comboBoxId, }) {
|
|
74
|
+
// Get event bus for menu synchronization
|
|
75
|
+
const { emit, on } = useEventBus();
|
|
76
|
+
// Internal state for uncontrolled mode
|
|
77
|
+
const [internalSelectedKey, setInternalSelectedKey] = useState(defaultSelectedKey ?? null);
|
|
78
|
+
const [internalInputValue, setInternalInputValue] = useState(defaultInputValue ?? '');
|
|
79
|
+
const isControlledKey = selectedKey !== undefined;
|
|
80
|
+
const isControlledInput = inputValue !== undefined;
|
|
81
|
+
const effectiveSelectedKey = isControlledKey
|
|
82
|
+
? selectedKey
|
|
83
|
+
: internalSelectedKey;
|
|
84
|
+
const effectiveInputValue = isControlledInput
|
|
85
|
+
? inputValue
|
|
86
|
+
: internalInputValue;
|
|
87
|
+
// Popover state
|
|
88
|
+
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
|
89
|
+
// Listen for other menus opening and close this one if needed
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const unsubscribe = on('popover:open', (data) => {
|
|
92
|
+
if (data.menuId !== comboBoxId && isPopoverOpen) {
|
|
93
|
+
setIsPopoverOpen(false);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return unsubscribe;
|
|
97
|
+
}, [on, comboBoxId, isPopoverOpen]);
|
|
98
|
+
// Emit event when this combobox opens
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (isPopoverOpen) {
|
|
101
|
+
emit('popover:open', { menuId: comboBoxId });
|
|
102
|
+
}
|
|
103
|
+
}, [isPopoverOpen, emit, comboBoxId]);
|
|
104
|
+
return {
|
|
105
|
+
effectiveSelectedKey,
|
|
106
|
+
effectiveInputValue,
|
|
107
|
+
isPopoverOpen,
|
|
108
|
+
setIsPopoverOpen,
|
|
109
|
+
setInternalSelectedKey,
|
|
110
|
+
setInternalInputValue,
|
|
111
|
+
isControlledKey,
|
|
112
|
+
isControlledInput,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function useComboBoxFiltering({ children, effectiveInputValue, filter, }) {
|
|
116
|
+
const [isFilterActive, setIsFilterActive] = useState(false);
|
|
117
|
+
const { contains } = useFilter({ sensitivity: 'base' });
|
|
118
|
+
const textFilterFn = useMemo(() => (filter === false ? () => true : filter || contains), [filter, contains]);
|
|
119
|
+
// Filter children based on input value
|
|
120
|
+
const filteredChildren = useMemo(() => {
|
|
121
|
+
const term = effectiveInputValue.trim();
|
|
122
|
+
if (!isFilterActive || !term || !children) {
|
|
123
|
+
return children;
|
|
124
|
+
}
|
|
125
|
+
const nodeMatches = (node) => {
|
|
126
|
+
if (!node?.props)
|
|
127
|
+
return false;
|
|
128
|
+
const textValue = node.props.textValue ||
|
|
129
|
+
(typeof node.props.children === 'string' ? node.props.children : '') ||
|
|
130
|
+
String(node.props.children || '');
|
|
131
|
+
return textFilterFn(textValue, term);
|
|
132
|
+
};
|
|
133
|
+
const filterChildren = (childNodes) => {
|
|
134
|
+
if (!childNodes)
|
|
135
|
+
return null;
|
|
136
|
+
const childArray = Array.isArray(childNodes) ? childNodes : [childNodes];
|
|
137
|
+
const filteredNodes = [];
|
|
138
|
+
childArray.forEach((child) => {
|
|
139
|
+
if (!child || typeof child !== 'object') {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (child.type === BaseSection ||
|
|
143
|
+
child.type?.displayName === 'Section') {
|
|
144
|
+
const sectionChildren = Array.isArray(child.props.children)
|
|
145
|
+
? child.props.children
|
|
146
|
+
: [child.props.children];
|
|
147
|
+
const filteredSectionChildren = sectionChildren.filter((sectionChild) => {
|
|
148
|
+
return (sectionChild &&
|
|
149
|
+
typeof sectionChild === 'object' &&
|
|
150
|
+
nodeMatches(sectionChild));
|
|
151
|
+
});
|
|
152
|
+
if (filteredSectionChildren.length > 0) {
|
|
153
|
+
filteredNodes.push(cloneElement(child, {
|
|
154
|
+
key: child.key,
|
|
155
|
+
children: filteredSectionChildren,
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else if (child.type === Item) {
|
|
160
|
+
if (nodeMatches(child)) {
|
|
161
|
+
filteredNodes.push(child);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (nodeMatches(child)) {
|
|
165
|
+
filteredNodes.push(child);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return filteredNodes;
|
|
169
|
+
};
|
|
170
|
+
return filterChildren(children);
|
|
171
|
+
}, [isFilterActive, children, effectiveInputValue, textFilterFn]);
|
|
172
|
+
return {
|
|
173
|
+
filteredChildren,
|
|
174
|
+
isFilterActive,
|
|
175
|
+
setIsFilterActive,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function useCompositeFocus({ wrapperRef, popoverRef, onFocus, onBlur, isDisabled, }) {
|
|
179
|
+
const wasInsideRef = useRef(false);
|
|
180
|
+
const rafRef = useRef(null);
|
|
181
|
+
const checkFocus = useCallback(() => {
|
|
182
|
+
if (isDisabled)
|
|
183
|
+
return;
|
|
184
|
+
const activeElement = document.activeElement;
|
|
185
|
+
const isInside = (wrapperRef.current?.contains(activeElement) ?? false) ||
|
|
186
|
+
(popoverRef.current?.contains(activeElement) ?? false);
|
|
187
|
+
if (isInside !== wasInsideRef.current) {
|
|
188
|
+
wasInsideRef.current = isInside;
|
|
189
|
+
if (isInside) {
|
|
190
|
+
onFocus?.();
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
onBlur?.();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}, [wrapperRef, popoverRef, onFocus, onBlur, isDisabled]);
|
|
197
|
+
const handleFocusOrBlur = useCallback((e) => {
|
|
198
|
+
// Cancel any pending check
|
|
199
|
+
if (rafRef.current !== null) {
|
|
200
|
+
cancelAnimationFrame(rafRef.current);
|
|
201
|
+
}
|
|
202
|
+
// Schedule focus check for next frame
|
|
203
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
204
|
+
rafRef.current = null;
|
|
205
|
+
checkFocus();
|
|
206
|
+
});
|
|
207
|
+
}, [checkFocus]);
|
|
208
|
+
// Cleanup on unmount
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
return () => {
|
|
211
|
+
if (rafRef.current !== null) {
|
|
212
|
+
cancelAnimationFrame(rafRef.current);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}, []);
|
|
216
|
+
return {
|
|
217
|
+
compositeFocusProps: {
|
|
218
|
+
onFocus: handleFocusOrBlur,
|
|
219
|
+
onBlur: handleFocusOrBlur,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function useComboBoxKeyboard({ isPopoverOpen, listStateRef, hasResults, allowsCustomValue, effectiveInputValue, isClearable, onSelectionChange, onClearValue, onOpenPopover, onClosePopover, inputRef, setIsFilterActive, onKeyDown, }) {
|
|
224
|
+
const { keyboardProps } = useKeyboard({
|
|
225
|
+
onKeyDown: (e) => {
|
|
226
|
+
// Call user's handler first
|
|
227
|
+
onKeyDown?.(e);
|
|
228
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
229
|
+
e.preventDefault();
|
|
230
|
+
// Open popover if closed
|
|
231
|
+
if (!isPopoverOpen) {
|
|
232
|
+
// If opening with no filtered results, disable filter to show all items
|
|
233
|
+
if (!hasResults) {
|
|
234
|
+
setIsFilterActive(false);
|
|
235
|
+
}
|
|
236
|
+
onOpenPopover();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const listState = listStateRef.current;
|
|
240
|
+
if (!listState)
|
|
241
|
+
return;
|
|
242
|
+
const { selectionManager, collection, disabledKeys } = listState;
|
|
243
|
+
// Helper to collect visible item keys (supports sections)
|
|
244
|
+
const collectVisibleKeys = (nodes, out) => {
|
|
245
|
+
for (const node of nodes) {
|
|
246
|
+
if (node.type === 'item') {
|
|
247
|
+
if (!disabledKeys?.has(node.key)) {
|
|
248
|
+
out.push(node.key);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else if (node.childNodes) {
|
|
252
|
+
collectVisibleKeys(node.childNodes, out);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const visibleKeys = [];
|
|
257
|
+
collectVisibleKeys(collection, visibleKeys);
|
|
258
|
+
if (visibleKeys.length === 0)
|
|
259
|
+
return;
|
|
260
|
+
const isArrowDown = e.key === 'ArrowDown';
|
|
261
|
+
const currentKey = selectionManager.focusedKey;
|
|
262
|
+
let nextKey = null;
|
|
263
|
+
if (currentKey == null) {
|
|
264
|
+
nextKey = isArrowDown
|
|
265
|
+
? visibleKeys[0]
|
|
266
|
+
: visibleKeys[visibleKeys.length - 1];
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
const currentIndex = visibleKeys.indexOf(currentKey);
|
|
270
|
+
if (currentIndex !== -1) {
|
|
271
|
+
const newIndex = currentIndex + (isArrowDown ? 1 : -1);
|
|
272
|
+
if (newIndex >= 0 && newIndex < visibleKeys.length) {
|
|
273
|
+
nextKey = visibleKeys[newIndex];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
nextKey = isArrowDown
|
|
278
|
+
? visibleKeys[0]
|
|
279
|
+
: visibleKeys[visibleKeys.length - 1];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (nextKey != null) {
|
|
283
|
+
if (listState.lastFocusSourceRef) {
|
|
284
|
+
listState.lastFocusSourceRef.current = 'keyboard';
|
|
285
|
+
}
|
|
286
|
+
selectionManager.setFocusedKey(nextKey);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else if (e.key === 'Enter') {
|
|
290
|
+
if (!hasResults) {
|
|
291
|
+
e.preventDefault();
|
|
292
|
+
if (allowsCustomValue) {
|
|
293
|
+
const value = effectiveInputValue;
|
|
294
|
+
onSelectionChange(value ?? null);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
onSelectionChange(null);
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (isPopoverOpen) {
|
|
302
|
+
const listState = listStateRef.current;
|
|
303
|
+
if (!listState)
|
|
304
|
+
return;
|
|
305
|
+
const keyToSelect = listState.selectionManager.focusedKey;
|
|
306
|
+
if (keyToSelect != null) {
|
|
307
|
+
e.preventDefault();
|
|
308
|
+
listState.selectionManager.select(keyToSelect, e);
|
|
309
|
+
// Ensure the popover closes even if selection stays the same
|
|
310
|
+
onClosePopover();
|
|
311
|
+
inputRef.current?.focus();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else if (e.key === 'Escape') {
|
|
316
|
+
if (isPopoverOpen) {
|
|
317
|
+
e.preventDefault();
|
|
318
|
+
onClosePopover();
|
|
319
|
+
inputRef.current?.focus();
|
|
320
|
+
}
|
|
321
|
+
else if (effectiveInputValue && isClearable) {
|
|
322
|
+
e.preventDefault();
|
|
323
|
+
onClearValue();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
else if (e.key === 'Home' || e.key === 'End') {
|
|
327
|
+
if (isPopoverOpen) {
|
|
328
|
+
e.preventDefault();
|
|
329
|
+
const listState = listStateRef.current;
|
|
330
|
+
if (!listState)
|
|
331
|
+
return;
|
|
332
|
+
const { selectionManager, collection, disabledKeys } = listState;
|
|
333
|
+
// Helper to collect visible item keys (supports sections)
|
|
334
|
+
const collectVisibleKeys = (nodes, out) => {
|
|
335
|
+
for (const node of nodes) {
|
|
336
|
+
if (node.type === 'item') {
|
|
337
|
+
if (!disabledKeys?.has(node.key)) {
|
|
338
|
+
out.push(node.key);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else if (node.childNodes) {
|
|
342
|
+
collectVisibleKeys(node.childNodes, out);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
const visibleKeys = [];
|
|
347
|
+
collectVisibleKeys(collection, visibleKeys);
|
|
348
|
+
if (visibleKeys.length === 0)
|
|
349
|
+
return;
|
|
350
|
+
const targetKey = e.key === 'Home'
|
|
351
|
+
? visibleKeys[0]
|
|
352
|
+
: visibleKeys[visibleKeys.length - 1];
|
|
353
|
+
if (listState.lastFocusSourceRef) {
|
|
354
|
+
listState.lastFocusSourceRef.current = 'keyboard';
|
|
355
|
+
}
|
|
356
|
+
selectionManager.setFocusedKey(targetKey);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
return { keyboardProps };
|
|
362
|
+
}
|
|
363
|
+
const ComboBoxInput = forwardRef(function ComboBoxInput({ inputRef, id, value, placeholder, isDisabled, isReadOnly, autoFocus, size, mods, inputStyles, keyboardProps, focusProps, onChange, onFocus, isPopoverOpen, hasResults, comboBoxId, listStateRef, isLoading, allowsCustomValue, }, ref) {
|
|
364
|
+
const combinedRef = useCombinedRefs(ref, inputRef);
|
|
365
|
+
return (_jsx(InputElement, { ref: combinedRef, qa: "Input", id: id, type: "text", value: value, placeholder: placeholder, isDisabled: isDisabled, readOnly: isReadOnly, autoFocus: autoFocus, "data-autofocus": autoFocus ? '' : undefined, onChange: onChange, onFocus: onFocus, onBlur: focusProps.onBlur, ...keyboardProps, styles: inputStyles, mods: mods, "data-size": size, role: "combobox", "aria-expanded": isPopoverOpen && hasResults, "aria-haspopup": "listbox", "aria-controls": isPopoverOpen && hasResults
|
|
366
|
+
? `ComboBoxListBox-${comboBoxId}`
|
|
367
|
+
: undefined, "aria-activedescendant": isPopoverOpen &&
|
|
368
|
+
hasResults &&
|
|
369
|
+
listStateRef.current?.selectionManager.focusedKey != null
|
|
370
|
+
? `ListBoxItem-${listStateRef.current?.selectionManager.focusedKey}`
|
|
371
|
+
: undefined }));
|
|
372
|
+
});
|
|
373
|
+
function ComboBoxOverlay({ isOpen, triggerRef, popoverRef, listBoxRef, direction, shouldFlip, overlayOffset, comboBoxWidth, comboBoxId, overlayStyles, listBoxStyles, optionStyles, sectionStyles, headingStyles, effectiveSelectedKey, isDisabled, disabledKeys, items, children, listStateRef, onSelectionChange, onClose, label, ariaLabel, compositeFocusProps, }) {
|
|
374
|
+
// Overlay positioning
|
|
375
|
+
const { overlayProps: overlayPositionProps, placement, updatePosition, } = useOverlayPosition({
|
|
376
|
+
targetRef: triggerRef,
|
|
377
|
+
overlayRef: popoverRef,
|
|
378
|
+
placement: `${direction} start`,
|
|
379
|
+
shouldFlip,
|
|
380
|
+
isOpen,
|
|
381
|
+
offset: overlayOffset,
|
|
382
|
+
});
|
|
383
|
+
// Overlay behavior (dismiss on outside click, escape)
|
|
384
|
+
const { overlayProps: overlayBehaviorProps } = useOverlay({
|
|
385
|
+
onClose,
|
|
386
|
+
shouldCloseOnBlur: true,
|
|
387
|
+
isOpen,
|
|
388
|
+
isDismissable: true,
|
|
389
|
+
shouldCloseOnInteractOutside: (el) => {
|
|
390
|
+
const menuTriggerEl = el.closest('[data-popover-trigger]');
|
|
391
|
+
if (!menuTriggerEl)
|
|
392
|
+
return true;
|
|
393
|
+
if (menuTriggerEl === triggerRef?.current)
|
|
394
|
+
return true;
|
|
395
|
+
return false;
|
|
396
|
+
},
|
|
397
|
+
}, popoverRef);
|
|
398
|
+
// Update position when overlay opens
|
|
399
|
+
useLayoutEffect(() => {
|
|
400
|
+
if (isOpen) {
|
|
401
|
+
// Use double RAF to ensure layout is complete before positioning
|
|
402
|
+
requestAnimationFrame(() => {
|
|
403
|
+
requestAnimationFrame(() => {
|
|
404
|
+
updatePosition?.();
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}, [isOpen, updatePosition]);
|
|
409
|
+
// Extract primary placement direction for consistent styling
|
|
410
|
+
const placementDirection = placement?.split(' ')[0] || direction;
|
|
411
|
+
const overlayContent = (_jsx(DisplayTransition, { exposeUnmounted: true, isShown: isOpen, children: ({ phase, isShown, ref: transitionRef }) => (_jsx(ComboBoxOverlayElement, { ...mergeProps(overlayPositionProps, overlayBehaviorProps, compositeFocusProps), ref: (value) => {
|
|
412
|
+
transitionRef(value);
|
|
413
|
+
popoverRef.current = value;
|
|
414
|
+
}, "data-placement": placementDirection, "data-phase": phase, mods: {
|
|
415
|
+
open: isShown,
|
|
416
|
+
hidden: phase === 'unmounted',
|
|
417
|
+
}, styles: overlayStyles, style: {
|
|
418
|
+
'--min-width': comboBoxWidth ? `${comboBoxWidth}px` : undefined,
|
|
419
|
+
...overlayPositionProps.style,
|
|
420
|
+
}, children: _jsx(ListBox, { ref: listBoxRef, focusOnHover: true, disableSelectionToggle: true, id: `ComboBoxListBox-${comboBoxId}`, "aria-label": ariaLabel || (typeof label === 'string' ? label : 'Options'), selectedKey: effectiveSelectedKey, selectionMode: "single", isDisabled: isDisabled, disabledKeys: disabledKeys, shouldUseVirtualFocus: true, items: items, styles: listBoxStyles, optionStyles: optionStyles, sectionStyles: sectionStyles, headingStyles: headingStyles, stateRef: listStateRef, mods: {
|
|
421
|
+
popover: true,
|
|
422
|
+
}, onSelectionChange: onSelectionChange, children: children }) })) }));
|
|
423
|
+
return _jsx(Portal, { children: overlayContent });
|
|
424
|
+
}
|
|
425
|
+
// ============================================================================
|
|
426
|
+
// Main Component: ComboBox
|
|
427
|
+
// ============================================================================
|
|
66
428
|
export const ComboBox = forwardRef(function ComboBox(props, ref) {
|
|
67
429
|
props = useProviderProps(props);
|
|
68
430
|
props = useFormProps(props);
|
|
69
431
|
props = useFieldProps(props, {
|
|
70
432
|
valuePropsMapper: ({ value, onChange }) => {
|
|
71
433
|
return {
|
|
72
|
-
selectedKey
|
|
73
|
-
|
|
74
|
-
onInputChange(val) {
|
|
75
|
-
if (!props.allowsCustomValue) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
onChange(val);
|
|
79
|
-
},
|
|
434
|
+
// Form value maps to selectedKey (the committed value) in both modes
|
|
435
|
+
selectedKey: value ?? null,
|
|
80
436
|
onSelectionChange(val) {
|
|
81
|
-
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
437
|
+
// Commit selection changes to the form
|
|
84
438
|
onChange(val);
|
|
85
439
|
},
|
|
86
440
|
};
|
|
87
441
|
},
|
|
88
442
|
});
|
|
89
|
-
let { qa, label, extra, labelStyles, isRequired, necessityIndicator, validationState, icon, prefix, isDisabled,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
...props,
|
|
94
|
-
defaultFilter: filter || contains,
|
|
95
|
-
filter: undefined,
|
|
96
|
-
allowsEmptyCollection: isAsync,
|
|
97
|
-
menuTrigger,
|
|
98
|
-
};
|
|
99
|
-
let state = useComboBoxState(comboBoxStateProps);
|
|
443
|
+
let { qa, label, extra, labelStyles, isRequired, necessityIndicator, validationState, id, icon, prefix, isDisabled, autoFocus, wrapperRef, inputRef, triggerRef, popoverRef, listBoxRef, isLoading, inputStyles, optionStyles, triggerStyles, listBoxStyles, overlayStyles, fieldStyles, suffix, hideTrigger, message, description, size = 'medium', direction = 'bottom', shouldFlip = true, popoverTrigger = 'input', suffixPosition = 'before', filter, styles, labelSuffix, selectedKey, defaultSelectedKey, inputValue, defaultInputValue, onInputChange, isClearable, onClear, placeholder, allowsCustomValue, shouldCommitOnBlur = true, clearOnBlur, items, children: renderChildren, sectionStyles, headingStyles, isReadOnly, overlayOffset = 8, onSelectionChange: externalOnSelectionChange, sortSelectedToTop: sortSelectedToTopProp, onFocus, onBlur, onKeyDown, ...otherProps } = props;
|
|
444
|
+
// Generate ID for label-input linking if not provided
|
|
445
|
+
const generatedId = useId();
|
|
446
|
+
const inputId = id || generatedId;
|
|
100
447
|
// Generate a unique ID for this combobox instance
|
|
101
448
|
const comboBoxId = useMemo(() => generateRandomId(), []);
|
|
102
|
-
//
|
|
103
|
-
const {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
emit('popover:open', { menuId: comboBoxId });
|
|
118
|
-
}
|
|
119
|
-
}, [state.isOpen, emit, comboBoxId]);
|
|
449
|
+
// State management hook
|
|
450
|
+
const { effectiveSelectedKey, effectiveInputValue, isPopoverOpen, setIsPopoverOpen, setInternalSelectedKey, setInternalInputValue, isControlledKey, isControlledInput, } = useComboBoxState({
|
|
451
|
+
selectedKey,
|
|
452
|
+
defaultSelectedKey,
|
|
453
|
+
inputValue,
|
|
454
|
+
defaultInputValue,
|
|
455
|
+
comboBoxId,
|
|
456
|
+
});
|
|
457
|
+
// Track if sortSelectedToTop was explicitly provided
|
|
458
|
+
const sortSelectedToTopExplicit = sortSelectedToTopProp !== undefined;
|
|
459
|
+
// Default to true if items are provided, false otherwise
|
|
460
|
+
const sortSelectedToTop = sortSelectedToTopProp ?? (items ? true : false);
|
|
461
|
+
// Cache for sorted items array when using `items` prop
|
|
462
|
+
const cachedItemsOrder = useRef(null);
|
|
463
|
+
const selectionWhenClosed = useRef(null);
|
|
120
464
|
styles = extractStyles(otherProps, PROP_STYLES, styles);
|
|
121
465
|
ref = useCombinedRefs(ref);
|
|
122
466
|
wrapperRef = useCombinedRefs(wrapperRef);
|
|
@@ -124,62 +468,320 @@ export const ComboBox = forwardRef(function ComboBox(props, ref) {
|
|
|
124
468
|
triggerRef = useCombinedRefs(triggerRef);
|
|
125
469
|
popoverRef = useCombinedRefs(popoverRef);
|
|
126
470
|
listBoxRef = useCombinedRefs(listBoxRef);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
471
|
+
// Sort items with selected on top if enabled
|
|
472
|
+
const getSortedItems = useCallback(() => {
|
|
473
|
+
if (!items)
|
|
474
|
+
return items;
|
|
475
|
+
if (!sortSelectedToTop)
|
|
476
|
+
return items;
|
|
477
|
+
// Reuse cached order if available
|
|
478
|
+
if (cachedItemsOrder.current) {
|
|
479
|
+
return cachedItemsOrder.current;
|
|
480
|
+
}
|
|
481
|
+
// Warn if explicitly requested but not supported
|
|
482
|
+
if (sortSelectedToTopExplicit && !items) {
|
|
483
|
+
console.warn('ComboBox: sortSelectedToTop only works with the items prop. ' +
|
|
484
|
+
'Sorting will be skipped when using JSX children.');
|
|
485
|
+
return items;
|
|
486
|
+
}
|
|
487
|
+
const selectedKey = isPopoverOpen
|
|
488
|
+
? effectiveSelectedKey
|
|
489
|
+
: selectionWhenClosed.current;
|
|
490
|
+
if (!selectedKey)
|
|
491
|
+
return items;
|
|
492
|
+
const itemsArray = Array.isArray(items) ? items : Array.from(items);
|
|
493
|
+
const selectedItem = itemsArray.find((item) => {
|
|
494
|
+
const key = item?.key ?? item?.id;
|
|
495
|
+
return key != null && String(key) === String(selectedKey);
|
|
496
|
+
});
|
|
497
|
+
if (!selectedItem)
|
|
498
|
+
return items;
|
|
499
|
+
const sorted = [
|
|
500
|
+
selectedItem,
|
|
501
|
+
...itemsArray.filter((item) => {
|
|
502
|
+
const key = item?.key ?? item?.id;
|
|
503
|
+
return key == null || String(key) !== String(selectedKey);
|
|
504
|
+
}),
|
|
505
|
+
];
|
|
506
|
+
if (isPopoverOpen) {
|
|
507
|
+
cachedItemsOrder.current = sorted;
|
|
508
|
+
}
|
|
509
|
+
return sorted;
|
|
510
|
+
}, [
|
|
511
|
+
items,
|
|
512
|
+
sortSelectedToTop,
|
|
513
|
+
sortSelectedToTopExplicit,
|
|
514
|
+
effectiveSelectedKey,
|
|
515
|
+
isPopoverOpen,
|
|
516
|
+
]);
|
|
517
|
+
const sortedItems = getSortedItems();
|
|
518
|
+
// Preserve the original `children` (may be a render function) before we
|
|
519
|
+
// potentially overwrite it.
|
|
520
|
+
let children = renderChildren;
|
|
521
|
+
const renderFn = renderChildren;
|
|
522
|
+
if (sortedItems && typeof renderFn === 'function') {
|
|
523
|
+
try {
|
|
524
|
+
const itemsArray = Array.from(sortedItems);
|
|
525
|
+
children = itemsArray.map((item, idx) => {
|
|
526
|
+
const rendered = renderFn(item);
|
|
527
|
+
if (React.isValidElement(rendered) &&
|
|
528
|
+
rendered.key == null) {
|
|
529
|
+
return React.cloneElement(rendered, {
|
|
530
|
+
key: rendered?.key ?? item?.key ?? idx,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
return rendered;
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
// If conversion fails, proceed with the original children
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// Invalidate cached sorting whenever items change
|
|
541
|
+
useEffect(() => {
|
|
542
|
+
cachedItemsOrder.current = null;
|
|
543
|
+
}, [items]);
|
|
544
|
+
// Capture selection when popover closes
|
|
545
|
+
useEffect(() => {
|
|
546
|
+
if (!isPopoverOpen) {
|
|
547
|
+
selectionWhenClosed.current =
|
|
548
|
+
effectiveSelectedKey != null ? String(effectiveSelectedKey) : null;
|
|
549
|
+
cachedItemsOrder.current = null;
|
|
550
|
+
}
|
|
551
|
+
}, [isPopoverOpen, effectiveSelectedKey]);
|
|
552
|
+
// Filtering hook
|
|
553
|
+
const { filteredChildren, isFilterActive, setIsFilterActive } = useComboBoxFiltering({
|
|
554
|
+
children,
|
|
555
|
+
effectiveInputValue,
|
|
556
|
+
filter,
|
|
136
557
|
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
558
|
+
// Freeze filtered children during close animation to prevent visual jumps
|
|
559
|
+
const frozenFilteredChildrenRef = useRef(null);
|
|
560
|
+
useEffect(() => {
|
|
561
|
+
// Update frozen children only when popover is open
|
|
562
|
+
if (isPopoverOpen) {
|
|
563
|
+
frozenFilteredChildrenRef.current = filteredChildren;
|
|
564
|
+
}
|
|
565
|
+
}, [isPopoverOpen, filteredChildren]);
|
|
566
|
+
// Use frozen children during close animation, fresh children when open
|
|
567
|
+
const displayedFilteredChildren = isPopoverOpen
|
|
568
|
+
? filteredChildren
|
|
569
|
+
: frozenFilteredChildrenRef.current ?? filteredChildren;
|
|
570
|
+
const { isFocused, focusProps } = useFocus({ isDisabled });
|
|
571
|
+
// Composite blur handler - fires when focus leaves the entire component
|
|
572
|
+
const handleCompositeBlur = useEvent(() => {
|
|
573
|
+
// Always disable filter on blur
|
|
574
|
+
setIsFilterActive(false);
|
|
575
|
+
// In allowsCustomValue mode with shouldCommitOnBlur, commit the input value
|
|
576
|
+
if (allowsCustomValue &&
|
|
577
|
+
shouldCommitOnBlur &&
|
|
578
|
+
effectiveInputValue &&
|
|
579
|
+
effectiveSelectedKey == null) {
|
|
580
|
+
externalOnSelectionChange?.(effectiveInputValue);
|
|
581
|
+
if (!isControlledKey) {
|
|
582
|
+
setInternalSelectedKey(effectiveInputValue);
|
|
583
|
+
}
|
|
584
|
+
// Call user's onBlur callback
|
|
585
|
+
onBlur?.();
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
// In clearOnBlur mode (only for non-custom-value mode), clear selection and input
|
|
589
|
+
if (clearOnBlur && !allowsCustomValue) {
|
|
590
|
+
externalOnSelectionChange?.(null);
|
|
591
|
+
if (!isControlledKey) {
|
|
592
|
+
setInternalSelectedKey(null);
|
|
593
|
+
}
|
|
594
|
+
if (!isControlledInput) {
|
|
595
|
+
setInternalInputValue('');
|
|
596
|
+
}
|
|
597
|
+
onInputChange?.('');
|
|
598
|
+
// Call user's onBlur callback
|
|
599
|
+
onBlur?.();
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
// Reset input to show current selection (or empty if none)
|
|
603
|
+
const nextValue = effectiveSelectedKey != null ? getItemLabel(effectiveSelectedKey) : '';
|
|
604
|
+
if (!isControlledInput) {
|
|
605
|
+
setInternalInputValue(nextValue);
|
|
154
606
|
}
|
|
155
|
-
|
|
607
|
+
onInputChange?.(nextValue);
|
|
608
|
+
// Call user's onBlur callback
|
|
609
|
+
onBlur?.();
|
|
610
|
+
});
|
|
611
|
+
// Composite focus hook - handles focus tracking across wrapper and portaled popover
|
|
612
|
+
const { compositeFocusProps } = useCompositeFocus({
|
|
613
|
+
wrapperRef,
|
|
614
|
+
popoverRef,
|
|
615
|
+
onFocus,
|
|
616
|
+
onBlur: handleCompositeBlur,
|
|
617
|
+
isDisabled,
|
|
618
|
+
});
|
|
156
619
|
let isInvalid = validationState === 'invalid';
|
|
157
620
|
let validationIcon = isInvalid ? InvalidIcon : ValidIcon;
|
|
158
621
|
let validation = cloneElement(validationIcon);
|
|
622
|
+
// Ref to access internal ListBox state
|
|
623
|
+
const listStateRef = useRef(null);
|
|
624
|
+
const focusInitAttemptsRef = useRef(0);
|
|
625
|
+
// Helper to get label from collection item
|
|
626
|
+
const getItemLabel = useCallback((key) => {
|
|
627
|
+
const item = listStateRef.current?.collection?.getItem(key);
|
|
628
|
+
return item?.textValue || String(key);
|
|
629
|
+
}, []);
|
|
630
|
+
// Selection change handler
|
|
631
|
+
const handleSelectionChange = useEvent((selection) => {
|
|
632
|
+
// Extract single key from selection (we only support single selection)
|
|
633
|
+
const key = Array.isArray(selection) ? selection[0] : selection;
|
|
634
|
+
// Update selected key
|
|
635
|
+
if (!isControlledKey) {
|
|
636
|
+
setInternalSelectedKey(key ?? null);
|
|
637
|
+
}
|
|
638
|
+
// Update input value to show selected item label
|
|
639
|
+
if (key != null) {
|
|
640
|
+
setIsFilterActive(false);
|
|
641
|
+
const label = getItemLabel(key);
|
|
642
|
+
if (!isControlledInput) {
|
|
643
|
+
setInternalInputValue(label);
|
|
644
|
+
}
|
|
645
|
+
onInputChange?.(label);
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
// Clear input when selection is cleared
|
|
649
|
+
if (!isControlledInput) {
|
|
650
|
+
setInternalInputValue('');
|
|
651
|
+
}
|
|
652
|
+
onInputChange?.('');
|
|
653
|
+
setIsFilterActive(false);
|
|
654
|
+
}
|
|
655
|
+
externalOnSelectionChange?.(key);
|
|
656
|
+
// Close popover after selection
|
|
657
|
+
setIsPopoverOpen(false);
|
|
658
|
+
// Focus input
|
|
659
|
+
setTimeout(() => {
|
|
660
|
+
inputRef.current?.focus();
|
|
661
|
+
}, 0);
|
|
662
|
+
});
|
|
663
|
+
// Input change handler
|
|
664
|
+
const handleInputChange = useEvent((e) => {
|
|
665
|
+
const value = e.target.value;
|
|
666
|
+
// Update input value
|
|
667
|
+
if (!isControlledInput) {
|
|
668
|
+
setInternalInputValue(value);
|
|
669
|
+
}
|
|
670
|
+
onInputChange?.(value);
|
|
671
|
+
const trimmed = value.trim();
|
|
672
|
+
setIsFilterActive(trimmed.length > 0);
|
|
673
|
+
// Only clear selection in allowsCustomValue mode
|
|
674
|
+
// In normal mode, typing just filters - selection stays until explicitly changed
|
|
675
|
+
if (allowsCustomValue && effectiveSelectedKey != null) {
|
|
676
|
+
if (!isControlledKey) {
|
|
677
|
+
setInternalSelectedKey(null);
|
|
678
|
+
}
|
|
679
|
+
externalOnSelectionChange?.(null);
|
|
680
|
+
}
|
|
681
|
+
// Open popover based on trigger
|
|
682
|
+
if (popoverTrigger !== 'manual' && value && !isPopoverOpen) {
|
|
683
|
+
setIsPopoverOpen(true);
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
// Initialize input value from defaultInputValue or defaultSelectedKey (uncontrolled mode only, one-time)
|
|
687
|
+
const [hasInitialized, setHasInitialized] = useState(false);
|
|
688
|
+
useEffect(() => {
|
|
689
|
+
// Only initialize once, in uncontrolled input mode
|
|
690
|
+
if (hasInitialized || isControlledInput)
|
|
691
|
+
return;
|
|
692
|
+
// Priority 1: defaultInputValue takes precedence
|
|
693
|
+
if (defaultInputValue !== undefined) {
|
|
694
|
+
setInternalInputValue(defaultInputValue);
|
|
695
|
+
setHasInitialized(true);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
// Priority 2: fall back to defaultSelectedKey's label
|
|
699
|
+
if (defaultSelectedKey) {
|
|
700
|
+
// Wait for collection to be ready
|
|
701
|
+
if (!listStateRef.current?.collection)
|
|
702
|
+
return;
|
|
703
|
+
const label = getItemLabel(defaultSelectedKey);
|
|
704
|
+
setInternalInputValue(label);
|
|
705
|
+
setHasInitialized(true);
|
|
706
|
+
}
|
|
707
|
+
}, [
|
|
708
|
+
hasInitialized,
|
|
709
|
+
isControlledInput,
|
|
710
|
+
defaultInputValue,
|
|
711
|
+
defaultSelectedKey,
|
|
712
|
+
getItemLabel,
|
|
713
|
+
children,
|
|
714
|
+
]);
|
|
715
|
+
// Sync input value with controlled selectedKey
|
|
716
|
+
useEffect(() => {
|
|
717
|
+
// Only run when selectedKey is controlled but inputValue is uncontrolled
|
|
718
|
+
if (!isControlledKey || isControlledInput)
|
|
719
|
+
return;
|
|
720
|
+
// Wait for collection to be ready
|
|
721
|
+
if (!listStateRef.current?.collection)
|
|
722
|
+
return;
|
|
723
|
+
// Get the expected label for the current selection
|
|
724
|
+
const expectedLabel = effectiveSelectedKey != null ? getItemLabel(effectiveSelectedKey) : '';
|
|
725
|
+
// Update the input value to match the selected key's label
|
|
726
|
+
setInternalInputValue(expectedLabel);
|
|
727
|
+
}, [isControlledKey, isControlledInput, effectiveSelectedKey, getItemLabel]);
|
|
728
|
+
// Input focus handler
|
|
729
|
+
const handleInputFocus = useEvent((e) => {
|
|
730
|
+
// Call focus props handler if it exists
|
|
731
|
+
focusProps.onFocus?.(e);
|
|
732
|
+
if (popoverTrigger === 'focus' && !isPopoverOpen) {
|
|
733
|
+
setIsPopoverOpen(true);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
// Input blur handler - just handles internal focus props
|
|
737
|
+
const handleInputBlur = useEvent((e) => {
|
|
738
|
+
focusProps.onBlur?.(e);
|
|
739
|
+
});
|
|
159
740
|
// Clear button logic
|
|
160
|
-
let hasValue =
|
|
161
|
-
?
|
|
162
|
-
:
|
|
163
|
-
let showClearButton = isClearable && hasValue && !isDisabled && !
|
|
741
|
+
let hasValue = allowsCustomValue
|
|
742
|
+
? effectiveInputValue !== ''
|
|
743
|
+
: effectiveSelectedKey != null;
|
|
744
|
+
let showClearButton = isClearable && hasValue && !isDisabled && !isReadOnly;
|
|
745
|
+
const hasResults = Boolean(displayedFilteredChildren &&
|
|
746
|
+
(Array.isArray(displayedFilteredChildren)
|
|
747
|
+
? displayedFilteredChildren.length > 0
|
|
748
|
+
: displayedFilteredChildren !== null));
|
|
164
749
|
// Clear function
|
|
165
750
|
let clearValue = useEvent(() => {
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (props.allowsCustomValue) {
|
|
170
|
-
props.onInputChange?.('');
|
|
751
|
+
// Clear input
|
|
752
|
+
if (!isControlledInput) {
|
|
753
|
+
setInternalInputValue('');
|
|
171
754
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
state.close();
|
|
755
|
+
onInputChange?.('');
|
|
756
|
+
// Clear selection
|
|
757
|
+
if (!isControlledKey) {
|
|
758
|
+
setInternalSelectedKey(null);
|
|
177
759
|
}
|
|
178
|
-
|
|
760
|
+
externalOnSelectionChange?.(null);
|
|
761
|
+
// Close popover
|
|
762
|
+
if (isPopoverOpen) {
|
|
763
|
+
setIsPopoverOpen(false);
|
|
764
|
+
}
|
|
765
|
+
// Focus input
|
|
179
766
|
inputRef.current?.focus();
|
|
180
|
-
|
|
767
|
+
onClear?.();
|
|
768
|
+
});
|
|
769
|
+
// Keyboard navigation hook
|
|
770
|
+
const { keyboardProps } = useComboBoxKeyboard({
|
|
771
|
+
isPopoverOpen,
|
|
772
|
+
listStateRef,
|
|
773
|
+
hasResults,
|
|
774
|
+
allowsCustomValue,
|
|
775
|
+
effectiveInputValue,
|
|
776
|
+
isClearable,
|
|
777
|
+
onSelectionChange: handleSelectionChange,
|
|
778
|
+
onClearValue: clearValue,
|
|
779
|
+
onOpenPopover: () => setIsPopoverOpen(true),
|
|
780
|
+
onClosePopover: () => setIsPopoverOpen(false),
|
|
781
|
+
inputRef,
|
|
782
|
+
setIsFilterActive,
|
|
783
|
+
onKeyDown,
|
|
181
784
|
});
|
|
182
|
-
let comboBoxWidth = wrapperRef?.current?.offsetWidth;
|
|
183
785
|
if (icon) {
|
|
184
786
|
icon = _jsx("div", { "data-element": "InputIcon", children: icon });
|
|
185
787
|
if (prefix) {
|
|
@@ -193,7 +795,7 @@ export const ComboBox = forwardRef(function ComboBox(props, ref) {
|
|
|
193
795
|
invalid: isInvalid,
|
|
194
796
|
valid: validationState === 'valid',
|
|
195
797
|
disabled: isDisabled,
|
|
196
|
-
hovered:
|
|
798
|
+
hovered: false,
|
|
197
799
|
focused: isFocused,
|
|
198
800
|
loading: isLoading,
|
|
199
801
|
prefix: !!prefix,
|
|
@@ -203,84 +805,98 @@ export const ComboBox = forwardRef(function ComboBox(props, ref) {
|
|
|
203
805
|
isInvalid,
|
|
204
806
|
validationState,
|
|
205
807
|
isDisabled,
|
|
206
|
-
isHovered,
|
|
207
808
|
isFocused,
|
|
208
809
|
isLoading,
|
|
209
810
|
prefix,
|
|
210
811
|
showClearButton,
|
|
211
812
|
]);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
813
|
+
const comboBoxWidth = wrapperRef?.current?.offsetWidth;
|
|
814
|
+
const shouldShowPopover = Boolean(isPopoverOpen && hasResults);
|
|
815
|
+
// Close popover if no results
|
|
816
|
+
useEffect(() => {
|
|
817
|
+
if (isPopoverOpen && !hasResults) {
|
|
818
|
+
setIsPopoverOpen(false);
|
|
216
819
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
820
|
+
}, [isPopoverOpen, hasResults]);
|
|
821
|
+
const ensureInitialFocus = useCallback(() => {
|
|
822
|
+
if (!shouldShowPopover)
|
|
823
|
+
return;
|
|
824
|
+
const listState = listStateRef.current;
|
|
825
|
+
if (!listState)
|
|
826
|
+
return;
|
|
827
|
+
const { selectionManager, collection, disabledKeys, lastFocusSourceRef } = listState;
|
|
828
|
+
if (!selectionManager || !collection)
|
|
829
|
+
return;
|
|
830
|
+
const collectFirstKey = () => {
|
|
831
|
+
for (const node of collection) {
|
|
832
|
+
if (node.type === 'item') {
|
|
833
|
+
if (!disabledKeys?.has(node.key))
|
|
834
|
+
return node.key;
|
|
230
835
|
}
|
|
231
|
-
else if (
|
|
232
|
-
|
|
233
|
-
.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
e.preventDefault();
|
|
238
|
-
props.onSelectionChange?.(null);
|
|
836
|
+
else if (node.childNodes) {
|
|
837
|
+
for (const child of node.childNodes) {
|
|
838
|
+
if (child.type === 'item' && !disabledKeys?.has(child.key)) {
|
|
839
|
+
return child.key;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
239
842
|
}
|
|
240
|
-
// If a custom value is allowed, we need to check if the input value is in the collection.
|
|
241
843
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// If the input value is not in the collection, we need to reset the value.
|
|
251
|
-
if (!props.allowsCustomValue &&
|
|
252
|
-
inputRef.current?.value &&
|
|
253
|
-
![...state.collection]
|
|
254
|
-
.map((i) => i.textValue)
|
|
255
|
-
.includes(inputRef.current?.value)) {
|
|
256
|
-
props.onSelectionChange?.(null);
|
|
844
|
+
return null;
|
|
845
|
+
};
|
|
846
|
+
if (lastFocusSourceRef)
|
|
847
|
+
lastFocusSourceRef.current = 'keyboard';
|
|
848
|
+
if (selectionManager.focusedKey == null) {
|
|
849
|
+
const keyToFocus = effectiveSelectedKey != null ? effectiveSelectedKey : collectFirstKey();
|
|
850
|
+
if (keyToFocus != null)
|
|
851
|
+
selectionManager.setFocusedKey(keyToFocus);
|
|
257
852
|
}
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
853
|
+
}, [shouldShowPopover, effectiveSelectedKey]);
|
|
854
|
+
useLayoutEffect(() => {
|
|
855
|
+
if (!shouldShowPopover)
|
|
856
|
+
return;
|
|
857
|
+
focusInitAttemptsRef.current = 0;
|
|
858
|
+
const tick = () => {
|
|
859
|
+
if (!shouldShowPopover)
|
|
860
|
+
return;
|
|
861
|
+
ensureInitialFocus();
|
|
862
|
+
focusInitAttemptsRef.current += 1;
|
|
863
|
+
// Try a few frames to wait for collection to mount
|
|
864
|
+
if (focusInitAttemptsRef.current < 8 &&
|
|
865
|
+
listStateRef.current?.selectionManager?.focusedKey == null) {
|
|
866
|
+
requestAnimationFrame(tick);
|
|
867
|
+
}
|
|
265
868
|
};
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
869
|
+
requestAnimationFrame(() => requestAnimationFrame(tick));
|
|
870
|
+
}, [shouldShowPopover, ensureInitialFocus]);
|
|
871
|
+
const comboBoxField = (_jsxs(ComboBoxWrapperElement, { ref: wrapperRef, qa: qa || 'ComboBox', mods: mods, styles: styles, style: {
|
|
269
872
|
zIndex: isFocused ? 1 : 'initial',
|
|
270
|
-
}, "data-size": size, children: [prefix ? _jsx("div", { "data-element": "Prefix", children: prefix }) : null, _jsx(
|
|
271
|
-
pressed:
|
|
272
|
-
focused: isTriggerFocused,
|
|
273
|
-
hovered: isTriggerHovered,
|
|
873
|
+
}, "data-size": size, ...compositeFocusProps, children: [prefix ? _jsx("div", { "data-element": "Prefix", children: prefix }) : null, _jsx(ComboBoxInput, { inputRef: inputRef, id: inputId, value: effectiveInputValue, placeholder: placeholder, isDisabled: isDisabled, isReadOnly: isReadOnly, autoFocus: autoFocus, size: size, mods: mods, inputStyles: inputStyles, keyboardProps: keyboardProps, focusProps: { ...focusProps, onBlur: handleInputBlur }, isPopoverOpen: isPopoverOpen, hasResults: hasResults, comboBoxId: comboBoxId, listStateRef: listStateRef, isLoading: isLoading, allowsCustomValue: allowsCustomValue, onChange: handleInputChange, onFocus: handleInputFocus }), _jsxs("div", { "data-element": "Suffix", children: [suffixPosition === 'before' ? suffix : null, validationState || isLoading ? (_jsxs(_Fragment, { children: [validationState && !isLoading ? validation : null, isLoading ? _jsx(LoadingIcon, {}) : null] })) : null, suffixPosition === 'after' ? suffix : null, showClearButton && (_jsx(ItemAction, { icon: _jsx(CloseIcon, {}), size: size, theme: validationState === 'invalid' ? 'danger' : undefined, qa: "ComboBoxClearButton", "data-no-trigger": hideTrigger ? '' : undefined, "aria-label": "Clear value", onPress: clearValue })), !hideTrigger ? (_jsx(ItemAction, { ref: triggerRef, "data-popover-trigger": true, icon: _jsx(DirectionIcon, { to: isPopoverOpen ? 'up' : 'down' }), qa: "ComboBoxTrigger", mods: {
|
|
874
|
+
pressed: isPopoverOpen,
|
|
274
875
|
disabled: isDisabled,
|
|
275
876
|
loading: isLoading,
|
|
276
|
-
}, "data-size": size, isDisabled: isDisabled, styles: triggerStyles,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
877
|
+
}, "data-size": size, isDisabled: isDisabled, styles: triggerStyles, "aria-expanded": isPopoverOpen, "aria-haspopup": "listbox", "aria-label": "Show options", onPress: () => {
|
|
878
|
+
if (!isDisabled) {
|
|
879
|
+
const willOpen = !isPopoverOpen;
|
|
880
|
+
setIsPopoverOpen(willOpen);
|
|
881
|
+
if (willOpen) {
|
|
882
|
+
inputRef.current?.focus();
|
|
883
|
+
// If opening with no filtered results, disable filter to show all items
|
|
884
|
+
if (!hasResults) {
|
|
885
|
+
setIsFilterActive(false);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
} })) : null] }), _jsx(ComboBoxOverlay, { isOpen: shouldShowPopover, triggerRef: wrapperRef, popoverRef: popoverRef, listBoxRef: listBoxRef, direction: direction, shouldFlip: shouldFlip, overlayOffset: overlayOffset, comboBoxWidth: comboBoxWidth, comboBoxId: comboBoxId, overlayStyles: overlayStyles, listBoxStyles: listBoxStyles, optionStyles: optionStyles, sectionStyles: sectionStyles, headingStyles: headingStyles, effectiveSelectedKey: effectiveSelectedKey, isDisabled: isDisabled, disabledKeys: props.disabledKeys, items: sortedItems, listStateRef: listStateRef, label: label, ariaLabel: props['aria-label'], compositeFocusProps: compositeFocusProps, onSelectionChange: handleSelectionChange, onClose: () => setIsPopoverOpen(false), children: displayedFilteredChildren })] }));
|
|
890
|
+
const { children: _, ...propsWithoutChildren } = props;
|
|
891
|
+
const finalProps = {
|
|
892
|
+
...propsWithoutChildren,
|
|
893
|
+
styles: fieldStyles,
|
|
894
|
+
labelProps: { ...props.labelProps, for: inputId },
|
|
895
|
+
};
|
|
896
|
+
return wrapWithField(comboBoxField, ref, mergeProps(finalProps, {}));
|
|
281
897
|
});
|
|
282
898
|
ComboBox.Item = Item;
|
|
283
|
-
ComboBox.Section =
|
|
899
|
+
ComboBox.Section = BaseSection;
|
|
284
900
|
Object.defineProperty(ComboBox, 'cubeInputType', {
|
|
285
901
|
value: 'ComboBox',
|
|
286
902
|
enumerable: false,
|