@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
|
@@ -1167,7 +1167,12 @@ describe('Selector', () => {
|
|
|
1167
1167
|
render(
|
|
1168
1168
|
<Selector label="Fruit" options={[]} onChange={() => {}} isLoading />,
|
|
1169
1169
|
);
|
|
1170
|
-
|
|
1170
|
+
const trigger = screen.getByRole('combobox', {name: 'Fruit'});
|
|
1171
|
+
expect(trigger).toHaveAttribute('aria-busy', 'true');
|
|
1172
|
+
expect(screen.getByRole('status', {name: 'Loading'})).toBeInTheDocument();
|
|
1173
|
+
|
|
1174
|
+
await user.click(trigger);
|
|
1175
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
1171
1176
|
|
|
1172
1177
|
// useAnnounce writes on the next animation frame, so an immediate read
|
|
1173
1178
|
// would pass whether or not anything was announced.
|
|
@@ -2434,6 +2439,318 @@ describe('Selector', () => {
|
|
|
2434
2439
|
expect(trigger).toHaveAttribute('tabIndex', '-1');
|
|
2435
2440
|
});
|
|
2436
2441
|
});
|
|
2442
|
+
describe('isReadOnly', () => {
|
|
2443
|
+
const readOnlyCases = [
|
|
2444
|
+
{presentation: 'popover', hasSearch: false},
|
|
2445
|
+
{presentation: 'popover', hasSearch: true},
|
|
2446
|
+
{presentation: 'bottom-sheet', hasSearch: false},
|
|
2447
|
+
{presentation: 'bottom-sheet', hasSearch: true},
|
|
2448
|
+
{presentation: 'adaptive', hasSearch: false},
|
|
2449
|
+
{presentation: 'adaptive', hasSearch: true},
|
|
2450
|
+
] as const;
|
|
2451
|
+
|
|
2452
|
+
const sheetTransitionCases = [
|
|
2453
|
+
{presentation: 'bottom-sheet', hasSearch: false},
|
|
2454
|
+
{presentation: 'bottom-sheet', hasSearch: true},
|
|
2455
|
+
{presentation: 'adaptive', hasSearch: false},
|
|
2456
|
+
{presentation: 'adaptive', hasSearch: true},
|
|
2457
|
+
] as const;
|
|
2458
|
+
|
|
2459
|
+
const disabledPrecedenceCases = (
|
|
2460
|
+
['popover', 'bottom-sheet', 'adaptive'] as const
|
|
2461
|
+
).flatMap(presentation =>
|
|
2462
|
+
[false, true].flatMap(hasSearch =>
|
|
2463
|
+
[false, true].map(hasDisabledMessage => ({
|
|
2464
|
+
presentation,
|
|
2465
|
+
hasSearch,
|
|
2466
|
+
hasDisabledMessage,
|
|
2467
|
+
})),
|
|
2468
|
+
),
|
|
2469
|
+
);
|
|
2470
|
+
|
|
2471
|
+
function stubCompactTouch(presentation: 'bottom-sheet' | 'adaptive') {
|
|
2472
|
+
if (presentation !== 'adaptive') {
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
vi.mocked(matchMedia).mockImplementation(
|
|
2476
|
+
(query: string) =>
|
|
2477
|
+
({
|
|
2478
|
+
matches: query === '(max-width: 768px) and (pointer: coarse)',
|
|
2479
|
+
media: query,
|
|
2480
|
+
onchange: null,
|
|
2481
|
+
addEventListener: vi.fn(),
|
|
2482
|
+
removeEventListener: vi.fn(),
|
|
2483
|
+
addListener: vi.fn(),
|
|
2484
|
+
removeListener: vi.fn(),
|
|
2485
|
+
dispatchEvent: vi.fn(),
|
|
2486
|
+
}) as MediaQueryList,
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
it.each(readOnlyCases)(
|
|
2491
|
+
'uses a read-only combobox with no rendered popup ($presentation, search=$hasSearch)',
|
|
2492
|
+
async ({presentation, hasSearch}) => {
|
|
2493
|
+
const user = userEvent.setup();
|
|
2494
|
+
const {container} = render(
|
|
2495
|
+
<form>
|
|
2496
|
+
<Selector
|
|
2497
|
+
label="Fruit"
|
|
2498
|
+
htmlName="fruit"
|
|
2499
|
+
options={OPTIONS}
|
|
2500
|
+
value="Banana"
|
|
2501
|
+
onChange={() => {}}
|
|
2502
|
+
hasClear
|
|
2503
|
+
hasSearch={hasSearch}
|
|
2504
|
+
presentation={presentation}
|
|
2505
|
+
isDefaultOpen
|
|
2506
|
+
isReadOnly
|
|
2507
|
+
/>
|
|
2508
|
+
</form>,
|
|
2509
|
+
);
|
|
2510
|
+
|
|
2511
|
+
const trigger = screen.getByRole('combobox', {name: 'Fruit'});
|
|
2512
|
+
expect(trigger.tagName).toBe('BUTTON');
|
|
2513
|
+
expect(trigger).toHaveTextContent('Banana');
|
|
2514
|
+
expect(trigger).toHaveAttribute('aria-readonly', 'true');
|
|
2515
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
2516
|
+
const readOnlyDescription = screen.getByText('Read only');
|
|
2517
|
+
expect(trigger.getAttribute('aria-describedby')).toContain(
|
|
2518
|
+
readOnlyDescription.id,
|
|
2519
|
+
);
|
|
2520
|
+
expect(trigger).not.toHaveAttribute('aria-controls');
|
|
2521
|
+
expect(trigger).not.toHaveAttribute('aria-haspopup');
|
|
2522
|
+
expect(screen.getAllByRole('combobox', h)).toHaveLength(1);
|
|
2523
|
+
expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
|
|
2524
|
+
expect(screen.queryByRole('dialog', h)).not.toBeInTheDocument();
|
|
2525
|
+
expect(
|
|
2526
|
+
screen.queryByRole('button', {name: 'Clear Fruit'}),
|
|
2527
|
+
).not.toBeInTheDocument();
|
|
2528
|
+
expect(
|
|
2529
|
+
container.querySelector('.astryx-selector-indicator-icon'),
|
|
2530
|
+
).toBeNull();
|
|
2531
|
+
expect(container.querySelector('.astryx-selector')).toHaveAttribute(
|
|
2532
|
+
'data-readonly',
|
|
2533
|
+
'readonly',
|
|
2534
|
+
);
|
|
2535
|
+
expect(
|
|
2536
|
+
new FormData(container.querySelector('form')!).get('fruit'),
|
|
2537
|
+
).toBe('Banana');
|
|
2538
|
+
|
|
2539
|
+
await user.tab();
|
|
2540
|
+
expect(trigger).toHaveFocus();
|
|
2541
|
+
},
|
|
2542
|
+
);
|
|
2543
|
+
|
|
2544
|
+
it('localizes the accessible read-only description', () => {
|
|
2545
|
+
render(
|
|
2546
|
+
<InternationalizationProvider
|
|
2547
|
+
locale="fr"
|
|
2548
|
+
overrides={{fr: {'@astryx.input.readOnly': 'Lecture seule'}}}>
|
|
2549
|
+
<Selector label="Fruit" options={OPTIONS} value="Banana" isReadOnly />
|
|
2550
|
+
</InternationalizationProvider>,
|
|
2551
|
+
);
|
|
2552
|
+
|
|
2553
|
+
const trigger = screen.getByRole('combobox', {name: 'Fruit'});
|
|
2554
|
+
const description = screen.getByText('Lecture seule');
|
|
2555
|
+
expect(trigger.getAttribute('aria-describedby')).toContain(
|
|
2556
|
+
description.id,
|
|
2557
|
+
);
|
|
2558
|
+
});
|
|
2559
|
+
|
|
2560
|
+
it('keeps loading feedback while blocking the selection surface', async () => {
|
|
2561
|
+
const user = userEvent.setup();
|
|
2562
|
+
render(
|
|
2563
|
+
<Selector
|
|
2564
|
+
label="Fruit"
|
|
2565
|
+
options={OPTIONS}
|
|
2566
|
+
value="Apple"
|
|
2567
|
+
isLoading
|
|
2568
|
+
isReadOnly
|
|
2569
|
+
/>,
|
|
2570
|
+
);
|
|
2571
|
+
|
|
2572
|
+
const trigger = screen.getByRole('combobox', {name: 'Fruit'});
|
|
2573
|
+
expect(trigger).toHaveAttribute('aria-busy', 'true');
|
|
2574
|
+
expect(screen.getByRole('status', {name: 'Loading'})).toBeInTheDocument();
|
|
2575
|
+
|
|
2576
|
+
await user.click(trigger);
|
|
2577
|
+
expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
|
|
2578
|
+
});
|
|
2579
|
+
|
|
2580
|
+
it('blocks pointer, keyboard, and typeahead changes', async () => {
|
|
2581
|
+
const user = userEvent.setup();
|
|
2582
|
+
const onChange = vi.fn();
|
|
2583
|
+
const changeAction = vi.fn();
|
|
2584
|
+
render(
|
|
2585
|
+
<Selector
|
|
2586
|
+
label="Fruit"
|
|
2587
|
+
options={OPTIONS}
|
|
2588
|
+
value="Apple"
|
|
2589
|
+
onChange={onChange}
|
|
2590
|
+
changeAction={changeAction}
|
|
2591
|
+
isReadOnly
|
|
2592
|
+
/>,
|
|
2593
|
+
);
|
|
2594
|
+
|
|
2595
|
+
const trigger = screen.getByRole('combobox');
|
|
2596
|
+
await user.click(trigger);
|
|
2597
|
+
trigger.focus();
|
|
2598
|
+
await user.keyboard('{Enter}{ArrowDown}c{Backspace}');
|
|
2599
|
+
|
|
2600
|
+
expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
|
|
2601
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
2602
|
+
expect(changeAction).not.toHaveBeenCalled();
|
|
2603
|
+
});
|
|
2604
|
+
|
|
2605
|
+
it.each([false, true])(
|
|
2606
|
+
'restores popover focus when an open selector becomes read-only (search=%s)',
|
|
2607
|
+
async hasSearch => {
|
|
2608
|
+
const user = userEvent.setup();
|
|
2609
|
+
const selector = (isReadOnly: boolean) => (
|
|
2610
|
+
<Selector
|
|
2611
|
+
label="Fruit"
|
|
2612
|
+
options={OPTIONS}
|
|
2613
|
+
value="Apple"
|
|
2614
|
+
hasSearch={hasSearch}
|
|
2615
|
+
isReadOnly={isReadOnly}
|
|
2616
|
+
/>
|
|
2617
|
+
);
|
|
2618
|
+
const {rerender} = render(selector(false));
|
|
2619
|
+
const editableTrigger = screen.getByRole(
|
|
2620
|
+
hasSearch ? 'button' : 'combobox',
|
|
2621
|
+
{name: 'Fruit'},
|
|
2622
|
+
);
|
|
2623
|
+
await user.click(editableTrigger);
|
|
2624
|
+
if (hasSearch) {
|
|
2625
|
+
await waitFor(() =>
|
|
2626
|
+
expect(screen.getByRole('combobox', h)).toHaveFocus(),
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
rerender(selector(true));
|
|
2631
|
+
|
|
2632
|
+
const readOnlyTrigger = screen.getByRole('combobox', {name: 'Fruit'});
|
|
2633
|
+
await waitFor(() => expect(readOnlyTrigger).toHaveFocus());
|
|
2634
|
+
expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
|
|
2635
|
+
},
|
|
2636
|
+
);
|
|
2637
|
+
|
|
2638
|
+
it.each(sheetTransitionCases)(
|
|
2639
|
+
'finishes $presentation close and restores focus when read-only changes (search=$hasSearch)',
|
|
2640
|
+
async ({presentation, hasSearch}) => {
|
|
2641
|
+
stubCompactTouch(presentation);
|
|
2642
|
+
const user = userEvent.setup();
|
|
2643
|
+
const selector = (isReadOnly: boolean) => (
|
|
2644
|
+
<Selector
|
|
2645
|
+
label="Fruit"
|
|
2646
|
+
options={OPTIONS}
|
|
2647
|
+
value="Apple"
|
|
2648
|
+
hasSearch={hasSearch}
|
|
2649
|
+
presentation={presentation}
|
|
2650
|
+
isReadOnly={isReadOnly}
|
|
2651
|
+
/>
|
|
2652
|
+
);
|
|
2653
|
+
const {rerender} = render(selector(false));
|
|
2654
|
+
const editableTrigger = screen.getByRole(
|
|
2655
|
+
hasSearch ? 'button' : 'combobox',
|
|
2656
|
+
{name: 'Fruit'},
|
|
2657
|
+
);
|
|
2658
|
+
await user.click(editableTrigger);
|
|
2659
|
+
const dialog = await screen.findByRole('dialog', {name: 'Fruit'});
|
|
2660
|
+
const panel = dialog.querySelector<HTMLElement>('.astryx-bottom-sheet');
|
|
2661
|
+
expect(panel).not.toBeNull();
|
|
2662
|
+
await waitFor(() =>
|
|
2663
|
+
expect(
|
|
2664
|
+
hasSearch
|
|
2665
|
+
? screen.getByRole('combobox', h)
|
|
2666
|
+
: screen.getByRole('listbox'),
|
|
2667
|
+
).toHaveFocus(),
|
|
2668
|
+
);
|
|
2669
|
+
|
|
2670
|
+
rerender(selector(true));
|
|
2671
|
+
|
|
2672
|
+
await waitFor(() => expect(dialog).toHaveAttribute('inert'));
|
|
2673
|
+
fireEvent.transitionEnd(panel!, {propertyName: 'transform'});
|
|
2674
|
+
const readOnlyTrigger = screen.getByRole('combobox', {name: 'Fruit'});
|
|
2675
|
+
await waitFor(() => expect(readOnlyTrigger).toHaveFocus());
|
|
2676
|
+
expect(screen.queryByRole('dialog', h)).not.toBeInTheDocument();
|
|
2677
|
+
expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
|
|
2678
|
+
},
|
|
2679
|
+
);
|
|
2680
|
+
|
|
2681
|
+
it.each(disabledPrecedenceCases)(
|
|
2682
|
+
'matches disabled-only semantics for $presentation (search=$hasSearch, reason=$hasDisabledMessage)',
|
|
2683
|
+
async ({presentation, hasSearch, hasDisabledMessage}) => {
|
|
2684
|
+
if (presentation === 'adaptive') {
|
|
2685
|
+
stubCompactTouch(presentation);
|
|
2686
|
+
}
|
|
2687
|
+
const user = userEvent.setup();
|
|
2688
|
+
const onChange = vi.fn();
|
|
2689
|
+
const selector = (isReadOnly: boolean) => (
|
|
2690
|
+
<form>
|
|
2691
|
+
<Selector
|
|
2692
|
+
label="Fruit"
|
|
2693
|
+
htmlName="fruit"
|
|
2694
|
+
options={OPTIONS}
|
|
2695
|
+
value="Banana"
|
|
2696
|
+
onChange={onChange}
|
|
2697
|
+
hasClear
|
|
2698
|
+
hasSearch={hasSearch}
|
|
2699
|
+
presentation={presentation}
|
|
2700
|
+
isDisabled
|
|
2701
|
+
isReadOnly={isReadOnly}
|
|
2702
|
+
disabledMessage={
|
|
2703
|
+
hasDisabledMessage ? 'Locked by policy' : undefined
|
|
2704
|
+
}
|
|
2705
|
+
/>
|
|
2706
|
+
</form>
|
|
2707
|
+
);
|
|
2708
|
+
const {container, rerender} = render(selector(false));
|
|
2709
|
+
const getTrigger = () =>
|
|
2710
|
+
screen.getByRole(hasSearch ? 'button' : 'combobox', {name: 'Fruit'});
|
|
2711
|
+
const snapshot = () => {
|
|
2712
|
+
const trigger = getTrigger();
|
|
2713
|
+
const root = container.querySelector('.astryx-selector');
|
|
2714
|
+
return {
|
|
2715
|
+
tagName: trigger.tagName,
|
|
2716
|
+
role: trigger.getAttribute('role'),
|
|
2717
|
+
hasPopup: trigger.getAttribute('aria-haspopup'),
|
|
2718
|
+
expanded: trigger.getAttribute('aria-expanded'),
|
|
2719
|
+
controls: trigger.getAttribute('aria-controls'),
|
|
2720
|
+
readOnly: trigger.getAttribute('aria-readonly'),
|
|
2721
|
+
disabled: trigger.matches(':disabled'),
|
|
2722
|
+
ariaDisabled: trigger.getAttribute('aria-disabled'),
|
|
2723
|
+
tabIndex: trigger.tabIndex,
|
|
2724
|
+
rootClassName: root?.className,
|
|
2725
|
+
rootDisabled: root?.getAttribute('data-disabled'),
|
|
2726
|
+
rootReadOnly: root?.getAttribute('data-readonly'),
|
|
2727
|
+
hasIndicator:
|
|
2728
|
+
root?.querySelector('.astryx-selector-indicator-icon') != null,
|
|
2729
|
+
listboxes: screen.queryAllByRole('listbox', h).length,
|
|
2730
|
+
dialogs: screen.queryAllByRole('dialog', h).length,
|
|
2731
|
+
formEntries: [
|
|
2732
|
+
...new FormData(container.querySelector('form')!).entries(),
|
|
2733
|
+
],
|
|
2734
|
+
};
|
|
2735
|
+
};
|
|
2736
|
+
const disabledOnly = snapshot();
|
|
2737
|
+
|
|
2738
|
+
rerender(selector(true));
|
|
2739
|
+
|
|
2740
|
+
expect(snapshot()).toEqual(disabledOnly);
|
|
2741
|
+
expect(disabledOnly.rootDisabled).toBe('disabled');
|
|
2742
|
+
expect(disabledOnly.rootReadOnly).toBeNull();
|
|
2743
|
+
expect(disabledOnly.readOnly).toBeNull();
|
|
2744
|
+
expect(disabledOnly.formEntries).toEqual([]);
|
|
2745
|
+
const trigger = getTrigger();
|
|
2746
|
+
await user.click(trigger);
|
|
2747
|
+
trigger.focus();
|
|
2748
|
+
await user.keyboard('{Enter}{ArrowDown}c{Backspace}');
|
|
2749
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
2750
|
+
},
|
|
2751
|
+
);
|
|
2752
|
+
});
|
|
2753
|
+
|
|
2437
2754
|
describe('form participation', () => {
|
|
2438
2755
|
it('submits the selected value under htmlName', () => {
|
|
2439
2756
|
const {container} = render(
|
|
@@ -244,6 +244,16 @@ const styles = stylex.create({
|
|
|
244
244
|
':active': 'none',
|
|
245
245
|
},
|
|
246
246
|
},
|
|
247
|
+
triggerReadOnly: {
|
|
248
|
+
cursor: 'default',
|
|
249
|
+
},
|
|
250
|
+
triggerGhostReadOnly: {
|
|
251
|
+
backgroundImage: 'none',
|
|
252
|
+
transform: {
|
|
253
|
+
default: 'none',
|
|
254
|
+
':active': 'none',
|
|
255
|
+
},
|
|
256
|
+
},
|
|
247
257
|
|
|
248
258
|
// Clear button
|
|
249
259
|
statusButton: {
|
|
@@ -506,6 +516,17 @@ interface SelectorPropsBase<
|
|
|
506
516
|
*/
|
|
507
517
|
isDisabled?: boolean;
|
|
508
518
|
|
|
519
|
+
/**
|
|
520
|
+
* Whether the selector is read-only.
|
|
521
|
+
* The selected value stays visible, focusable, and included in form
|
|
522
|
+
* submission, and retains its combobox identity with `aria-readonly`. The
|
|
523
|
+
* selection surface and editing affordances are removed.
|
|
524
|
+
* Unlike `isDisabled`, a read-only selector is not dimmed and stays in the
|
|
525
|
+
* tab order. `isDisabled` takes precedence when both are set.
|
|
526
|
+
* @default false
|
|
527
|
+
*/
|
|
528
|
+
isReadOnly?: boolean;
|
|
529
|
+
|
|
509
530
|
/**
|
|
510
531
|
* Explains why the selector is disabled. When set together with
|
|
511
532
|
* `isDisabled`, the selector shows a tooltip with this text on hover and
|
|
@@ -804,6 +825,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
804
825
|
isOptional = false,
|
|
805
826
|
isRequired = false,
|
|
806
827
|
isDisabled = false,
|
|
828
|
+
isReadOnly = false,
|
|
807
829
|
disabledMessage,
|
|
808
830
|
options,
|
|
809
831
|
value,
|
|
@@ -851,6 +873,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
851
873
|
variant === 'ghost' && statusVariant === 'attached'
|
|
852
874
|
? 'detached'
|
|
853
875
|
: statusVariant;
|
|
876
|
+
const isEffectivelyReadOnly = isReadOnly && !isDisabled;
|
|
854
877
|
|
|
855
878
|
// Normalize null to undefined for internal use (null is the clear sentinel)
|
|
856
879
|
const normalizedValue = value === null ? undefined : value;
|
|
@@ -862,11 +885,12 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
862
885
|
const descriptionId = useId();
|
|
863
886
|
const statusMessageId = useId();
|
|
864
887
|
const inputLabelId = useId();
|
|
888
|
+
const readOnlyDescriptionId = useId();
|
|
865
889
|
const searchId = useId();
|
|
866
890
|
// Measure from the same outer control that usePopover anchors to; using the
|
|
867
891
|
// shorter inner button makes every size's selected row land too low.
|
|
868
892
|
const anchorRef = useRef<HTMLDivElement>(null);
|
|
869
|
-
const triggerRef = useRef<
|
|
893
|
+
const triggerRef = useRef<HTMLElement>(null);
|
|
870
894
|
const searchRef = useRef<HTMLInputElement>(null);
|
|
871
895
|
const inputGroup = useInputGroup();
|
|
872
896
|
|
|
@@ -923,6 +947,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
923
947
|
? statusTooltip.describedBy
|
|
924
948
|
: null,
|
|
925
949
|
showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
|
|
950
|
+
isEffectivelyReadOnly ? readOnlyDescriptionId : null,
|
|
926
951
|
],
|
|
927
952
|
inputGroup,
|
|
928
953
|
);
|
|
@@ -985,16 +1010,27 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
985
1010
|
},
|
|
986
1011
|
});
|
|
987
1012
|
const {popover} = surface;
|
|
1013
|
+
const hideSurface = surface.hide;
|
|
1014
|
+
const isSurfaceOpen = surface.isOpen;
|
|
988
1015
|
const keepOpenProps = useKeepLayerOpenProps(popover.id, popover.isOpen);
|
|
989
1016
|
|
|
990
|
-
// Open dropdown on mount when isDefaultOpen is true
|
|
1017
|
+
// Open dropdown on mount when isDefaultOpen is true and interaction is allowed.
|
|
991
1018
|
useEffect(() => {
|
|
992
|
-
if (isDefaultOpen) {
|
|
1019
|
+
if (isDefaultOpen && !isEffectivelyReadOnly) {
|
|
993
1020
|
surface.show();
|
|
994
1021
|
}
|
|
995
1022
|
// eslint-disable-next-line @eslint-react/exhaustive-deps -- mount-only: isDefaultOpen is not reactive
|
|
996
1023
|
}, []);
|
|
997
1024
|
|
|
1025
|
+
// Read-only is controlled by caller policy, so an already-open surface must
|
|
1026
|
+
// close when that policy changes. The presentation controller keeps a sheet
|
|
1027
|
+
// mounted through its exit and lets BottomSheet return final focus.
|
|
1028
|
+
useEffect(() => {
|
|
1029
|
+
if (isEffectivelyReadOnly && isSurfaceOpen) {
|
|
1030
|
+
hideSurface();
|
|
1031
|
+
}
|
|
1032
|
+
}, [isEffectivelyReadOnly, isSurfaceOpen, hideSurface]);
|
|
1033
|
+
|
|
998
1034
|
// Announce the filtered result count from the query-change handlers (matching
|
|
999
1035
|
// BaseTypeahead) rather than a reactive effect: computing the count for the
|
|
1000
1036
|
// next query here fires the announcement exactly once per keystroke and does
|
|
@@ -1145,7 +1181,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1145
1181
|
// highlight on it and Delete/Backspace could clear a value the action has
|
|
1146
1182
|
// already replaced.
|
|
1147
1183
|
value: optimisticValue,
|
|
1148
|
-
isDisabled,
|
|
1184
|
+
isDisabled: isDisabled || isEffectivelyReadOnly,
|
|
1149
1185
|
isOpen: surface.isOpen,
|
|
1150
1186
|
hasSearch,
|
|
1151
1187
|
onOpen: useCallback(() => {
|
|
@@ -1197,14 +1233,17 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1197
1233
|
|
|
1198
1234
|
const handleTriggerKeyDown = useCallback(
|
|
1199
1235
|
(e: React.KeyboardEvent) => {
|
|
1236
|
+
if (isDisabled || isEffectivelyReadOnly) {
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1200
1239
|
// With hasSearch the query input owns typing, so type-to-select is off.
|
|
1201
|
-
if (!
|
|
1240
|
+
if (!hasSearch && typeahead.onKeyDown(e)) {
|
|
1202
1241
|
e.preventDefault();
|
|
1203
1242
|
return;
|
|
1204
1243
|
}
|
|
1205
1244
|
onKeyDown(e);
|
|
1206
1245
|
},
|
|
1207
|
-
[isDisabled, hasSearch, typeahead, onKeyDown],
|
|
1246
|
+
[isDisabled, isEffectivelyReadOnly, hasSearch, typeahead, onKeyDown],
|
|
1208
1247
|
);
|
|
1209
1248
|
|
|
1210
1249
|
// Keep the highlighted option visible during keyboard navigation. The
|
|
@@ -1628,30 +1667,55 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1628
1667
|
</div>
|
|
1629
1668
|
);
|
|
1630
1669
|
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1670
|
+
let selectionSurface: ReactNode = null;
|
|
1671
|
+
if (surface.activePresentation === 'bottom-sheet') {
|
|
1672
|
+
if (!isEffectivelyReadOnly || surface.isSheetPresented) {
|
|
1673
|
+
selectionSurface = (
|
|
1674
|
+
<SelectorBottomSheet
|
|
1675
|
+
isOpen={surface.isSheetOpen}
|
|
1676
|
+
onOpenChange={surface.onSheetOpenChange}
|
|
1677
|
+
finalFocusRef={triggerRef}
|
|
1678
|
+
initialFocusRef={hasSearch ? searchRef : listboxRef}
|
|
1679
|
+
label={label}>
|
|
1680
|
+
{panelContent}
|
|
1681
|
+
</SelectorBottomSheet>
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
} else if (!isEffectivelyReadOnly) {
|
|
1685
|
+
selectionSurface = popover.render(panelContent, {
|
|
1686
|
+
placement: popoverPlacement,
|
|
1687
|
+
alignment: 'start',
|
|
1688
|
+
// The system's standard menu clearance, except in overlay mode:
|
|
1689
|
+
// there the measured negative margin owns the block geometry and
|
|
1690
|
+
// the menu is meant to sit on the trigger, not clear it.
|
|
1691
|
+
offset: shouldOverlaySelectedItem
|
|
1692
|
+
? undefined
|
|
1693
|
+
: spacingVars['--spacing-1'],
|
|
1694
|
+
xstyle: [styles.popover, layerAnimations[popoverPlacement]],
|
|
1695
|
+
style: popoverOffsetStyle,
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
const triggerSharedProps: React.HTMLAttributes<HTMLElement> = {
|
|
1700
|
+
...rest,
|
|
1701
|
+
id: triggerId,
|
|
1702
|
+
'aria-describedby': ariaDescribedBy,
|
|
1703
|
+
'aria-labelledby': ariaLabelledBy,
|
|
1704
|
+
'aria-required': isEffectivelyRequired ? 'true' : undefined,
|
|
1705
|
+
'aria-invalid': status?.type === 'error' ? 'true' : undefined,
|
|
1706
|
+
'aria-busy': isBusy || undefined,
|
|
1707
|
+
onKeyDown: handleTriggerKeyDown,
|
|
1708
|
+
onFocus: event => {
|
|
1709
|
+
onFocus?.(event);
|
|
1710
|
+
surface.onTriggerFocus(event);
|
|
1711
|
+
},
|
|
1712
|
+
tabIndex: isDisabled && !showsDisabledMessage ? -1 : 0,
|
|
1713
|
+
...stylex.props(
|
|
1714
|
+
styles.trigger,
|
|
1715
|
+
isEffectivelyReadOnly && styles.triggerReadOnly,
|
|
1716
|
+
inputGroup && styles.triggerButtonInGroup,
|
|
1717
|
+
),
|
|
1718
|
+
};
|
|
1655
1719
|
|
|
1656
1720
|
const selectorContent = (
|
|
1657
1721
|
<>
|
|
@@ -1672,6 +1736,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1672
1736
|
size,
|
|
1673
1737
|
status: status?.type ?? null,
|
|
1674
1738
|
disabled: isDisabled ? 'disabled' : null,
|
|
1739
|
+
readonly: isEffectivelyReadOnly ? 'readonly' : null,
|
|
1675
1740
|
}),
|
|
1676
1741
|
stylex.props(
|
|
1677
1742
|
inputWrapperStyles.base,
|
|
@@ -1683,7 +1748,11 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1683
1748
|
surface.isTriggerFocusRingSuppressed &&
|
|
1684
1749
|
selectorPresentationStyles.pointerRestoredFocus,
|
|
1685
1750
|
isDisabled && inputWrapperStyles.disabled,
|
|
1751
|
+
isEffectivelyReadOnly && styles.triggerReadOnly,
|
|
1686
1752
|
variant === 'ghost' && isDisabled && styles.triggerGhostDisabled,
|
|
1753
|
+
variant === 'ghost' &&
|
|
1754
|
+
isEffectivelyReadOnly &&
|
|
1755
|
+
styles.triggerGhostReadOnly,
|
|
1687
1756
|
!selectedItem && styles.triggerPlaceholder,
|
|
1688
1757
|
variant !== 'ghost' &&
|
|
1689
1758
|
status &&
|
|
@@ -1704,45 +1773,43 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1704
1773
|
{inputGroup && (
|
|
1705
1774
|
<VisuallyHidden id={inputLabelId}>{label}</VisuallyHidden>
|
|
1706
1775
|
)}
|
|
1776
|
+
{isEffectivelyReadOnly && (
|
|
1777
|
+
<VisuallyHidden id={readOnlyDescriptionId}>
|
|
1778
|
+
{t('@astryx.input.readOnly')}
|
|
1779
|
+
</VisuallyHidden>
|
|
1780
|
+
)}
|
|
1707
1781
|
<button
|
|
1708
|
-
|
|
1709
|
-
|
|
1782
|
+
{...triggerSharedProps}
|
|
1783
|
+
ref={triggerRef as React.Ref<HTMLButtonElement>}
|
|
1710
1784
|
type="button"
|
|
1711
|
-
//
|
|
1712
|
-
//
|
|
1713
|
-
//
|
|
1714
|
-
|
|
1715
|
-
{
|
|
1785
|
+
// The read-only trigger stays a combobox even when hasSearch is set:
|
|
1786
|
+
// no search input is rendered in that state, and preserving the role
|
|
1787
|
+
// keeps the control's programmatic identity stable. Editable search
|
|
1788
|
+
// mode still moves combobox semantics to the popup input.
|
|
1789
|
+
role={isEffectivelyReadOnly || !hasSearch ? 'combobox' : undefined}
|
|
1716
1790
|
aria-haspopup={
|
|
1717
|
-
|
|
1791
|
+
isEffectivelyReadOnly
|
|
1792
|
+
? undefined
|
|
1793
|
+
: surface.activePresentation === 'bottom-sheet'
|
|
1794
|
+
? 'dialog'
|
|
1795
|
+
: 'listbox'
|
|
1718
1796
|
}
|
|
1719
|
-
aria-expanded={surface.isOpen}
|
|
1720
|
-
aria-controls={listboxId}
|
|
1797
|
+
aria-expanded={isEffectivelyReadOnly ? false : surface.isOpen}
|
|
1798
|
+
aria-controls={isEffectivelyReadOnly ? undefined : listboxId}
|
|
1799
|
+
aria-readonly={isEffectivelyReadOnly || undefined}
|
|
1721
1800
|
aria-activedescendant={
|
|
1722
|
-
!
|
|
1801
|
+
!isEffectivelyReadOnly &&
|
|
1802
|
+
!hasSearch &&
|
|
1803
|
+
surface.isOpen &&
|
|
1804
|
+
highlightedIndex >= 0
|
|
1723
1805
|
? getItemId(highlightedIndex)
|
|
1724
1806
|
: undefined
|
|
1725
1807
|
}
|
|
1726
|
-
aria-describedby={ariaDescribedBy}
|
|
1727
|
-
aria-labelledby={ariaLabelledBy}
|
|
1728
|
-
aria-required={isEffectivelyRequired ? 'true' : undefined}
|
|
1729
|
-
aria-invalid={status?.type === 'error' ? 'true' : undefined}
|
|
1730
|
-
aria-busy={isBusy || undefined}
|
|
1731
1808
|
// With a disabledMessage the trigger keeps focusability via
|
|
1732
1809
|
// aria-disabled so the reason is focus-discoverable; activation is
|
|
1733
1810
|
// still blocked by the isDisabled guards in useCombobox.
|
|
1734
1811
|
disabled={isDisabled && !showsDisabledMessage}
|
|
1735
|
-
aria-disabled={showsDisabledMessage ? 'true' : undefined}
|
|
1736
|
-
onKeyDown={handleTriggerKeyDown}
|
|
1737
|
-
onFocus={event => {
|
|
1738
|
-
onFocus?.(event);
|
|
1739
|
-
surface.onTriggerFocus(event);
|
|
1740
|
-
}}
|
|
1741
|
-
tabIndex={isDisabled && !showsDisabledMessage ? -1 : 0}
|
|
1742
|
-
{...stylex.props(
|
|
1743
|
-
styles.trigger,
|
|
1744
|
-
inputGroup && styles.triggerButtonInGroup,
|
|
1745
|
-
)}>
|
|
1812
|
+
aria-disabled={showsDisabledMessage ? 'true' : undefined}>
|
|
1746
1813
|
{valueContent}
|
|
1747
1814
|
</button>
|
|
1748
1815
|
{htmlName != null && (
|
|
@@ -1756,7 +1823,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1756
1823
|
/>
|
|
1757
1824
|
)}
|
|
1758
1825
|
{isBusy && <Spinner size="sm" />}
|
|
1759
|
-
{hasClear && value != null && !isDisabled && (
|
|
1826
|
+
{hasClear && value != null && !isDisabled && !isEffectivelyReadOnly && (
|
|
1760
1827
|
<InternalInputClearButton
|
|
1761
1828
|
{...keepOpenProps}
|
|
1762
1829
|
label={t('@astryx.selector.clearLabel', {label})}
|
|
@@ -1798,7 +1865,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1798
1865
|
xstyle={styles.triggerIcon}
|
|
1799
1866
|
/>
|
|
1800
1867
|
)
|
|
1801
|
-
) : (
|
|
1868
|
+
) : !isEffectivelyReadOnly ? (
|
|
1802
1869
|
<Icon
|
|
1803
1870
|
icon="chevronDown"
|
|
1804
1871
|
size="sm"
|
|
@@ -1820,7 +1887,7 @@ export function Selector<T extends SelectorOptionType>(
|
|
|
1820
1887
|
state: surface.isOpen ? 'expanded' : 'collapsed',
|
|
1821
1888
|
})}
|
|
1822
1889
|
/>
|
|
1823
|
-
)}
|
|
1890
|
+
) : null}
|
|
1824
1891
|
</div>
|
|
1825
1892
|
|
|
1826
1893
|
{selectionSurface}
|