@astryxdesign/core 0.3.0 → 0.4.0-canary.3112e99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +156 -0
- package/dist/AlertDialog/AlertDialog.d.ts +10 -2
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +22 -3
- package/dist/AppShell/AppShell.d.ts +2 -2
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +40 -9
- package/dist/AspectRatio/AspectRatio.d.ts +13 -0
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +9 -0
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +33 -20
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +2 -3
- package/dist/Banner/Banner.d.ts +3 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +32 -17
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +21 -8
- package/dist/Breadcrumbs/index.d.ts +2 -1
- package/dist/Breadcrumbs/index.d.ts.map +1 -1
- package/dist/Breadcrumbs/index.js +1 -0
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +12 -16
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +2 -1
- package/dist/Calendar/styles.d.ts +0 -2
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +0 -2
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +8 -12
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +2 -1
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +3 -1
- package/dist/Chat/ChatSendButton.d.ts.map +1 -1
- package/dist/Chat/ChatSendButton.js +4 -3
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +17 -17
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +2 -6
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +36 -125
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +3 -11
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +84 -67
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +35 -15
- package/dist/CommandPalette/CommandPaletteItem.d.ts +1 -1
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +6 -3
- package/dist/CommandPalette/CommandPaletteList.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +32 -31
- package/dist/ContextMenu/ContextMenu.d.ts +2 -2
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +1 -1
- package/dist/ContextMenu/index.d.ts +2 -1
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/ContextMenu/index.js +1 -0
- package/dist/DateInput/DateInput.d.ts +9 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +47 -33
- package/dist/DateRangeInput/DateRangeInput.d.ts +9 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +55 -32
- package/dist/DateTimeInput/DateTimeInput.d.ts +9 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +54 -30
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +4 -4
- package/dist/DropdownMenu/DropdownMenu.d.ts +33 -8
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +62 -20
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +8 -11
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +32 -31
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts +25 -0
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuDivider.js +66 -0
- package/dist/DropdownMenu/DropdownMenuItem.d.ts +15 -2
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +20 -8
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioGroup.js +10 -3
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +5 -5
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +24 -67
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +2 -8
- package/dist/DropdownMenu/index.d.ts +2 -1
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/index.js +4 -0
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
- package/dist/DropdownMenu/renderDropdownItems.js +35 -27
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +9 -5
- package/dist/Field/InputClearButton.d.ts +16 -3
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +15 -4
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +21 -10
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +3 -19
- package/dist/Indicator/CheckIndicator.d.ts +25 -0
- package/dist/Indicator/CheckIndicator.d.ts.map +1 -0
- package/dist/Indicator/CheckIndicator.js +155 -0
- package/dist/Indicator/CheckboxIndicator.d.ts +22 -0
- package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -0
- package/dist/Indicator/CheckboxIndicator.js +173 -0
- package/dist/Indicator/RadioIndicator.d.ts +23 -0
- package/dist/Indicator/RadioIndicator.d.ts.map +1 -0
- package/dist/Indicator/RadioIndicator.js +137 -0
- package/dist/Indicator/index.d.ts +24 -0
- package/dist/Indicator/index.d.ts.map +1 -0
- package/dist/Indicator/index.js +26 -0
- package/dist/Indicator/indicator.markers.stylex.d.ts +16 -0
- package/dist/Indicator/indicator.markers.stylex.d.ts.map +1 -0
- package/dist/Indicator/indicator.markers.stylex.js +21 -0
- package/dist/Indicator/indicatorRegistry.d.ts +71 -0
- package/dist/Indicator/indicatorRegistry.d.ts.map +1 -0
- package/dist/Indicator/indicatorRegistry.js +93 -0
- package/dist/Indicator/types.d.ts +177 -0
- package/dist/Indicator/types.d.ts.map +1 -0
- package/dist/Indicator/types.js +1 -0
- package/dist/Indicator/useIndicator.d.ts +18 -0
- package/dist/Indicator/useIndicator.d.ts.map +1 -0
- package/dist/Indicator/useIndicator.js +30 -0
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +4 -8
- package/dist/Layer/useLayer.d.ts +14 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +25 -2
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +80 -103
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +4 -5
- package/dist/MobileNav/MobileNav.js +8 -8
- package/dist/MoreMenu/MoreMenu.d.ts +14 -1
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +4 -0
- package/dist/MultiSelector/MultiSelector.d.ts +8 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +119 -95
- package/dist/NumberInput/NumberInput.d.ts +27 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +230 -42
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +2 -3
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +61 -63
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +6 -8
- package/dist/Popover/usePopover.d.ts +15 -0
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +14 -2
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +7 -0
- package/dist/ProgressBar/ProgressBar.d.ts +11 -4
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +95 -6
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +29 -84
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +2 -3
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +2 -3
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +3 -11
- package/dist/Selector/Selector.d.ts +12 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +222 -111
- package/dist/Selector/hooks.d.ts +18 -6
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +57 -40
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +1 -1
- package/dist/SideNav/SideNavCollapseButton.d.ts +1 -1
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +29 -14
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +56 -20
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +28 -22
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +2 -6
- package/dist/Stack/stack.stylex.d.ts +3 -3
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +14 -13
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +2 -3
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +41 -19
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +8 -0
- package/dist/Table/TableRow.d.ts +1 -1
- package/dist/Table/TableRow.d.ts.map +1 -1
- package/dist/Table/TableRow.js +4 -2
- package/dist/Table/index.d.ts +1 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +22 -18
- package/dist/Table/plugins/rowExpansion/index.d.ts +1 -1
- package/dist/Table/plugins/rowExpansion/index.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/index.js +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +41 -76
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +94 -345
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +23 -4
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeData.js +29 -33
- package/dist/Table/tableContextMenu.d.ts.map +1 -1
- package/dist/Table/tableContextMenu.js +2 -1
- package/dist/Table/types.d.ts +11 -0
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/TextArea/TextArea.d.ts +10 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +16 -6
- package/dist/TextInput/TextInput.d.ts +10 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +13 -19
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +32 -26
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +14 -17
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +14 -3
- package/dist/Timestamp/TimestampHoverCard.d.ts.map +1 -1
- package/dist/Timestamp/TimestampHoverCard.js +16 -34
- package/dist/Timestamp/formatInstant.d.ts +11 -1
- package/dist/Timestamp/formatInstant.d.ts.map +1 -1
- package/dist/Timestamp/formatInstant.js +3 -1
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +20 -11
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +4 -2
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +3 -19
- package/dist/TopNav/TopNav.d.ts.map +1 -1
- package/dist/TopNav/TopNav.js +6 -5
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +54 -16
- package/dist/TopNav/TopNavItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavItem.js +3 -9
- package/dist/TopNav/TopNavMegaMenu.d.ts +7 -0
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +146 -60
- package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenuItem.js +22 -3
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +83 -38
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +13 -0
- package/dist/TreeList/TreeListBranches.d.ts +1 -1
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +14 -4
- package/dist/TreeList/TreeListItem.d.ts +11 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +57 -30
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +16 -8
- package/dist/astryx.css +84 -96
- package/dist/astryx.umd.js +56 -56
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/containerReveal.stylex.d.ts +61 -0
- package/dist/hooks/containerReveal.stylex.d.ts.map +1 -0
- package/dist/hooks/containerReveal.stylex.js +63 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/useClipboard.d.ts +59 -0
- package/dist/hooks/useClipboard.d.ts.map +1 -0
- package/dist/hooks/useClipboard.js +106 -0
- package/dist/hooks/useContainerReveal.d.ts +14 -8
- package/dist/hooks/useContainerReveal.d.ts.map +1 -1
- package/dist/hooks/useContainerReveal.js +28 -69
- package/dist/hooks/useIndicatorFocusRing.d.ts +28 -0
- package/dist/hooks/useIndicatorFocusRing.d.ts.map +1 -0
- package/dist/hooks/useIndicatorFocusRing.js +96 -0
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +17 -4
- package/dist/hooks/useLongPress.d.ts.map +1 -1
- package/dist/hooks/useLongPress.js +12 -2
- package/dist/hooks/useScrollLock.d.ts.map +1 -1
- package/dist/hooks/useScrollLock.js +33 -18
- package/dist/hooks/useStreamingText.d.ts +18 -3
- package/dist/hooks/useStreamingText.d.ts.map +1 -1
- package/dist/hooks/useStreamingText.js +47 -7
- package/dist/hooks/useTreeFocus.d.ts.map +1 -1
- package/dist/hooks/useTreeFocus.js +9 -3
- package/dist/hooks/useTypeahead.d.ts +3 -2
- package/dist/hooks/useTypeahead.d.ts.map +1 -1
- package/dist/hooks/useTypeahead.js +19 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/defineTheme.d.ts +18 -2
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +10 -1
- package/dist/theme/derivedVarRegistry.d.ts +8 -0
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +14 -13
- package/dist/theme/expandColorScale.d.ts +3 -1
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +17 -7
- package/dist/theme/expandTypeScale.d.ts.map +1 -1
- package/dist/theme/expandTypeScale.js +17 -0
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +11 -0
- package/dist/theme/index.d.ts +2 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/theme/tokens.stylex.d.ts +12 -0
- package/dist/theme/tokens.stylex.d.ts.map +1 -1
- package/dist/theme/tokens.stylex.js +23 -0
- package/dist/utils/focusOutline.stylex.d.ts +122 -0
- package/dist/utils/focusOutline.stylex.d.ts.map +1 -0
- package/dist/utils/focusOutline.stylex.js +143 -0
- package/dist/utils/focusReturn.d.ts +24 -0
- package/dist/utils/focusReturn.d.ts.map +1 -0
- package/dist/utils/focusReturn.js +29 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +6 -1
- package/dist/utils/mergeRefs.d.ts.map +1 -1
- package/dist/utils/mergeRefs.js +7 -5
- package/dist/utils/rtlStyles.d.ts +2 -2
- package/dist/utils/rtlStyles.js +2 -2
- package/dist/utils/themeProps.d.ts +20 -1
- package/dist/utils/themeProps.d.ts.map +1 -1
- package/dist/utils/themeProps.js +8 -2
- package/locales/en.json +52 -0
- package/locales/pseudo.json +39 -0
- package/package.json +8 -3
- package/src/AlertDialog/AlertDialog.doc.mjs +16 -3
- package/src/AlertDialog/AlertDialog.test.tsx +208 -1
- package/src/AlertDialog/AlertDialog.tsx +21 -3
- package/src/AppShell/AppShell.doc.mjs +11 -0
- package/src/AppShell/AppShell.test.tsx +139 -0
- package/src/AppShell/AppShell.tsx +32 -22
- package/src/AspectRatio/AspectRatio.doc.mjs +27 -3
- package/src/AspectRatio/AspectRatio.test.tsx +30 -0
- package/src/AspectRatio/AspectRatio.tsx +13 -0
- package/src/Avatar/Avatar.doc.mjs +2 -13
- package/src/Avatar/Avatar.test.tsx +33 -8
- package/src/Avatar/Avatar.tsx +52 -50
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -9
- package/src/Banner/Banner.test.tsx +58 -1
- package/src/Banner/Banner.tsx +33 -11
- package/src/Breadcrumbs/BreadcrumbItem.tsx +16 -6
- package/src/Breadcrumbs/index.ts +5 -1
- package/src/Button/Button.doc.mjs +2 -2
- package/src/Button/Button.tsx +22 -41
- package/src/Calendar/Calendar.tsx +2 -1
- package/src/Calendar/styles.ts +0 -8
- package/src/Carousel/Carousel.doc.mjs +3 -0
- package/src/Carousel/Carousel.tsx +10 -6
- package/src/Chat/Chat.doc.mjs +2 -0
- package/src/Chat/ChatDictationButton.tsx +7 -1
- package/src/Chat/ChatLayoutScrollButton.test.tsx +22 -0
- package/src/Chat/ChatLayoutScrollButton.tsx +8 -1
- package/src/Chat/ChatSendButton.tsx +2 -3
- package/src/Chat/ChatToolCalls.test.tsx +35 -0
- package/src/Chat/ChatToolCalls.tsx +36 -16
- package/src/Chat/useTriggerMenu.tsx +2 -5
- package/src/CheckboxInput/CheckboxInput.doc.mjs +4 -2
- package/src/CheckboxInput/CheckboxInput.test.tsx +120 -0
- package/src/CheckboxInput/CheckboxInput.tsx +45 -202
- package/src/ClickableCard/ClickableCard.tsx +6 -11
- package/src/CodeBlock/CodeBlock.doc.mjs +3 -0
- package/src/CodeBlock/CodeBlock.test.tsx +91 -0
- package/src/CodeBlock/CodeBlock.tsx +57 -80
- package/src/Collapsible/Collapsible.tsx +22 -18
- package/src/CommandPalette/CommandPalette.test.tsx +29 -0
- package/src/CommandPalette/CommandPaletteItem.tsx +7 -4
- package/src/CommandPalette/CommandPaletteList.tsx +2 -2
- package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
- package/src/ComplexSelector/ComplexSelector.test.tsx +73 -0
- package/src/ComplexSelector/ComplexSelector.tsx +41 -28
- package/src/ContextMenu/ContextMenu.doc.mjs +1 -1
- package/src/ContextMenu/ContextMenu.test.tsx +41 -0
- package/src/ContextMenu/ContextMenu.tsx +3 -3
- package/src/ContextMenu/index.ts +5 -1
- package/src/DateInput/DateInput.doc.mjs +18 -3
- package/src/DateInput/DateInput.test.tsx +112 -11
- package/src/DateInput/DateInput.tsx +44 -25
- package/src/DateRangeInput/DateRangeInput.doc.mjs +10 -2
- package/src/DateRangeInput/DateRangeInput.test.tsx +110 -14
- package/src/DateRangeInput/DateRangeInput.tsx +27 -21
- package/src/DateTimeInput/DateTimeInput.doc.mjs +16 -2
- package/src/DateTimeInput/DateTimeInput.test.tsx +154 -1
- package/src/DateTimeInput/DateTimeInput.tsx +58 -17
- package/src/Dialog/Dialog.tsx +2 -9
- package/src/DropdownMenu/DropdownMenu.doc.mjs +5 -9
- package/src/DropdownMenu/DropdownMenu.test.tsx +534 -4
- package/src/DropdownMenu/DropdownMenu.tsx +113 -52
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +27 -30
- package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +34 -0
- package/src/DropdownMenu/DropdownMenuDivider.tsx +81 -0
- package/src/DropdownMenu/DropdownMenuItem.doc.mjs +39 -4
- package/src/DropdownMenu/DropdownMenuItem.tsx +48 -10
- package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +2 -1
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +30 -76
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +25 -19
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +44 -14
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +1 -5
- package/src/DropdownMenu/index.ts +8 -1
- package/src/DropdownMenu/renderDropdownItems.tsx +33 -34
- package/src/EmptyState/EmptyState.doc.mjs +4 -0
- package/src/EmptyState/EmptyState.test.tsx +62 -0
- package/src/EmptyState/EmptyState.tsx +16 -6
- package/src/Field/Field.doc.mjs +1 -0
- package/src/Field/InputClearButton.test.tsx +117 -0
- package/src/Field/InputClearButton.tsx +30 -3
- package/src/FileInput/FileInput.tsx +21 -8
- package/src/HoverCard/useHoverCard.tsx +2 -23
- package/src/Icon/Icon.doc.mjs +2 -1
- package/src/Indicator/CheckIndicator.tsx +174 -0
- package/src/Indicator/CheckboxIndicator.tsx +257 -0
- package/src/Indicator/Indicator.doc.mjs +344 -0
- package/src/Indicator/Indicator.test.tsx +393 -0
- package/src/Indicator/RadioIndicator.tsx +206 -0
- package/src/Indicator/index.ts +46 -0
- package/src/Indicator/indicator.markers.stylex.ts +19 -0
- package/src/Indicator/indicatorRegistry.test.tsx +106 -0
- package/src/Indicator/indicatorRegistry.ts +120 -0
- package/src/Indicator/types.ts +197 -0
- package/src/Indicator/useIndicator.ts +39 -0
- package/src/Item/Item.doc.mjs +1 -1
- package/src/Item/Item.tsx +7 -14
- package/src/Layer/useLayer.doc.mjs +2 -2
- package/src/Layer/useLayer.test.tsx +85 -0
- package/src/Layer/useLayer.tsx +39 -1
- package/src/Lightbox/Lightbox.doc.mjs +1 -1
- package/src/Lightbox/Lightbox.tsx +45 -49
- package/src/Link/Link.tsx +4 -11
- package/src/MobileNav/MobileNav.tsx +3 -3
- package/src/MoreMenu/MoreMenu.doc.mjs +31 -0
- package/src/MoreMenu/MoreMenu.test.tsx +68 -1
- package/src/MoreMenu/MoreMenu.tsx +19 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +16 -1
- package/src/MultiSelector/MultiSelector.test.tsx +259 -16
- package/src/MultiSelector/MultiSelector.tsx +144 -121
- package/src/NumberInput/NumberInput.doc.mjs +265 -50
- package/src/NumberInput/NumberInput.test.tsx +726 -21
- package/src/NumberInput/NumberInput.tsx +353 -59
- package/src/Outline/Outline.tsx +2 -5
- package/src/Pagination/Pagination.doc.mjs +1 -1
- package/src/Pagination/Pagination.test.tsx +22 -7
- package/src/Pagination/Pagination.tsx +38 -35
- package/src/Popover/Popover.doc.mjs +2 -0
- package/src/Popover/Popover.tsx +6 -7
- package/src/Popover/usePopover.tsx +37 -4
- package/src/PowerSearch/PowerSearch.tsx +1 -1
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +42 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +7 -0
- package/src/ProgressBar/ProgressBar.doc.mjs +27 -5
- package/src/ProgressBar/ProgressBar.test.tsx +224 -1
- package/src/ProgressBar/ProgressBar.tsx +126 -30
- package/src/RadioList/RadioList.doc.mjs +4 -2
- package/src/RadioList/RadioListItem.tsx +31 -130
- package/src/Resizable/ResizeHandle.tsx +2 -9
- package/src/SegmentedControl/SegmentedControlItem.tsx +2 -9
- package/src/SelectableCard/SelectableCard.tsx +4 -9
- package/src/Selector/Selector.doc.mjs +15 -4
- package/src/Selector/Selector.test.tsx +1234 -98
- package/src/Selector/Selector.tsx +254 -126
- package/src/Selector/hooks.ts +77 -50
- package/src/SideNav/SideNav.test.tsx +46 -0
- package/src/SideNav/SideNav.tsx +1 -0
- package/src/SideNav/SideNavCollapseButton.tsx +15 -10
- package/src/SideNav/SideNavHeading.tsx +48 -10
- package/src/SideNav/SideNavItem.tsx +20 -12
- package/src/Slider/Slider.tsx +2 -11
- package/src/Stack/Stack.doc.mjs +110 -0
- package/src/StatusDot/StatusDot.doc.mjs +12 -3
- package/src/Switch/Switch.test.tsx +32 -0
- package/src/Switch/Switch.tsx +18 -10
- package/src/TabList/Tab.tsx +2 -9
- package/src/TabList/TabMenu.tsx +14 -21
- package/src/Table/BaseTable.tsx +11 -0
- package/src/Table/Table.test.tsx +31 -0
- package/src/Table/TableRow.tsx +6 -0
- package/src/Table/index.ts +1 -4
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +30 -14
- package/src/Table/plugins/rowExpansion/index.ts +1 -4
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +125 -175
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +115 -458
- package/src/Table/plugins/sortable/useTableSortable.tsx +2 -6
- package/src/Table/plugins/tree/useTableTreeData.tsx +43 -24
- package/src/Table/tableContextMenu.test.tsx +27 -0
- package/src/Table/tableContextMenu.tsx +1 -0
- package/src/Table/types.ts +11 -0
- package/src/Table/useTableGroupedRows.doc.mjs +1 -1
- package/src/Table/useTableRowExpansion.doc.mjs +43 -66
- package/src/TextArea/TextArea.doc.mjs +42 -2
- package/src/TextArea/TextArea.test.tsx +219 -0
- package/src/TextArea/TextArea.tsx +34 -7
- package/src/TextInput/TextInput.doc.mjs +18 -2
- package/src/TextInput/TextInput.test.tsx +189 -0
- package/src/TextInput/TextInput.tsx +25 -31
- package/src/Thumbnail/Thumbnail.tsx +2 -9
- package/src/TimeInput/TimeInput.doc.mjs +2 -2
- package/src/TimeInput/TimeInput.test.tsx +84 -3
- package/src/TimeInput/TimeInput.tsx +26 -28
- package/src/Timestamp/Timestamp.doc.mjs +4 -3
- package/src/Timestamp/Timestamp.test.tsx +91 -14
- package/src/Timestamp/Timestamp.tsx +14 -2
- package/src/Timestamp/TimestampHoverCard.tsx +13 -35
- package/src/Timestamp/formatInstant.ts +16 -1
- package/src/Token/Token.tsx +5 -28
- package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
- package/src/Tokenizer/Tokenizer.test.tsx +45 -3
- package/src/Tokenizer/Tokenizer.tsx +10 -2
- package/src/Tooltip/useTooltip.tsx +2 -23
- package/src/TopNav/TopNav.tsx +1 -3
- package/src/TopNav/TopNavHeading.tsx +42 -13
- package/src/TopNav/TopNavItem.tsx +3 -21
- package/src/TopNav/TopNavMegaMenu.doc.mjs +5 -0
- package/src/TopNav/TopNavMegaMenu.test.tsx +433 -33
- package/src/TopNav/TopNavMegaMenu.tsx +118 -53
- package/src/TopNav/TopNavMegaMenuItem.tsx +2 -9
- package/src/TopNav/TopNavMenu.tsx +34 -37
- package/src/TreeList/TreeList.doc.mjs +5 -0
- package/src/TreeList/TreeList.test.tsx +208 -8
- package/src/TreeList/TreeList.tsx +22 -0
- package/src/TreeList/TreeListBranches.tsx +25 -2
- package/src/TreeList/TreeListItem.tsx +89 -52
- package/src/Typeahead/BaseTypeahead.tsx +19 -6
- package/src/Typeahead/Typeahead.doc.mjs +1 -0
- package/src/Typeahead/Typeahead.test.tsx +76 -3
- package/src/hooks/containerReveal.stylex.ts +102 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useClipboard.doc.mjs +87 -0
- package/src/hooks/useClipboard.test.tsx +151 -0
- package/src/hooks/useClipboard.ts +135 -0
- package/src/hooks/useContainerReveal.doc.mjs +7 -7
- package/src/hooks/useContainerReveal.test.tsx +42 -62
- package/src/hooks/useContainerReveal.ts +36 -91
- package/src/hooks/useImageMode.test.ts +1 -1
- package/src/hooks/useIndicatorFocusRing.tsx +120 -0
- package/src/hooks/useInputStatusIcon.tsx +8 -8
- package/src/hooks/useLongPress.test.tsx +18 -0
- package/src/hooks/useLongPress.ts +12 -2
- package/src/hooks/useScrollLock.test.ts +72 -0
- package/src/hooks/useScrollLock.ts +51 -19
- package/src/hooks/useStreamingText.test.ts +46 -1
- package/src/hooks/useStreamingText.ts +49 -7
- package/src/hooks/useTreeFocus.test.tsx +50 -0
- package/src/hooks/useTreeFocus.ts +14 -3
- package/src/hooks/useTypeahead.test.tsx +150 -0
- package/src/hooks/useTypeahead.ts +24 -9
- package/src/i18n/InternationalizationProvider.doc.mjs +7 -2
- package/src/index.ts +1 -0
- package/src/theme/defineTheme.ts +27 -0
- package/src/theme/derivedVarRegistry.test.ts +21 -1
- package/src/theme/derivedVarRegistry.ts +19 -6
- package/src/theme/expandColorScale.test.ts +14 -0
- package/src/theme/expandColorScale.ts +24 -9
- package/src/theme/expandTypeScale.test.ts +43 -0
- package/src/theme/expandTypeScale.ts +17 -0
- package/src/theme/generateThemeRules.test.ts +78 -20
- package/src/theme/generateThemeRules.ts +11 -0
- package/src/theme/index.ts +15 -0
- package/src/theme/tokens.stylex.ts +18 -0
- package/src/theme/tokens.test.ts +37 -0
- package/src/utils/focusOutline.stylex.ts +177 -0
- package/src/utils/focusReturn.test.ts +42 -0
- package/src/utils/focusReturn.ts +31 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/mergeRefs.test.ts +48 -0
- package/src/utils/mergeRefs.ts +12 -6
- package/src/utils/rtlStyles.ts +2 -2
- package/src/utils/themeProps.ts +26 -4
- package/dist/CheckboxInput/checkbox.markers.stylex.d.ts +0 -9
- package/dist/CheckboxInput/checkbox.markers.stylex.d.ts.map +0 -1
- package/dist/CheckboxInput/checkbox.markers.stylex.js +0 -14
- package/dist/RadioList/radio.markers.stylex.d.ts +0 -7
- package/dist/RadioList/radio.markers.stylex.d.ts.map +0 -1
- package/dist/RadioList/radio.markers.stylex.js +0 -12
- package/dist/hooks/containerReveal.pool.stylex.d.ts +0 -303
- package/dist/hooks/containerReveal.pool.stylex.d.ts.map +0 -1
- package/dist/hooks/containerReveal.pool.stylex.js +0 -292
- package/src/CheckboxInput/checkbox.markers.stylex.ts +0 -12
- package/src/RadioList/radio.markers.stylex.ts +0 -10
- package/src/hooks/containerReveal.pool.stylex.ts +0 -571
|
@@ -17,6 +17,19 @@
|
|
|
17
17
|
|
|
18
18
|
import {useEffect} from 'react';
|
|
19
19
|
|
|
20
|
+
interface ScrollLockSnapshot {
|
|
21
|
+
scrollX: number;
|
|
22
|
+
scrollY: number;
|
|
23
|
+
overflow: string;
|
|
24
|
+
position: string;
|
|
25
|
+
top: string;
|
|
26
|
+
left: string;
|
|
27
|
+
right: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let lockCount = 0;
|
|
31
|
+
let originalBodyState: ScrollLockSnapshot | null = null;
|
|
32
|
+
|
|
20
33
|
/**
|
|
21
34
|
* Locks body scroll when `isLocked` is true.
|
|
22
35
|
*
|
|
@@ -36,28 +49,47 @@ export function useScrollLock(isLocked: boolean): void {
|
|
|
36
49
|
return;
|
|
37
50
|
}
|
|
38
51
|
|
|
39
|
-
const scrollX = window.scrollX;
|
|
40
|
-
const scrollY = window.scrollY;
|
|
41
52
|
const {body} = document;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
|
|
54
|
+
if (lockCount === 0) {
|
|
55
|
+
const scrollX = window.scrollX;
|
|
56
|
+
const scrollY = window.scrollY;
|
|
57
|
+
|
|
58
|
+
originalBodyState = {
|
|
59
|
+
scrollX,
|
|
60
|
+
scrollY,
|
|
61
|
+
overflow: body.style.overflow,
|
|
62
|
+
position: body.style.position,
|
|
63
|
+
top: body.style.top,
|
|
64
|
+
left: body.style.left,
|
|
65
|
+
right: body.style.right,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
body.style.overflow = 'hidden';
|
|
69
|
+
body.style.position = 'fixed';
|
|
70
|
+
body.style.top = `-${scrollY}px`;
|
|
71
|
+
body.style.left = '0';
|
|
72
|
+
body.style.right = '0';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
lockCount += 1;
|
|
53
76
|
|
|
54
77
|
return () => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
lockCount -= 1;
|
|
79
|
+
|
|
80
|
+
if (lockCount !== 0 || originalBodyState == null) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const state = originalBodyState;
|
|
85
|
+
originalBodyState = null;
|
|
86
|
+
|
|
87
|
+
body.style.overflow = state.overflow;
|
|
88
|
+
body.style.position = state.position;
|
|
89
|
+
body.style.top = state.top;
|
|
90
|
+
body.style.left = state.left;
|
|
91
|
+
body.style.right = state.right;
|
|
92
|
+
window.scrollTo(state.scrollX, state.scrollY);
|
|
61
93
|
};
|
|
62
94
|
}, [isLocked]);
|
|
63
95
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
|
|
4
4
|
import {renderHook, act} from '@testing-library/react';
|
|
5
|
-
import {useStreamingText} from './useStreamingText';
|
|
5
|
+
import {snapToGraphemeBoundary, useStreamingText} from './useStreamingText';
|
|
6
6
|
|
|
7
7
|
describe('useStreamingText', () => {
|
|
8
8
|
let rafCallbacks: ((time: number) => void)[];
|
|
@@ -189,4 +189,49 @@ describe('useStreamingText', () => {
|
|
|
189
189
|
// (or close to it — the hook drains charsPerTick per tickMs)
|
|
190
190
|
expect(result.current.length).toBeGreaterThan(targetText.length * 0.5);
|
|
191
191
|
});
|
|
192
|
+
|
|
193
|
+
it('never reveals a tick boundary landing mid-surrogate-pair or mid-ZWJ-emoji-sequence (#4779)', () => {
|
|
194
|
+
// natural speed advances 10 UTF-16 code units per tick. 9 ASCII chars
|
|
195
|
+
// (indices 0-8) followed by a 4-person ZWJ family emoji (indices 9-19,
|
|
196
|
+
// one grapheme cluster, 11 code units) means the first tick's raw
|
|
197
|
+
// boundary (index 10) lands one code unit into the family emoji's first
|
|
198
|
+
// surrogate pair -- exactly the failure case from the issue.
|
|
199
|
+
const family = '\u{1F468}\u{1F469}\u{1F467}\u{1F466}';
|
|
200
|
+
const targetText = 'a'.repeat(9) + family + 'end';
|
|
201
|
+
const {result} = renderHook(() => useStreamingText(targetText, true));
|
|
202
|
+
|
|
203
|
+
expect(rafCallbacks.length).toBeGreaterThan(0);
|
|
204
|
+
const cb = rafCallbacks.pop()!;
|
|
205
|
+
act(() => cb(performance.now() + 20));
|
|
206
|
+
|
|
207
|
+
// Without the fix this would be 'a'.repeat(9) + a lone high surrogate
|
|
208
|
+
// (renders as U+FFFD). With the fix, the whole not-yet-complete family
|
|
209
|
+
// cluster is held back rather than rendering a broken glyph.
|
|
210
|
+
expect(result.current).toBe('a'.repeat(9));
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('snapToGraphemeBoundary', () => {
|
|
215
|
+
it('does not split a surrogate pair', () => {
|
|
216
|
+
const text = 'a\u{1F389}b'; // 🎉 is a surrogate pair: indices 1 (high) and 2 (low)
|
|
217
|
+
expect(snapToGraphemeBoundary(text, 2)).toBe(1);
|
|
218
|
+
expect(snapToGraphemeBoundary(text, 1)).toBe(1);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('does not split a ZWJ emoji sequence', () => {
|
|
222
|
+
const family = '\u{1F468}\u{1F469}\u{1F467}\u{1F466}';
|
|
223
|
+
const text = 'x' + family + 'y';
|
|
224
|
+
const familyStart = 1;
|
|
225
|
+
const familyEnd = 1 + family.length;
|
|
226
|
+
for (let offset = familyStart + 1; offset < familyEnd; offset++) {
|
|
227
|
+
expect(snapToGraphemeBoundary(text, offset)).toBe(familyStart);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('leaves boundary offsets (start, end, plain-ASCII midpoints) unchanged', () => {
|
|
232
|
+
const text = 'hello';
|
|
233
|
+
expect(snapToGraphemeBoundary(text, 0)).toBe(0);
|
|
234
|
+
expect(snapToGraphemeBoundary(text, 5)).toBe(5);
|
|
235
|
+
expect(snapToGraphemeBoundary(text, 3)).toBe(3);
|
|
236
|
+
});
|
|
192
237
|
});
|
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
* @position Core hook; smooths bursty streamed text into steady character reveal
|
|
10
10
|
*
|
|
11
11
|
* Decouples the arrival rate of streamed chunks from the display rate,
|
|
12
|
-
* draining characters at a steady pace using requestAnimationFrame.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* draining characters at a steady pace using requestAnimationFrame. The
|
|
13
|
+
* reveal cadence advances by a fixed number of UTF-16 code units per tick;
|
|
14
|
+
* the rendered slice is then snapped back to the nearest grapheme cluster
|
|
15
|
+
* boundary (see snapToGraphemeBoundary) so a tick landing inside a
|
|
16
|
+
* surrogate pair, flag sequence, or ZWJ emoji doesn't render a lone
|
|
17
|
+
* surrogate or a partial glyph for one frame.
|
|
15
18
|
*
|
|
16
19
|
* Animation timing derives from Astryx motion tokens (via useTheme) when
|
|
17
20
|
* an Theme provider is present. Falls back to sensible defaults when
|
|
@@ -68,6 +71,43 @@ const CHARS_PER_TICK = {
|
|
|
68
71
|
instant: Infinity,
|
|
69
72
|
} as const;
|
|
70
73
|
|
|
74
|
+
// Reuse a single segmenter when the runtime supports Intl.Segmenter.
|
|
75
|
+
// Granularity is fixed and locale doesn't affect grapheme-cluster
|
|
76
|
+
// boundaries, so one instance is safe to share across calls.
|
|
77
|
+
const graphemeSegmenter =
|
|
78
|
+
typeof Intl.Segmenter === 'function'
|
|
79
|
+
? new Intl.Segmenter(undefined, {granularity: 'grapheme'})
|
|
80
|
+
: null;
|
|
81
|
+
|
|
82
|
+
function isLowSurrogate(codeUnit: number): boolean {
|
|
83
|
+
return codeUnit >= 0xdc00 && codeUnit <= 0xdfff;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Snap a UTF-16 code-unit offset down to the start of the grapheme cluster
|
|
88
|
+
* it falls inside, so slicing `text` at the returned offset never splits a
|
|
89
|
+
* surrogate pair, flag sequence, or ZWJ emoji mid-glyph. Offsets at the
|
|
90
|
+
* start/end of the string are returned unchanged (nothing to snap).
|
|
91
|
+
*
|
|
92
|
+
* Falls back to snapping off a lone low surrogate when `Intl.Segmenter` is
|
|
93
|
+
* unavailable — narrower than full grapheme-cluster awareness (a ZWJ
|
|
94
|
+
* sequence or flag pair can still split in that path), but it's what's
|
|
95
|
+
* achievable without a full grapheme-aware dependency, and it's strictly
|
|
96
|
+
* better than the unguarded default of never checking at all.
|
|
97
|
+
*/
|
|
98
|
+
export function snapToGraphemeBoundary(text: string, offset: number): number {
|
|
99
|
+
if (offset <= 0 || offset >= text.length) {
|
|
100
|
+
return offset;
|
|
101
|
+
}
|
|
102
|
+
if (graphemeSegmenter) {
|
|
103
|
+
// `containing` only returns undefined for an out-of-range index; offset
|
|
104
|
+
// is already guarded to [1, text.length - 1] above, so this is always
|
|
105
|
+
// defined at runtime — the `??` is to satisfy the type, which is wider.
|
|
106
|
+
return graphemeSegmenter.segment(text).containing(offset)?.index ?? offset;
|
|
107
|
+
}
|
|
108
|
+
return isLowSurrogate(text.charCodeAt(offset)) ? offset - 1 : offset;
|
|
109
|
+
}
|
|
110
|
+
|
|
71
111
|
/**
|
|
72
112
|
* Smooths bursty streamed text into a steady character-by-character reveal.
|
|
73
113
|
*
|
|
@@ -76,9 +116,11 @@ const CHARS_PER_TICK = {
|
|
|
76
116
|
* reduced motion, the progressive reveal is skipped entirely and the full
|
|
77
117
|
* `targetText` is returned immediately.
|
|
78
118
|
*
|
|
79
|
-
* The
|
|
80
|
-
*
|
|
81
|
-
*
|
|
119
|
+
* The reveal cadence itself advances by a fixed number of UTF-16 code units
|
|
120
|
+
* per tick, not on word or markdown-syntax boundaries — but the rendered
|
|
121
|
+
* slice is always snapped back to the nearest grapheme cluster boundary, so
|
|
122
|
+
* a tick landing mid-surrogate-pair or mid-emoji-sequence never renders a
|
|
123
|
+
* broken glyph.
|
|
82
124
|
*
|
|
83
125
|
* @example
|
|
84
126
|
* ```
|
|
@@ -191,5 +233,5 @@ export function useStreamingText(
|
|
|
191
233
|
return targetText;
|
|
192
234
|
}
|
|
193
235
|
|
|
194
|
-
return targetText.slice(0, displayedLen);
|
|
236
|
+
return targetText.slice(0, snapToGraphemeBoundary(targetText, displayedLen));
|
|
195
237
|
}
|
|
@@ -261,4 +261,54 @@ describe('useTreeFocus activation + typeahead', () => {
|
|
|
261
261
|
fireEvent.keyDown(tree, {key: 'c'});
|
|
262
262
|
expect(screen.getByTestId('c')).toHaveFocus();
|
|
263
263
|
});
|
|
264
|
+
|
|
265
|
+
it('typeahead cycles through same-letter matches on repeated presses', () => {
|
|
266
|
+
const nodes: Node[] = [
|
|
267
|
+
{id: 'apple', label: 'Apple', level: 1},
|
|
268
|
+
{id: 'apricot', label: 'Apricot', level: 1},
|
|
269
|
+
{id: 'avocado', label: 'Avocado', level: 1},
|
|
270
|
+
];
|
|
271
|
+
render(<Tree collapsed={nodes} />);
|
|
272
|
+
const tree = screen.getByRole('tree');
|
|
273
|
+
screen.getByTestId('apple').focus();
|
|
274
|
+
|
|
275
|
+
fireEvent.keyDown(tree, {key: 'a'});
|
|
276
|
+
expect(screen.getByTestId('apricot')).toHaveFocus();
|
|
277
|
+
|
|
278
|
+
// A repeat must cycle, not extend the query to "aa" (as useTypeahead does).
|
|
279
|
+
fireEvent.keyDown(tree, {key: 'a'});
|
|
280
|
+
expect(screen.getByTestId('avocado')).toHaveFocus();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('typeahead searches from the top when no treeitem has focus', () => {
|
|
284
|
+
const nodes: Node[] = [
|
|
285
|
+
{id: 'apple', label: 'Apple', level: 1},
|
|
286
|
+
{id: 'banana', label: 'Banana', level: 1},
|
|
287
|
+
{id: 'avocado', label: 'Avocado', level: 1},
|
|
288
|
+
];
|
|
289
|
+
render(<Tree collapsed={nodes} />);
|
|
290
|
+
const tree = screen.getByRole('tree');
|
|
291
|
+
|
|
292
|
+
// No .focus() call: with no current item, the first match must win.
|
|
293
|
+
fireEvent.keyDown(tree, {key: 'a'});
|
|
294
|
+
expect(screen.getByTestId('apple')).toHaveFocus();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('typeahead keeps focus on an item that still matches the refined buffer', () => {
|
|
298
|
+
const nodes: Node[] = [
|
|
299
|
+
{id: 'apple', label: 'Apple', level: 1},
|
|
300
|
+
{id: 'banana', label: 'Banana', level: 1},
|
|
301
|
+
{id: 'apricot', label: 'Apricot', level: 1},
|
|
302
|
+
];
|
|
303
|
+
render(<Tree collapsed={nodes} />);
|
|
304
|
+
const tree = screen.getByRole('tree');
|
|
305
|
+
screen.getByTestId('apple').focus();
|
|
306
|
+
|
|
307
|
+
fireEvent.keyDown(tree, {key: 'a'});
|
|
308
|
+
expect(screen.getByTestId('apricot')).toHaveFocus();
|
|
309
|
+
|
|
310
|
+
// "ap" refines the search; Apricot still matches, so focus holds.
|
|
311
|
+
fireEvent.keyDown(tree, {key: 'p'});
|
|
312
|
+
expect(screen.getByTestId('apricot')).toHaveFocus();
|
|
313
|
+
});
|
|
264
314
|
});
|
|
@@ -377,14 +377,25 @@ export function useTreeFocus<T extends HTMLElement = HTMLElement>(
|
|
|
377
377
|
if (state.timer != null) {
|
|
378
378
|
clearTimeout(state.timer);
|
|
379
379
|
}
|
|
380
|
-
|
|
380
|
+
// "aa" would match nothing; a repeat means "next match" (useTypeahead).
|
|
381
|
+
const char = e.key.toLowerCase();
|
|
382
|
+
const isRepeatSameChar =
|
|
383
|
+
state.buffer.length > 0 &&
|
|
384
|
+
state.buffer.split('').every(c => c === char);
|
|
385
|
+
state.buffer = isRepeatSameChar ? char : state.buffer + char;
|
|
381
386
|
state.timer = setTimeout(() => {
|
|
382
387
|
typeaheadRef.current.buffer = '';
|
|
383
388
|
}, typeaheadResetMs) as unknown as number;
|
|
384
389
|
|
|
385
390
|
const query = state.buffer;
|
|
386
|
-
|
|
387
|
-
const
|
|
391
|
+
// A longer query is refining, so the current item may still be the match.
|
|
392
|
+
const hasCurrent = currentIndex >= 0;
|
|
393
|
+
const start = hasCurrent ? currentIndex : 0;
|
|
394
|
+
const offset = hasCurrent && query.length === 1 ? 1 : 0;
|
|
395
|
+
const ordered = [
|
|
396
|
+
...items.slice(start + offset),
|
|
397
|
+
...items.slice(0, start + offset),
|
|
398
|
+
];
|
|
388
399
|
const match = ordered.find(
|
|
389
400
|
item =>
|
|
390
401
|
!itemDisabled(item) &&
|
|
@@ -106,4 +106,154 @@ describe('useTypeahead', () => {
|
|
|
106
106
|
expect(api.onKeyDown(key(' '))).toBe(false);
|
|
107
107
|
expect(onMatch).not.toHaveBeenCalled();
|
|
108
108
|
});
|
|
109
|
+
|
|
110
|
+
it('extends a live buffer with a space', () => {
|
|
111
|
+
const onMatch = vi.fn();
|
|
112
|
+
const {result} = renderHook(() =>
|
|
113
|
+
useTypeahead({
|
|
114
|
+
getItemLabels: () => ['Newark', 'New York'],
|
|
115
|
+
onMatch,
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
for (const k of 'new ') {
|
|
119
|
+
result.current.onKeyDown(key(k));
|
|
120
|
+
}
|
|
121
|
+
result.current.onKeyDown(key('y'));
|
|
122
|
+
// "new y" reaches New York; a space that ended the buffer instead would
|
|
123
|
+
// leave "newy", which matches nothing.
|
|
124
|
+
expect(onMatch).toHaveBeenLastCalledWith(1);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('ignores a Space chorded with ctrl or meta even mid-buffer', () => {
|
|
128
|
+
const onMatch = vi.fn();
|
|
129
|
+
const {result} = renderHook(() =>
|
|
130
|
+
useTypeahead({
|
|
131
|
+
getItemLabels: () => ['Melon', 'New York'],
|
|
132
|
+
onMatch,
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
expect(result.current.onKeyDown(key('n'))).toBe(true);
|
|
136
|
+
expect(onMatch).toHaveBeenLastCalledWith(1);
|
|
137
|
+
|
|
138
|
+
// A chord is not typing. Consuming it would append a raw space and leave
|
|
139
|
+
// the buffer as "n ", poisoning every keystroke until the reset window.
|
|
140
|
+
expect(
|
|
141
|
+
result.current.onKeyDown(
|
|
142
|
+
new KeyboardEvent('keydown', {key: ' ', ctrlKey: true}),
|
|
143
|
+
),
|
|
144
|
+
).toBe(false);
|
|
145
|
+
expect(
|
|
146
|
+
result.current.onKeyDown(
|
|
147
|
+
new KeyboardEvent('keydown', {key: ' ', metaKey: true}),
|
|
148
|
+
),
|
|
149
|
+
).toBe(false);
|
|
150
|
+
|
|
151
|
+
result.current.onKeyDown(key('e'));
|
|
152
|
+
expect(onMatch).toHaveBeenCalledTimes(2);
|
|
153
|
+
expect(onMatch).toHaveBeenLastCalledWith(1);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('starts a fresh single-character search after the current item', () => {
|
|
157
|
+
const {onMatch, api} = setup({current: 0}); // Apple is current
|
|
158
|
+
api.onKeyDown(key('a'));
|
|
159
|
+
// A single-character search begins at current + 1, so pressing the current
|
|
160
|
+
// item's own initial advances instead of re-matching it into a dead key.
|
|
161
|
+
expect(onMatch).toHaveBeenCalledWith(1); // Apricot
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('searches from the top when there is no current item', () => {
|
|
165
|
+
// The last item also matches, so this distinguishes "from the top" from
|
|
166
|
+
// wrapping backwards off -1 and hitting the bottom of the list first.
|
|
167
|
+
const onMatch = vi.fn();
|
|
168
|
+
const {result} = renderHook(() =>
|
|
169
|
+
useTypeahead({
|
|
170
|
+
getItemLabels: () => ['Apple', 'Berry', 'Avocado'],
|
|
171
|
+
onMatch,
|
|
172
|
+
getCurrentIndex: () => -1,
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
result.current.onKeyDown(key('a'));
|
|
176
|
+
expect(onMatch).toHaveBeenCalledWith(0); // Apple, not Avocado
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('keeps the current item in range once the buffer is multi-character', () => {
|
|
180
|
+
const {onMatch, api} = setup({current: 0});
|
|
181
|
+
api.onKeyDown(key('a')); // single char: advances to Apricot
|
|
182
|
+
expect(onMatch).toHaveBeenLastCalledWith(1);
|
|
183
|
+
api.onKeyDown(key('p')); // "ap" refines, so Apple is a candidate again
|
|
184
|
+
expect(onMatch).toHaveBeenLastCalledWith(0);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('skips unmatchable label slots without shifting the reported index', () => {
|
|
188
|
+
// Callers may pass null/empty entries to keep the index mapping 1:1 with
|
|
189
|
+
// their own items — menus pass el.textContent, which can be null.
|
|
190
|
+
const onMatch = vi.fn();
|
|
191
|
+
const {result} = renderHook(() =>
|
|
192
|
+
useTypeahead({
|
|
193
|
+
getItemLabels: () => [null, '', ' ', undefined, 'Apple'],
|
|
194
|
+
onMatch,
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
result.current.onKeyDown(key('a'));
|
|
198
|
+
expect(onMatch).toHaveBeenCalledWith(4);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('treats any negative or stale current index safely', () => {
|
|
202
|
+
// A sentinel other than -1, and an index left over from a longer list:
|
|
203
|
+
// neither may search backwards or report an index outside the list.
|
|
204
|
+
const fromBelowZero = vi.fn();
|
|
205
|
+
const {result: below} = renderHook(() =>
|
|
206
|
+
useTypeahead({
|
|
207
|
+
getItemLabels: () => LABELS,
|
|
208
|
+
onMatch: fromBelowZero,
|
|
209
|
+
getCurrentIndex: () => -5,
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
below.current.onKeyDown(key('a'));
|
|
213
|
+
expect(fromBelowZero).toHaveBeenCalledWith(0); // Apple, from the top
|
|
214
|
+
|
|
215
|
+
const fromStale = vi.fn();
|
|
216
|
+
const {result: stale} = renderHook(() =>
|
|
217
|
+
useTypeahead({
|
|
218
|
+
getItemLabels: () => LABELS,
|
|
219
|
+
onMatch: fromStale,
|
|
220
|
+
getCurrentIndex: () => 7,
|
|
221
|
+
}),
|
|
222
|
+
);
|
|
223
|
+
stale.current.onKeyDown(key('b'));
|
|
224
|
+
expect(fromStale).toHaveBeenCalledWith(2); // Banana, in range
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('wraps onto the only item when the list has one entry', () => {
|
|
228
|
+
// "Advance past the current item" is about cycling; with one item the
|
|
229
|
+
// cycle is itself. The caller decides whether that is a no-op.
|
|
230
|
+
const onMatch = vi.fn();
|
|
231
|
+
const {result} = renderHook(() =>
|
|
232
|
+
useTypeahead({
|
|
233
|
+
getItemLabels: () => ['Apple'],
|
|
234
|
+
onMatch,
|
|
235
|
+
getCurrentIndex: () => 0,
|
|
236
|
+
}),
|
|
237
|
+
);
|
|
238
|
+
expect(result.current.onKeyDown(key('a'))).toBe(true);
|
|
239
|
+
expect(onMatch).toHaveBeenCalledWith(0);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('matches characters composed with Option/Alt', () => {
|
|
243
|
+
const onMatch = vi.fn();
|
|
244
|
+
const {result} = renderHook(() =>
|
|
245
|
+
useTypeahead({
|
|
246
|
+
getItemLabels: () => ['Ångström', 'Berlin'],
|
|
247
|
+
onMatch,
|
|
248
|
+
getCurrentIndex: () => -1,
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
// Option+a on macOS emits a printable 'å' with altKey set. Excluding it
|
|
252
|
+
// makes accented labels untypeable; real chords still carry ctrl/meta.
|
|
253
|
+
const handled = result.current.onKeyDown(
|
|
254
|
+
new KeyboardEvent('keydown', {key: 'å', altKey: true}),
|
|
255
|
+
);
|
|
256
|
+
expect(handled).toBe(true);
|
|
257
|
+
expect(onMatch).toHaveBeenCalledWith(0);
|
|
258
|
+
});
|
|
109
259
|
});
|
|
@@ -32,8 +32,9 @@ export interface UseTypeaheadOptions {
|
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* The index to start searching from — typically the currently focused item,
|
|
35
|
-
* so repeated presses of the same letter cycle through matches.
|
|
36
|
-
*
|
|
35
|
+
* so repeated presses of the same letter cycle through matches. Any negative
|
|
36
|
+
* value (the default) means nothing is current: the search runs from the top
|
|
37
|
+
* and never skips the first item.
|
|
37
38
|
*/
|
|
38
39
|
getCurrentIndex?: () => number;
|
|
39
40
|
|
|
@@ -67,9 +68,12 @@ export interface UseTypeaheadReturn {
|
|
|
67
68
|
function isPrintableCharacter(e: React.KeyboardEvent | KeyboardEvent): boolean {
|
|
68
69
|
return (
|
|
69
70
|
e.key.length === 1 &&
|
|
71
|
+
// Alt alone is not excluded: Option+letter on macOS composes a printable
|
|
72
|
+
// character (Option+a → "å"), and dropping those makes accented labels
|
|
73
|
+
// untypeable. Real chords still carry ctrl or meta — including AltGr,
|
|
74
|
+
// which sets ctrlKey on Windows and Linux.
|
|
70
75
|
!e.ctrlKey &&
|
|
71
76
|
!e.metaKey &&
|
|
72
|
-
!e.altKey &&
|
|
73
77
|
// A lone space is used for activation in menus, not typeahead-from-empty.
|
|
74
78
|
e.key !== ' '
|
|
75
79
|
);
|
|
@@ -133,8 +137,14 @@ export function useTypeahead(options: UseTypeaheadOptions): UseTypeaheadReturn {
|
|
|
133
137
|
const onKeyDown = useCallback(
|
|
134
138
|
(e: React.KeyboardEvent | KeyboardEvent): boolean => {
|
|
135
139
|
// A bare Space with no active buffer is not typeahead (menus activate on
|
|
136
|
-
// Space); once the user is mid-typing, Space extends the query.
|
|
137
|
-
|
|
140
|
+
// Space); once the user is mid-typing, Space extends the query. Chorded
|
|
141
|
+
// with ctrl or meta it is neither — that is an OS or IME shortcut, and
|
|
142
|
+
// consuming it would append a raw space that poisons the whole buffer.
|
|
143
|
+
const isSpaceMidType =
|
|
144
|
+
e.key === ' ' &&
|
|
145
|
+
!e.ctrlKey &&
|
|
146
|
+
!e.metaKey &&
|
|
147
|
+
bufferRef.current.length > 0;
|
|
138
148
|
if (!isPrintableCharacter(e) && !isSpaceMidType) {
|
|
139
149
|
return false;
|
|
140
150
|
}
|
|
@@ -154,11 +164,16 @@ export function useTypeahead(options: UseTypeaheadOptions): UseTypeaheadReturn {
|
|
|
154
164
|
bufferRef.current = nextBuffer;
|
|
155
165
|
scheduleReset();
|
|
156
166
|
|
|
157
|
-
const
|
|
167
|
+
const current = getCurrentIndex?.() ?? -1;
|
|
158
168
|
const count = labels.length;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
169
|
+
const hasCurrent = current >= 0;
|
|
170
|
+
const start = hasCurrent ? current : 0;
|
|
171
|
+
// A single-character search starts AFTER the current item, so pressing a
|
|
172
|
+
// letter walks to the next item beginning with it instead of re-matching
|
|
173
|
+
// the one already selected (native <select> and APG getIndexByLetter).
|
|
174
|
+
// Once the buffer is longer it is refining a match, so the current item
|
|
175
|
+
// stays in range. With nothing current there is nothing to advance past.
|
|
176
|
+
const offset = hasCurrent && nextBuffer.length === 1 ? 1 : 0;
|
|
162
177
|
|
|
163
178
|
for (let i = 0; i < count; i++) {
|
|
164
179
|
const index = (start + offset + i + count) % count;
|
|
@@ -40,7 +40,7 @@ export const docs = {
|
|
|
40
40
|
{
|
|
41
41
|
guidance: true,
|
|
42
42
|
description:
|
|
43
|
-
'Set the `dir` attribute on `<html>` (or a wrapping element) yourself
|
|
43
|
+
'Set the `dir` attribute on `<html>` (or a wrapping element) yourself; the provider does not set it. Astryx components mirror layout and directional icons from the DOM `dir`, so an RTL locale won\'t visually mirror without it. Use `getLocaleDirection(locale)` to derive the value for both the provider and the DOM.',
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
guidance: false,
|
|
@@ -76,7 +76,7 @@ export const docs = {
|
|
|
76
76
|
type: "'ltr' | 'rtl'",
|
|
77
77
|
required: false,
|
|
78
78
|
description:
|
|
79
|
-
'Explicit text-direction override for the context. When omitted, direction is derived from `locale` via `Intl.Locale.getTextInfo()`. This sets the direction Astryx reads, but it does NOT set the DOM `dir` attribute
|
|
79
|
+
'Explicit text-direction override for the context. When omitted, direction is derived from `locale` via `Intl.Locale.getTextInfo()`. This sets the direction Astryx reads, but it does NOT set the DOM `dir` attribute; you must set `dir` on `<html>` (or a wrapping element) yourself, since Astryx components mirror layout and directional icons from the DOM `dir`, not from this prop. Set both to the same value and keep them in sync.',
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
82
|
name: 'children',
|
|
@@ -163,6 +163,11 @@ export const docsDense = {
|
|
|
163
163
|
description:
|
|
164
164
|
'Use real BCP 47 tags like `fr`, `pt-BR`, or `ar`; regional locales fall back to base language before English.',
|
|
165
165
|
},
|
|
166
|
+
{
|
|
167
|
+
guidance: true,
|
|
168
|
+
description:
|
|
169
|
+
'Set `dir` on `<html>` (or a wrapper) yourself; the provider does not. Astryx mirrors layout and directional icons from the DOM `dir`, so an RTL locale will not mirror without it. Use `getLocaleDirection(locale)` for both the provider and the DOM.',
|
|
170
|
+
},
|
|
166
171
|
{
|
|
167
172
|
guidance: false,
|
|
168
173
|
description:
|
package/src/index.ts
CHANGED
package/src/theme/defineTheme.ts
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
import type {IconRegistry} from '../Icon/globalIconRegistry';
|
|
34
|
+
import type {IndicatorRegistry} from '../Indicator/types';
|
|
34
35
|
import type {TypographyConfig, FontWeight} from './types';
|
|
35
36
|
import {
|
|
36
37
|
resolveOnMedia,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
colorDefaults,
|
|
42
43
|
spacingDefaults,
|
|
43
44
|
sizeDefaults,
|
|
45
|
+
focusDefaults,
|
|
44
46
|
radiusDefaults,
|
|
45
47
|
shadowDefaults,
|
|
46
48
|
durationDefaults,
|
|
@@ -73,6 +75,7 @@ export type CoreTokenName =
|
|
|
73
75
|
| keyof typeof colorDefaults
|
|
74
76
|
| keyof typeof spacingDefaults
|
|
75
77
|
| keyof typeof sizeDefaults
|
|
78
|
+
| keyof typeof focusDefaults
|
|
76
79
|
| keyof typeof radiusDefaults
|
|
77
80
|
| keyof typeof shadowDefaults
|
|
78
81
|
| keyof typeof durationDefaults
|
|
@@ -280,6 +283,19 @@ export interface DefineThemeInput {
|
|
|
280
283
|
components?: ComponentStyleMap;
|
|
281
284
|
/** Icon registry — maps semantic icon names to React nodes */
|
|
282
285
|
icons?: Partial<IconRegistry>;
|
|
286
|
+
/**
|
|
287
|
+
* Indicator overrides — replaces the components that draw stateful control
|
|
288
|
+
* visuals with the theme's own, by name.
|
|
289
|
+
*
|
|
290
|
+
* Replacement is by indicator name, not per call site, so a single entry
|
|
291
|
+
* reaches every component that draws that indicator: mapping `check` to
|
|
292
|
+
* `RadioIndicator` gives radio visuals to every single-selection mark in the
|
|
293
|
+
* app.
|
|
294
|
+
*
|
|
295
|
+
* Each entry is checked against its indicator's family, so a replacement
|
|
296
|
+
* must accept the states that family passes.
|
|
297
|
+
*/
|
|
298
|
+
indicators?: IndicatorRegistry;
|
|
283
299
|
/**
|
|
284
300
|
* Default syntax highlighting theme for code components.
|
|
285
301
|
* Sets --color-syntax-* tokens at the theme root. Can be overridden
|
|
@@ -329,6 +345,8 @@ export interface DefinedTheme {
|
|
|
329
345
|
components?: ComponentStyleMap;
|
|
330
346
|
/** Icon registry */
|
|
331
347
|
icons?: Partial<IconRegistry>;
|
|
348
|
+
/** Indicator overrides for stateful control visuals, keyed by name */
|
|
349
|
+
indicators?: IndicatorRegistry;
|
|
332
350
|
/** Whether this theme has been pre-compiled by theme build CLI */
|
|
333
351
|
__built?: true;
|
|
334
352
|
/**
|
|
@@ -361,6 +379,7 @@ export const tokenDefaults: Record<string, string> = {
|
|
|
361
379
|
...colorDefaults,
|
|
362
380
|
...spacingDefaults,
|
|
363
381
|
...sizeDefaults,
|
|
382
|
+
...focusDefaults,
|
|
364
383
|
...radiusDefaults,
|
|
365
384
|
...shadowDefaults,
|
|
366
385
|
...durationDefaults,
|
|
@@ -622,11 +641,19 @@ export function defineTheme(input: DefineThemeInput): DefinedTheme {
|
|
|
622
641
|
? {...base.icons, ...input.icons}
|
|
623
642
|
: (input.icons ?? base?.icons);
|
|
624
643
|
|
|
644
|
+
// Indicator overrides merge by name, like icons: a child theme replacing one
|
|
645
|
+
// indicator keeps the ones its base replaced.
|
|
646
|
+
const indicators =
|
|
647
|
+
input.indicators && base?.indicators
|
|
648
|
+
? {...base.indicators, ...input.indicators}
|
|
649
|
+
: (input.indicators ?? base?.indicators);
|
|
650
|
+
|
|
625
651
|
const theme: DefinedTheme = {
|
|
626
652
|
name: input.name,
|
|
627
653
|
tokens,
|
|
628
654
|
components,
|
|
629
655
|
icons,
|
|
656
|
+
indicators,
|
|
630
657
|
__inputTokens: input.tokens,
|
|
631
658
|
__onDark,
|
|
632
659
|
__onLight,
|