@astryxdesign/core 0.5.2-canary.2da7a39 → 0.5.2-canary.2e51213
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/dist/AppShell/AppShell.d.ts +2 -2
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +10 -7
- package/dist/Banner/Banner.d.ts +5 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +7 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +22 -15
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +2 -2
- package/dist/Chat/ChatComposer.d.ts +2 -2
- package/dist/Chat/ChatComposer.d.ts.map +1 -1
- package/dist/Chat/ChatComposer.js +21 -2
- package/dist/Chat/ChatToolCalls.d.ts +1 -1
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +12 -34
- package/dist/Chat/useChatNewMessages.js +3 -3
- package/dist/CheckboxList/CheckboxListItem.d.ts +5 -3
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts +47 -13
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +269 -162
- package/dist/DateTimeInput/NativeDateSegment.d.ts +24 -0
- package/dist/DateTimeInput/NativeDateSegment.d.ts.map +1 -0
- package/dist/DateTimeInput/NativeDateSegment.js +235 -0
- package/dist/DateTimeInput/NativeTimeSegment.d.ts +30 -0
- package/dist/DateTimeInput/NativeTimeSegment.d.ts.map +1 -0
- package/dist/DateTimeInput/NativeTimeSegment.js +248 -0
- package/dist/DateTimeInput/index.d.ts +1 -1
- package/dist/DateTimeInput/index.d.ts.map +1 -1
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +95 -0
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -0
- package/dist/DateTimeInput/nativePickerSegmentStyles.js +103 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +2 -4
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +10 -4
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +3 -5
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +4 -2
- package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
- package/dist/Layer/useTouchTrigger.js +2 -4
- package/dist/Layout/Layout.d.ts +7 -8
- package/dist/Layout/Layout.d.ts.map +1 -1
- package/dist/Layout/Layout.js +7 -8
- package/dist/MultiSelector/MultiSelector.d.ts +11 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +89 -45
- package/dist/RadioList/RadioListItem.d.ts +30 -5
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +17 -4
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +102 -44
- package/dist/Resizable/index.d.ts +2 -0
- package/dist/Resizable/index.d.ts.map +1 -1
- package/dist/Resizable/index.js +1 -0
- package/dist/Resizable/useResizable.d.ts +89 -8
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +521 -38
- package/dist/Resizable/utils.d.ts +35 -0
- package/dist/Resizable/utils.d.ts.map +1 -0
- package/dist/Resizable/utils.js +27 -0
- package/dist/Selector/Selector.d.ts +10 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +105 -57
- package/dist/Selector/useSelectorPresentation.d.ts +1 -0
- package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
- package/dist/Selector/useSelectorPresentation.js +17 -2
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +3 -5
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +9 -10
- package/dist/Stepper/Step.d.ts +2 -1
- package/dist/Stepper/Step.d.ts.map +1 -1
- package/dist/Stepper/Step.js +75 -11
- package/dist/Stepper/Stepper.d.ts.map +1 -1
- package/dist/Stepper/Stepper.js +1 -0
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +2 -3
- package/dist/Table/index.d.ts +16 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +4 -0
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +8 -9
- package/dist/Table/plugins/rowStatus/index.d.ts +1 -1
- package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -1
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +11 -30
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
- package/dist/Table/plugins/rowStatus/useTableRowStatus.js +108 -62
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +9 -1
- package/dist/Text/useTruncation.d.ts +1 -1
- package/dist/Text/useTruncation.d.ts.map +1 -1
- package/dist/Text/useTruncation.js +8 -7
- package/dist/TimeInput/TimeInput.d.ts +16 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +98 -60
- package/dist/TimeInput/index.d.ts +1 -1
- package/dist/TimeInput/index.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.d.ts +2 -2
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +4 -145
- package/dist/Timestamp/formatRelativeTime.d.ts +20 -0
- package/dist/Timestamp/formatRelativeTime.d.ts.map +1 -0
- package/dist/Timestamp/formatRelativeTime.js +81 -0
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +125 -77
- package/dist/Tokenizer/Tokenizer.d.ts +0 -38
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +94 -26
- package/dist/Tokenizer/useEndLaneReserve.d.ts +31 -0
- package/dist/Tokenizer/useEndLaneReserve.d.ts.map +1 -0
- package/dist/Tokenizer/useEndLaneReserve.js +136 -0
- package/dist/Tooltip/useTooltip.d.ts +1 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +53 -42
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +85 -30
- package/dist/Typeahead/Typeahead.d.ts +0 -21
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +108 -67
- package/dist/Typeahead/busyIndicatorLane.d.ts +62 -0
- package/dist/Typeahead/busyIndicatorLane.d.ts.map +1 -0
- package/dist/Typeahead/busyIndicatorLane.js +98 -0
- package/dist/astryx.css +25 -7
- package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -1
- package/dist/hooks/useFocusReturnVisibility.js +3 -5
- package/dist/hooks/useIsomorphicLayoutEffect.d.ts +2 -2
- package/dist/hooks/useIsomorphicLayoutEffect.d.ts.map +1 -1
- package/dist/hooks/useOverflow.js +11 -11
- package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
- package/dist/hooks/useScrollOverflow.js +7 -4
- package/dist/theme/defineTheme.d.ts +13 -0
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +7 -0
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +1 -1
- package/dist/theme/localTokens.d.ts +18 -0
- package/dist/theme/localTokens.d.ts.map +1 -0
- package/dist/theme/localTokens.js +162 -0
- package/dist/utils/interactionModality.d.ts +6 -28
- package/dist/utils/interactionModality.d.ts.map +1 -1
- package/dist/utils/interactionModality.js +72 -37
- package/dist/utils/sharedResizeObserver.d.ts +17 -9
- package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
- package/dist/utils/sharedResizeObserver.js +54 -12
- package/locales/en.json +8 -0
- package/locales/pseudo.json +6 -0
- package/package.json +7 -2
- package/src/AlertDialog/useImperativeAlertDialog.test.tsx +163 -0
- package/src/AppShell/AppShell.doc.mjs +4 -3
- package/src/AppShell/AppShell.test.tsx +21 -0
- package/src/AppShell/AppShell.tsx +12 -7
- package/src/Banner/Banner.doc.mjs +5 -1
- package/src/Banner/Banner.tsx +12 -1
- package/src/BottomSheet/BottomSheet.doc.mjs +31 -0
- package/src/BottomSheet/BottomSheet.spec.md +178 -0
- package/src/Breadcrumbs/BreadcrumbItem.tsx +18 -9
- package/src/Breadcrumbs/Breadcrumbs.doc.mjs +4 -1
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +12 -0
- package/src/Button/Button.doc.mjs +56 -0
- package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
- package/src/Calendar/hooks/useCalendarConstraints.test.ts +153 -0
- package/src/Calendar/hooks/useCalendarDays.test.ts +265 -0
- package/src/Calendar/hooks/useCalendarNavigation.test.ts +252 -0
- package/src/Carousel/Carousel.test.tsx +83 -0
- package/src/Carousel/Carousel.tsx +8 -2
- package/src/Chat/ChatComposer.doc.mjs +7 -4
- package/src/Chat/ChatComposer.test.tsx +84 -2
- package/src/Chat/ChatComposer.tsx +45 -3
- package/src/Chat/ChatSendButton.test.tsx +189 -0
- package/src/Chat/ChatToolCalls.doc.mjs +1 -1
- package/src/Chat/ChatToolCalls.test.tsx +33 -1
- package/src/Chat/ChatToolCalls.tsx +13 -34
- package/src/Chat/useChatNewMessages.ts +3 -3
- package/src/Chat/useChatPasteAsToken.test.ts +191 -0
- package/src/CheckboxList/CheckboxList.doc.mjs +172 -31
- package/src/CheckboxList/CheckboxList.spec.md +215 -0
- package/src/CheckboxList/CheckboxList.test.tsx +45 -0
- package/src/CheckboxList/CheckboxListItem.doc.mjs +11 -9
- package/src/CheckboxList/CheckboxListItem.tsx +5 -3
- package/src/Collapsible/useCollapsible.test.tsx +236 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
- package/src/CommandPalette/CommandPalette.spec.md +224 -0
- package/src/CommandPalette/CommandPaletteEmpty.test.tsx +101 -0
- package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
- package/src/ComplexSelector/ComplexSelector.spec.md +190 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +48 -0
- package/src/ContextMenu/ContextMenu.spec.md +207 -0
- package/src/DateTimeInput/DateTimeInput.doc.mjs +43 -23
- package/src/DateTimeInput/DateTimeInput.tsx +388 -181
- package/src/DateTimeInput/DateTimeInputTouch.test.tsx +10 -2
- package/src/DateTimeInput/NativeDateSegment.tsx +308 -0
- package/src/DateTimeInput/NativePickerSegments.test.tsx +626 -0
- package/src/DateTimeInput/NativeTimeSegment.tsx +331 -0
- package/src/DateTimeInput/index.ts +1 -0
- package/src/DateTimeInput/nativePickerSegmentStyles.ts +120 -0
- package/src/Divider/Divider.doc.mjs +24 -0
- package/src/Divider/Divider.spec.md +157 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +104 -0
- package/src/DropdownMenu/DropdownMenu.spec.md +246 -0
- package/src/DropdownMenu/DropdownMenu.tsx +2 -4
- package/src/Field/Field.doc.mjs +1 -0
- package/src/Field/Field.test.tsx +39 -0
- package/src/Field/Field.tsx +18 -1
- package/src/Field/InputClearButton.test.tsx +68 -6
- package/src/Field/PanelSearchInput.tsx +3 -5
- package/src/FieldStatus/FieldStatus.doc.mjs +21 -0
- package/src/FieldStatus/FieldStatus.spec.md +213 -0
- package/src/FieldStatus/FieldStatus.test.tsx +17 -0
- package/src/FieldStatus/FieldStatus.tsx +7 -2
- package/src/Grid/Grid.doc.mjs +19 -0
- package/src/Grid/Grid.spec.md +162 -0
- package/src/Icon/Icon.doc.mjs +196 -48
- package/src/Icon/Icon.spec.md +192 -0
- package/src/IconButton/IconButton.doc.mjs +38 -0
- package/src/Kbd/Kbd.doc.mjs +18 -0
- package/src/Kbd/Kbd.spec.md +142 -0
- package/src/Layer/useTouchTrigger.ts +2 -4
- package/src/Layout/Layout.doc.mjs +57 -17
- package/src/Layout/Layout.spec.md +235 -0
- package/src/Layout/Layout.test.tsx +1 -1
- package/src/Layout/Layout.tsx +7 -8
- package/src/Layout/LayoutHeader.doc.mjs +9 -0
- package/src/Layout/LayoutPanel.doc.mjs +4 -3
- package/src/Lightbox/Lightbox.doc.mjs +149 -33
- package/src/Lightbox/Lightbox.spec.md +198 -0
- package/src/Lightbox/useLightbox.test.tsx +263 -0
- package/src/Markdown/Markdown.doc.mjs +60 -0
- package/src/Markdown/Markdown.spec.md +207 -0
- package/src/MobileNav/MobileNav.doc.mjs +140 -32
- package/src/MobileNav/MobileNav.spec.md +209 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +42 -0
- package/src/MoreMenu/MoreMenu.spec.md +222 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +124 -1
- package/src/MultiSelector/MultiSelector.spec.md +293 -0
- package/src/MultiSelector/MultiSelector.test.tsx +305 -2
- package/src/MultiSelector/MultiSelector.tsx +124 -55
- package/src/NavIcon/NavIcon.doc.mjs +17 -0
- package/src/NavIcon/NavIcon.spec.md +134 -0
- package/src/NavMenu/NavHeadingMenu.spec.md +210 -0
- package/src/NavMenu/NavMenu.doc.mjs +37 -0
- package/src/Outline/Outline.doc.mjs +35 -0
- package/src/Outline/Outline.spec.md +162 -0
- package/src/OverflowList/OverflowList.doc.mjs +24 -0
- package/src/OverflowList/OverflowList.spec.md +157 -0
- package/src/Pagination/Pagination.doc.mjs +74 -0
- package/src/Pagination/Pagination.spec.md +226 -0
- package/src/Popover/Popover.doc.mjs +264 -65
- package/src/Popover/Popover.spec.md +421 -0
- package/src/Popover/usePopover.doc.mjs +208 -33
- package/src/PowerSearch/PowerSearch.spec.md +291 -0
- package/src/PowerSearch/PowerSearchFilterEditor.test.tsx +253 -0
- package/src/PowerSearch/PowerSearchToken.test.tsx +228 -0
- package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
- package/src/ProgressBar/ProgressBar.spec.md +261 -0
- package/src/RadioList/RadioList.test.tsx +136 -2
- package/src/RadioList/RadioListItem.doc.mjs +30 -11
- package/src/RadioList/RadioListItem.tsx +44 -8
- package/src/Resizable/Resizable.doc.mjs +130 -24
- package/src/Resizable/Resizable.spec.md +246 -0
- package/src/Resizable/ResizeHandle.test.tsx +177 -56
- package/src/Resizable/ResizeHandle.tsx +128 -45
- package/src/Resizable/index.ts +2 -0
- package/src/Resizable/shippedDirections.test.ts +462 -0
- package/src/Resizable/useResizable.doc.mjs +124 -29
- package/src/Resizable/useResizable.test.ts +1277 -3
- package/src/Resizable/useResizable.ts +760 -48
- package/src/Resizable/utils.test.ts +74 -0
- package/src/Resizable/utils.ts +35 -0
- package/src/Section/Section.doc.mjs +17 -0
- package/src/Section/Section.spec.md +139 -0
- package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
- package/src/SegmentedControl/SegmentedControl.spec.md +152 -0
- package/src/Selector/Selector.doc.mjs +133 -97
- package/src/Selector/Selector.spec.md +300 -0
- package/src/Selector/Selector.test.tsx +318 -1
- package/src/Selector/Selector.tsx +127 -60
- package/src/Selector/useSelectorPresentation.ts +24 -4
- package/src/SideNav/SideNav.test.tsx +3 -3
- package/src/SideNav/SideNavCollapseButton.doc.mjs +8 -0
- package/src/SideNav/SideNavItem.doc.mjs +1 -1
- package/src/Skeleton/Skeleton.doc.mjs +12 -0
- package/src/Skeleton/Skeleton.spec.md +127 -0
- package/src/Slider/Slider.doc.mjs +184 -30
- package/src/Slider/Slider.spec.md +224 -0
- package/src/Slider/Slider.tsx +2 -5
- package/src/Spinner/Spinner.doc.mjs +5 -5
- package/src/Spinner/Spinner.test.tsx +32 -7
- package/src/Spinner/Spinner.tsx +46 -14
- package/src/Stack/Stack.doc.mjs +22 -0
- package/src/Stack/Stack.spec.md +155 -0
- package/src/StatusDot/StatusDot.doc.mjs +17 -0
- package/src/StatusDot/StatusDot.spec.md +138 -0
- package/src/Stepper/Step.doc.mjs +1 -1
- package/src/Stepper/Step.tsx +118 -5
- package/src/Stepper/Stepper.doc.mjs +51 -1
- package/src/Stepper/Stepper.spec.md +245 -0
- package/src/Stepper/Stepper.test.tsx +315 -0
- package/src/Stepper/Stepper.tsx +8 -0
- package/src/Switch/Switch.doc.mjs +40 -0
- package/src/Switch/Switch.spec.md +175 -0
- package/src/TabList/TabList.tsx +2 -3
- package/src/Table/Table.doc.mjs +102 -24
- package/src/Table/Table.perf.test.tsx +51 -1
- package/src/Table/Table.spec.md +325 -0
- package/src/Table/TableHeaderCell.test.tsx +291 -0
- package/src/Table/index.ts +19 -4
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -12
- package/src/Table/plugins/filtering/useTableFilterState.test.tsx +143 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +2 -1
- package/src/Table/plugins/rowStatus/index.ts +1 -4
- package/src/Table/plugins/rowStatus/useTableRowStatus.public.test.ts +92 -0
- package/src/Table/plugins/rowStatus/useTableRowStatus.spec.md +354 -0
- package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +431 -56
- package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +155 -84
- package/src/Table/plugins/sortable/useTableSortable.test.tsx +72 -0
- package/src/Table/plugins/sortable/useTableSortable.tsx +18 -1
- package/src/Table/useTableGroupedRows.doc.mjs +2 -2
- package/src/Table/useTableRowStatus.doc.mjs +5 -5
- package/src/Text/Text.doc.mjs +190 -43
- package/src/Text/Text.spec.md +206 -0
- package/src/Text/useTruncation.ts +8 -7
- package/src/TextArea/TextArea.doc.mjs +255 -45
- package/src/TextArea/TextArea.spec.md +205 -0
- package/src/TimeInput/NativeTimeInput.test.tsx +269 -0
- package/src/TimeInput/TimeInput.doc.mjs +47 -23
- package/src/TimeInput/TimeInput.tsx +109 -50
- package/src/TimeInput/index.ts +1 -0
- package/src/Timestamp/Timestamp.doc.mjs +2 -2
- package/src/Timestamp/Timestamp.test.tsx +75 -3
- package/src/Timestamp/Timestamp.tsx +7 -153
- package/src/Timestamp/formatRelativeTime.ts +96 -0
- package/src/Toast/ToastViewport.test.tsx +194 -0
- package/src/Toast/ToastViewport.tsx +136 -58
- package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +2 -0
- package/src/Tokenizer/Tokenizer.spec.md +293 -0
- package/src/Tokenizer/Tokenizer.test.tsx +335 -1
- package/src/Tokenizer/Tokenizer.tsx +129 -46
- package/src/Tokenizer/useEndLaneReserve.ts +146 -0
- package/src/Toolbar/Toolbar.doc.mjs +19 -0
- package/src/Toolbar/Toolbar.spec.md +174 -0
- package/src/Tooltip/Tooltip.doc.mjs +17 -0
- package/src/Tooltip/Tooltip.spec.md +142 -0
- package/src/Tooltip/Tooltip.test.tsx +21 -1
- package/src/Tooltip/useTooltip.test.tsx +318 -0
- package/src/Tooltip/useTooltip.tsx +60 -39
- package/src/TreeList/TreeList.doc.mjs +58 -0
- package/src/TreeList/TreeList.spec.md +224 -0
- package/src/Typeahead/BaseTypeahead.tsx +96 -25
- package/src/Typeahead/Typeahead.doc.mjs +89 -1
- package/src/Typeahead/Typeahead.spec.md +257 -0
- package/src/Typeahead/Typeahead.test.tsx +502 -1
- package/src/Typeahead/Typeahead.tsx +187 -69
- package/src/Typeahead/TypeaheadItem.test.tsx +93 -2
- package/src/Typeahead/busyIndicatorLane.tsx +109 -0
- package/src/__tests__/stylexDeclarations.ts +51 -0
- package/src/hooks/useFocusReturnVisibility.ts +3 -5
- package/src/hooks/useOverflow.ts +11 -11
- package/src/hooks/useScrollLock.test.ts +103 -0
- package/src/hooks/useScrollOverflow.ts +7 -4
- package/src/i18n/useTranslator.test.tsx +197 -0
- package/src/theme/defineTheme.test.ts +227 -0
- package/src/theme/defineTheme.ts +30 -0
- package/src/theme/derivedVarRegistry.test.ts +6 -0
- package/src/theme/generateThemeRules.ts +4 -1
- package/src/theme/localTokens.ts +272 -0
- package/src/theme/themingTargets.test.ts +126 -28
- package/src/utils/interactionModality.test.ts +83 -0
- package/src/utils/interactionModality.ts +88 -36
- package/src/utils/sharedResizeObserver.test.ts +124 -5
- package/src/utils/sharedResizeObserver.ts +60 -14
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 3
|
|
3
|
+
template_version: 3
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:MultiSelector
|
|
6
|
+
authority: draft
|
|
7
|
+
archive_reason: null
|
|
8
|
+
superseded_by: null
|
|
9
|
+
approved_by: null
|
|
10
|
+
approved_at: null
|
|
11
|
+
owners: [cixzhang]
|
|
12
|
+
review_triggers: [public-api, behavior, theming, accessibility]
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/MultiSelector/MultiSelector.test.tsx,
|
|
16
|
+
packages/core/src/BottomSheet/BottomSheet.test.tsx,
|
|
17
|
+
packages/core/src/CheckboxInput/CheckboxInput.test.tsx,
|
|
18
|
+
packages/core/src/Divider/Divider.test.tsx,
|
|
19
|
+
packages/core/src/Field/Field.test.tsx,
|
|
20
|
+
packages/core/src/Icon/Icon.test.tsx,
|
|
21
|
+
packages/core/src/Text/Text.test.tsx,
|
|
22
|
+
scripts/check-knowledge.mjs,
|
|
23
|
+
]
|
|
24
|
+
modules: []
|
|
25
|
+
families: [family:input-fields, family:overlay-dismissal]
|
|
26
|
+
design_specs: []
|
|
27
|
+
architecture:
|
|
28
|
+
[
|
|
29
|
+
architecture:component-theming-surface,
|
|
30
|
+
architecture:layer-runtime,
|
|
31
|
+
architecture:public-component-api,
|
|
32
|
+
]
|
|
33
|
+
contributing: []
|
|
34
|
+
system_specs: [spec:AST-011/DEC-1]
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# MultiSelector component contract
|
|
38
|
+
|
|
39
|
+
## Intent
|
|
40
|
+
|
|
41
|
+
MultiSelector renders a multi-selection control with an optional standalone Field
|
|
42
|
+
shell and one shared panel content tree in either an anchored pointer popup or a
|
|
43
|
+
touch-oriented BottomSheet. It also supports a read-only state that preserves and
|
|
44
|
+
submits the selected values without exposing that panel or an editing affordance.
|
|
45
|
+
This draft records current consumer anatomy, theming ownership, and read-only
|
|
46
|
+
behavior.
|
|
47
|
+
|
|
48
|
+
## Compatibility and migration
|
|
49
|
+
|
|
50
|
+
- Released default preserved: `yes`
|
|
51
|
+
- `isReadOnly` is additive and defaults to `false`. It preserves selected values,
|
|
52
|
+
focus, and form participation while removing selection-surface and editing
|
|
53
|
+
affordances. `isDisabled` takes precedence when both are set.
|
|
54
|
+
- Existing DOM, styling, targets, and public API remain unchanged when the prop is
|
|
55
|
+
omitted.
|
|
56
|
+
- Controlled/uncontrolled behavior: unchanged
|
|
57
|
+
- Migration decision: none
|
|
58
|
+
|
|
59
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
60
|
+
|
|
61
|
+
## Ownership boundary
|
|
62
|
+
|
|
63
|
+
**Owns**
|
|
64
|
+
|
|
65
|
+
- The painted Trigger and its current `multi-selector` target.
|
|
66
|
+
- Indicator icon, Search row, Option row, Section heading, Empty state, and
|
|
67
|
+
Pointer popup presentation through the other six current MultiSelector targets.
|
|
68
|
+
- One shared panel-content tree containing optional search, option, divider,
|
|
69
|
+
section, and empty-state content regardless of which presentation hosts it.
|
|
70
|
+
|
|
71
|
+
**Does not own / non-goals**
|
|
72
|
+
|
|
73
|
+
- Standalone field-shell presentation — rendered by Field and themed through
|
|
74
|
+
Field's current `field` target; omitted when MultiSelector is inside InputGroup.
|
|
75
|
+
- Shared clear actions — rendered by Field's InputClearButton and themed through
|
|
76
|
+
Field's current `input-clear-button` target.
|
|
77
|
+
- Option checkbox presentation — rendered by CheckboxInput and themed through
|
|
78
|
+
CheckboxInput's current `checkbox-indicator` target.
|
|
79
|
+
- Public option dividers — rendered by Divider and themed through Divider's
|
|
80
|
+
current `divider` target.
|
|
81
|
+
- General Icon presentation for start, search, and status icons — rendered by
|
|
82
|
+
Icon and themed through Icon's current `icon` target.
|
|
83
|
+
- The Touch sheet heading — rendered by Heading and themed through Text's current
|
|
84
|
+
`heading` target.
|
|
85
|
+
- The Touch sheet panel — rendered by BottomSheet and themed through
|
|
86
|
+
BottomSheet's current `bottom-sheet` target.
|
|
87
|
+
- Arbitrary ReactNode start content supplied by the caller.
|
|
88
|
+
- Shared layer lifecycle, positioning, and dismissal behavior.
|
|
89
|
+
|
|
90
|
+
## Public concepts
|
|
91
|
+
|
|
92
|
+
This table names the additive concept introduced by `spec:AST-011`. Complete prop
|
|
93
|
+
syntax and examples remain in `MultiSelector.doc.mjs`.
|
|
94
|
+
|
|
95
|
+
| Concept | Closed values or states | Meaning | Availability | Default | Owner | Stability | Invalid-value behavior |
|
|
96
|
+
| --------------- | ----------------------- | --------------------------------------------------------- | -------------- | ------- | ------ | --------- | ---------------------- |
|
|
97
|
+
| read-only state | `false`, `true` | Preserves and submits values without selection affordance | Closed trigger | `false` | Caller | additive | Boolean normalization |
|
|
98
|
+
|
|
99
|
+
## Behavioral and layout contract
|
|
100
|
+
|
|
101
|
+
| ID | Candidate invariant | Basis | Draft review state |
|
|
102
|
+
| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -------------------------------------------------- |
|
|
103
|
+
| FR1 | The current standalone presentation contains a Field and painted Trigger; the supported InputGroup path renders the same Trigger without a Field. Optional start content, clear action, status icon, or Indicator icon follows supplied props and state. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
|
|
104
|
+
| FR2 | The shared panel content contains the optional Search row, zero or more Option rows, optional public Option dividers and Section headings, and an Empty state when the applicable result set is empty and the value is not loading. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
|
|
105
|
+
| FR3 | Trigger, Indicator icon, Search row, Option row, Section heading, Empty state, and Pointer popup carry the seven current non-deprecated MultiSelector targets documented in `MultiSelector.doc.mjs`. | Current source, docs, and tests | Verified current behavior; no target change |
|
|
106
|
+
| FR4 | When present, Field, shared clear actions, option checkbox indicators, public Option dividers, general icons, Touch sheet heading, and Touch sheet retain their Field, CheckboxInput, Divider, Icon, Text, and BottomSheet theming owners. | Current composition and owners | Verified current behavior; no target change |
|
|
107
|
+
| FR5 | Pointer popup and Touch sheet are separate surface anatomy rows, but both host the same `panelContent` tree; the Touch sheet additionally renders its Heading. Changing presentation does not create a second search, option, divider, section, or empty-state content model. | Current source and tests | Verified current behavior; no normalization |
|
|
108
|
+
| FR6 | `multi-selector-clear-icon` remains a deprecated compatibility alias for `input-clear-icon`; it is not a current target and does not claim a separate anatomy part. | Current public target metadata | Verified current compatibility state |
|
|
109
|
+
| FR7 | While `isReadOnly` is true, selected values remain focusable and form-submittable, while the selection surface, clear action, disclosure indicator, and every value-change path are unavailable. | `spec:AST-011`, docs, and tests | Accepted read-only behavior |
|
|
110
|
+
|
|
111
|
+
### Allowed variation
|
|
112
|
+
|
|
113
|
+
- Standalone MultiSelector renders Field; the supported InputGroup path omits it.
|
|
114
|
+
- `presentation="popover"` uses the Pointer popup; `presentation="bottom-sheet"`
|
|
115
|
+
uses the Touch sheet and its Heading; `presentation="adaptive"` selects between
|
|
116
|
+
those existing branches from current modality and viewport behavior.
|
|
117
|
+
- Search row, Option divider, Section heading, Empty state, select-all Option row,
|
|
118
|
+
and both clear actions are optional under their documented prop and data conditions.
|
|
119
|
+
- Semantic names and icon component types render through Icon. Arbitrary ReactNode
|
|
120
|
+
start content renders directly and stays caller-owned.
|
|
121
|
+
- Trigger text, labels, badges, and custom option content may vary inside the
|
|
122
|
+
Trigger or Option row without creating another MultiSelector target.
|
|
123
|
+
|
|
124
|
+
### Representative states
|
|
125
|
+
|
|
126
|
+
| State | Required invariant | Allowed variation |
|
|
127
|
+
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
128
|
+
| Standalone or InputGroup | Field is present only standalone; Trigger anatomy and targets remain the same in both paths. | InputGroup owns grouped layout and labeling support. |
|
|
129
|
+
| Pointer presentation | Pointer popup carries `multi-selector-popup` and hosts the shared panel content. | Search, groups, options, and empty content follow their ordinary states. |
|
|
130
|
+
| Touch presentation | BottomSheet owns the Sheet panel, Text owns its Heading, and the sheet hosts the same shared panel content. | Adaptive or explicit selection may choose this branch. |
|
|
131
|
+
| Search enabled with a query | Search row owns `multi-selector-search`; its icons/actions keep their shared owners. | Results may be flat, grouped, or empty. |
|
|
132
|
+
| Option, select all, selected, disabled | Option row owns `multi-selector-option` and reflects its current size/state metadata. | Checkbox state and custom row content vary independently. |
|
|
133
|
+
| Attached or tooltip status | Status icon replaces the Indicator icon. | Tooltip status wraps the icon in a button without changing Icon owner. |
|
|
134
|
+
| Read-only | Values stay focusable and submittable; panel, clear, and disclosure affordances are absent. | Trigger display, status, and busy presentation remain available. |
|
|
135
|
+
|
|
136
|
+
### Transformation and precedence order
|
|
137
|
+
|
|
138
|
+
- No new search, selection, presentation, or styling precedence rule is introduced.
|
|
139
|
+
|
|
140
|
+
### Performance and resources
|
|
141
|
+
|
|
142
|
+
- No new performance or resource rule is introduced.
|
|
143
|
+
|
|
144
|
+
## Accessibility contract
|
|
145
|
+
|
|
146
|
+
Existing field, combobox, listbox, checkbox, live-region, focus, and dismissal
|
|
147
|
+
behavior remains unchanged when editable. A read-only value stays focusable,
|
|
148
|
+
retains `role="combobox"`, and exposes `aria-readonly="true"` plus
|
|
149
|
+
`aria-expanded="false"`. Its rendered content remains the combobox value. It does
|
|
150
|
+
not expose `aria-controls`, `aria-activedescendant`, or an active selection
|
|
151
|
+
surface. The implicit listbox popup describes the widget's selection capability;
|
|
152
|
+
read-only state communicates that its values cannot currently change. Chromium's
|
|
153
|
+
button-hosted combobox mapping omits the ARIA read-only property, so a localized
|
|
154
|
+
accessible description also announces that state. Search and non-search modes use
|
|
155
|
+
the same read-only combobox semantics because neither exposes a search control in
|
|
156
|
+
this state.
|
|
157
|
+
|
|
158
|
+
## Design relationships
|
|
159
|
+
|
|
160
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
161
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------ | ----------------- | ------------------ |
|
|
162
|
+
| Field | Provides the label and optional supporting content outside InputGroup; omitted inside InputGroup. | Field component | Supporting | FR1, FR4 |
|
|
163
|
+
| Trigger | Presents the current selection or placeholder and opens the active presentation. | Current source and public docs | Prominent | FR1, FR3 |
|
|
164
|
+
| Icon-rendered start icon | Presents a semantic or component icon through Icon. | Icon component | Supporting | FR1, FR4 |
|
|
165
|
+
| Caller-rendered start content | Presents arbitrary caller content directly in the trigger's start slot. | Caller-supplied content | Context-dependent | FR1 |
|
|
166
|
+
| Trigger clear button | Removes every selected value through the shared field clear action. | Field component | Supporting | FR1, FR4 |
|
|
167
|
+
| Status icon | Presents attached or tooltip validation state through Icon. | Icon component | Supporting | FR1, FR4 |
|
|
168
|
+
| Indicator icon | Presents disclosure state when status presentation does not replace it. | Current source and public docs | Supporting | FR1, FR3 |
|
|
169
|
+
| Search row | Provides query entry at the top of the shared panel content. | Current source and public docs | Supporting | FR2, FR3, FR5 |
|
|
170
|
+
| Search icon | Presents the search affordance through Icon. | Icon component | Supporting | FR2, FR4 |
|
|
171
|
+
| Search clear button | Clears a present query through the shared field clear action. | Field component | Supporting | FR2, FR4 |
|
|
172
|
+
| Option row | Presents one option or the select-all choice and owns row interaction/state. | Current source and public docs | Prominent | FR2, FR3 |
|
|
173
|
+
| Option checkbox indicator | Presents the option's selected, unselected, or indeterminate state through CheckboxInput. | CheckboxInput component | Supporting | FR2, FR4 |
|
|
174
|
+
| Option divider | Separates adjacent groups when a public divider entry is present in the options data. | Divider component | Supporting | FR2, FR4 |
|
|
175
|
+
| Section heading | Labels a visible group of option rows. | Current source and public docs | Supporting | FR2, FR3 |
|
|
176
|
+
| Empty state | Presents no-options or no-results content when the panel has no selectable rows to show. | Current source and public docs | Prominent | FR2, FR3 |
|
|
177
|
+
| Pointer popup | Paints the anchored pointer surface around the shared panel content. | Current source and public docs | Prominent | FR3, FR5 |
|
|
178
|
+
| Touch sheet heading | Names the bottom-sheet presentation above the shared panel content. | Text component | Prominent | FR4, FR5 |
|
|
179
|
+
| Touch sheet | Paints the BottomSheet surface around the same shared panel content. | BottomSheet component | Prominent | FR4, FR5 |
|
|
180
|
+
|
|
181
|
+
The seven current MultiSelector targets remain on their shipped visible elements.
|
|
182
|
+
Nested shared primitives keep their own owners. The Pointer popup and Touch sheet
|
|
183
|
+
are intentionally separate rows because they use different surface owners; the
|
|
184
|
+
Touch sheet adds a Heading owned by Text. Search, option, divider, section, and
|
|
185
|
+
empty-state content remains one shared tree.
|
|
186
|
+
|
|
187
|
+
### Theming anatomy
|
|
188
|
+
|
|
189
|
+
<!-- anatomy-theming:v1 -->
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"Field": {"delegatesTo": {"owner": "component:Field", "target": "field"}},
|
|
194
|
+
"Trigger": {"target": "multi-selector"},
|
|
195
|
+
"Icon-rendered start icon": {
|
|
196
|
+
"delegatesTo": {"owner": "component:Icon", "target": "icon"}
|
|
197
|
+
},
|
|
198
|
+
"Caller-rendered start content": {
|
|
199
|
+
"none": {
|
|
200
|
+
"reason": "intentional: Arbitrary ReactNode content is caller-owned and receives no MultiSelector target."
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"Trigger clear button": {
|
|
204
|
+
"delegatesTo": {
|
|
205
|
+
"owner": "component:Field",
|
|
206
|
+
"target": "input-clear-button"
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
"Status icon": {
|
|
210
|
+
"delegatesTo": {"owner": "component:Icon", "target": "icon"}
|
|
211
|
+
},
|
|
212
|
+
"Indicator icon": {"target": "multi-selector-indicator-icon"},
|
|
213
|
+
"Search row": {"target": "multi-selector-search"},
|
|
214
|
+
"Search icon": {
|
|
215
|
+
"delegatesTo": {"owner": "component:Icon", "target": "icon"}
|
|
216
|
+
},
|
|
217
|
+
"Search clear button": {
|
|
218
|
+
"delegatesTo": {
|
|
219
|
+
"owner": "component:Field",
|
|
220
|
+
"target": "input-clear-button"
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
"Option row": {"target": "multi-selector-option"},
|
|
224
|
+
"Option checkbox indicator": {
|
|
225
|
+
"delegatesTo": {
|
|
226
|
+
"owner": "component:CheckboxInput",
|
|
227
|
+
"target": "checkbox-indicator"
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"Option divider": {
|
|
231
|
+
"delegatesTo": {"owner": "component:Divider", "target": "divider"}
|
|
232
|
+
},
|
|
233
|
+
"Section heading": {"target": "multi-selector-section-heading"},
|
|
234
|
+
"Empty state": {"target": "multi-selector-empty-state"},
|
|
235
|
+
"Pointer popup": {"target": "multi-selector-popup"},
|
|
236
|
+
"Touch sheet heading": {
|
|
237
|
+
"delegatesTo": {"owner": "component:Text", "target": "heading"}
|
|
238
|
+
},
|
|
239
|
+
"Touch sheet": {
|
|
240
|
+
"delegatesTo": {
|
|
241
|
+
"owner": "component:BottomSheet",
|
|
242
|
+
"target": "bottom-sheet"
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Family and system relationships
|
|
249
|
+
|
|
250
|
+
- `architecture:public-component-api` and `spec:AST-011/DEC-1` own the additive
|
|
251
|
+
read-only API and its cross-input meaning.
|
|
252
|
+
- `architecture:component-theming-surface` owns anatomy qualification, local
|
|
253
|
+
target mapping, delegation, and exclusion of deprecated aliases.
|
|
254
|
+
- `architecture:layer-runtime` owns the current Popover host and the distinction
|
|
255
|
+
between browser popover and native-dialog sheet presentation.
|
|
256
|
+
- `family:overlay-dismissal` owns shared Escape and platform-close ordering. The
|
|
257
|
+
Pointer popup participates through its composed Popover owner; the Touch sheet
|
|
258
|
+
inherits BottomSheet's current local-only adoption gap.
|
|
259
|
+
- Field, CheckboxInput, Divider, Icon, Text, and BottomSheet retain ownership of
|
|
260
|
+
their delegated targets and presentation.
|
|
261
|
+
|
|
262
|
+
## Verification map
|
|
263
|
+
|
|
264
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
265
|
+
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
266
|
+
| FR1, FR2 | `MultiSelector.test.tsx` render, search, divider, grouping, status, select-all, and empty-state suites plus InputGroup source inspection | Standalone/InputGroup, search, divided, selected, empty, status | Removing a documented part or misreporting Field presence conflicts with existing structure, content assertions, or source. | `audit:MultiSelector/anatomy` |
|
|
267
|
+
| FR3, FR6 | Component target suites, source inspection, and public target inventory | All seven current targets and deprecated alias | A current target is missed, invented, moved, or confused with the deprecated clear-icon alias. | `audit:MultiSelector/theming` |
|
|
268
|
+
| FR4 | Composed owner source/tests for Field, CheckboxInput, Divider, Icon, Text, and BottomSheet | Field omission/presence, clear actions, checkbox, divider, icons, heading, sheet | A shared part gains the wrong local owner or its documented owner no longer renders it. | `audit:MultiSelector/theming` |
|
|
269
|
+
| FR5 | `MultiSelector.test.tsx` presentation suites and `panelContent` source inspection | Explicit popover, explicit sheet, adaptive touch | A presentation stops using its owner or receives a divergent panel-content implementation. | `audit:MultiSelector/overlay` |
|
|
270
|
+
| FR7 | read-only interaction, form, ARIA, and theme-state tests | search/non-search, clearable, open→read-only, disabled precedence | A value changes, popup or edit affordance remains, form value disappears, or read-only semantics are absent. | `audit:MultiSelector/accessibility` |
|
|
271
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and current local targets | Missing, extra, prefixed, stale, or multiply assigned mappings fail repository validation. | `audit:MultiSelector/theming` |
|
|
272
|
+
|
|
273
|
+
Existing component tests directly assert the Trigger, Indicator icon, Search row,
|
|
274
|
+
Option row, Section heading, Empty state, and Pointer popup targets. Presentation
|
|
275
|
+
tests directly distinguish the Popover and BottomSheet branches. Source inspection
|
|
276
|
+
distinguishes standalone Field presence from InputGroup omission and confirms that both surface branches receive the same `panelContent`, while the touch branch
|
|
277
|
+
adds Heading; Divider, Text, BottomSheet, and other shared primitive tests provide
|
|
278
|
+
owner evidence rather than new MultiSelector targets.
|
|
279
|
+
|
|
280
|
+
## Decision log
|
|
281
|
+
|
|
282
|
+
None. This draft records current facts and introduces no component-local design,
|
|
283
|
+
API, theming, or layer-system decision.
|
|
284
|
+
|
|
285
|
+
## Open questions
|
|
286
|
+
|
|
287
|
+
None.
|
|
288
|
+
|
|
289
|
+
## Content boundary
|
|
290
|
+
|
|
291
|
+
This file does not duplicate consumer prop tables, examples, search or selection
|
|
292
|
+
algorithms, implementation steps, or shared layer and theming rules. It links to
|
|
293
|
+
their owners.
|
|
@@ -1140,7 +1140,9 @@ describe('MultiSelector', () => {
|
|
|
1140
1140
|
/>,
|
|
1141
1141
|
);
|
|
1142
1142
|
|
|
1143
|
-
|
|
1143
|
+
const trigger = screen.getByRole('combobox', {name: 'Fruit'});
|
|
1144
|
+
await user.click(trigger);
|
|
1145
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
1144
1146
|
|
|
1145
1147
|
// useAnnounce writes on the next animation frame, so an immediate read
|
|
1146
1148
|
// would pass whether or not anything was announced.
|
|
@@ -1395,7 +1397,7 @@ describe('MultiSelector', () => {
|
|
|
1395
1397
|
expect(group).toHaveAttribute('aria-label', 'Citrus');
|
|
1396
1398
|
});
|
|
1397
1399
|
|
|
1398
|
-
it('shows loading state with aria-busy', () => {
|
|
1400
|
+
it('shows loading state with a spinner and aria-busy', () => {
|
|
1399
1401
|
render(
|
|
1400
1402
|
<MultiSelector
|
|
1401
1403
|
label="Fruit"
|
|
@@ -1406,6 +1408,7 @@ describe('MultiSelector', () => {
|
|
|
1406
1408
|
/>,
|
|
1407
1409
|
);
|
|
1408
1410
|
expect(screen.getByRole('combobox')).toHaveAttribute('aria-busy', 'true');
|
|
1411
|
+
expect(screen.getByRole('status', {name: 'Loading'})).toBeInTheDocument();
|
|
1409
1412
|
});
|
|
1410
1413
|
|
|
1411
1414
|
it('renders with custom selectAllLabel', async () => {
|
|
@@ -1885,6 +1888,306 @@ describe('MultiSelector', () => {
|
|
|
1885
1888
|
expect(trigger).toHaveAttribute('tabIndex', '-1');
|
|
1886
1889
|
});
|
|
1887
1890
|
});
|
|
1891
|
+
describe('isReadOnly', () => {
|
|
1892
|
+
const readOnlyCases = [
|
|
1893
|
+
{presentation: 'popover', hasSearch: false},
|
|
1894
|
+
{presentation: 'popover', hasSearch: true},
|
|
1895
|
+
{presentation: 'bottom-sheet', hasSearch: false},
|
|
1896
|
+
{presentation: 'bottom-sheet', hasSearch: true},
|
|
1897
|
+
{presentation: 'adaptive', hasSearch: false},
|
|
1898
|
+
{presentation: 'adaptive', hasSearch: true},
|
|
1899
|
+
] as const;
|
|
1900
|
+
|
|
1901
|
+
const sheetTransitionCases = [
|
|
1902
|
+
{presentation: 'bottom-sheet', hasSearch: false},
|
|
1903
|
+
{presentation: 'bottom-sheet', hasSearch: true},
|
|
1904
|
+
{presentation: 'adaptive', hasSearch: false},
|
|
1905
|
+
{presentation: 'adaptive', hasSearch: true},
|
|
1906
|
+
] as const;
|
|
1907
|
+
|
|
1908
|
+
const disabledPrecedenceCases = (
|
|
1909
|
+
['popover', 'bottom-sheet', 'adaptive'] as const
|
|
1910
|
+
).flatMap(presentation =>
|
|
1911
|
+
[false, true].flatMap(hasSearch =>
|
|
1912
|
+
[false, true].map(hasDisabledMessage => ({
|
|
1913
|
+
presentation,
|
|
1914
|
+
hasSearch,
|
|
1915
|
+
hasDisabledMessage,
|
|
1916
|
+
})),
|
|
1917
|
+
),
|
|
1918
|
+
);
|
|
1919
|
+
|
|
1920
|
+
function stubCompactTouch(presentation: 'bottom-sheet' | 'adaptive') {
|
|
1921
|
+
if (presentation !== 'adaptive') {
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
vi.mocked(matchMedia).mockImplementation(
|
|
1925
|
+
(query: string) =>
|
|
1926
|
+
({
|
|
1927
|
+
matches: query === '(max-width: 768px) and (pointer: coarse)',
|
|
1928
|
+
media: query,
|
|
1929
|
+
onchange: null,
|
|
1930
|
+
addEventListener: vi.fn(),
|
|
1931
|
+
removeEventListener: vi.fn(),
|
|
1932
|
+
addListener: vi.fn(),
|
|
1933
|
+
removeListener: vi.fn(),
|
|
1934
|
+
dispatchEvent: vi.fn(),
|
|
1935
|
+
}) as MediaQueryList,
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
it.each(readOnlyCases)(
|
|
1940
|
+
'uses a read-only combobox with no rendered popup ($presentation, search=$hasSearch)',
|
|
1941
|
+
async ({presentation, hasSearch}) => {
|
|
1942
|
+
const user = userEvent.setup();
|
|
1943
|
+
const {container} = render(
|
|
1944
|
+
<form>
|
|
1945
|
+
<MultiSelector
|
|
1946
|
+
label="Fruit"
|
|
1947
|
+
htmlName="fruit"
|
|
1948
|
+
options={defaultOptions}
|
|
1949
|
+
value={['Apple', 'Banana']}
|
|
1950
|
+
onChange={() => {}}
|
|
1951
|
+
hasClear
|
|
1952
|
+
hasSearch={hasSearch}
|
|
1953
|
+
presentation={presentation}
|
|
1954
|
+
isDefaultOpen
|
|
1955
|
+
triggerDisplay="labels"
|
|
1956
|
+
isReadOnly
|
|
1957
|
+
/>
|
|
1958
|
+
</form>,
|
|
1959
|
+
);
|
|
1960
|
+
|
|
1961
|
+
const trigger = screen.getByRole('combobox', {name: 'Fruit'});
|
|
1962
|
+
expect(trigger.tagName).toBe('BUTTON');
|
|
1963
|
+
expect(trigger).toHaveTextContent('Apple, Banana');
|
|
1964
|
+
expect(trigger).toHaveAttribute('aria-readonly', 'true');
|
|
1965
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
1966
|
+
const readOnlyDescription = screen.getByText('Read only');
|
|
1967
|
+
expect(trigger.getAttribute('aria-describedby')).toContain(
|
|
1968
|
+
readOnlyDescription.id,
|
|
1969
|
+
);
|
|
1970
|
+
expect(trigger).not.toHaveAttribute('aria-controls');
|
|
1971
|
+
expect(trigger).not.toHaveAttribute('aria-haspopup');
|
|
1972
|
+
expect(screen.getAllByRole('combobox', h)).toHaveLength(1);
|
|
1973
|
+
expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
|
|
1974
|
+
expect(screen.queryByRole('dialog', h)).not.toBeInTheDocument();
|
|
1975
|
+
expect(
|
|
1976
|
+
screen.queryByRole('button', {name: 'Clear all Fruit'}),
|
|
1977
|
+
).not.toBeInTheDocument();
|
|
1978
|
+
expect(
|
|
1979
|
+
container.querySelector('.astryx-multi-selector-indicator-icon'),
|
|
1980
|
+
).toBeNull();
|
|
1981
|
+
expect(
|
|
1982
|
+
container.querySelector('.astryx-multi-selector'),
|
|
1983
|
+
).toHaveAttribute('data-readonly', 'readonly');
|
|
1984
|
+
expect(
|
|
1985
|
+
new FormData(container.querySelector('form')!).getAll('fruit'),
|
|
1986
|
+
).toEqual(['Apple', 'Banana']);
|
|
1987
|
+
|
|
1988
|
+
await user.tab();
|
|
1989
|
+
expect(trigger).toHaveFocus();
|
|
1990
|
+
},
|
|
1991
|
+
);
|
|
1992
|
+
|
|
1993
|
+
it('keeps loading feedback while blocking the selection surface', async () => {
|
|
1994
|
+
const user = userEvent.setup();
|
|
1995
|
+
render(
|
|
1996
|
+
<MultiSelector
|
|
1997
|
+
label="Fruit"
|
|
1998
|
+
options={defaultOptions}
|
|
1999
|
+
value={['Apple']}
|
|
2000
|
+
onChange={() => {}}
|
|
2001
|
+
isLoading
|
|
2002
|
+
isReadOnly
|
|
2003
|
+
/>,
|
|
2004
|
+
);
|
|
2005
|
+
|
|
2006
|
+
const trigger = screen.getByRole('combobox', {name: 'Fruit'});
|
|
2007
|
+
expect(trigger).toHaveAttribute('aria-busy', 'true');
|
|
2008
|
+
expect(screen.getByRole('status', {name: 'Loading'})).toBeInTheDocument();
|
|
2009
|
+
|
|
2010
|
+
await user.click(trigger);
|
|
2011
|
+
expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
|
|
2012
|
+
});
|
|
2013
|
+
|
|
2014
|
+
it('blocks pointer, keyboard, and typeahead changes', async () => {
|
|
2015
|
+
const user = userEvent.setup();
|
|
2016
|
+
const onChange = vi.fn();
|
|
2017
|
+
const changeAction = vi.fn();
|
|
2018
|
+
render(
|
|
2019
|
+
<MultiSelector
|
|
2020
|
+
label="Fruit"
|
|
2021
|
+
options={defaultOptions}
|
|
2022
|
+
value={['Apple']}
|
|
2023
|
+
onChange={onChange}
|
|
2024
|
+
changeAction={changeAction}
|
|
2025
|
+
isReadOnly
|
|
2026
|
+
/>,
|
|
2027
|
+
);
|
|
2028
|
+
|
|
2029
|
+
const trigger = screen.getByRole('combobox');
|
|
2030
|
+
await user.click(trigger);
|
|
2031
|
+
trigger.focus();
|
|
2032
|
+
await user.keyboard('{Enter}{ArrowDown}b{Backspace}');
|
|
2033
|
+
|
|
2034
|
+
expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
|
|
2035
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
2036
|
+
expect(changeAction).not.toHaveBeenCalled();
|
|
2037
|
+
});
|
|
2038
|
+
|
|
2039
|
+
it.each([false, true])(
|
|
2040
|
+
'restores popover focus when an open selector becomes read-only (search=%s)',
|
|
2041
|
+
async hasSearch => {
|
|
2042
|
+
const user = userEvent.setup();
|
|
2043
|
+
const selector = (isReadOnly: boolean) => (
|
|
2044
|
+
<MultiSelector
|
|
2045
|
+
label="Fruit"
|
|
2046
|
+
options={defaultOptions}
|
|
2047
|
+
value={['Apple']}
|
|
2048
|
+
onChange={() => {}}
|
|
2049
|
+
hasSearch={hasSearch}
|
|
2050
|
+
isReadOnly={isReadOnly}
|
|
2051
|
+
/>
|
|
2052
|
+
);
|
|
2053
|
+
const {rerender} = render(selector(false));
|
|
2054
|
+
const editableTrigger = screen.getByRole(
|
|
2055
|
+
hasSearch ? 'button' : 'combobox',
|
|
2056
|
+
{name: 'Fruit'},
|
|
2057
|
+
);
|
|
2058
|
+
await user.click(editableTrigger);
|
|
2059
|
+
if (hasSearch) {
|
|
2060
|
+
await waitFor(() =>
|
|
2061
|
+
expect(screen.getByRole('combobox', h)).toHaveFocus(),
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
rerender(selector(true));
|
|
2066
|
+
|
|
2067
|
+
const readOnlyTrigger = screen.getByRole('combobox', {name: 'Fruit'});
|
|
2068
|
+
await waitFor(() => expect(readOnlyTrigger).toHaveFocus());
|
|
2069
|
+
expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
|
|
2070
|
+
},
|
|
2071
|
+
);
|
|
2072
|
+
|
|
2073
|
+
it.each(sheetTransitionCases)(
|
|
2074
|
+
'finishes $presentation close and restores focus when read-only changes (search=$hasSearch)',
|
|
2075
|
+
async ({presentation, hasSearch}) => {
|
|
2076
|
+
stubCompactTouch(presentation);
|
|
2077
|
+
const user = userEvent.setup();
|
|
2078
|
+
const selector = (isReadOnly: boolean) => (
|
|
2079
|
+
<MultiSelector
|
|
2080
|
+
label="Fruit"
|
|
2081
|
+
options={defaultOptions}
|
|
2082
|
+
value={['Apple']}
|
|
2083
|
+
onChange={() => {}}
|
|
2084
|
+
hasSearch={hasSearch}
|
|
2085
|
+
presentation={presentation}
|
|
2086
|
+
isReadOnly={isReadOnly}
|
|
2087
|
+
/>
|
|
2088
|
+
);
|
|
2089
|
+
const {rerender} = render(selector(false));
|
|
2090
|
+
const editableTrigger = screen.getByRole(
|
|
2091
|
+
hasSearch ? 'button' : 'combobox',
|
|
2092
|
+
{name: 'Fruit'},
|
|
2093
|
+
);
|
|
2094
|
+
await user.click(editableTrigger);
|
|
2095
|
+
const dialog = await screen.findByRole('dialog', {name: 'Fruit'});
|
|
2096
|
+
const panel = dialog.querySelector<HTMLElement>('.astryx-bottom-sheet');
|
|
2097
|
+
expect(panel).not.toBeNull();
|
|
2098
|
+
await waitFor(() =>
|
|
2099
|
+
expect(
|
|
2100
|
+
hasSearch
|
|
2101
|
+
? screen.getByRole('combobox', h)
|
|
2102
|
+
: screen.getByRole('listbox'),
|
|
2103
|
+
).toHaveFocus(),
|
|
2104
|
+
);
|
|
2105
|
+
|
|
2106
|
+
rerender(selector(true));
|
|
2107
|
+
|
|
2108
|
+
await waitFor(() => expect(dialog).toHaveAttribute('inert'));
|
|
2109
|
+
fireEvent.transitionEnd(panel!, {propertyName: 'transform'});
|
|
2110
|
+
const readOnlyTrigger = screen.getByRole('combobox', {name: 'Fruit'});
|
|
2111
|
+
await waitFor(() => expect(readOnlyTrigger).toHaveFocus());
|
|
2112
|
+
expect(screen.queryByRole('dialog', h)).not.toBeInTheDocument();
|
|
2113
|
+
expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
|
|
2114
|
+
},
|
|
2115
|
+
);
|
|
2116
|
+
|
|
2117
|
+
it.each(disabledPrecedenceCases)(
|
|
2118
|
+
'matches disabled-only semantics for $presentation (search=$hasSearch, reason=$hasDisabledMessage)',
|
|
2119
|
+
async ({presentation, hasSearch, hasDisabledMessage}) => {
|
|
2120
|
+
if (presentation === 'adaptive') {
|
|
2121
|
+
stubCompactTouch(presentation);
|
|
2122
|
+
}
|
|
2123
|
+
const user = userEvent.setup();
|
|
2124
|
+
const onChange = vi.fn();
|
|
2125
|
+
const selector = (isReadOnly: boolean) => (
|
|
2126
|
+
<form>
|
|
2127
|
+
<MultiSelector
|
|
2128
|
+
label="Fruit"
|
|
2129
|
+
htmlName="fruit"
|
|
2130
|
+
options={defaultOptions}
|
|
2131
|
+
value={['Apple', 'Banana']}
|
|
2132
|
+
onChange={onChange}
|
|
2133
|
+
hasClear
|
|
2134
|
+
hasSearch={hasSearch}
|
|
2135
|
+
presentation={presentation}
|
|
2136
|
+
isDisabled
|
|
2137
|
+
isReadOnly={isReadOnly}
|
|
2138
|
+
disabledMessage={
|
|
2139
|
+
hasDisabledMessage ? 'Locked by policy' : undefined
|
|
2140
|
+
}
|
|
2141
|
+
/>
|
|
2142
|
+
</form>
|
|
2143
|
+
);
|
|
2144
|
+
const {container, rerender} = render(selector(false));
|
|
2145
|
+
const getTrigger = () =>
|
|
2146
|
+
screen.getByRole(hasSearch ? 'button' : 'combobox', {name: 'Fruit'});
|
|
2147
|
+
const snapshot = () => {
|
|
2148
|
+
const trigger = getTrigger();
|
|
2149
|
+
const root = container.querySelector('.astryx-multi-selector');
|
|
2150
|
+
return {
|
|
2151
|
+
tagName: trigger.tagName,
|
|
2152
|
+
role: trigger.getAttribute('role'),
|
|
2153
|
+
hasPopup: trigger.getAttribute('aria-haspopup'),
|
|
2154
|
+
expanded: trigger.getAttribute('aria-expanded'),
|
|
2155
|
+
controls: trigger.getAttribute('aria-controls'),
|
|
2156
|
+
readOnly: trigger.getAttribute('aria-readonly'),
|
|
2157
|
+
disabled: trigger.matches(':disabled'),
|
|
2158
|
+
ariaDisabled: trigger.getAttribute('aria-disabled'),
|
|
2159
|
+
tabIndex: trigger.tabIndex,
|
|
2160
|
+
rootClassName: root?.className,
|
|
2161
|
+
rootDisabled: root?.getAttribute('data-disabled'),
|
|
2162
|
+
rootReadOnly: root?.getAttribute('data-readonly'),
|
|
2163
|
+
hasIndicator:
|
|
2164
|
+
root?.querySelector('.astryx-multi-selector-indicator-icon') !=
|
|
2165
|
+
null,
|
|
2166
|
+
listboxes: screen.queryAllByRole('listbox', h).length,
|
|
2167
|
+
dialogs: screen.queryAllByRole('dialog', h).length,
|
|
2168
|
+
formEntries: [
|
|
2169
|
+
...new FormData(container.querySelector('form')!).entries(),
|
|
2170
|
+
],
|
|
2171
|
+
};
|
|
2172
|
+
};
|
|
2173
|
+
const disabledOnly = snapshot();
|
|
2174
|
+
|
|
2175
|
+
rerender(selector(true));
|
|
2176
|
+
|
|
2177
|
+
expect(snapshot()).toEqual(disabledOnly);
|
|
2178
|
+
expect(disabledOnly.rootDisabled).toBe('disabled');
|
|
2179
|
+
expect(disabledOnly.rootReadOnly).toBeNull();
|
|
2180
|
+
expect(disabledOnly.readOnly).toBeNull();
|
|
2181
|
+
expect(disabledOnly.formEntries).toEqual([]);
|
|
2182
|
+
const trigger = getTrigger();
|
|
2183
|
+
await user.click(trigger);
|
|
2184
|
+
trigger.focus();
|
|
2185
|
+
await user.keyboard('{Enter}{ArrowDown}b{Backspace}');
|
|
2186
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
2187
|
+
},
|
|
2188
|
+
);
|
|
2189
|
+
});
|
|
2190
|
+
|
|
1888
2191
|
describe('form participation', () => {
|
|
1889
2192
|
it('submits one entry per selected value under htmlName', () => {
|
|
1890
2193
|
const {container} = render(
|