@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
|
@@ -345,7 +345,10 @@ export function generateThemeRules(theme: DefinedTheme): string[] {
|
|
|
345
345
|
const val = (key: string): string => tokens[key] || `var(${key})`;
|
|
346
346
|
|
|
347
347
|
// 1. Token block — CSS custom properties on :scope
|
|
348
|
-
const tokenEntries =
|
|
348
|
+
const tokenEntries = [
|
|
349
|
+
...Object.entries(tokens),
|
|
350
|
+
...Object.entries(theme.localTokens ?? {}),
|
|
351
|
+
];
|
|
349
352
|
if (tokenEntries.length > 0) {
|
|
350
353
|
const declarations = tokenEntries
|
|
351
354
|
.map(([prop, value]) => ` ${prop}: ${value};`)
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import type {ComponentStyleMap, DefinedTheme, TokenValue} from './defineTheme';
|
|
4
|
+
import type {ResolvedOnMedia} from './onMediaTokens';
|
|
5
|
+
|
|
6
|
+
const LOCAL_TOKEN_PREFIX = '--astryx-theme-';
|
|
7
|
+
const THEME_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
8
|
+
const LOCAL_TOKEN_SUFFIX_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
9
|
+
const CSS_VAR_PATTERN = /var\(\s*(--[^,\s)]+)/gi;
|
|
10
|
+
|
|
11
|
+
export interface ResolvedLocalTokenContract {
|
|
12
|
+
localTokens: Record<string, string>;
|
|
13
|
+
owners: Record<string, string>;
|
|
14
|
+
lineage: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function hasOwn(object: object, key: PropertyKey): boolean {
|
|
18
|
+
return Object.prototype.hasOwnProperty.call(object, key);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isExactLocalTokenName(name: string, owner: string): boolean {
|
|
22
|
+
const prefix = `${LOCAL_TOKEN_PREFIX}${owner}-`;
|
|
23
|
+
return (
|
|
24
|
+
name.startsWith(prefix) &&
|
|
25
|
+
LOCAL_TOKEN_SUFFIX_PATTERN.test(name.slice(prefix.length))
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveTokenValue(value: TokenValue, path: string): string {
|
|
30
|
+
if (typeof value === 'string') {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
if (
|
|
34
|
+
Array.isArray(value) &&
|
|
35
|
+
value.length === 2 &&
|
|
36
|
+
typeof value[0] === 'string' &&
|
|
37
|
+
typeof value[1] === 'string'
|
|
38
|
+
) {
|
|
39
|
+
return `light-dark(${value[0]}, ${value[1]})`;
|
|
40
|
+
}
|
|
41
|
+
throw new Error(
|
|
42
|
+
`${path} must be a CSS string or a [light, dark] string tuple.`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function collectLocalReferences(value: unknown, refs: Set<string>): void {
|
|
47
|
+
if (typeof value === 'string') {
|
|
48
|
+
CSS_VAR_PATTERN.lastIndex = 0;
|
|
49
|
+
for (
|
|
50
|
+
let match = CSS_VAR_PATTERN.exec(value);
|
|
51
|
+
match;
|
|
52
|
+
match = CSS_VAR_PATTERN.exec(value)
|
|
53
|
+
) {
|
|
54
|
+
const name = match[1];
|
|
55
|
+
if (name.startsWith(LOCAL_TOKEN_PREFIX)) {
|
|
56
|
+
refs.add(name);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
for (const item of value) {
|
|
63
|
+
collectLocalReferences(item, refs);
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (value && typeof value === 'object') {
|
|
68
|
+
for (const nested of Object.values(value)) {
|
|
69
|
+
collectLocalReferences(nested, refs);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function assertNoLocalTokenCycles(localTokens: Record<string, string>): void {
|
|
75
|
+
const dependencies = new Map<string, string[]>();
|
|
76
|
+
for (const [name, value] of Object.entries(localTokens)) {
|
|
77
|
+
const refs = new Set<string>();
|
|
78
|
+
collectLocalReferences(value, refs);
|
|
79
|
+
dependencies.set(
|
|
80
|
+
name,
|
|
81
|
+
[...refs].filter(reference => hasOwn(localTokens, reference)),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const visiting = new Set<string>();
|
|
86
|
+
const visited = new Set<string>();
|
|
87
|
+
const visit = (name: string, path: string[]): void => {
|
|
88
|
+
if (visiting.has(name)) {
|
|
89
|
+
const start = path.indexOf(name);
|
|
90
|
+
const cycle = [...path.slice(start), name].join(' -> ');
|
|
91
|
+
throw new Error(`Theme-local token cycle detected: ${cycle}.`);
|
|
92
|
+
}
|
|
93
|
+
if (visited.has(name)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
visiting.add(name);
|
|
97
|
+
for (const dependency of dependencies.get(name) ?? []) {
|
|
98
|
+
visit(dependency, [...path, name]);
|
|
99
|
+
}
|
|
100
|
+
visiting.delete(name);
|
|
101
|
+
visited.add(name);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
for (const name of Object.keys(localTokens)) {
|
|
105
|
+
visit(name, []);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function assertDeclaredReferences(
|
|
110
|
+
localTokens: Record<string, string>,
|
|
111
|
+
components: ComponentStyleMap | undefined,
|
|
112
|
+
onDark: ResolvedOnMedia | undefined,
|
|
113
|
+
onLight: ResolvedOnMedia | undefined,
|
|
114
|
+
): void {
|
|
115
|
+
const refs = new Set<string>();
|
|
116
|
+
collectLocalReferences(localTokens, refs);
|
|
117
|
+
collectLocalReferences(components, refs);
|
|
118
|
+
collectLocalReferences(onDark?.components, refs);
|
|
119
|
+
collectLocalReferences(onLight?.components, refs);
|
|
120
|
+
|
|
121
|
+
for (const reference of refs) {
|
|
122
|
+
if (!hasOwn(localTokens, reference)) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Theme-local token reference "${reference}" has no declaration in the enrolled theme lineage.`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function assertInheritedContract(
|
|
131
|
+
themeName: string,
|
|
132
|
+
base: DefinedTheme,
|
|
133
|
+
): asserts base is DefinedTheme & {
|
|
134
|
+
localTokens: Record<string, string>;
|
|
135
|
+
__localTokenOwners: Record<string, string>;
|
|
136
|
+
__localTokenLineage: string[];
|
|
137
|
+
} {
|
|
138
|
+
const lineage = base.__localTokenLineage;
|
|
139
|
+
if (
|
|
140
|
+
!Array.isArray(lineage) ||
|
|
141
|
+
lineage.length === 0 ||
|
|
142
|
+
lineage[lineage.length - 1] !== base.name ||
|
|
143
|
+
lineage.some(name => typeof name !== 'string' || name.length === 0)
|
|
144
|
+
) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`defineTheme("${themeName}"): the base theme has invalid theme-local token lineage metadata.`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (
|
|
150
|
+
!base.localTokens ||
|
|
151
|
+
typeof base.localTokens !== 'object' ||
|
|
152
|
+
Array.isArray(base.localTokens) ||
|
|
153
|
+
!base.__localTokenOwners ||
|
|
154
|
+
typeof base.__localTokenOwners !== 'object' ||
|
|
155
|
+
Array.isArray(base.__localTokenOwners)
|
|
156
|
+
) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`defineTheme("${themeName}"): the base theme has incomplete theme-local token metadata.`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const [name, value] of Object.entries(base.localTokens)) {
|
|
163
|
+
const owner = base.__localTokenOwners[name];
|
|
164
|
+
if (
|
|
165
|
+
typeof value !== 'string' ||
|
|
166
|
+
!owner ||
|
|
167
|
+
!lineage.includes(owner) ||
|
|
168
|
+
!isExactLocalTokenName(name, owner)
|
|
169
|
+
) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`defineTheme("${themeName}"): inherited local token "${name}" does not match its exact lineage metadata.`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (const name of Object.keys(base.__localTokenOwners)) {
|
|
176
|
+
if (!hasOwn(base.localTokens, name)) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`defineTheme("${themeName}"): inherited local-token owner metadata names undeclared token "${name}".`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Resolve and validate the opt-in theme-local token contract.
|
|
186
|
+
*
|
|
187
|
+
* Themes that omit `localTokens` and do not extend an enrolled base bypass this
|
|
188
|
+
* function's reserved-namespace checks so legacy token behavior stays intact.
|
|
189
|
+
*/
|
|
190
|
+
export function resolveLocalTokenContract(
|
|
191
|
+
input: {
|
|
192
|
+
name: string;
|
|
193
|
+
localTokens?: Record<string, TokenValue>;
|
|
194
|
+
},
|
|
195
|
+
base: DefinedTheme | undefined,
|
|
196
|
+
tokens: Record<string, string>,
|
|
197
|
+
components: ComponentStyleMap | undefined,
|
|
198
|
+
onDark: ResolvedOnMedia | undefined,
|
|
199
|
+
onLight: ResolvedOnMedia | undefined,
|
|
200
|
+
): ResolvedLocalTokenContract | undefined {
|
|
201
|
+
const directlyEnrolled = hasOwn(input, 'localTokens');
|
|
202
|
+
const inherited = base?.__localTokenLineage !== undefined;
|
|
203
|
+
|
|
204
|
+
if (!directlyEnrolled && !inherited) {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (directlyEnrolled && !THEME_NAME_PATTERN.test(input.name)) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`defineTheme("${input.name}"): themes using localTokens require a stable lower-kebab name.`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (inherited && base) {
|
|
215
|
+
assertInheritedContract(input.name, base);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const localTokens = {...base?.localTokens};
|
|
219
|
+
const owners = {...base?.__localTokenOwners};
|
|
220
|
+
const declarations = input.localTokens;
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
directlyEnrolled &&
|
|
224
|
+
(declarations === null ||
|
|
225
|
+
declarations === undefined ||
|
|
226
|
+
typeof declarations !== 'object' ||
|
|
227
|
+
Array.isArray(declarations))
|
|
228
|
+
) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
`defineTheme("${input.name}"): localTokens must be a token map.`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const [name, value] of Object.entries(declarations ?? {})) {
|
|
235
|
+
const inheritedOwner = owners[name];
|
|
236
|
+
if (!inheritedOwner) {
|
|
237
|
+
const expectedPrefix = `${LOCAL_TOKEN_PREFIX}${input.name}-`;
|
|
238
|
+
if (!isExactLocalTokenName(name, input.name)) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`defineTheme("${input.name}"): local token "${name}" must use the exact namespace "${expectedPrefix}" followed by a lowercase kebab-case purpose.`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
owners[name] = input.name;
|
|
244
|
+
}
|
|
245
|
+
localTokens[name] = resolveTokenValue(
|
|
246
|
+
value,
|
|
247
|
+
`defineTheme("${input.name}").localTokens["${name}"]`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (const name of Object.keys(localTokens)) {
|
|
252
|
+
if (!owners[name]) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`defineTheme("${input.name}"): inherited local token "${name}" has no owner metadata.`,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
if (hasOwn(tokens, name)) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`defineTheme("${input.name}"): token "${name}" cannot be declared in both tokens and localTokens.`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
assertDeclaredReferences(localTokens, components, onDark, onLight);
|
|
265
|
+
assertNoLocalTokenCycles(localTokens);
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
localTokens,
|
|
269
|
+
owners,
|
|
270
|
+
lineage: [...(base?.__localTokenLineage ?? []), input.name],
|
|
271
|
+
};
|
|
272
|
+
}
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
4
4
|
/**
|
|
5
5
|
* @file Guards `theming.targets` against the real `themeProps()` call sites (#3741).
|
|
6
|
-
* @input
|
|
7
|
-
*
|
|
6
|
+
* @input Core/Lab component sources (*.tsx/*.ts), their `{Name}.doc.mjs`
|
|
7
|
+
* files, and the Lab promotion-candidate manifest.
|
|
8
|
+
* @output Vitest failures naming each undocumented class / visual prop on the
|
|
9
|
+
* stable Core surface or a capability-participating Lab component.
|
|
8
10
|
* @position Sibling of derivedVarRegistry.test.ts, which already validates the
|
|
9
11
|
* OTHER fields of the same `theming` block (`vars`, `derived`). `targets` was
|
|
10
12
|
* the one field with no machine check, so it drifted — twice (#3652, #3680).
|
|
@@ -22,15 +24,36 @@
|
|
|
22
24
|
* `visualProps` or `states`. Docs may list MORE than the source passes —
|
|
23
25
|
* components forward props they don't themselves reflect (Timestamp passes
|
|
24
26
|
* `{format}` but documents `type`/`color`/`format`), and that is intentional.
|
|
27
|
+
*
|
|
28
|
+
* Lab is capability-based: a component participates when it already declares
|
|
29
|
+
* `theming.targets` or enters the existing promotion-candidate manifest. Other
|
|
30
|
+
* Lab components remain free to iterate with runtime hooks that are not yet a
|
|
31
|
+
* documented public theming promise.
|
|
25
32
|
*/
|
|
26
33
|
|
|
27
34
|
import {describe, it, expect} from 'vitest';
|
|
28
35
|
import {readdirSync, readFileSync} from 'node:fs';
|
|
29
|
-
import {join, relative} from 'node:path';
|
|
36
|
+
import {basename, join, relative} from 'node:path';
|
|
30
37
|
import ts from 'typescript';
|
|
31
38
|
import {stableClassName} from '../naming';
|
|
32
39
|
|
|
33
|
-
const
|
|
40
|
+
const CORE_SRC_DIR = join(__dirname, '..');
|
|
41
|
+
const LAB_SRC_DIR = join(__dirname, '../../../lab/src');
|
|
42
|
+
const LAB_PROMOTION_MANIFEST = join(
|
|
43
|
+
__dirname,
|
|
44
|
+
'../../../../internal/lab-readiness/manifest.mjs',
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
interface LabPromotionCandidate {
|
|
48
|
+
sourceDir: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const {CANDIDATES: labPromotionCandidates} = require(
|
|
52
|
+
LAB_PROMOTION_MANIFEST,
|
|
53
|
+
) as {CANDIDATES: LabPromotionCandidate[]};
|
|
54
|
+
const LAB_PROMOTION_DIRS = new Set(
|
|
55
|
+
labPromotionCandidates.map(candidate => candidate.sourceDir),
|
|
56
|
+
);
|
|
34
57
|
|
|
35
58
|
// ---------------------------------------------------------------------------
|
|
36
59
|
// Source scanning: find themeProps() call sites via the TypeScript AST
|
|
@@ -314,6 +337,7 @@ type DocBlock = {theming?: {targets?: DocTarget[]}};
|
|
|
314
337
|
type ComponentDocModule = {docs?: DocBlock; docsZh?: DocBlock};
|
|
315
338
|
|
|
316
339
|
interface ComponentInfo {
|
|
340
|
+
packageName: 'core' | 'lab';
|
|
317
341
|
dir: string;
|
|
318
342
|
sites: ThemeTargetSite[];
|
|
319
343
|
/** The doc blocks that carry theming.targets, by the key they live under. */
|
|
@@ -328,7 +352,10 @@ interface ComponentInfo {
|
|
|
328
352
|
* Reads the file as text rather than requiring it: this runs for directories
|
|
329
353
|
* that have no doc of their own, so most candidates are misses.
|
|
330
354
|
*/
|
|
331
|
-
function docFilesDocumenting(
|
|
355
|
+
function docFilesDocumenting(
|
|
356
|
+
srcDir: string,
|
|
357
|
+
classNames: Set<string>,
|
|
358
|
+
): string[] {
|
|
332
359
|
const matches: string[] = [];
|
|
333
360
|
const walk = (dir: string): void => {
|
|
334
361
|
for (const entry of readdirSync(dir, {withFileTypes: true})) {
|
|
@@ -346,18 +373,47 @@ function docFilesDocumenting(classNames: Set<string>): string[] {
|
|
|
346
373
|
}
|
|
347
374
|
}
|
|
348
375
|
};
|
|
349
|
-
walk(
|
|
376
|
+
walk(srcDir);
|
|
350
377
|
return matches;
|
|
351
378
|
}
|
|
352
379
|
|
|
353
|
-
|
|
380
|
+
/**
|
|
381
|
+
* Every directory under `srcDir`, at any depth, relative to `srcDir`.
|
|
382
|
+
*
|
|
383
|
+
* Not just the top level: a component's sources are not always its own direct
|
|
384
|
+
* children — Table's plugins render from `Table/plugins/<name>/`. Scanning only
|
|
385
|
+
* the top level exempted every one of those from this guard, which is the same
|
|
386
|
+
* silent-exemption shape #3741 was filed about.
|
|
387
|
+
*
|
|
388
|
+
* Takes `srcDir` rather than closing over a module constant so it serves both
|
|
389
|
+
* packages, which is what the Lab enrollment above needs.
|
|
390
|
+
*/
|
|
391
|
+
function sourceDirs(srcDir: string): string[] {
|
|
392
|
+
const out: string[] = [];
|
|
393
|
+
const walk = (rel: string): void => {
|
|
394
|
+
for (const entry of readdirSync(join(srcDir, rel), {
|
|
395
|
+
withFileTypes: true,
|
|
396
|
+
})) {
|
|
397
|
+
if (entry.isDirectory()) {
|
|
398
|
+
const child = rel === '' ? entry.name : join(rel, entry.name);
|
|
399
|
+
out.push(child);
|
|
400
|
+
walk(child);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
walk('');
|
|
405
|
+
return out;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function discoverComponents(
|
|
409
|
+
srcDir: string,
|
|
410
|
+
packageName: ComponentInfo['packageName'],
|
|
411
|
+
requiredDirs: ReadonlySet<string> = new Set(),
|
|
412
|
+
): ComponentInfo[] {
|
|
354
413
|
const results: ComponentInfo[] = [];
|
|
355
|
-
const dirs = readdirSync(SRC_DIR, {withFileTypes: true})
|
|
356
|
-
.filter(d => d.isDirectory())
|
|
357
|
-
.map(d => d.name);
|
|
358
414
|
|
|
359
|
-
for (const dir of
|
|
360
|
-
const dirPath = join(
|
|
415
|
+
for (const dir of sourceDirs(srcDir)) {
|
|
416
|
+
const dirPath = join(srcDir, dir);
|
|
361
417
|
const dirEntries = readdirSync(dirPath);
|
|
362
418
|
|
|
363
419
|
const sourceFiles = dirEntries.filter(
|
|
@@ -387,9 +443,13 @@ function discoverComponents(): ComponentInfo[] {
|
|
|
387
443
|
// Both paths match the on-disk listing rather than existsSync: on
|
|
388
444
|
// case-insensitive filesystems existsSync would match a differently-cased
|
|
389
445
|
// doc file that CI never checks. (Same guard as derivedVarRegistry.test.ts.)
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
446
|
+
// `basename`, not `dir`: a nested source dir is a path
|
|
447
|
+
// (`Table/plugins/foo`), and its own doc file is named for the last
|
|
448
|
+
// segment.
|
|
449
|
+
const ownDoc = `${basename(dir)}.doc.mjs`;
|
|
450
|
+
const docFiles = dirEntries.includes(ownDoc)
|
|
451
|
+
? [join(dirPath, ownDoc)]
|
|
452
|
+
: docFilesDocumenting(srcDir, new Set(sites.map(s => s.className)));
|
|
393
453
|
if (docFiles.length === 0) {
|
|
394
454
|
continue;
|
|
395
455
|
}
|
|
@@ -404,18 +464,23 @@ function discoverComponents(): ComponentInfo[] {
|
|
|
404
464
|
}
|
|
405
465
|
for (const key of ['docs', 'docsZh'] as const) {
|
|
406
466
|
const targets = mod[key]?.theming?.targets;
|
|
407
|
-
//
|
|
408
|
-
//
|
|
467
|
+
// A normal Core or Lab component enrolls by declaring a theming
|
|
468
|
+
// surface. Promotion candidates are also enrolled here so a missing
|
|
469
|
+
// block is a failure rather than a silently skipped readiness gap.
|
|
409
470
|
if (targets != null) {
|
|
410
|
-
docBlocks.push({key, file: relative(
|
|
471
|
+
docBlocks.push({key, file: relative(srcDir, docFile), targets});
|
|
411
472
|
}
|
|
412
473
|
}
|
|
413
474
|
}
|
|
414
|
-
|
|
475
|
+
const participates =
|
|
476
|
+
packageName === 'core'
|
|
477
|
+
? docBlocks.length > 0
|
|
478
|
+
: requiredDirs.has(dir) || docBlocks.length > 0;
|
|
479
|
+
if (!participates) {
|
|
415
480
|
continue;
|
|
416
481
|
}
|
|
417
482
|
|
|
418
|
-
results.push({dir, sites, docBlocks});
|
|
483
|
+
results.push({packageName, dir, sites, docBlocks});
|
|
419
484
|
}
|
|
420
485
|
return results;
|
|
421
486
|
}
|
|
@@ -425,24 +490,57 @@ function discoverComponents(): ComponentInfo[] {
|
|
|
425
490
|
// ---------------------------------------------------------------------------
|
|
426
491
|
|
|
427
492
|
describe('theming.targets matches the themeProps() call sites', () => {
|
|
428
|
-
const components =
|
|
493
|
+
const components = [
|
|
494
|
+
...discoverComponents(CORE_SRC_DIR, 'core'),
|
|
495
|
+
...discoverComponents(LAB_SRC_DIR, 'lab', LAB_PROMOTION_DIRS),
|
|
496
|
+
];
|
|
497
|
+
|
|
498
|
+
it('finds participating Core and Lab components', () => {
|
|
499
|
+
// A refactor that renames themeProps or drops the Lab package from this
|
|
500
|
+
// inventory must not silently disable either side of the guard.
|
|
501
|
+
expect(components.some(component => component.packageName === 'core')).toBe(
|
|
502
|
+
true,
|
|
503
|
+
);
|
|
504
|
+
expect(components.some(component => component.packageName === 'lab')).toBe(
|
|
505
|
+
true,
|
|
506
|
+
);
|
|
507
|
+
});
|
|
429
508
|
|
|
430
|
-
it('
|
|
431
|
-
|
|
432
|
-
|
|
509
|
+
it('enrolls every Lab promotion candidate and explicit theming capability', () => {
|
|
510
|
+
const labDirs = new Set(
|
|
511
|
+
components
|
|
512
|
+
.filter(component => component.packageName === 'lab')
|
|
513
|
+
.map(component => component.dir),
|
|
514
|
+
);
|
|
515
|
+
expect([...LAB_PROMOTION_DIRS].filter(dir => !labDirs.has(dir))).toEqual(
|
|
516
|
+
[],
|
|
517
|
+
);
|
|
518
|
+
expect(LAB_PROMOTION_DIRS.has('CircularProgress')).toBe(false);
|
|
519
|
+
expect(labDirs.has('CircularProgress')).toBe(true);
|
|
520
|
+
expect(labDirs.has('Schedule')).toBe(false);
|
|
433
521
|
});
|
|
434
522
|
|
|
435
|
-
for (const {dir, sites, docBlocks} of components) {
|
|
523
|
+
for (const {packageName, dir, sites, docBlocks} of components) {
|
|
524
|
+
const componentLabel = `${packageName}/${dir}`;
|
|
436
525
|
const renderedClasses = [...new Set(sites.map(s => s.className))].sort();
|
|
437
526
|
|
|
527
|
+
it(`${componentLabel}: participating components declare theming metadata`, () => {
|
|
528
|
+
expect(
|
|
529
|
+
docBlocks,
|
|
530
|
+
`${componentLabel} participates in the public theming contract but has ` +
|
|
531
|
+
`no loadable .doc.mjs theming.targets. Lab components participate ` +
|
|
532
|
+
`when they declare theming.targets or enter the promotion manifest.`,
|
|
533
|
+
).not.toHaveLength(0);
|
|
534
|
+
});
|
|
535
|
+
|
|
438
536
|
for (const {key, file, targets} of docBlocks) {
|
|
439
537
|
const documented = new Set(targets.map(t => t.className));
|
|
440
538
|
|
|
441
|
-
it(`${
|
|
539
|
+
it(`${componentLabel} (${file} ${key}): every rendered class is documented`, () => {
|
|
442
540
|
const undocumented = renderedClasses.filter(c => !documented.has(c));
|
|
443
541
|
expect(
|
|
444
542
|
undocumented,
|
|
445
|
-
`${
|
|
543
|
+
`${componentLabel} renders ${undocumented.length} astryx-* class(es) that ` +
|
|
446
544
|
`${file} ${key}.theming.targets does not document: ` +
|
|
447
545
|
`${undocumented.join(', ')}. An undocumented class is an ` +
|
|
448
546
|
`unthemeable element — theme authors and codegen read targets[] ` +
|
|
@@ -450,7 +548,7 @@ describe('theming.targets matches the themeProps() call sites', () => {
|
|
|
450
548
|
).toEqual([]);
|
|
451
549
|
});
|
|
452
550
|
|
|
453
|
-
it(`${
|
|
551
|
+
it(`${componentLabel} (${file} ${key}): every visual prop passed to themeProps is documented`, () => {
|
|
454
552
|
const missing: string[] = [];
|
|
455
553
|
for (const site of sites) {
|
|
456
554
|
const target = targets.find(t => t.className === site.className);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file interactionModality.test.ts
|
|
5
|
+
* @input The interaction-modality utility loaded more than once
|
|
6
|
+
* @output Verifies document-wide listener and modality state continuity
|
|
7
|
+
* @position Unit coverage for interactionModality.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {afterEach, describe, expect, it, vi} from 'vitest';
|
|
11
|
+
|
|
12
|
+
const isolatedDocument = document.implementation.createHTMLDocument();
|
|
13
|
+
|
|
14
|
+
async function loadTracker() {
|
|
15
|
+
vi.resetModules();
|
|
16
|
+
return import('./interactionModality');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
vi.unstubAllGlobals();
|
|
21
|
+
vi.restoreAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('interactionModality', () => {
|
|
25
|
+
it('does not touch the document when the module loads', async () => {
|
|
26
|
+
vi.stubGlobal('document', isolatedDocument);
|
|
27
|
+
const addEventListener = vi.spyOn(isolatedDocument, 'addEventListener');
|
|
28
|
+
|
|
29
|
+
await loadTracker();
|
|
30
|
+
|
|
31
|
+
expect(addEventListener).not.toHaveBeenCalled();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('shares one listener pair and tracks input between consumers', async () => {
|
|
35
|
+
vi.stubGlobal('document', isolatedDocument);
|
|
36
|
+
const addEventListener = vi.spyOn(isolatedDocument, 'addEventListener');
|
|
37
|
+
const removeEventListener = vi.spyOn(
|
|
38
|
+
isolatedDocument,
|
|
39
|
+
'removeEventListener',
|
|
40
|
+
);
|
|
41
|
+
const firstModule = await loadTracker();
|
|
42
|
+
const secondModule = await loadTracker();
|
|
43
|
+
|
|
44
|
+
const firstConsumer =
|
|
45
|
+
firstModule.__startInteractionModalityTrackingForTest();
|
|
46
|
+
const secondConsumer =
|
|
47
|
+
secondModule.__startInteractionModalityTrackingForTest();
|
|
48
|
+
|
|
49
|
+
expect(
|
|
50
|
+
addEventListener.mock.calls.filter(([type]) => type === 'pointerdown'),
|
|
51
|
+
).toHaveLength(1);
|
|
52
|
+
expect(
|
|
53
|
+
addEventListener.mock.calls.filter(([type]) => type === 'keydown'),
|
|
54
|
+
).toHaveLength(1);
|
|
55
|
+
|
|
56
|
+
isolatedDocument.dispatchEvent(new Event('pointerdown'));
|
|
57
|
+
expect(secondModule.getInteractionModality()).toBe('pointer');
|
|
58
|
+
|
|
59
|
+
firstConsumer();
|
|
60
|
+
secondConsumer();
|
|
61
|
+
isolatedDocument.dispatchEvent(new KeyboardEvent('keydown', {key: 'Tab'}));
|
|
62
|
+
expect(firstModule.getInteractionModality()).toBe('keyboard');
|
|
63
|
+
expect(removeEventListener).not.toHaveBeenCalled();
|
|
64
|
+
|
|
65
|
+
secondModule.__startInteractionModalityTrackingForTest()();
|
|
66
|
+
expect(
|
|
67
|
+
addEventListener.mock.calls.filter(([type]) => type === 'pointerdown'),
|
|
68
|
+
).toHaveLength(1);
|
|
69
|
+
expect(
|
|
70
|
+
addEventListener.mock.calls.filter(([type]) => type === 'keydown'),
|
|
71
|
+
).toHaveLength(1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('uses the keyboard-safe default without a document', async () => {
|
|
75
|
+
vi.stubGlobal('document', undefined);
|
|
76
|
+
const serverModule = await loadTracker();
|
|
77
|
+
|
|
78
|
+
expect(serverModule.getInteractionModality()).toBe('keyboard');
|
|
79
|
+
expect(() =>
|
|
80
|
+
serverModule.__startInteractionModalityTrackingForTest()(),
|
|
81
|
+
).not.toThrow();
|
|
82
|
+
});
|
|
83
|
+
});
|