@cube-dev/ui-kit 0.81.0 → 0.82.1
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 +20 -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 +2 -2
- package/es/components/fields/Checkbox/CheckboxGroup.js +2 -2
- 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 +792 -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 +2 -2
- package/es/components/fields/FilterListBox/FilterListBox.js +36 -46
- package/es/components/fields/FilterListBox/index.js +1 -1
- package/es/components/fields/FilterPicker/FilterPicker.js +83 -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 +25 -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 +2 -2
- package/es/components/fields/RadioGroup/RadioGroup.js +2 -2
- 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 +73 -54
- 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 +2 -2
- 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 +2 -2
- 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 +3 -3
- package/es/components/fields/TextInput/index.js +1 -1
- package/es/components/fields/TextInputMapper/TextInputMapper.js +3 -3
- 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 +21 -5
- 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/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/shared/form.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.1
|
|
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, 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,432 @@ 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
|
-
let isAsync = loadingState != null;
|
|
91
|
-
let { contains } = useFilter({ sensitivity: 'base' });
|
|
92
|
-
let comboBoxStateProps = {
|
|
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, form, ...otherProps } = props;
|
|
100
444
|
// Generate a unique ID for this combobox instance
|
|
101
445
|
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]);
|
|
446
|
+
// State management hook
|
|
447
|
+
const { effectiveSelectedKey, effectiveInputValue, isPopoverOpen, setIsPopoverOpen, setInternalSelectedKey, setInternalInputValue, isControlledKey, isControlledInput, } = useComboBoxState({
|
|
448
|
+
selectedKey,
|
|
449
|
+
defaultSelectedKey,
|
|
450
|
+
inputValue,
|
|
451
|
+
defaultInputValue,
|
|
452
|
+
comboBoxId,
|
|
453
|
+
});
|
|
454
|
+
// Track if sortSelectedToTop was explicitly provided
|
|
455
|
+
const sortSelectedToTopExplicit = sortSelectedToTopProp !== undefined;
|
|
456
|
+
// Default to true if items are provided, false otherwise
|
|
457
|
+
const sortSelectedToTop = sortSelectedToTopProp ?? (items ? true : false);
|
|
458
|
+
// Cache for sorted items array when using `items` prop
|
|
459
|
+
const cachedItemsOrder = useRef(null);
|
|
460
|
+
const selectionWhenClosed = useRef(null);
|
|
120
461
|
styles = extractStyles(otherProps, PROP_STYLES, styles);
|
|
121
462
|
ref = useCombinedRefs(ref);
|
|
122
463
|
wrapperRef = useCombinedRefs(wrapperRef);
|
|
@@ -124,62 +465,320 @@ export const ComboBox = forwardRef(function ComboBox(props, ref) {
|
|
|
124
465
|
triggerRef = useCombinedRefs(triggerRef);
|
|
125
466
|
popoverRef = useCombinedRefs(popoverRef);
|
|
126
467
|
listBoxRef = useCombinedRefs(listBoxRef);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
468
|
+
// Sort items with selected on top if enabled
|
|
469
|
+
const getSortedItems = useCallback(() => {
|
|
470
|
+
if (!items)
|
|
471
|
+
return items;
|
|
472
|
+
if (!sortSelectedToTop)
|
|
473
|
+
return items;
|
|
474
|
+
// Reuse cached order if available
|
|
475
|
+
if (cachedItemsOrder.current) {
|
|
476
|
+
return cachedItemsOrder.current;
|
|
477
|
+
}
|
|
478
|
+
// Warn if explicitly requested but not supported
|
|
479
|
+
if (sortSelectedToTopExplicit && !items) {
|
|
480
|
+
console.warn('ComboBox: sortSelectedToTop only works with the items prop. ' +
|
|
481
|
+
'Sorting will be skipped when using JSX children.');
|
|
482
|
+
return items;
|
|
483
|
+
}
|
|
484
|
+
const selectedKey = isPopoverOpen
|
|
485
|
+
? effectiveSelectedKey
|
|
486
|
+
: selectionWhenClosed.current;
|
|
487
|
+
if (!selectedKey)
|
|
488
|
+
return items;
|
|
489
|
+
const itemsArray = Array.isArray(items) ? items : Array.from(items);
|
|
490
|
+
const selectedItem = itemsArray.find((item) => {
|
|
491
|
+
const key = item?.key ?? item?.id;
|
|
492
|
+
return key != null && String(key) === String(selectedKey);
|
|
493
|
+
});
|
|
494
|
+
if (!selectedItem)
|
|
495
|
+
return items;
|
|
496
|
+
const sorted = [
|
|
497
|
+
selectedItem,
|
|
498
|
+
...itemsArray.filter((item) => {
|
|
499
|
+
const key = item?.key ?? item?.id;
|
|
500
|
+
return key == null || String(key) !== String(selectedKey);
|
|
501
|
+
}),
|
|
502
|
+
];
|
|
503
|
+
if (isPopoverOpen) {
|
|
504
|
+
cachedItemsOrder.current = sorted;
|
|
505
|
+
}
|
|
506
|
+
return sorted;
|
|
507
|
+
}, [
|
|
508
|
+
items,
|
|
509
|
+
sortSelectedToTop,
|
|
510
|
+
sortSelectedToTopExplicit,
|
|
511
|
+
effectiveSelectedKey,
|
|
512
|
+
isPopoverOpen,
|
|
513
|
+
]);
|
|
514
|
+
const sortedItems = getSortedItems();
|
|
515
|
+
// Preserve the original `children` (may be a render function) before we
|
|
516
|
+
// potentially overwrite it.
|
|
517
|
+
let children = renderChildren;
|
|
518
|
+
const renderFn = renderChildren;
|
|
519
|
+
if (sortedItems && typeof renderFn === 'function') {
|
|
520
|
+
try {
|
|
521
|
+
const itemsArray = Array.from(sortedItems);
|
|
522
|
+
children = itemsArray.map((item, idx) => {
|
|
523
|
+
const rendered = renderFn(item);
|
|
524
|
+
if (React.isValidElement(rendered) &&
|
|
525
|
+
rendered.key == null) {
|
|
526
|
+
return React.cloneElement(rendered, {
|
|
527
|
+
key: rendered?.key ?? item?.key ?? idx,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
return rendered;
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
// If conversion fails, proceed with the original children
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
// Invalidate cached sorting whenever items change
|
|
538
|
+
useEffect(() => {
|
|
539
|
+
cachedItemsOrder.current = null;
|
|
540
|
+
}, [items]);
|
|
541
|
+
// Capture selection when popover closes
|
|
542
|
+
useEffect(() => {
|
|
543
|
+
if (!isPopoverOpen) {
|
|
544
|
+
selectionWhenClosed.current =
|
|
545
|
+
effectiveSelectedKey != null ? String(effectiveSelectedKey) : null;
|
|
546
|
+
cachedItemsOrder.current = null;
|
|
547
|
+
}
|
|
548
|
+
}, [isPopoverOpen, effectiveSelectedKey]);
|
|
549
|
+
// Filtering hook
|
|
550
|
+
const { filteredChildren, isFilterActive, setIsFilterActive } = useComboBoxFiltering({
|
|
551
|
+
children,
|
|
552
|
+
effectiveInputValue,
|
|
553
|
+
filter,
|
|
136
554
|
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
555
|
+
// Freeze filtered children during close animation to prevent visual jumps
|
|
556
|
+
const frozenFilteredChildrenRef = useRef(null);
|
|
557
|
+
useEffect(() => {
|
|
558
|
+
// Update frozen children only when popover is open
|
|
559
|
+
if (isPopoverOpen) {
|
|
560
|
+
frozenFilteredChildrenRef.current = filteredChildren;
|
|
561
|
+
}
|
|
562
|
+
}, [isPopoverOpen, filteredChildren]);
|
|
563
|
+
// Use frozen children during close animation, fresh children when open
|
|
564
|
+
const displayedFilteredChildren = isPopoverOpen
|
|
565
|
+
? filteredChildren
|
|
566
|
+
: frozenFilteredChildrenRef.current ?? filteredChildren;
|
|
567
|
+
const { isFocused, focusProps } = useFocus({ isDisabled });
|
|
568
|
+
// Composite blur handler - fires when focus leaves the entire component
|
|
569
|
+
const handleCompositeBlur = useEvent(() => {
|
|
570
|
+
// Always disable filter on blur
|
|
571
|
+
setIsFilterActive(false);
|
|
572
|
+
// In allowsCustomValue mode with shouldCommitOnBlur, commit the input value
|
|
573
|
+
if (allowsCustomValue &&
|
|
574
|
+
shouldCommitOnBlur &&
|
|
575
|
+
effectiveInputValue &&
|
|
576
|
+
effectiveSelectedKey == null) {
|
|
577
|
+
externalOnSelectionChange?.(effectiveInputValue);
|
|
578
|
+
if (!isControlledKey) {
|
|
579
|
+
setInternalSelectedKey(effectiveInputValue);
|
|
580
|
+
}
|
|
581
|
+
// Call user's onBlur callback
|
|
582
|
+
onBlur?.();
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
// In clearOnBlur mode (only for non-custom-value mode), clear selection and input
|
|
586
|
+
if (clearOnBlur && !allowsCustomValue) {
|
|
587
|
+
externalOnSelectionChange?.(null);
|
|
588
|
+
if (!isControlledKey) {
|
|
589
|
+
setInternalSelectedKey(null);
|
|
590
|
+
}
|
|
591
|
+
if (!isControlledInput) {
|
|
592
|
+
setInternalInputValue('');
|
|
593
|
+
}
|
|
594
|
+
onInputChange?.('');
|
|
595
|
+
// Call user's onBlur callback
|
|
596
|
+
onBlur?.();
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
// Reset input to show current selection (or empty if none)
|
|
600
|
+
const nextValue = effectiveSelectedKey != null ? getItemLabel(effectiveSelectedKey) : '';
|
|
601
|
+
if (!isControlledInput) {
|
|
602
|
+
setInternalInputValue(nextValue);
|
|
154
603
|
}
|
|
155
|
-
|
|
604
|
+
onInputChange?.(nextValue);
|
|
605
|
+
// Call user's onBlur callback
|
|
606
|
+
onBlur?.();
|
|
607
|
+
});
|
|
608
|
+
// Composite focus hook - handles focus tracking across wrapper and portaled popover
|
|
609
|
+
const { compositeFocusProps } = useCompositeFocus({
|
|
610
|
+
wrapperRef,
|
|
611
|
+
popoverRef,
|
|
612
|
+
onFocus,
|
|
613
|
+
onBlur: handleCompositeBlur,
|
|
614
|
+
isDisabled,
|
|
615
|
+
});
|
|
156
616
|
let isInvalid = validationState === 'invalid';
|
|
157
617
|
let validationIcon = isInvalid ? InvalidIcon : ValidIcon;
|
|
158
618
|
let validation = cloneElement(validationIcon);
|
|
619
|
+
// Ref to access internal ListBox state
|
|
620
|
+
const listStateRef = useRef(null);
|
|
621
|
+
const focusInitAttemptsRef = useRef(0);
|
|
622
|
+
// Helper to get label from collection item
|
|
623
|
+
const getItemLabel = useCallback((key) => {
|
|
624
|
+
const item = listStateRef.current?.collection?.getItem(key);
|
|
625
|
+
return item?.textValue || String(key);
|
|
626
|
+
}, []);
|
|
627
|
+
// Selection change handler
|
|
628
|
+
const handleSelectionChange = useEvent((selection) => {
|
|
629
|
+
// Extract single key from selection (we only support single selection)
|
|
630
|
+
const key = Array.isArray(selection) ? selection[0] : selection;
|
|
631
|
+
// Update selected key
|
|
632
|
+
if (!isControlledKey) {
|
|
633
|
+
setInternalSelectedKey(key ?? null);
|
|
634
|
+
}
|
|
635
|
+
// Update input value to show selected item label
|
|
636
|
+
if (key != null) {
|
|
637
|
+
setIsFilterActive(false);
|
|
638
|
+
const label = getItemLabel(key);
|
|
639
|
+
if (!isControlledInput) {
|
|
640
|
+
setInternalInputValue(label);
|
|
641
|
+
}
|
|
642
|
+
onInputChange?.(label);
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
// Clear input when selection is cleared
|
|
646
|
+
if (!isControlledInput) {
|
|
647
|
+
setInternalInputValue('');
|
|
648
|
+
}
|
|
649
|
+
onInputChange?.('');
|
|
650
|
+
setIsFilterActive(false);
|
|
651
|
+
}
|
|
652
|
+
externalOnSelectionChange?.(key);
|
|
653
|
+
// Close popover after selection
|
|
654
|
+
setIsPopoverOpen(false);
|
|
655
|
+
// Focus input
|
|
656
|
+
setTimeout(() => {
|
|
657
|
+
inputRef.current?.focus();
|
|
658
|
+
}, 0);
|
|
659
|
+
});
|
|
660
|
+
// Input change handler
|
|
661
|
+
const handleInputChange = useEvent((e) => {
|
|
662
|
+
const value = e.target.value;
|
|
663
|
+
// Update input value
|
|
664
|
+
if (!isControlledInput) {
|
|
665
|
+
setInternalInputValue(value);
|
|
666
|
+
}
|
|
667
|
+
onInputChange?.(value);
|
|
668
|
+
const trimmed = value.trim();
|
|
669
|
+
setIsFilterActive(trimmed.length > 0);
|
|
670
|
+
// Only clear selection in allowsCustomValue mode
|
|
671
|
+
// In normal mode, typing just filters - selection stays until explicitly changed
|
|
672
|
+
if (allowsCustomValue && effectiveSelectedKey != null) {
|
|
673
|
+
if (!isControlledKey) {
|
|
674
|
+
setInternalSelectedKey(null);
|
|
675
|
+
}
|
|
676
|
+
externalOnSelectionChange?.(null);
|
|
677
|
+
}
|
|
678
|
+
// Open popover based on trigger
|
|
679
|
+
if (popoverTrigger !== 'manual' && value && !isPopoverOpen) {
|
|
680
|
+
setIsPopoverOpen(true);
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
// Initialize input value from defaultInputValue or defaultSelectedKey (uncontrolled mode only, one-time)
|
|
684
|
+
const [hasInitialized, setHasInitialized] = useState(false);
|
|
685
|
+
useEffect(() => {
|
|
686
|
+
// Only initialize once, in uncontrolled input mode
|
|
687
|
+
if (hasInitialized || isControlledInput)
|
|
688
|
+
return;
|
|
689
|
+
// Priority 1: defaultInputValue takes precedence
|
|
690
|
+
if (defaultInputValue !== undefined) {
|
|
691
|
+
setInternalInputValue(defaultInputValue);
|
|
692
|
+
setHasInitialized(true);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
// Priority 2: fall back to defaultSelectedKey's label
|
|
696
|
+
if (defaultSelectedKey) {
|
|
697
|
+
// Wait for collection to be ready
|
|
698
|
+
if (!listStateRef.current?.collection)
|
|
699
|
+
return;
|
|
700
|
+
const label = getItemLabel(defaultSelectedKey);
|
|
701
|
+
setInternalInputValue(label);
|
|
702
|
+
setHasInitialized(true);
|
|
703
|
+
}
|
|
704
|
+
}, [
|
|
705
|
+
hasInitialized,
|
|
706
|
+
isControlledInput,
|
|
707
|
+
defaultInputValue,
|
|
708
|
+
defaultSelectedKey,
|
|
709
|
+
getItemLabel,
|
|
710
|
+
children,
|
|
711
|
+
]);
|
|
712
|
+
// Sync input value with controlled selectedKey
|
|
713
|
+
useEffect(() => {
|
|
714
|
+
// Only run when selectedKey is controlled but inputValue is uncontrolled
|
|
715
|
+
if (!isControlledKey || isControlledInput)
|
|
716
|
+
return;
|
|
717
|
+
// Wait for collection to be ready
|
|
718
|
+
if (!listStateRef.current?.collection)
|
|
719
|
+
return;
|
|
720
|
+
// Get the expected label for the current selection
|
|
721
|
+
const expectedLabel = effectiveSelectedKey != null ? getItemLabel(effectiveSelectedKey) : '';
|
|
722
|
+
// Update the input value to match the selected key's label
|
|
723
|
+
setInternalInputValue(expectedLabel);
|
|
724
|
+
}, [isControlledKey, isControlledInput, effectiveSelectedKey, getItemLabel]);
|
|
725
|
+
// Input focus handler
|
|
726
|
+
const handleInputFocus = useEvent((e) => {
|
|
727
|
+
// Call focus props handler if it exists
|
|
728
|
+
focusProps.onFocus?.(e);
|
|
729
|
+
if (popoverTrigger === 'focus' && !isPopoverOpen) {
|
|
730
|
+
setIsPopoverOpen(true);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
// Input blur handler - just handles internal focus props
|
|
734
|
+
const handleInputBlur = useEvent((e) => {
|
|
735
|
+
focusProps.onBlur?.(e);
|
|
736
|
+
});
|
|
159
737
|
// Clear button logic
|
|
160
|
-
let hasValue =
|
|
161
|
-
?
|
|
162
|
-
:
|
|
163
|
-
let showClearButton = isClearable && hasValue && !isDisabled && !
|
|
738
|
+
let hasValue = allowsCustomValue
|
|
739
|
+
? effectiveInputValue !== ''
|
|
740
|
+
: effectiveSelectedKey != null;
|
|
741
|
+
let showClearButton = isClearable && hasValue && !isDisabled && !isReadOnly;
|
|
742
|
+
const hasResults = Boolean(displayedFilteredChildren &&
|
|
743
|
+
(Array.isArray(displayedFilteredChildren)
|
|
744
|
+
? displayedFilteredChildren.length > 0
|
|
745
|
+
: displayedFilteredChildren !== null));
|
|
164
746
|
// Clear function
|
|
165
747
|
let clearValue = useEvent(() => {
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (props.allowsCustomValue) {
|
|
170
|
-
props.onInputChange?.('');
|
|
748
|
+
// Clear input
|
|
749
|
+
if (!isControlledInput) {
|
|
750
|
+
setInternalInputValue('');
|
|
171
751
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
state.close();
|
|
752
|
+
onInputChange?.('');
|
|
753
|
+
// Clear selection
|
|
754
|
+
if (!isControlledKey) {
|
|
755
|
+
setInternalSelectedKey(null);
|
|
177
756
|
}
|
|
178
|
-
|
|
757
|
+
externalOnSelectionChange?.(null);
|
|
758
|
+
// Close popover
|
|
759
|
+
if (isPopoverOpen) {
|
|
760
|
+
setIsPopoverOpen(false);
|
|
761
|
+
}
|
|
762
|
+
// Focus input
|
|
179
763
|
inputRef.current?.focus();
|
|
180
|
-
|
|
764
|
+
onClear?.();
|
|
765
|
+
});
|
|
766
|
+
// Keyboard navigation hook
|
|
767
|
+
const { keyboardProps } = useComboBoxKeyboard({
|
|
768
|
+
isPopoverOpen,
|
|
769
|
+
listStateRef,
|
|
770
|
+
hasResults,
|
|
771
|
+
allowsCustomValue,
|
|
772
|
+
effectiveInputValue,
|
|
773
|
+
isClearable,
|
|
774
|
+
onSelectionChange: handleSelectionChange,
|
|
775
|
+
onClearValue: clearValue,
|
|
776
|
+
onOpenPopover: () => setIsPopoverOpen(true),
|
|
777
|
+
onClosePopover: () => setIsPopoverOpen(false),
|
|
778
|
+
inputRef,
|
|
779
|
+
setIsFilterActive,
|
|
780
|
+
onKeyDown,
|
|
181
781
|
});
|
|
182
|
-
let comboBoxWidth = wrapperRef?.current?.offsetWidth;
|
|
183
782
|
if (icon) {
|
|
184
783
|
icon = _jsx("div", { "data-element": "InputIcon", children: icon });
|
|
185
784
|
if (prefix) {
|
|
@@ -193,7 +792,7 @@ export const ComboBox = forwardRef(function ComboBox(props, ref) {
|
|
|
193
792
|
invalid: isInvalid,
|
|
194
793
|
valid: validationState === 'valid',
|
|
195
794
|
disabled: isDisabled,
|
|
196
|
-
hovered:
|
|
795
|
+
hovered: false,
|
|
197
796
|
focused: isFocused,
|
|
198
797
|
loading: isLoading,
|
|
199
798
|
prefix: !!prefix,
|
|
@@ -203,84 +802,97 @@ export const ComboBox = forwardRef(function ComboBox(props, ref) {
|
|
|
203
802
|
isInvalid,
|
|
204
803
|
validationState,
|
|
205
804
|
isDisabled,
|
|
206
|
-
isHovered,
|
|
207
805
|
isFocused,
|
|
208
806
|
isLoading,
|
|
209
807
|
prefix,
|
|
210
808
|
showClearButton,
|
|
211
809
|
]);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
810
|
+
const comboBoxWidth = wrapperRef?.current?.offsetWidth;
|
|
811
|
+
const shouldShowPopover = Boolean(isPopoverOpen && hasResults);
|
|
812
|
+
// Close popover if no results
|
|
813
|
+
useEffect(() => {
|
|
814
|
+
if (isPopoverOpen && !hasResults) {
|
|
815
|
+
setIsPopoverOpen(false);
|
|
216
816
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
817
|
+
}, [isPopoverOpen, hasResults]);
|
|
818
|
+
const ensureInitialFocus = useCallback(() => {
|
|
819
|
+
if (!shouldShowPopover)
|
|
820
|
+
return;
|
|
821
|
+
const listState = listStateRef.current;
|
|
822
|
+
if (!listState)
|
|
823
|
+
return;
|
|
824
|
+
const { selectionManager, collection, disabledKeys, lastFocusSourceRef } = listState;
|
|
825
|
+
if (!selectionManager || !collection)
|
|
826
|
+
return;
|
|
827
|
+
const collectFirstKey = () => {
|
|
828
|
+
for (const node of collection) {
|
|
829
|
+
if (node.type === 'item') {
|
|
830
|
+
if (!disabledKeys?.has(node.key))
|
|
831
|
+
return node.key;
|
|
230
832
|
}
|
|
231
|
-
else if (
|
|
232
|
-
|
|
233
|
-
.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
e.preventDefault();
|
|
238
|
-
props.onSelectionChange?.(null);
|
|
833
|
+
else if (node.childNodes) {
|
|
834
|
+
for (const child of node.childNodes) {
|
|
835
|
+
if (child.type === 'item' && !disabledKeys?.has(child.key)) {
|
|
836
|
+
return child.key;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
239
839
|
}
|
|
240
|
-
// If a custom value is allowed, we need to check if the input value is in the collection.
|
|
241
840
|
}
|
|
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);
|
|
841
|
+
return null;
|
|
842
|
+
};
|
|
843
|
+
if (lastFocusSourceRef)
|
|
844
|
+
lastFocusSourceRef.current = 'keyboard';
|
|
845
|
+
if (selectionManager.focusedKey == null) {
|
|
846
|
+
const keyToFocus = effectiveSelectedKey != null ? effectiveSelectedKey : collectFirstKey();
|
|
847
|
+
if (keyToFocus != null)
|
|
848
|
+
selectionManager.setFocusedKey(keyToFocus);
|
|
257
849
|
}
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
850
|
+
}, [shouldShowPopover, effectiveSelectedKey]);
|
|
851
|
+
useLayoutEffect(() => {
|
|
852
|
+
if (!shouldShowPopover)
|
|
853
|
+
return;
|
|
854
|
+
focusInitAttemptsRef.current = 0;
|
|
855
|
+
const tick = () => {
|
|
856
|
+
if (!shouldShowPopover)
|
|
857
|
+
return;
|
|
858
|
+
ensureInitialFocus();
|
|
859
|
+
focusInitAttemptsRef.current += 1;
|
|
860
|
+
// Try a few frames to wait for collection to mount
|
|
861
|
+
if (focusInitAttemptsRef.current < 8 &&
|
|
862
|
+
listStateRef.current?.selectionManager?.focusedKey == null) {
|
|
863
|
+
requestAnimationFrame(tick);
|
|
864
|
+
}
|
|
265
865
|
};
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
866
|
+
requestAnimationFrame(() => requestAnimationFrame(tick));
|
|
867
|
+
}, [shouldShowPopover, ensureInitialFocus]);
|
|
868
|
+
const comboBoxField = (_jsxs(ComboBoxWrapperElement, { ref: wrapperRef, qa: qa || 'ComboBox', mods: mods, styles: styles, style: {
|
|
269
869
|
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,
|
|
870
|
+
}, "data-size": size, ...compositeFocusProps, children: [prefix ? _jsx("div", { "data-element": "Prefix", children: prefix }) : null, _jsx(ComboBoxInput, { inputRef: inputRef, id: id, 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: {
|
|
871
|
+
pressed: isPopoverOpen,
|
|
274
872
|
disabled: isDisabled,
|
|
275
873
|
loading: isLoading,
|
|
276
|
-
}, "data-size": size, isDisabled: isDisabled, styles: triggerStyles,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
874
|
+
}, "data-size": size, isDisabled: isDisabled, styles: triggerStyles, "aria-expanded": isPopoverOpen, "aria-haspopup": "listbox", "aria-label": "Show options", onPress: () => {
|
|
875
|
+
if (!isDisabled) {
|
|
876
|
+
const willOpen = !isPopoverOpen;
|
|
877
|
+
setIsPopoverOpen(willOpen);
|
|
878
|
+
if (willOpen) {
|
|
879
|
+
inputRef.current?.focus();
|
|
880
|
+
// If opening with no filtered results, disable filter to show all items
|
|
881
|
+
if (!hasResults) {
|
|
882
|
+
setIsFilterActive(false);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
} })) : 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 })] }));
|
|
887
|
+
const { children: _, ...propsWithoutChildren } = props;
|
|
888
|
+
const finalProps = {
|
|
889
|
+
...propsWithoutChildren,
|
|
890
|
+
styles: fieldStyles,
|
|
891
|
+
};
|
|
892
|
+
return wrapWithField(comboBoxField, ref, mergeProps(finalProps, {}));
|
|
281
893
|
});
|
|
282
894
|
ComboBox.Item = Item;
|
|
283
|
-
ComboBox.Section =
|
|
895
|
+
ComboBox.Section = BaseSection;
|
|
284
896
|
Object.defineProperty(ComboBox, 'cubeInputType', {
|
|
285
897
|
value: 'ComboBox',
|
|
286
898
|
enumerable: false,
|