@astryxdesign/core 0.1.7 → 0.1.8-canary.0c4d8d7
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 +65 -0
- package/README.md +30 -19
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +6 -2
- package/dist/AppShell/AppShellMobileContext.d.ts +8 -0
- package/dist/AppShell/AppShellMobileContext.d.ts.map +1 -1
- package/dist/Avatar/Avatar.d.ts +10 -5
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +12 -8
- package/dist/Avatar/AvatarSizeContext.d.ts +1 -1
- package/dist/Avatar/AvatarSizeContext.js +1 -1
- package/dist/Avatar/AvatarStatusDot.d.ts +2 -2
- package/dist/Avatar/AvatarStatusDot.js +2 -2
- package/dist/AvatarGroup/AvatarGroup.d.ts +2 -2
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.js +2 -2
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +16 -14
- package/dist/Banner/Banner.d.ts +9 -1
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +31 -2
- package/dist/Button/Button.d.ts +8 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +31 -2
- package/dist/ButtonGroup/ButtonGroup.d.ts +9 -1
- package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
- package/dist/ButtonGroup/ButtonGroup.js +30 -1
- package/dist/Card/Card.d.ts +8 -2
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +28 -2
- package/dist/Carousel/Carousel.d.ts +6 -2
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +55 -5
- package/dist/Chat/ChatComposer.d.ts +8 -0
- package/dist/Chat/ChatComposer.d.ts.map +1 -1
- package/dist/Chat/ChatComposer.js +31 -6
- package/dist/Chat/ChatComposerDrawer.d.ts +2 -0
- package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
- package/dist/Chat/ChatComposerDrawer.js +11 -1
- package/dist/Chat/ChatComposerInput.d.ts +20 -3
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +19 -3
- package/dist/Chat/ChatLayout.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatMessage.d.ts +1 -1
- package/dist/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/Chat/ChatMessage.js +1 -1
- package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
- package/dist/Chat/ChatSendButton.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.d.ts +5 -1
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +12 -3
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +10 -4
- package/dist/ClickableCard/ClickableCard.d.ts +9 -2
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +3 -0
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +8 -1
- package/dist/Collapsible/Collapsible.d.ts +11 -1
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +36 -4
- package/dist/CommandPalette/CommandPaletteInput.d.ts +7 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +6 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +4 -3
- package/dist/ContextMenu/index.d.ts +3 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/ContextMenu/index.js +7 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +10 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +2 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +8 -4
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +88 -0
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +169 -0
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.js +18 -0
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +68 -0
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuRadioGroup.js +69 -0
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +69 -0
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +178 -0
- package/dist/DropdownMenu/index.d.ts +5 -1
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/index.js +10 -1
- package/dist/DropdownMenu/menuItemRoles.d.ts +13 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -0
- package/dist/DropdownMenu/menuItemRoles.js +15 -0
- package/dist/FieldStatus/FieldStatus.d.ts +8 -0
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +22 -4
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +10 -2
- package/dist/HoverCard/HoverCard.d.ts +9 -1
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +2 -0
- package/dist/HoverCard/useHoverCard.d.ts +8 -0
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +7 -2
- package/dist/Icon/Icon.d.ts +43 -13
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +62 -27
- package/dist/Layer/useLayer.d.ts +6 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +2 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +6 -1
- package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
- package/dist/MobileNav/MobileNavToggle.js +4 -0
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +39 -12
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +16 -0
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/Outline/Outline.d.ts +68 -3
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +128 -26
- package/dist/Outline/useScrollSpy.d.ts +27 -7
- package/dist/Outline/useScrollSpy.d.ts.map +1 -1
- package/dist/Outline/useScrollSpy.js +202 -70
- package/dist/OverflowList/OverflowList.d.ts +20 -3
- package/dist/OverflowList/OverflowList.d.ts.map +1 -1
- package/dist/OverflowList/OverflowList.js +29 -4
- package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
- package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.d.ts +9 -2
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +14 -11
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +4 -2
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +16 -0
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +21 -8
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +15 -1
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +2 -2
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +2 -0
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +1 -1
- package/dist/Table/Table.d.ts +1 -1
- package/dist/Table/Table.js +1 -1
- package/dist/Table/index.d.ts +4 -0
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +2 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +24 -2
- package/dist/Table/plugins/rowStatus/index.d.ts +3 -0
- package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -0
- package/dist/Table/plugins/rowStatus/index.js +3 -0
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +60 -0
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -0
- package/dist/Table/plugins/rowStatus/useTableRowStatus.js +145 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts +12 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +4 -1
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +18 -7
- package/dist/Table/plugins/tree/index.d.ts +5 -0
- package/dist/Table/plugins/tree/index.d.ts.map +1 -0
- package/dist/Table/plugins/tree/index.js +4 -0
- package/dist/Table/plugins/tree/useTableTreeData.d.ts +38 -0
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -0
- package/dist/Table/plugins/tree/useTableTreeData.js +358 -0
- package/dist/Table/plugins/tree/useTableTreeState.d.ts +51 -0
- package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -0
- package/dist/Table/plugins/tree/useTableTreeState.js +175 -0
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +6 -3
- package/dist/Thumbnail/Thumbnail.d.ts +8 -1
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +46 -8
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +15 -4
- package/dist/Tokenizer/Tokenizer.d.ts +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +16 -4
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +8 -1
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +18 -8
- package/dist/TopNav/TopNavMegaMenu.js +10 -4
- package/dist/TopNav/TopNavMenu.d.ts +2 -1
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +68 -6
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +5 -14
- package/dist/astryx.css +51 -16
- package/dist/astryx.umd.js +56 -56
- package/dist/astryx.umd.js.map +4 -4
- package/dist/authoring/doc.d.ts.map +1 -1
- package/dist/hooks/computeOverflow.d.ts +47 -0
- package/dist/hooks/computeOverflow.d.ts.map +1 -0
- package/dist/hooks/computeOverflow.js +213 -0
- package/dist/hooks/useOverflow.d.ts +19 -0
- package/dist/hooks/useOverflow.d.ts.map +1 -1
- package/dist/hooks/useOverflow.js +37 -25
- package/dist/theme/Theme.js +1 -1
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.js +1 -1
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +51 -0
- package/dist/theme/hct.d.ts +0 -1
- package/dist/theme/hct.d.ts.map +1 -1
- package/dist/theme/hct.js +0 -3
- package/dist/utils/types.d.ts +13 -0
- package/dist/utils/types.d.ts.map +1 -1
- package/docs.mjs +32 -461
- package/locales/en.json +212 -172
- package/locales/fr-FR.json +10 -0
- package/locales/pseudo.json +30 -0
- package/package.json +3 -1
- package/scripts/postinstall.mjs +93 -0
- package/src/AppShell/AppShell.tsx +6 -0
- package/src/AppShell/AppShellMobileContext.tsx +8 -0
- package/src/AppShell/useAppShellMobile.doc.mjs +8 -0
- package/src/Avatar/Avatar.doc.mjs +6 -6
- package/src/Avatar/Avatar.tsx +16 -11
- package/src/Avatar/AvatarSizeContext.ts +1 -1
- package/src/Avatar/AvatarStatusDot.tsx +2 -2
- package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -1
- package/src/AvatarGroup/AvatarGroup.test.tsx +17 -3
- package/src/AvatarGroup/AvatarGroup.tsx +3 -3
- package/src/AvatarGroup/AvatarGroupOverflow.doc.mjs +4 -4
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +21 -5
- package/src/Banner/Banner.doc.mjs +9 -0
- package/src/Banner/Banner.test.tsx +30 -0
- package/src/Banner/Banner.tsx +47 -3
- package/src/Button/Button.doc.mjs +9 -0
- package/src/Button/Button.test.tsx +49 -0
- package/src/Button/Button.tsx +25 -1
- package/src/ButtonGroup/ButtonGroup.doc.mjs +3 -0
- package/src/ButtonGroup/ButtonGroup.test.tsx +37 -0
- package/src/ButtonGroup/ButtonGroup.tsx +32 -0
- package/src/Card/Card.doc.mjs +9 -0
- package/src/Card/Card.test.tsx +21 -3
- package/src/Card/Card.tsx +35 -2
- package/src/Carousel/Carousel.doc.mjs +1 -0
- package/src/Carousel/Carousel.test.tsx +149 -1
- package/src/Carousel/Carousel.tsx +62 -5
- package/src/Chat/Chat.doc.mjs +1 -1
- package/src/Chat/ChatComposer.doc.mjs +9 -0
- package/src/Chat/ChatComposer.test.tsx +35 -0
- package/src/Chat/ChatComposer.tsx +58 -9
- package/src/Chat/ChatComposerDrawer.test.tsx +61 -0
- package/src/Chat/ChatComposerDrawer.tsx +11 -1
- package/src/Chat/ChatComposerInput.doc.mjs +7 -0
- package/src/Chat/ChatComposerInput.test.tsx +87 -0
- package/src/Chat/ChatComposerInput.tsx +35 -3
- package/src/Chat/ChatLayout.tsx +3 -1
- package/src/Chat/ChatLayoutScrollButton.tsx +3 -1
- package/src/Chat/ChatMessage.tsx +4 -2
- package/src/Chat/ChatMessageMetadata.tsx +1 -2
- package/src/Chat/ChatSendButton.tsx +5 -1
- package/src/Chat/ChatToolCalls.test.tsx +64 -0
- package/src/Chat/ChatToolCalls.tsx +13 -1
- package/src/Chat/useTriggerMenu.tsx +3 -1
- package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
- package/src/CheckboxInput/CheckboxInput.test.tsx +32 -1
- package/src/CheckboxList/CheckboxList.test.tsx +9 -1
- package/src/Citation/Citation.test.tsx +51 -0
- package/src/Citation/Citation.tsx +8 -2
- package/src/ClickableCard/ClickableCard.doc.mjs +2 -0
- package/src/ClickableCard/ClickableCard.test.tsx +34 -0
- package/src/ClickableCard/ClickableCard.tsx +11 -1
- package/src/CodeBlock/CodeBlock.test.tsx +56 -0
- package/src/CodeBlock/CodeBlock.tsx +9 -1
- package/src/Collapsible/Collapsible.doc.mjs +7 -0
- package/src/Collapsible/Collapsible.test.tsx +80 -0
- package/src/Collapsible/Collapsible.tsx +58 -5
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +26 -9
- package/src/CommandPalette/CommandPaletteInput.test.tsx +34 -0
- package/src/CommandPalette/CommandPaletteInput.tsx +11 -0
- package/src/ContextMenu/ContextMenu.test.tsx +75 -1
- package/src/ContextMenu/ContextMenu.tsx +10 -5
- package/src/ContextMenu/index.ts +15 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +35 -0
- package/src/DateTimeInput/DateTimeInput.tsx +14 -2
- package/src/Dialog/Dialog.tsx +12 -2
- package/src/DropdownMenu/DropdownMenu.doc.mjs +23 -0
- package/src/DropdownMenu/DropdownMenu.tsx +11 -6
- package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +95 -0
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +236 -0
- package/src/DropdownMenu/DropdownMenuContext.tsx +29 -1
- package/src/DropdownMenu/DropdownMenuItem.doc.mjs +7 -1
- package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +80 -0
- package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +118 -0
- package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +80 -0
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +232 -0
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +163 -0
- package/src/DropdownMenu/index.ts +23 -3
- package/src/DropdownMenu/menuItemRoles.ts +22 -0
- package/src/Field/Field.doc.mjs +1 -1
- package/src/Field/Field.test.tsx +57 -12
- package/src/Field/FieldLabel.doc.mjs +1 -1
- package/src/FieldStatus/FieldStatus.doc.mjs +17 -5
- package/src/FieldStatus/FieldStatus.test.tsx +78 -40
- package/src/FieldStatus/FieldStatus.tsx +22 -4
- package/src/FileInput/FileInput.test.tsx +52 -3
- package/src/FileInput/FileInput.tsx +13 -2
- package/src/HoverCard/HoverCard.doc.mjs +13 -0
- package/src/HoverCard/HoverCard.test.tsx +21 -2
- package/src/HoverCard/HoverCard.tsx +11 -0
- package/src/HoverCard/useHoverCard.doc.mjs +2 -0
- package/src/HoverCard/useHoverCard.tsx +24 -2
- package/src/Icon/Icon.doc.mjs +12 -0
- package/src/Icon/Icon.test.tsx +142 -0
- package/src/Icon/Icon.tsx +98 -30
- package/src/IconButton/IconButton.doc.mjs +8 -0
- package/src/IconButton/IconButton.test.tsx +15 -18
- package/src/Item/Item.doc.mjs +1 -1
- package/src/Layer/useLayer.tsx +8 -0
- package/src/MobileNav/MobileNav.tsx +12 -1
- package/src/MobileNav/MobileNavToggle.test.tsx +116 -0
- package/src/MobileNav/MobileNavToggle.tsx +9 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +4 -2
- package/src/MultiSelector/MultiSelector.test.tsx +162 -1
- package/src/MultiSelector/MultiSelector.tsx +55 -12
- package/src/MultiSelector/hooks.ts +17 -0
- package/src/NumberInput/NumberInput.doc.mjs +2 -2
- package/src/NumberInput/NumberInput.test.tsx +11 -7
- package/src/NumberInput/NumberInput.tsx +1 -2
- package/src/Outline/Outline.doc.mjs +123 -3
- package/src/Outline/Outline.test.tsx +735 -0
- package/src/Outline/Outline.tsx +183 -33
- package/src/Outline/useScrollSpy.ts +248 -72
- package/src/OverflowList/OverflowList.doc.mjs +39 -6
- package/src/OverflowList/OverflowList.test.tsx +111 -0
- package/src/OverflowList/OverflowList.tsx +48 -8
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +67 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
- package/src/PowerSearch/usePowerSearchSource.ts +1 -2
- package/src/SelectableCard/SelectableCard.doc.mjs +2 -0
- package/src/SelectableCard/SelectableCard.test.tsx +46 -4
- package/src/SelectableCard/SelectableCard.tsx +27 -14
- package/src/Selector/Selector.test.tsx +69 -0
- package/src/Selector/Selector.tsx +5 -1
- package/src/Selector/SelectorOption.doc.mjs +1 -1
- package/src/Selector/hooks.ts +17 -0
- package/src/SideNav/SideNav.test.tsx +119 -3
- package/src/SideNav/SideNavCollapseButton.tsx +6 -1
- package/src/SideNav/SideNavHeading.tsx +24 -8
- package/src/SideNav/SideNavItem.doc.mjs +2 -2
- package/src/SideNav/SideNavItem.tsx +5 -1
- package/src/Slider/Slider.test.tsx +62 -2
- package/src/Slider/Slider.tsx +15 -1
- package/src/Switch/Switch.doc.mjs +1 -1
- package/src/Switch/Switch.test.tsx +32 -1
- package/src/TabList/Tab.tsx +5 -1
- package/src/TabList/TabList.doc.mjs +3 -3
- package/src/TabList/TabList.test.tsx +66 -0
- package/src/TabList/TabList.tsx +8 -0
- package/src/TabList/TabMenu.tsx +4 -1
- package/src/Table/Table.doc.mjs +2 -0
- package/src/Table/Table.tsx +1 -1
- package/src/Table/index.ts +12 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +6 -2
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +90 -1
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +37 -5
- package/src/Table/plugins/rowStatus/index.ts +7 -0
- package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +138 -0
- package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +189 -0
- package/src/Table/plugins/selection/useTableSelection.test.tsx +16 -0
- package/src/Table/plugins/selection/useTableSelection.tsx +18 -1
- package/src/Table/plugins/sortable/useTableSortable.test.tsx +95 -9
- package/src/Table/plugins/sortable/useTableSortable.tsx +23 -7
- package/src/Table/plugins/tree/index.ts +12 -0
- package/src/Table/plugins/tree/useTableTreeData-perf.test.tsx +163 -0
- package/src/Table/plugins/tree/useTableTreeData.test.tsx +594 -0
- package/src/Table/plugins/tree/useTableTreeData.tsx +495 -0
- package/src/Table/plugins/tree/useTableTreeState.test.tsx +636 -0
- package/src/Table/plugins/tree/useTableTreeState.tsx +253 -0
- package/src/Table/useBaseTablePlugins.ts +6 -2
- package/src/Table/useTableRowStatus.doc.mjs +30 -0
- package/src/Table/useTableSelection.doc.mjs +65 -14
- package/src/Table/useTableTreeData.doc.mjs +75 -0
- package/src/Table/useTableTreeState.doc.mjs +98 -0
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextArea/TextArea.test.tsx +9 -1
- package/src/TextInput/TextInput.doc.mjs +1 -1
- package/src/Thumbnail/Thumbnail.doc.mjs +9 -0
- package/src/Thumbnail/Thumbnail.test.tsx +38 -8
- package/src/Thumbnail/Thumbnail.tsx +28 -1
- package/src/TimeInput/TimeInput.test.tsx +76 -1
- package/src/TimeInput/TimeInput.tsx +19 -6
- package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
- package/src/Tokenizer/Tokenizer.test.tsx +137 -2
- package/src/Tokenizer/Tokenizer.tsx +16 -4
- package/src/Toolbar/Toolbar.tsx +10 -3
- package/src/TopNav/TopNav.test.tsx +80 -1
- package/src/TopNav/TopNavHeading.tsx +24 -8
- package/src/TopNav/TopNavMegaMenu.test.tsx +149 -1
- package/src/TopNav/TopNavMegaMenu.tsx +10 -4
- package/src/TopNav/TopNavMenu.test.tsx +118 -4
- package/src/TopNav/TopNavMenu.tsx +83 -5
- package/src/TreeList/TreeList.test.tsx +32 -1
- package/src/TreeList/TreeListItem.tsx +19 -10
- package/src/authoring/doc.ts +1 -3
- package/src/docs-script.test.mjs +18 -13
- package/src/hooks/computeOverflow.test.ts +422 -0
- package/src/hooks/computeOverflow.ts +290 -0
- package/src/hooks/useOverflow.test.ts +96 -4
- package/src/hooks/useOverflow.ts +77 -33
- package/src/postinstall.test.mjs +71 -0
- package/src/theme/Theme.doc.mjs +4 -4
- package/src/theme/Theme.tsx +1 -1
- package/src/theme/defineTheme.ts +1 -1
- package/src/theme/generateThemeRules.test.ts +65 -0
- package/src/theme/generateThemeRules.ts +56 -0
- package/src/theme/hct.ts +0 -4
- package/src/utils/types.ts +14 -0
- package/dist/TreeList/treeListItem.markers.stylex.d.ts +0 -16
- package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +0 -1
- package/dist/TreeList/treeListItem.markers.stylex.js +0 -22
- package/src/TreeList/treeListItem.markers.stylex.ts +0 -20
|
@@ -45,6 +45,13 @@ export const docs = {
|
|
|
45
45
|
description: 'Whether the thumbnail is disabled.',
|
|
46
46
|
default: 'false',
|
|
47
47
|
},
|
|
48
|
+
{
|
|
49
|
+
name: 'elevation',
|
|
50
|
+
type: "'none' | 'low' | 'med' | 'high'",
|
|
51
|
+
description:
|
|
52
|
+
'Resting shadow depth. `none` (the default) keeps the existing hover-only shadow; set a level to raise the tile at rest.',
|
|
53
|
+
default: "'none'",
|
|
54
|
+
},
|
|
48
55
|
{
|
|
49
56
|
name: 'xstyle',
|
|
50
57
|
type: 'StyleXStyles',
|
|
@@ -101,6 +108,7 @@ export const docsZh = {
|
|
|
101
108
|
onClick: '\u70B9\u51FB\u5904\u7406\u5668\u3002\u6DFB\u52A0\u6309\u94AE\u8BED\u4E49\u548C\u60AC\u505C\u9634\u5F71\u3002',
|
|
102
109
|
isLoading: '\u663E\u793A\u9AA8\u67B6\u5C4F\uFF08\u65E0 src\uFF09\u6216\u4E0A\u4F20\u8986\u76D6\u5C42\uFF08\u6709 src\uFF09\u3002',
|
|
103
110
|
isDisabled: '\u662F\u5426\u7981\u7528\u7F29\u7565\u56FE\u3002',
|
|
111
|
+
elevation: '\u9759\u6B62\u9634\u5F71\u6DF1\u5EA6\uFF1Anone\uFF08\u9ED8\u8BA4\uFF0C\u4FDD\u7559\u4EC5\u60AC\u505C\u9634\u5F71\uFF09| low | med | high\u3002',
|
|
104
112
|
xstyle: '\u7528\u4E8E\u5E03\u5C40\u81EA\u5B9A\u4E49\u7684 StyleX \u6837\u5F0F\u3002\u5FC5\u987B\u662F stylex.create() \u7684\u503C\uFF0C\u800C\u975E\u5185\u8054\u6837\u5F0F\u5BF9\u8C61\u3002',
|
|
105
113
|
className: '\u6839\u5143\u7D20\u7684 CSS \u7C7B\u540D\u3002\u5EFA\u8BAE\u4F7F\u7528 xstyle\u3002',
|
|
106
114
|
style: '\u6839\u5143\u7D20\u7684\u5185\u8054\u6837\u5F0F\u3002\u5EFA\u8BAE\u4F7F\u7528 xstyle\u3002',
|
|
@@ -141,6 +149,7 @@ export const docsDense = {
|
|
|
141
149
|
onClick: '(e) => void. Adds button semantics + hover shadow.',
|
|
142
150
|
isLoading: 'Skeleton (no src) or upload overlay (with src). Default: false.',
|
|
143
151
|
isDisabled: 'Disabled state. Default: false.',
|
|
152
|
+
elevation: 'Resting shadow depth: none (keeps hover-only shadow) | low | med | high.',
|
|
144
153
|
xstyle: 'stylex.create() for layout.',
|
|
145
154
|
className: 'CSS class. Prefer xstyle.',
|
|
146
155
|
style: 'Inline styles. Prefer xstyle.',
|
|
@@ -21,16 +21,19 @@ describe('Thumbnail', () => {
|
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
it('shows skeleton when isLoading with no src', () => {
|
|
24
|
-
const {container} = render(
|
|
25
|
-
<Thumbnail isLoading data-testid="thumb" />,
|
|
26
|
-
);
|
|
24
|
+
const {container} = render(<Thumbnail isLoading data-testid="thumb" />);
|
|
27
25
|
expect(container.querySelector('.astryx-skeleton')).toBeInTheDocument();
|
|
28
26
|
expect(screen.queryByRole('img')).toBeNull();
|
|
29
27
|
});
|
|
30
28
|
|
|
31
29
|
it('shows image with upload overlay when isLoading with src', () => {
|
|
32
30
|
render(
|
|
33
|
-
<Thumbnail
|
|
31
|
+
<Thumbnail
|
|
32
|
+
src="/local.jpg"
|
|
33
|
+
alt="Uploading"
|
|
34
|
+
isLoading
|
|
35
|
+
data-testid="thumb"
|
|
36
|
+
/>,
|
|
34
37
|
);
|
|
35
38
|
const img = screen.getByRole('img');
|
|
36
39
|
expect(img).toHaveAttribute('src', '/local.jpg');
|
|
@@ -75,7 +78,6 @@ describe('Thumbnail', () => {
|
|
|
75
78
|
).toBeInTheDocument();
|
|
76
79
|
});
|
|
77
80
|
|
|
78
|
-
|
|
79
81
|
it('label is shown via tooltip, not as inline text', () => {
|
|
80
82
|
render(<Thumbnail label="photo.png" data-testid="thumb" />);
|
|
81
83
|
// Label should exist in DOM (tooltip) but not as a direct child text node
|
|
@@ -117,9 +119,7 @@ describe('Thumbnail', () => {
|
|
|
117
119
|
|
|
118
120
|
it('is not interactive when isLoading', () => {
|
|
119
121
|
const onClick = vi.fn();
|
|
120
|
-
render(
|
|
121
|
-
<Thumbnail src="/img.jpg" alt="Test" onClick={onClick} isLoading />,
|
|
122
|
-
);
|
|
122
|
+
render(<Thumbnail src="/img.jpg" alt="Test" onClick={onClick} isLoading />);
|
|
123
123
|
expect(screen.queryByRole('button')).toBeNull();
|
|
124
124
|
});
|
|
125
125
|
|
|
@@ -128,4 +128,34 @@ describe('Thumbnail', () => {
|
|
|
128
128
|
render(<Thumbnail ref={ref} data-testid="thumb" />);
|
|
129
129
|
expect(ref).toHaveBeenCalled();
|
|
130
130
|
});
|
|
131
|
+
|
|
132
|
+
describe('elevation', () => {
|
|
133
|
+
// The elevation class lives on the image container (the shadow surface),
|
|
134
|
+
// which is the root's first element child.
|
|
135
|
+
const imageContainerClass = (
|
|
136
|
+
elevation: 'none' | 'low' | 'med' | 'high',
|
|
137
|
+
) => {
|
|
138
|
+
const {container} = render(
|
|
139
|
+
<Thumbnail src="/x.png" alt="x" elevation={elevation} />,
|
|
140
|
+
);
|
|
141
|
+
return container.firstElementChild!.firstElementChild!.className;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
it('renders a distinct class for each elevation level', () => {
|
|
145
|
+
const classes = new Set([
|
|
146
|
+
imageContainerClass('none'),
|
|
147
|
+
imageContainerClass('low'),
|
|
148
|
+
imageContainerClass('med'),
|
|
149
|
+
imageContainerClass('high'),
|
|
150
|
+
]);
|
|
151
|
+
expect(classes.size).toBe(4);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('defaults to flat (elevation none)', () => {
|
|
155
|
+
const {container: def} = render(<Thumbnail src="/x.png" alt="x" />);
|
|
156
|
+
expect(def.firstElementChild!.firstElementChild!.className).toBe(
|
|
157
|
+
imageContainerClass('none'),
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
131
161
|
});
|
|
@@ -38,6 +38,7 @@ import {Tooltip} from '../Tooltip/Tooltip';
|
|
|
38
38
|
import {MediaTheme} from '../theme/MediaTheme';
|
|
39
39
|
import {useImageMode} from '../hooks/useImageMode';
|
|
40
40
|
import type {BaseProps} from '../BaseProps';
|
|
41
|
+
import type {Elevation} from '../utils/types';
|
|
41
42
|
import {mergeProps} from '../utils';
|
|
42
43
|
import {themeProps} from '../utils/themeProps';
|
|
43
44
|
import {useTranslator} from '../i18n';
|
|
@@ -90,6 +91,12 @@ export interface ThumbnailProps extends BaseProps<HTMLDivElement> {
|
|
|
90
91
|
* @default false
|
|
91
92
|
*/
|
|
92
93
|
isDisabled?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Resting elevation — the shadow depth the tile sits at. `none` (the default)
|
|
96
|
+
* keeps the existing hover-only shadow; a set level raises the tile at rest.
|
|
97
|
+
* @default 'none'
|
|
98
|
+
*/
|
|
99
|
+
elevation?: Elevation;
|
|
93
100
|
/**
|
|
94
101
|
* Test ID for testing frameworks.
|
|
95
102
|
*/
|
|
@@ -116,6 +123,10 @@ const styles = stylex.create({
|
|
|
116
123
|
borderRadius: radiusVars['--radius-element'],
|
|
117
124
|
overflow: 'hidden',
|
|
118
125
|
backgroundColor: colorVars['--color-neutral'],
|
|
126
|
+
// Resting elevation is set via --_thumbnail-elevation (see elevationStyles).
|
|
127
|
+
// Routing it through a var lets the interactive hover bump (below) override
|
|
128
|
+
// just the resting value without clobbering the elevation prop.
|
|
129
|
+
boxShadow: 'var(--_thumbnail-elevation, none)',
|
|
119
130
|
},
|
|
120
131
|
image: {
|
|
121
132
|
width: '100%',
|
|
@@ -144,7 +155,10 @@ const styles = stylex.create({
|
|
|
144
155
|
transitionDuration: durationVars['--duration-fast'],
|
|
145
156
|
transitionTimingFunction: easeVars['--ease-standard'],
|
|
146
157
|
boxShadow: {
|
|
147
|
-
default
|
|
158
|
+
// Rest at the elevation prop's level (default none); bump to med on hover
|
|
159
|
+
// as before. Reading the var here keeps a set elevation visible at rest
|
|
160
|
+
// instead of being reset to flat.
|
|
161
|
+
default: 'var(--_thumbnail-elevation, none)',
|
|
148
162
|
':hover': {
|
|
149
163
|
'@media (hover: hover)': shadowVars['--shadow-med'],
|
|
150
164
|
},
|
|
@@ -201,6 +215,17 @@ const styles = stylex.create({
|
|
|
201
215
|
},
|
|
202
216
|
});
|
|
203
217
|
|
|
218
|
+
// Resting elevation → shadow token. Sets --_thumbnail-elevation (read by the
|
|
219
|
+
// image container and the interactive hover style) rather than box-shadow
|
|
220
|
+
// directly, so a set resting elevation survives the interactive hover bump.
|
|
221
|
+
// 'none' is the default — the existing hover-only shadow is unchanged.
|
|
222
|
+
const elevationStyles = stylex.create({
|
|
223
|
+
none: {'--_thumbnail-elevation': 'none'},
|
|
224
|
+
low: {'--_thumbnail-elevation': shadowVars['--shadow-low']},
|
|
225
|
+
med: {'--_thumbnail-elevation': shadowVars['--shadow-med']},
|
|
226
|
+
high: {'--_thumbnail-elevation': shadowVars['--shadow-high']},
|
|
227
|
+
});
|
|
228
|
+
|
|
204
229
|
// =============================================================================
|
|
205
230
|
// Placeholder icon — a simple image silhouette
|
|
206
231
|
// =============================================================================
|
|
@@ -246,6 +271,7 @@ export function Thumbnail({
|
|
|
246
271
|
onClick,
|
|
247
272
|
isLoading = false,
|
|
248
273
|
isDisabled = false,
|
|
274
|
+
elevation = 'none',
|
|
249
275
|
xstyle,
|
|
250
276
|
className,
|
|
251
277
|
style,
|
|
@@ -311,6 +337,7 @@ export function Thumbnail({
|
|
|
311
337
|
<div
|
|
312
338
|
{...stylex.props(
|
|
313
339
|
styles.imageContainer,
|
|
340
|
+
elevationStyles[elevation],
|
|
314
341
|
isInteractive && styles.interactive,
|
|
315
342
|
)}>
|
|
316
343
|
{isInteractive ? (
|
|
@@ -9,12 +9,24 @@
|
|
|
9
9
|
* SYNC: When TimeInput.tsx changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
12
|
+
import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
|
|
13
13
|
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {TimeInput} from './TimeInput';
|
|
16
16
|
import {InputGroup, InputGroupText} from '../InputGroup';
|
|
17
17
|
import type {ISOTimeString} from '../utils';
|
|
18
|
+
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
19
|
+
|
|
20
|
+
function politeRegion(): HTMLElement | null {
|
|
21
|
+
return document.querySelector('[data-astryx-live-region="polite"]');
|
|
22
|
+
}
|
|
23
|
+
function assertiveRegion(): HTMLElement | null {
|
|
24
|
+
return document.querySelector('[data-astryx-live-region="assertive"]');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
__resetLiveRegionsForTest();
|
|
29
|
+
});
|
|
18
30
|
|
|
19
31
|
describe('TimeInput', () => {
|
|
20
32
|
it('renders with label', () => {
|
|
@@ -352,6 +364,69 @@ describe('TimeInput', () => {
|
|
|
352
364
|
// duplicated.
|
|
353
365
|
expect(container.querySelectorAll('svg')).toHaveLength(1);
|
|
354
366
|
});
|
|
367
|
+
|
|
368
|
+
// The grouped status node exists only for aria-describedby; announcing
|
|
369
|
+
// happens through the persistent useAnnounce regions because a live
|
|
370
|
+
// region mounted together with its content is not reliably announced.
|
|
371
|
+
it('keeps the grouped status node role-free while announcing via the persistent region', async () => {
|
|
372
|
+
render(
|
|
373
|
+
<InputGroup label="Schedule">
|
|
374
|
+
<InputGroupText>Starts</InputGroupText>
|
|
375
|
+
<TimeInput
|
|
376
|
+
label="Start time"
|
|
377
|
+
isLabelHidden
|
|
378
|
+
value={'09:00' as ISOTimeString}
|
|
379
|
+
onChange={() => {}}
|
|
380
|
+
status={{type: 'error', message: 'Start time is required'}}
|
|
381
|
+
/>
|
|
382
|
+
</InputGroup>,
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
const input = screen.getByRole('textbox', {
|
|
386
|
+
name: 'Schedule Start time',
|
|
387
|
+
});
|
|
388
|
+
const describedByIDs =
|
|
389
|
+
input.getAttribute('aria-describedby')?.split(' ') ?? [];
|
|
390
|
+
const statusNode = describedByIDs
|
|
391
|
+
.map(id => document.getElementById(id))
|
|
392
|
+
.find(el => el?.textContent === 'Start time is required');
|
|
393
|
+
expect(statusNode).toBeTruthy();
|
|
394
|
+
expect(statusNode).not.toHaveAttribute('role');
|
|
395
|
+
expect(statusNode).not.toHaveAttribute('aria-live');
|
|
396
|
+
|
|
397
|
+
await waitFor(() => {
|
|
398
|
+
expect(assertiveRegion()).toHaveTextContent('Start time is required');
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Regression: a grouped status message appearing after mount (the common
|
|
403
|
+
// validation flow) must land in the persistent announce region.
|
|
404
|
+
it('announces a grouped status message that appears after mount', async () => {
|
|
405
|
+
const grouped = (status?: {
|
|
406
|
+
type: 'error' | 'warning';
|
|
407
|
+
message: string;
|
|
408
|
+
}) => (
|
|
409
|
+
<InputGroup label="Schedule">
|
|
410
|
+
<InputGroupText>Starts</InputGroupText>
|
|
411
|
+
<TimeInput
|
|
412
|
+
label="Start time"
|
|
413
|
+
isLabelHidden
|
|
414
|
+
value={'09:00' as ISOTimeString}
|
|
415
|
+
onChange={() => {}}
|
|
416
|
+
status={status}
|
|
417
|
+
/>
|
|
418
|
+
</InputGroup>
|
|
419
|
+
);
|
|
420
|
+
const {rerender} = render(grouped());
|
|
421
|
+
expect(politeRegion()).toBeNull();
|
|
422
|
+
|
|
423
|
+
rerender(grouped({type: 'warning', message: 'Schedule is unusual'}));
|
|
424
|
+
await waitFor(() => {
|
|
425
|
+
expect(politeRegion()).toHaveTextContent('Schedule is unusual');
|
|
426
|
+
});
|
|
427
|
+
// Non-error statuses stay on the polite channel.
|
|
428
|
+
expect(assertiveRegion()).toHaveTextContent('');
|
|
429
|
+
});
|
|
355
430
|
});
|
|
356
431
|
|
|
357
432
|
describe('disabledMessage', () => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file TimeInput.tsx
|
|
7
|
-
* @input Uses React, useId, useState, useCallback, useRef, Field, Icon, InputGroupContext
|
|
7
|
+
* @input Uses React, useId, useState, useCallback, useRef, Field, Icon, InputGroupContext, useAnnounce
|
|
8
8
|
* @output Exports TimeInput component, TimeInputProps
|
|
9
9
|
* @position Core implementation; consumed by index.ts, tested by TimeInput.test.tsx
|
|
10
10
|
*
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
useId,
|
|
21
21
|
useState,
|
|
22
22
|
useCallback,
|
|
23
|
+
useEffect,
|
|
23
24
|
useRef,
|
|
24
25
|
useMemo,
|
|
25
26
|
useOptimistic,
|
|
@@ -64,6 +65,7 @@ import {
|
|
|
64
65
|
import type {BaseProps} from '../BaseProps';
|
|
65
66
|
import type {SizeValue} from '../utils/types';
|
|
66
67
|
import {useSize} from '../SizeContext/SizeContext';
|
|
68
|
+
import {useAnnounce} from '../hooks/useAnnounce';
|
|
67
69
|
import {useInputContainer} from '../hooks/useInputContainer';
|
|
68
70
|
import {useInputGroup} from '../InputGroup/InputGroupContext';
|
|
69
71
|
import {groupStyles} from '../InputGroup/groupStyles';
|
|
@@ -369,6 +371,21 @@ export function TimeInput({
|
|
|
369
371
|
const [optimisticValue, setOptimisticValue] = useOptimistic(value);
|
|
370
372
|
const isBusy = isLoading || optimisticValue !== value;
|
|
371
373
|
|
|
374
|
+
// In grouped mode the status message renders as a visually-hidden node that
|
|
375
|
+
// exists only for aria-describedby. Announce it through the persistent
|
|
376
|
+
// useAnnounce live regions instead of role/aria-live on that node — a live
|
|
377
|
+
// region mounted together with its content is not reliably announced.
|
|
378
|
+
// Ungrouped mode delegates to Field -> FieldStatus, which announces itself.
|
|
379
|
+
const announce = useAnnounce();
|
|
380
|
+
useEffect(() => {
|
|
381
|
+
if (inputGroup && status?.message) {
|
|
382
|
+
announce(
|
|
383
|
+
status.message,
|
|
384
|
+
status.type === 'error' ? 'assertive' : 'polite',
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}, [announce, inputGroup, status?.message, status?.type]);
|
|
388
|
+
|
|
372
389
|
// Disabled-reason tooltip. Disabled controls swallow pointer events, so the
|
|
373
390
|
// tooltip listeners attach to the input container (which already exists) and
|
|
374
391
|
// the input stays perceivable via aria-disabled instead of the disabled
|
|
@@ -611,11 +628,7 @@ export function TimeInput({
|
|
|
611
628
|
</VisuallyHidden>
|
|
612
629
|
)}
|
|
613
630
|
{inputGroup && status?.message && (
|
|
614
|
-
<VisuallyHidden
|
|
615
|
-
as="div"
|
|
616
|
-
id={statusMessageID}
|
|
617
|
-
role={status.type === 'error' ? 'alert' : 'status'}
|
|
618
|
-
aria-live={status.type === 'error' ? 'assertive' : 'polite'}>
|
|
631
|
+
<VisuallyHidden as="div" id={statusMessageID}>
|
|
619
632
|
{status.message}
|
|
620
633
|
</VisuallyHidden>
|
|
621
634
|
)}
|
|
@@ -31,7 +31,7 @@ export const docs = {
|
|
|
31
31
|
name: 'onChange',
|
|
32
32
|
type: '(items: T[], change: TokenizerChange<T>) => void',
|
|
33
33
|
description:
|
|
34
|
-
"Called when selection changes. The change argument includes the affected item and type ('add' | 'create' | 'remove' | 'reorder').",
|
|
34
|
+
"Called when selection changes. The change argument includes the affected item and type ('add' | 'create' | 'remove' | 'reorder'). Additions and removals (including Backspace on an empty input) are announced to screen readers via a polite live region.",
|
|
35
35
|
required: true,
|
|
36
36
|
},
|
|
37
37
|
{
|
|
@@ -9,12 +9,17 @@
|
|
|
9
9
|
* SYNC: When Tokenizer.tsx changes, update tests to match
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {describe, it, expect, vi, beforeAll, afterAll} from 'vitest';
|
|
12
|
+
import {describe, it, expect, vi, beforeAll, afterAll, afterEach} from 'vitest';
|
|
13
13
|
import {render, screen, fireEvent, act, waitFor} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {Tokenizer} from './Tokenizer';
|
|
16
|
+
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
16
17
|
import type {SearchSource, SearchableItem} from '../Typeahead/types';
|
|
17
18
|
|
|
19
|
+
function politeRegion(): HTMLElement | null {
|
|
20
|
+
return document.querySelector('[data-astryx-live-region="polite"]');
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
// Store original matches to restore later
|
|
19
24
|
const originalMatches = HTMLElement.prototype.matches;
|
|
20
25
|
|
|
@@ -60,6 +65,10 @@ afterAll(() => {
|
|
|
60
65
|
(HTMLElement.prototype as any).matches = originalMatches;
|
|
61
66
|
});
|
|
62
67
|
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
__resetLiveRegionsForTest();
|
|
70
|
+
});
|
|
71
|
+
|
|
63
72
|
// Test data
|
|
64
73
|
const users: SearchableItem[] = [
|
|
65
74
|
{id: '1', label: 'Alice'},
|
|
@@ -921,6 +930,130 @@ describe('Tokenizer', () => {
|
|
|
921
930
|
expect(screen.getByRole('combobox')).toBeDisabled();
|
|
922
931
|
});
|
|
923
932
|
});
|
|
933
|
+
describe('announcements', () => {
|
|
934
|
+
it('announces removal politely on Backspace with an empty input', async () => {
|
|
935
|
+
const onChange = vi.fn();
|
|
936
|
+
render(
|
|
937
|
+
<Tokenizer
|
|
938
|
+
label="Members"
|
|
939
|
+
searchSource={userSource}
|
|
940
|
+
value={[users[0], users[1]]}
|
|
941
|
+
onChange={onChange}
|
|
942
|
+
/>,
|
|
943
|
+
);
|
|
944
|
+
const input = screen.getByRole('combobox');
|
|
945
|
+
fireEvent.keyDown(input, {key: 'Backspace'});
|
|
946
|
+
expect(onChange).toHaveBeenCalledWith([users[0]], {
|
|
947
|
+
item: users[1],
|
|
948
|
+
type: 'remove',
|
|
949
|
+
});
|
|
950
|
+
await waitFor(() => {
|
|
951
|
+
expect(politeRegion()).toHaveTextContent('Removed Bob');
|
|
952
|
+
});
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
it("announces removal politely when clicking a token's remove button", async () => {
|
|
956
|
+
render(
|
|
957
|
+
<Tokenizer
|
|
958
|
+
label="Members"
|
|
959
|
+
searchSource={userSource}
|
|
960
|
+
value={[users[0], users[1]]}
|
|
961
|
+
onChange={() => {}}
|
|
962
|
+
/>,
|
|
963
|
+
);
|
|
964
|
+
fireEvent.click(screen.getByRole('button', {name: 'Remove Alice'}));
|
|
965
|
+
await waitFor(() => {
|
|
966
|
+
expect(politeRegion()).toHaveTextContent('Removed Alice');
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
it('announces addition politely when selecting a search result', async () => {
|
|
971
|
+
render(
|
|
972
|
+
<Tokenizer
|
|
973
|
+
label="Members"
|
|
974
|
+
searchSource={userSource}
|
|
975
|
+
value={[]}
|
|
976
|
+
onChange={() => {}}
|
|
977
|
+
hasEntriesOnFocus
|
|
978
|
+
debounceMs={0}
|
|
979
|
+
/>,
|
|
980
|
+
);
|
|
981
|
+
const input = screen.getByRole('combobox');
|
|
982
|
+
fireEvent.focus(input);
|
|
983
|
+
await act(async () => {
|
|
984
|
+
await new Promise(r => setTimeout(r, 50));
|
|
985
|
+
});
|
|
986
|
+
fireEvent.click(screen.getByText('Alice'));
|
|
987
|
+
await waitFor(() => {
|
|
988
|
+
expect(politeRegion()).toHaveTextContent('Added Alice');
|
|
989
|
+
});
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
it('announces addition politely when creating a token with hasCreate', async () => {
|
|
993
|
+
const emptySource: SearchSource = {
|
|
994
|
+
search: () => [],
|
|
995
|
+
bootstrap: () => [],
|
|
996
|
+
};
|
|
997
|
+
render(
|
|
998
|
+
<Tokenizer
|
|
999
|
+
label="Tags"
|
|
1000
|
+
searchSource={emptySource}
|
|
1001
|
+
value={[]}
|
|
1002
|
+
onChange={() => {}}
|
|
1003
|
+
hasCreate
|
|
1004
|
+
debounceMs={0}
|
|
1005
|
+
/>,
|
|
1006
|
+
);
|
|
1007
|
+
const input = screen.getByRole('combobox');
|
|
1008
|
+
await act(async () => {
|
|
1009
|
+
fireEvent.change(input, {target: {value: 'new-tag'}});
|
|
1010
|
+
});
|
|
1011
|
+
await act(async () => {
|
|
1012
|
+
await new Promise(r => setTimeout(r, 50));
|
|
1013
|
+
});
|
|
1014
|
+
fireEvent.click(screen.getByText('Create "new-tag"'));
|
|
1015
|
+
await waitFor(() => {
|
|
1016
|
+
expect(politeRegion()).toHaveTextContent('Added new-tag');
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
it('does not announce on mount', () => {
|
|
1021
|
+
render(
|
|
1022
|
+
<Tokenizer
|
|
1023
|
+
label="Members"
|
|
1024
|
+
searchSource={userSource}
|
|
1025
|
+
value={[users[0]]}
|
|
1026
|
+
onChange={() => {}}
|
|
1027
|
+
/>,
|
|
1028
|
+
);
|
|
1029
|
+
// The live regions are created lazily on first announce, so a mount
|
|
1030
|
+
// with pre-selected tokens must not create (or speak through) one.
|
|
1031
|
+
expect(politeRegion()).toBeNull();
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
it('does not announce add/remove while typing', async () => {
|
|
1035
|
+
render(
|
|
1036
|
+
<Tokenizer
|
|
1037
|
+
label="Members"
|
|
1038
|
+
searchSource={userSource}
|
|
1039
|
+
value={[]}
|
|
1040
|
+
onChange={() => {}}
|
|
1041
|
+
debounceMs={0}
|
|
1042
|
+
/>,
|
|
1043
|
+
);
|
|
1044
|
+
const input = screen.getByRole('combobox');
|
|
1045
|
+
await act(async () => {
|
|
1046
|
+
fireEvent.change(input, {target: {value: 'Ali'}});
|
|
1047
|
+
});
|
|
1048
|
+
await act(async () => {
|
|
1049
|
+
await new Promise(r => setTimeout(r, 50));
|
|
1050
|
+
});
|
|
1051
|
+
// BaseTypeahead announces result counts while typing (existing
|
|
1052
|
+
// behavior); typing alone must not produce add/remove announcements.
|
|
1053
|
+
expect(politeRegion()?.textContent ?? '').not.toMatch(/Added|Removed/);
|
|
1054
|
+
});
|
|
1055
|
+
});
|
|
1056
|
+
|
|
924
1057
|
describe('form participation', () => {
|
|
925
1058
|
it('submits one entry per token id under htmlName', () => {
|
|
926
1059
|
const {container} = render(
|
|
@@ -951,7 +1084,9 @@ describe('Tokenizer', () => {
|
|
|
951
1084
|
/>
|
|
952
1085
|
</form>,
|
|
953
1086
|
);
|
|
954
|
-
expect([
|
|
1087
|
+
expect([
|
|
1088
|
+
...new FormData(container.querySelector('form')!).keys(),
|
|
1089
|
+
]).toEqual([]);
|
|
955
1090
|
});
|
|
956
1091
|
});
|
|
957
1092
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file Tokenizer.tsx
|
|
7
|
-
* @input Uses React, BaseTypeahead, Field, Token
|
|
7
|
+
* @input Uses React, BaseTypeahead, Field, Token, useAnnounce
|
|
8
8
|
* @output Exports Tokenizer multi-select typeahead component
|
|
9
9
|
* @position Composed component; forwards DOM ref and exposes focus control via
|
|
10
10
|
* handleRef
|
|
@@ -43,6 +43,7 @@ import {renderIconSlot, type IconType} from '../Icon';
|
|
|
43
43
|
import {OverflowList} from '../OverflowList';
|
|
44
44
|
import {useLayer} from '../Layer/useLayer';
|
|
45
45
|
import {useTooltip} from '../Tooltip';
|
|
46
|
+
import {useAnnounce} from '../hooks/useAnnounce';
|
|
46
47
|
import {
|
|
47
48
|
colorVars,
|
|
48
49
|
spacingVars,
|
|
@@ -560,6 +561,12 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
560
561
|
[],
|
|
561
562
|
);
|
|
562
563
|
|
|
564
|
+
// Announce token add/remove politely via the persistent live region.
|
|
565
|
+
// Tokens previously appeared and disappeared silently — Backspace on an
|
|
566
|
+
// empty input removes the trailing token, and the per-token remove buttons
|
|
567
|
+
// gave no audible feedback either.
|
|
568
|
+
const announce = useAnnounce();
|
|
569
|
+
|
|
563
570
|
// Handle adding an item — detect creatable synthetic items
|
|
564
571
|
const handleAdd = useCallback(
|
|
565
572
|
(item: T | null) => {
|
|
@@ -584,6 +591,7 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
584
591
|
const realItem = base as T;
|
|
585
592
|
const newItems = [...value, realItem];
|
|
586
593
|
onChange(newItems, {item: realItem, type: 'create'});
|
|
594
|
+
announce(`Added ${createdValue}`);
|
|
587
595
|
return;
|
|
588
596
|
}
|
|
589
597
|
|
|
@@ -592,18 +600,22 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
592
600
|
}
|
|
593
601
|
const newItems = [...value, item];
|
|
594
602
|
onChange(newItems, {item, type: 'add'});
|
|
603
|
+
announce(`Added ${item.label}`);
|
|
595
604
|
},
|
|
596
|
-
[value, onChange, isAtMax, selectedIds, hasCreate],
|
|
605
|
+
[value, onChange, isAtMax, selectedIds, hasCreate, announce],
|
|
597
606
|
);
|
|
598
607
|
|
|
599
|
-
// Handle removing an item
|
|
608
|
+
// Handle removing an item. Single removal path: both Backspace on an empty
|
|
609
|
+
// input and the per-token remove buttons route through here, so the
|
|
610
|
+
// announcement covers both.
|
|
600
611
|
const handleRemove = useCallback(
|
|
601
612
|
(item: T) => {
|
|
602
613
|
const newItems = value.filter(v => v.id !== item.id);
|
|
603
614
|
onChange(newItems, {item, type: 'remove'});
|
|
615
|
+
announce(`Removed ${item.label}`);
|
|
604
616
|
inputRef.current?.focus();
|
|
605
617
|
},
|
|
606
|
-
[value, onChange],
|
|
618
|
+
[value, onChange, announce],
|
|
607
619
|
);
|
|
608
620
|
|
|
609
621
|
// Handle clearing all items
|
package/src/Toolbar/Toolbar.tsx
CHANGED
|
@@ -239,6 +239,7 @@ export function Toolbar({
|
|
|
239
239
|
const hasCenterContent = centerContent != null;
|
|
240
240
|
const hasStartContent = startContent != null;
|
|
241
241
|
const hasEndContent = endContent != null;
|
|
242
|
+
const hasBottomDivider = dividers?.includes('bottom') ?? false;
|
|
242
243
|
|
|
243
244
|
const gapVar = spacingVars[spacingStepToVar[gap]] as string;
|
|
244
245
|
|
|
@@ -316,9 +317,15 @@ export function Toolbar({
|
|
|
316
317
|
orientation === 'vertical' && styles.vertical,
|
|
317
318
|
sizeStyles.base,
|
|
318
319
|
dynamicStyles.gap(gapVar),
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
// Only drop the tab indicator through the toolbar's block padding
|
|
321
|
+
// onto the rail when a bottom divider is actually present.
|
|
322
|
+
// Without a divider, leave `--_tab-indicator-bottom` unset so a
|
|
323
|
+
// nested TabList's indicator keeps its own default (hugging the
|
|
324
|
+
// tab) instead of floating down into the toolbar's padding.
|
|
325
|
+
hasBottomDivider &&
|
|
326
|
+
dynamicStyles.tabIndicatorBottom(
|
|
327
|
+
`calc(-1 * (${blockPaddingVarForSize[size]} + 1px))`,
|
|
328
|
+
),
|
|
322
329
|
),
|
|
323
330
|
)}
|
|
324
331
|
{...props}>
|