@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
|
@@ -20,8 +20,15 @@ import {
|
|
|
20
20
|
} from 'vitest';
|
|
21
21
|
import {render, screen, fireEvent, waitFor, act} from '@testing-library/react';
|
|
22
22
|
import userEvent from '@testing-library/user-event';
|
|
23
|
+
import {Profiler, type ProfilerOnRenderCallback} from 'react';
|
|
23
24
|
import {Typeahead} from './Typeahead';
|
|
25
|
+
import {readFile} from 'node:fs/promises';
|
|
26
|
+
import {resolve} from 'node:path';
|
|
24
27
|
import {BaseTypeahead} from './BaseTypeahead';
|
|
28
|
+
import {
|
|
29
|
+
BusyIndicatorLaneProvider,
|
|
30
|
+
createBusyIndicatorLane,
|
|
31
|
+
} from './busyIndicatorLane';
|
|
25
32
|
import type {SearchSource, SearchableItem} from './types';
|
|
26
33
|
import {InternationalizationProvider} from '../i18n';
|
|
27
34
|
|
|
@@ -707,6 +714,126 @@ describe('Typeahead size', () => {
|
|
|
707
714
|
});
|
|
708
715
|
|
|
709
716
|
describe('BaseTypeahead hasEntriesOnFocus', () => {
|
|
717
|
+
it('does not commit a loading cycle for an empty synchronous bootstrap', async () => {
|
|
718
|
+
const bootstrap = vi.fn((): SearchableItem[] => []);
|
|
719
|
+
const onRender = vi.fn<ProfilerOnRenderCallback>();
|
|
720
|
+
render(
|
|
721
|
+
<Profiler id="typeahead" onRender={onRender}>
|
|
722
|
+
<BaseTypeahead
|
|
723
|
+
searchSource={{search: () => [], bootstrap}}
|
|
724
|
+
value={null}
|
|
725
|
+
onChange={() => {}}
|
|
726
|
+
hasEntriesOnFocus
|
|
727
|
+
/>
|
|
728
|
+
</Profiler>,
|
|
729
|
+
);
|
|
730
|
+
const input = screen.getByRole('combobox');
|
|
731
|
+
onRender.mockClear();
|
|
732
|
+
|
|
733
|
+
await act(async () => {
|
|
734
|
+
fireEvent.focus(input);
|
|
735
|
+
await Promise.resolve();
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
expect(bootstrap).toHaveBeenCalledOnce();
|
|
739
|
+
expect(onRender).not.toHaveBeenCalled();
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it('does not report loading for synchronous bootstrap results', async () => {
|
|
743
|
+
const lane = createBusyIndicatorLane();
|
|
744
|
+
const onLoadingChange = vi.fn(lane.onBusyChange);
|
|
745
|
+
render(
|
|
746
|
+
<BusyIndicatorLaneProvider
|
|
747
|
+
value={{...lane, onBusyChange: onLoadingChange}}>
|
|
748
|
+
<BaseTypeahead
|
|
749
|
+
searchSource={fruitSource}
|
|
750
|
+
value={null}
|
|
751
|
+
onChange={() => {}}
|
|
752
|
+
hasEntriesOnFocus
|
|
753
|
+
/>
|
|
754
|
+
</BusyIndicatorLaneProvider>,
|
|
755
|
+
);
|
|
756
|
+
const input = screen.getByRole('combobox');
|
|
757
|
+
|
|
758
|
+
fireEvent.focus(input);
|
|
759
|
+
await waitFor(() => {
|
|
760
|
+
expect(input).toHaveAttribute('aria-expanded', 'true');
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
expect(onLoadingChange).not.toHaveBeenCalled();
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
it('clears a pending search loading state before synchronous bootstrap', async () => {
|
|
767
|
+
let settleSearch: (items: SearchableItem[]) => void = () => {};
|
|
768
|
+
const searchSource: SearchSource = {
|
|
769
|
+
search: async () =>
|
|
770
|
+
new Promise<SearchableItem[]>(resolve => {
|
|
771
|
+
settleSearch = resolve;
|
|
772
|
+
}),
|
|
773
|
+
bootstrap: () => [],
|
|
774
|
+
};
|
|
775
|
+
render(
|
|
776
|
+
<BaseTypeahead
|
|
777
|
+
searchSource={searchSource}
|
|
778
|
+
value={null}
|
|
779
|
+
onChange={() => {}}
|
|
780
|
+
hasEntriesOnFocus
|
|
781
|
+
debounceMs={0}
|
|
782
|
+
/>,
|
|
783
|
+
);
|
|
784
|
+
const input = screen.getByRole('combobox');
|
|
785
|
+
|
|
786
|
+
fireEvent.change(input, {target: {value: 'a'}});
|
|
787
|
+
await waitFor(() => {
|
|
788
|
+
expect(screen.getByRole('status', {name: 'Loading'})).toBeInTheDocument();
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
fireEvent.change(input, {target: {value: ''}});
|
|
792
|
+
await waitFor(() => {
|
|
793
|
+
expect(
|
|
794
|
+
screen.queryByRole('status', {name: 'Loading'}),
|
|
795
|
+
).not.toBeInTheDocument();
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
await act(async () => {
|
|
799
|
+
settleSearch(fruits.slice(0, 1));
|
|
800
|
+
await Promise.resolve();
|
|
801
|
+
});
|
|
802
|
+
expect(input).toHaveAttribute('aria-expanded', 'false');
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
it('keeps the loading state for an asynchronous bootstrap', async () => {
|
|
806
|
+
let settle: (items: SearchableItem[]) => void = () => {};
|
|
807
|
+
const bootstrap = vi.fn(
|
|
808
|
+
async () =>
|
|
809
|
+
new Promise<SearchableItem[]>(resolve => {
|
|
810
|
+
settle = resolve;
|
|
811
|
+
}),
|
|
812
|
+
);
|
|
813
|
+
render(
|
|
814
|
+
<BaseTypeahead
|
|
815
|
+
searchSource={{search: () => [], bootstrap}}
|
|
816
|
+
value={null}
|
|
817
|
+
onChange={() => {}}
|
|
818
|
+
hasEntriesOnFocus
|
|
819
|
+
/>,
|
|
820
|
+
);
|
|
821
|
+
const input = screen.getByRole('combobox');
|
|
822
|
+
|
|
823
|
+
fireEvent.focus(input);
|
|
824
|
+
await waitFor(() => {
|
|
825
|
+
expect(screen.getByRole('status', {name: 'Loading'})).toBeInTheDocument();
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
await act(async () => {
|
|
829
|
+
settle([]);
|
|
830
|
+
await Promise.resolve();
|
|
831
|
+
});
|
|
832
|
+
expect(
|
|
833
|
+
screen.queryByRole('status', {name: 'Loading'}),
|
|
834
|
+
).not.toBeInTheDocument();
|
|
835
|
+
});
|
|
836
|
+
|
|
710
837
|
it('shows bootstrap results on mouse click', async () => {
|
|
711
838
|
render(
|
|
712
839
|
<BaseTypeahead
|
|
@@ -918,6 +1045,7 @@ describe('BaseTypeahead minQueryLength', () => {
|
|
|
918
1045
|
await waitFor(() => {
|
|
919
1046
|
expect(screen.getByRole('status', {name: 'Loading'})).toBeInTheDocument();
|
|
920
1047
|
});
|
|
1048
|
+
expect(input).toHaveAttribute('aria-busy', 'true');
|
|
921
1049
|
|
|
922
1050
|
fireEvent.change(input, {target: {value: 'Ap'}});
|
|
923
1051
|
await act(async () => {
|
|
@@ -928,6 +1056,7 @@ describe('BaseTypeahead minQueryLength', () => {
|
|
|
928
1056
|
expect(
|
|
929
1057
|
screen.queryByRole('status', {name: 'Loading'}),
|
|
930
1058
|
).not.toBeInTheDocument();
|
|
1059
|
+
expect(input).not.toHaveAttribute('aria-busy');
|
|
931
1060
|
expect(input).toHaveAttribute('aria-expanded', 'false');
|
|
932
1061
|
});
|
|
933
1062
|
});
|
|
@@ -1278,7 +1407,13 @@ describe('Typeahead disabledMessage', () => {
|
|
|
1278
1407
|
/>,
|
|
1279
1408
|
);
|
|
1280
1409
|
|
|
1281
|
-
|
|
1410
|
+
// The field itself, by its own class: the input no longer sits directly
|
|
1411
|
+
// inside it — it is in the content lane that bounds the value — and
|
|
1412
|
+
// `mouseEnter` does not bubble, so the hover has to land on the element
|
|
1413
|
+
// the tooltip is actually bound to.
|
|
1414
|
+
const container = document.querySelector(
|
|
1415
|
+
'.astryx-typeahead',
|
|
1416
|
+
) as HTMLElement;
|
|
1282
1417
|
const tooltip = screen.getByRole('tooltip', h);
|
|
1283
1418
|
expect(tooltip).toHaveTextContent('You need the Editor role');
|
|
1284
1419
|
|
|
@@ -1445,3 +1580,369 @@ describe('Typeahead statusVariant forwarding', () => {
|
|
|
1445
1580
|
);
|
|
1446
1581
|
});
|
|
1447
1582
|
});
|
|
1583
|
+
|
|
1584
|
+
describe('busy indicator ownership', () => {
|
|
1585
|
+
/** A source that stays in flight until the test settles it. */
|
|
1586
|
+
const pendingSource = () => {
|
|
1587
|
+
let settle: (items: SearchableItem[]) => void = () => {};
|
|
1588
|
+
return {
|
|
1589
|
+
source: {
|
|
1590
|
+
search: async () =>
|
|
1591
|
+
new Promise<SearchableItem[]>(resolve => {
|
|
1592
|
+
settle = resolve;
|
|
1593
|
+
}),
|
|
1594
|
+
bootstrap: () => [],
|
|
1595
|
+
},
|
|
1596
|
+
settle: (items: SearchableItem[] = []) => settle(items),
|
|
1597
|
+
};
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
it('renders its own named status for a direct caller', async () => {
|
|
1601
|
+
// BaseTypeaheadProps is re-exported from the package entry point, so the
|
|
1602
|
+
// base has direct callers this repo cannot see. They painted no indicator
|
|
1603
|
+
// of their own — the base did it for them — so it keeps doing it, and the
|
|
1604
|
+
// status stays a named one rather than a bare aria-busy that only reaches
|
|
1605
|
+
// assistive tech.
|
|
1606
|
+
const {source, settle} = pendingSource();
|
|
1607
|
+
render(
|
|
1608
|
+
<BaseTypeahead
|
|
1609
|
+
searchSource={source}
|
|
1610
|
+
value={null}
|
|
1611
|
+
onChange={() => {}}
|
|
1612
|
+
debounceMs={0}
|
|
1613
|
+
/>,
|
|
1614
|
+
);
|
|
1615
|
+
const input = screen.getByRole('combobox');
|
|
1616
|
+
|
|
1617
|
+
fireEvent.change(input, {target: {value: 'App'}});
|
|
1618
|
+
await waitFor(() => {
|
|
1619
|
+
expect(screen.getByRole('status', {name: 'Loading'})).toBeInTheDocument();
|
|
1620
|
+
});
|
|
1621
|
+
// A Spinner, not the static clock glyph this used to render: `clock`
|
|
1622
|
+
// means *time* everywhere else in core, and nothing about it moved while
|
|
1623
|
+
// a search was out.
|
|
1624
|
+
expect(
|
|
1625
|
+
screen.getByRole('status', {name: 'Loading'}).querySelector('svg'),
|
|
1626
|
+
).toBeInTheDocument();
|
|
1627
|
+
|
|
1628
|
+
await act(async () => {
|
|
1629
|
+
settle();
|
|
1630
|
+
await Promise.resolve();
|
|
1631
|
+
});
|
|
1632
|
+
expect(
|
|
1633
|
+
screen.queryByRole('status', {name: 'Loading'}),
|
|
1634
|
+
).not.toBeInTheDocument();
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
it('hands the indicator over to a field that takes it, and renders none itself', async () => {
|
|
1638
|
+
// Typeahead and Tokenizer paint the spinner in the one inline-end lane
|
|
1639
|
+
// they already own. Two indicators in one field is the defect this PR
|
|
1640
|
+
// exists to fix, so the base must yield rather than add to it.
|
|
1641
|
+
const {source, settle} = pendingSource();
|
|
1642
|
+
// A real lane with its notify spied, rather than a hand-built object: the
|
|
1643
|
+
// lane is a store now, and the base must drive the store the wrappers use.
|
|
1644
|
+
const lane = createBusyIndicatorLane();
|
|
1645
|
+
const onLoadingChange = vi.fn(lane.onBusyChange);
|
|
1646
|
+
render(
|
|
1647
|
+
<BusyIndicatorLaneProvider
|
|
1648
|
+
value={{...lane, onBusyChange: onLoadingChange}}>
|
|
1649
|
+
<BaseTypeahead
|
|
1650
|
+
searchSource={source}
|
|
1651
|
+
value={null}
|
|
1652
|
+
onChange={() => {}}
|
|
1653
|
+
debounceMs={0}
|
|
1654
|
+
/>
|
|
1655
|
+
</BusyIndicatorLaneProvider>,
|
|
1656
|
+
);
|
|
1657
|
+
const input = screen.getByRole('combobox');
|
|
1658
|
+
|
|
1659
|
+
fireEvent.change(input, {target: {value: 'App'}});
|
|
1660
|
+
await waitFor(() => {
|
|
1661
|
+
expect(input).toHaveAttribute('aria-busy', 'true');
|
|
1662
|
+
});
|
|
1663
|
+
// Scoped by name: the announcer for result counts is a role="status"
|
|
1664
|
+
// live region too, and it is not the indicator.
|
|
1665
|
+
expect(
|
|
1666
|
+
screen.queryByRole('status', {name: 'Loading'}),
|
|
1667
|
+
).not.toBeInTheDocument();
|
|
1668
|
+
expect(onLoadingChange).toHaveBeenLastCalledWith(true);
|
|
1669
|
+
|
|
1670
|
+
await act(async () => {
|
|
1671
|
+
settle();
|
|
1672
|
+
await Promise.resolve();
|
|
1673
|
+
});
|
|
1674
|
+
expect(onLoadingChange).toHaveBeenLastCalledWith(false);
|
|
1675
|
+
});
|
|
1676
|
+
|
|
1677
|
+
it('keeps the busy handoff out of the exported prop surface', async () => {
|
|
1678
|
+
// The handoff used to be `__onLoadingChange` on BaseTypeaheadProps, which
|
|
1679
|
+
// the package entry point re-exports — so a builder reading the exported
|
|
1680
|
+
// declaration would find it and could reasonably wire it, pinning a detail
|
|
1681
|
+
// between two wrappers and their base as permanent API. An `@internal` tag
|
|
1682
|
+
// is a note to a reader; a module boundary is the actual seam.
|
|
1683
|
+
const entry = await import('./index');
|
|
1684
|
+
expect(Object.keys(entry)).not.toContain('BusyIndicatorLaneProvider');
|
|
1685
|
+
expect(Object.keys(entry)).not.toContain('useBusyIndicatorLane');
|
|
1686
|
+
|
|
1687
|
+
const source = await readFile(
|
|
1688
|
+
resolve(__dirname, 'BaseTypeahead.tsx'),
|
|
1689
|
+
'utf8',
|
|
1690
|
+
);
|
|
1691
|
+
expect(source).not.toContain('__onLoadingChange');
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
it('reports each transition once, and reports nothing when nothing changed', async () => {
|
|
1695
|
+
// Edge-triggered on purpose. Every keystroke below the query threshold
|
|
1696
|
+
// clears the flag, and an unconditional report would hand the wrapper a
|
|
1697
|
+
// `false` per character — each one a state write, and on a field that is
|
|
1698
|
+
// re-rendering as the user types.
|
|
1699
|
+
const {source, settle} = pendingSource();
|
|
1700
|
+
// A real lane with its notify spied, rather than a hand-built object: the
|
|
1701
|
+
// lane is a store now, and the base must drive the store the wrappers use.
|
|
1702
|
+
const lane = createBusyIndicatorLane();
|
|
1703
|
+
const onLoadingChange = vi.fn(lane.onBusyChange);
|
|
1704
|
+
render(
|
|
1705
|
+
<BusyIndicatorLaneProvider
|
|
1706
|
+
value={{...lane, onBusyChange: onLoadingChange}}>
|
|
1707
|
+
<BaseTypeahead
|
|
1708
|
+
searchSource={source}
|
|
1709
|
+
value={null}
|
|
1710
|
+
onChange={() => {}}
|
|
1711
|
+
minQueryLength={3}
|
|
1712
|
+
debounceMs={0}
|
|
1713
|
+
/>
|
|
1714
|
+
</BusyIndicatorLaneProvider>,
|
|
1715
|
+
);
|
|
1716
|
+
const input = screen.getByRole('combobox');
|
|
1717
|
+
|
|
1718
|
+
// Below the threshold: no search, so nothing to report.
|
|
1719
|
+
fireEvent.change(input, {target: {value: 'A'}});
|
|
1720
|
+
fireEvent.change(input, {target: {value: 'Ap'}});
|
|
1721
|
+
await act(async () => {
|
|
1722
|
+
await Promise.resolve();
|
|
1723
|
+
});
|
|
1724
|
+
expect(onLoadingChange).not.toHaveBeenCalled();
|
|
1725
|
+
|
|
1726
|
+
fireEvent.change(input, {target: {value: 'App'}});
|
|
1727
|
+
await waitFor(() => {
|
|
1728
|
+
expect(onLoadingChange).toHaveBeenCalledTimes(1);
|
|
1729
|
+
});
|
|
1730
|
+
expect(onLoadingChange).toHaveBeenCalledWith(true);
|
|
1731
|
+
|
|
1732
|
+
// Back below the threshold: one report out, not one per keystroke.
|
|
1733
|
+
fireEvent.change(input, {target: {value: 'Ap'}});
|
|
1734
|
+
fireEvent.change(input, {target: {value: 'A'}});
|
|
1735
|
+
fireEvent.change(input, {target: {value: ''}});
|
|
1736
|
+
await act(async () => {
|
|
1737
|
+
settle();
|
|
1738
|
+
await Promise.resolve();
|
|
1739
|
+
});
|
|
1740
|
+
expect(onLoadingChange).toHaveBeenCalledTimes(2);
|
|
1741
|
+
expect(onLoadingChange).toHaveBeenLastCalledWith(false);
|
|
1742
|
+
});
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1745
|
+
describe('end controls stay in flow', () => {
|
|
1746
|
+
// The fix for the transform bug, expressed as a rule rather than a
|
|
1747
|
+
// measurement: these controls are ordinary flex siblings of the input, so
|
|
1748
|
+
// they take up room and nothing has to reserve it for them. jsdom performs
|
|
1749
|
+
// no layout, so what is asserted is the absence of the two things that
|
|
1750
|
+
// stopped that being true — an out-of-flow lane, and a padding reserve fed
|
|
1751
|
+
// by a measured width. The geometry itself is browser-verified in the PR.
|
|
1752
|
+
it('renders the clear button without taking it out of flow', () => {
|
|
1753
|
+
const {container} = render(
|
|
1754
|
+
<Typeahead
|
|
1755
|
+
label="Fruit"
|
|
1756
|
+
searchSource={fruitSource}
|
|
1757
|
+
value={fruits[0]}
|
|
1758
|
+
onChange={() => {}}
|
|
1759
|
+
/>,
|
|
1760
|
+
);
|
|
1761
|
+
const field = container.querySelector('.astryx-typeahead');
|
|
1762
|
+
const clear = screen.getByRole('button', {name: /clear/i});
|
|
1763
|
+
expect(field).toContainElement(clear);
|
|
1764
|
+
// The controls sit in a lane that is itself an ordinary in-flow child of
|
|
1765
|
+
// the field — not a box positioned over it, which is what reserved no
|
|
1766
|
+
// room and put the input underneath.
|
|
1767
|
+
const lane = clear.parentElement as HTMLElement;
|
|
1768
|
+
expect(lane.parentElement).toBe(field);
|
|
1769
|
+
expect(getComputedStyle(lane).position).not.toBe('absolute');
|
|
1770
|
+
});
|
|
1771
|
+
|
|
1772
|
+
it('never reserves room with a measured width', () => {
|
|
1773
|
+
// The custom property is Tokenizer's mechanism and must not reappear
|
|
1774
|
+
// here: a width measured in viewport space and spent as local padding is
|
|
1775
|
+
// wrong under any CSS transform (measured on Tokenizer's 123px lane,
|
|
1776
|
+
// scale(.5) covered 14px of the query and scale(2) left a 125.95px gap).
|
|
1777
|
+
const {container} = render(
|
|
1778
|
+
<Typeahead
|
|
1779
|
+
label="Fruit"
|
|
1780
|
+
searchSource={fruitSource}
|
|
1781
|
+
value={fruits[0]}
|
|
1782
|
+
onChange={() => {}}
|
|
1783
|
+
/>,
|
|
1784
|
+
);
|
|
1785
|
+
expect(
|
|
1786
|
+
container.querySelector('[style*="--_tokenizer-end-lane-width"]'),
|
|
1787
|
+
).toBeNull();
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
it('holds the controls at the inline end when a token shows', () => {
|
|
1791
|
+
// `auto` gives free space to the margin rather than to a sibling, so the
|
|
1792
|
+
// controls stay in the corner in the states where the content lane is not
|
|
1793
|
+
// the only flexible item in the row. Without it the clear button sat
|
|
1794
|
+
// against the token in mid-field instead of in the corner (measured:
|
|
1795
|
+
// x=39 in a 300px field, against TextInput's 281).
|
|
1796
|
+
render(
|
|
1797
|
+
<Typeahead
|
|
1798
|
+
label="Fruit"
|
|
1799
|
+
searchSource={fruitSource}
|
|
1800
|
+
value={fruits[0]}
|
|
1801
|
+
onChange={() => {}}
|
|
1802
|
+
/>,
|
|
1803
|
+
);
|
|
1804
|
+
const clear = screen.getByRole('button', {name: /clear/i});
|
|
1805
|
+
const lane = clear.parentElement as HTMLElement;
|
|
1806
|
+
expect(getComputedStyle(lane).marginInlineStart).toBe('auto');
|
|
1807
|
+
});
|
|
1808
|
+
|
|
1809
|
+
it('keeps the field on one row so the controls cannot be pushed off it', () => {
|
|
1810
|
+
// `flex-wrap` moves an item to a new line rather than shrinking it, so a
|
|
1811
|
+
// wrapping field put the controls on a row of their own once the token
|
|
1812
|
+
// got long. The shared field base does not wrap; this must not either.
|
|
1813
|
+
const {container} = render(
|
|
1814
|
+
<Typeahead
|
|
1815
|
+
label="Fruit"
|
|
1816
|
+
searchSource={fruitSource}
|
|
1817
|
+
value={fruits[0]}
|
|
1818
|
+
onChange={() => {}}
|
|
1819
|
+
/>,
|
|
1820
|
+
);
|
|
1821
|
+
const field = container.querySelector('.astryx-typeahead') as HTMLElement;
|
|
1822
|
+
expect(getComputedStyle(field).flexWrap).not.toBe('wrap');
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
it('keeps the input in flow while a token shows, so the field keeps its width', () => {
|
|
1826
|
+
// A field's width must not depend on its value. Every other field in the
|
|
1827
|
+
// family gets that for free: the `<input>` stays in flow and the field is
|
|
1828
|
+
// as wide as the input's own intrinsic width. This one used to take the
|
|
1829
|
+
// input out of flow and zero its width when a token showed, leaving the
|
|
1830
|
+
// field measuring the token — in a shrink-to-fit parent it snapped to the
|
|
1831
|
+
// value's length (199px to 57px in Chromium, #5560). Block parents hid it,
|
|
1832
|
+
// which is why no story caught it. jsdom resolves no layout, so assert the
|
|
1833
|
+
// mechanism: the input still occupies the row.
|
|
1834
|
+
render(
|
|
1835
|
+
<Typeahead
|
|
1836
|
+
label="Fruit"
|
|
1837
|
+
searchSource={fruitSource}
|
|
1838
|
+
value={fruits[0]}
|
|
1839
|
+
onChange={() => {}}
|
|
1840
|
+
/>,
|
|
1841
|
+
);
|
|
1842
|
+
const input = screen.getByRole('combobox');
|
|
1843
|
+
const style = getComputedStyle(input);
|
|
1844
|
+
expect(style.position).not.toBe('absolute');
|
|
1845
|
+
expect(style.width).not.toBe('0px');
|
|
1846
|
+
expect(style.flex).not.toBe('0 0 0');
|
|
1847
|
+
});
|
|
1848
|
+
|
|
1849
|
+
it('paints the token over the input rather than beside it', () => {
|
|
1850
|
+
// In flow the token would add its own width to the row — the same
|
|
1851
|
+
// value-dependent sizing from the other direction, where a long value
|
|
1852
|
+
// grows the field instead of collapsing it.
|
|
1853
|
+
const {container} = render(
|
|
1854
|
+
<Typeahead
|
|
1855
|
+
label="Fruit"
|
|
1856
|
+
searchSource={fruitSource}
|
|
1857
|
+
value={fruits[0]}
|
|
1858
|
+
onChange={() => {}}
|
|
1859
|
+
/>,
|
|
1860
|
+
);
|
|
1861
|
+
const token = container.querySelector('.astryx-token') as HTMLElement;
|
|
1862
|
+
expect(getComputedStyle(token).position).toBe('absolute');
|
|
1863
|
+
});
|
|
1864
|
+
|
|
1865
|
+
it('lets the token take the pointer that the hidden input would swallow', () => {
|
|
1866
|
+
// The input still covers that space, so it has to stop intercepting the
|
|
1867
|
+
// clicks that enter edit mode.
|
|
1868
|
+
render(
|
|
1869
|
+
<Typeahead
|
|
1870
|
+
label="Fruit"
|
|
1871
|
+
searchSource={fruitSource}
|
|
1872
|
+
value={fruits[0]}
|
|
1873
|
+
onChange={() => {}}
|
|
1874
|
+
/>,
|
|
1875
|
+
);
|
|
1876
|
+
expect(getComputedStyle(screen.getByRole('combobox')).pointerEvents).toBe(
|
|
1877
|
+
'none',
|
|
1878
|
+
);
|
|
1879
|
+
});
|
|
1880
|
+
});
|
|
1881
|
+
|
|
1882
|
+
describe('the value is bounded by the content lane', () => {
|
|
1883
|
+
// Keeping the input in flow fixed the field's width, but it left the token
|
|
1884
|
+
// positioned against the whole field, which has no idea where the end
|
|
1885
|
+
// controls start. A value longer than the input then ran under the clear
|
|
1886
|
+
// button and out past the field's own border — measured in Chromium at
|
|
1887
|
+
// 28-33px of overlap and up to 4px outside the border, worse than the 12px
|
|
1888
|
+
// of overlap on main. The lane is the box the value may occupy: an ordinary
|
|
1889
|
+
// flex item that ends exactly where the end lane begins.
|
|
1890
|
+
//
|
|
1891
|
+
// jsdom resolves no layout, so what is asserted here is the mechanism. The
|
|
1892
|
+
// geometry is browser-verified in the PR.
|
|
1893
|
+
const renderWithValue = () =>
|
|
1894
|
+
render(
|
|
1895
|
+
<Typeahead
|
|
1896
|
+
label="Fruit"
|
|
1897
|
+
searchSource={fruitSource}
|
|
1898
|
+
value={fruits[0]}
|
|
1899
|
+
onChange={() => {}}
|
|
1900
|
+
hasClear
|
|
1901
|
+
/>,
|
|
1902
|
+
);
|
|
1903
|
+
|
|
1904
|
+
it('puts the input and the token in one lane, inside the field', () => {
|
|
1905
|
+
const {container} = renderWithValue();
|
|
1906
|
+
const field = container.querySelector('.astryx-typeahead');
|
|
1907
|
+
const input = screen.getByRole('combobox');
|
|
1908
|
+
const token = container.querySelector('.astryx-token') as HTMLElement;
|
|
1909
|
+
|
|
1910
|
+
const lane = input.parentElement as HTMLElement;
|
|
1911
|
+
expect(lane).not.toBe(field);
|
|
1912
|
+
expect(lane.parentElement).toBe(field);
|
|
1913
|
+
// The token's containing block is the lane, which is what bounds it.
|
|
1914
|
+
expect(token.parentElement).toBe(lane);
|
|
1915
|
+
expect(getComputedStyle(lane).position).toBe('relative');
|
|
1916
|
+
});
|
|
1917
|
+
|
|
1918
|
+
it('lets the lane yield its whole width, so a narrow field cannot overflow', () => {
|
|
1919
|
+
// `min-width: 0` is the half of this that the earlier `200px` floor got
|
|
1920
|
+
// wrong: the field states no width of its own, so it still shrinks to
|
|
1921
|
+
// whatever a narrow parent or an InputGroup gives it.
|
|
1922
|
+
const {container} = renderWithValue();
|
|
1923
|
+
const lane = screen.getByRole('combobox').parentElement as HTMLElement;
|
|
1924
|
+
const style = getComputedStyle(lane);
|
|
1925
|
+
expect(style.minWidth).toBe('0');
|
|
1926
|
+
expect(style.flexGrow).toBe('1');
|
|
1927
|
+
// Nothing states a width: a narrow parent gets all of it back.
|
|
1928
|
+
expect(
|
|
1929
|
+
(container.querySelector('.astryx-typeahead') as HTMLElement).style
|
|
1930
|
+
.minWidth,
|
|
1931
|
+
).toBe('');
|
|
1932
|
+
});
|
|
1933
|
+
|
|
1934
|
+
it("anchors the token at the lane's end, not just its start", () => {
|
|
1935
|
+
// The bound that stops the value reaching the end controls. Anchored at
|
|
1936
|
+
// one end only, the token is capped by the field's own padding box, which
|
|
1937
|
+
// is past the clear button.
|
|
1938
|
+
const {container} = renderWithValue();
|
|
1939
|
+
const token = container.querySelector('.astryx-token') as HTMLElement;
|
|
1940
|
+
const style = getComputedStyle(token);
|
|
1941
|
+
expect(style.insetInlineEnd).toBe('0');
|
|
1942
|
+
// `fit-content` against that pair of insets is what shrink-wraps the
|
|
1943
|
+
// label yet still caps it at the lane; the `auto` end margin is what
|
|
1944
|
+
// keeps the pair from being over-constrained and dropping the end inset.
|
|
1945
|
+
expect(style.width).toBe('fit-content');
|
|
1946
|
+
expect(style.marginInlineEnd).toBe('auto');
|
|
1947
|
+
});
|
|
1948
|
+
});
|