@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
package/src/Field/Field.test.tsx
CHANGED
|
@@ -9,10 +9,22 @@
|
|
|
9
9
|
* SYNC: When Field.tsx changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {describe, it, expect, vi} from 'vitest';
|
|
13
|
-
import {render, screen} from '@testing-library/react';
|
|
12
|
+
import {describe, it, expect, vi, afterEach} from 'vitest';
|
|
13
|
+
import {render, screen, waitFor} from '@testing-library/react';
|
|
14
14
|
import {Field} from './Field';
|
|
15
15
|
import {FormLayoutContext} from '../FormLayout/FormLayoutContext';
|
|
16
|
+
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
17
|
+
|
|
18
|
+
function politeRegion(): HTMLElement | null {
|
|
19
|
+
return document.querySelector('[data-astryx-live-region="polite"]');
|
|
20
|
+
}
|
|
21
|
+
function assertiveRegion(): HTMLElement | null {
|
|
22
|
+
return document.querySelector('[data-astryx-live-region="assertive"]');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
__resetLiveRegionsForTest();
|
|
27
|
+
});
|
|
16
28
|
|
|
17
29
|
describe('Field', () => {
|
|
18
30
|
it('renders with label', () => {
|
|
@@ -228,7 +240,11 @@ describe('Field', () => {
|
|
|
228
240
|
expect(document.querySelector('svg')).not.toBeInTheDocument();
|
|
229
241
|
});
|
|
230
242
|
|
|
231
|
-
|
|
243
|
+
// The status message is announced through the persistent useAnnounce live
|
|
244
|
+
// regions rather than role/aria-live on the (conditionally mounted)
|
|
245
|
+
// FieldStatus element itself — regions born with their content are not
|
|
246
|
+
// reliably announced by assistive technology.
|
|
247
|
+
it('announces error status assertively via the persistent live region', async () => {
|
|
232
248
|
render(
|
|
233
249
|
<Field
|
|
234
250
|
label="Email"
|
|
@@ -237,12 +253,13 @@ describe('Field', () => {
|
|
|
237
253
|
<input id="email-input" />
|
|
238
254
|
</Field>,
|
|
239
255
|
);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
256
|
+
expect(screen.getByText('Invalid email')).not.toHaveAttribute('role');
|
|
257
|
+
await waitFor(() => {
|
|
258
|
+
expect(assertiveRegion()).toHaveTextContent('Invalid email');
|
|
259
|
+
});
|
|
243
260
|
});
|
|
244
261
|
|
|
245
|
-
it('
|
|
262
|
+
it('announces warning status politely via the persistent live region', async () => {
|
|
246
263
|
render(
|
|
247
264
|
<Field
|
|
248
265
|
label="Email"
|
|
@@ -251,9 +268,34 @@ describe('Field', () => {
|
|
|
251
268
|
<input id="email-input" />
|
|
252
269
|
</Field>,
|
|
253
270
|
);
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
271
|
+
expect(screen.getByText('Check this')).not.toHaveAttribute('aria-live');
|
|
272
|
+
await waitFor(() => {
|
|
273
|
+
expect(politeRegion()).toHaveTextContent('Check this');
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Regression: the status live region must NOT be born together with its
|
|
278
|
+
// content. A message that appears after mount (the common validation flow)
|
|
279
|
+
// has to land in the persistent announce region.
|
|
280
|
+
it('announces a status message that appears after mount', async () => {
|
|
281
|
+
const {rerender} = render(
|
|
282
|
+
<Field label="Email" inputID="email-input">
|
|
283
|
+
<input id="email-input" />
|
|
284
|
+
</Field>,
|
|
285
|
+
);
|
|
286
|
+
expect(assertiveRegion()).toBeNull();
|
|
287
|
+
|
|
288
|
+
rerender(
|
|
289
|
+
<Field
|
|
290
|
+
label="Email"
|
|
291
|
+
inputID="email-input"
|
|
292
|
+
status={{type: 'error', message: 'Email is required'}}>
|
|
293
|
+
<input id="email-input" />
|
|
294
|
+
</Field>,
|
|
295
|
+
);
|
|
296
|
+
await waitFor(() => {
|
|
297
|
+
expect(assertiveRegion()).toHaveTextContent('Email is required');
|
|
298
|
+
});
|
|
257
299
|
});
|
|
258
300
|
|
|
259
301
|
it('auto-generates description ID as {inputID}-desc when descriptionID is not provided', () => {
|
|
@@ -277,7 +319,10 @@ describe('Field', () => {
|
|
|
277
319
|
<input id="my-input" />
|
|
278
320
|
</Field>,
|
|
279
321
|
);
|
|
280
|
-
expect(screen.
|
|
322
|
+
expect(screen.getByText('Required')).toHaveAttribute(
|
|
323
|
+
'id',
|
|
324
|
+
'my-input-status',
|
|
325
|
+
);
|
|
281
326
|
});
|
|
282
327
|
|
|
283
328
|
it('warns when isOptional and isRequired are both set', () => {
|
|
@@ -421,7 +466,7 @@ describe('Field', () => {
|
|
|
421
466
|
</Field>,
|
|
422
467
|
{wrapper: horizontalLabelsWrapper},
|
|
423
468
|
);
|
|
424
|
-
const statusEl = screen.
|
|
469
|
+
const statusEl = screen.getByText('Required');
|
|
425
470
|
const inputEl = screen.getByTestId('email');
|
|
426
471
|
// Both status and input should be inside the same wrapper div (column 2)
|
|
427
472
|
expect(statusEl.parentElement).toBe(inputEl.parentElement);
|
|
@@ -48,7 +48,7 @@ export const docs = {
|
|
|
48
48
|
{
|
|
49
49
|
name: 'labelIcon',
|
|
50
50
|
type: 'IconType',
|
|
51
|
-
description: 'Icon before the label text. See `
|
|
51
|
+
description: 'Icon before the label text. See `astryx docs icons` for valid semantic names.',
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
54
|
name: 'labelTooltip',
|
|
@@ -8,7 +8,7 @@ export const docs = {
|
|
|
8
8
|
category: 'Data Input',
|
|
9
9
|
isHiddenFromOverview: true,
|
|
10
10
|
description:
|
|
11
|
-
'Status message component for form field validation feedback.',
|
|
11
|
+
'Status message component for form field validation feedback. Messages are announced to screen readers through persistent live regions (assertive for errors, polite otherwise), so conditional mounting is safe.',
|
|
12
12
|
theming: {
|
|
13
13
|
targets: [
|
|
14
14
|
{className: 'astryx-field-status', visualProps: ['type', 'variant']},
|
|
@@ -66,12 +66,24 @@ export const docs = {
|
|
|
66
66
|
/** @type {import('../docs-types').TranslationDoc} */
|
|
67
67
|
export const docsDense = {
|
|
68
68
|
description:
|
|
69
|
-
'Validation feedback message for fields/custom controls. Supports error, warning, success and attached/detached variants.',
|
|
69
|
+
'Validation feedback message for fields/custom controls. Supports error, warning, success and attached/detached variants. Announced via persistent live regions (assertive for errors, polite otherwise).',
|
|
70
70
|
usage: {
|
|
71
71
|
bestPractices: [
|
|
72
|
-
{
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
{
|
|
73
|
+
guidance: true,
|
|
74
|
+
description:
|
|
75
|
+
'Use attached status below bordered inputs when message belongs to that input.',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
guidance: true,
|
|
79
|
+
description:
|
|
80
|
+
'Use detached status for checkboxes, switches, and custom controls where overlap is visually awkward.',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
guidance: false,
|
|
84
|
+
description:
|
|
85
|
+
'Use FieldStatus for general alerts or page-level notices; use Banner or Toast instead.',
|
|
86
|
+
},
|
|
75
87
|
],
|
|
76
88
|
},
|
|
77
89
|
propDescriptions: {
|
|
@@ -9,15 +9,27 @@
|
|
|
9
9
|
* SYNC: When FieldStatus.tsx changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {describe, it, expect, vi} from 'vitest';
|
|
13
|
-
import {render, screen} from '@testing-library/react';
|
|
12
|
+
import {describe, it, expect, vi, afterEach} from 'vitest';
|
|
13
|
+
import {render, screen, waitFor} from '@testing-library/react';
|
|
14
14
|
import * as stylex from '@stylexjs/stylex';
|
|
15
15
|
import {FieldStatus} from './FieldStatus';
|
|
16
|
+
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
16
17
|
|
|
17
18
|
const testStyles = stylex.create({
|
|
18
19
|
custom: {color: 'rebeccapurple'},
|
|
19
20
|
});
|
|
20
21
|
|
|
22
|
+
function politeRegion(): HTMLElement | null {
|
|
23
|
+
return document.querySelector('[data-astryx-live-region="polite"]');
|
|
24
|
+
}
|
|
25
|
+
function assertiveRegion(): HTMLElement | null {
|
|
26
|
+
return document.querySelector('[data-astryx-live-region="assertive"]');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
__resetLiveRegionsForTest();
|
|
31
|
+
});
|
|
32
|
+
|
|
21
33
|
describe('FieldStatus', () => {
|
|
22
34
|
it('renders the message text', () => {
|
|
23
35
|
render(<FieldStatus type="error" message="This field is required" />);
|
|
@@ -29,47 +41,75 @@ describe('FieldStatus', () => {
|
|
|
29
41
|
expect(screen.getByTestId('fs').tagName).toBe('DIV');
|
|
30
42
|
});
|
|
31
43
|
|
|
32
|
-
describe('
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
describe('screen-reader announcements', () => {
|
|
45
|
+
// The rendered element is NOT itself a live region: FieldStatus is
|
|
46
|
+
// conditionally mounted by every caller, and live regions born together
|
|
47
|
+
// with their content are not reliably announced. Announcements go through
|
|
48
|
+
// the persistent useAnnounce singletons instead.
|
|
49
|
+
it('does not carry role or aria-live on the rendered element', () => {
|
|
50
|
+
render(<FieldStatus type="error" message="msg" data-testid="fs" />);
|
|
51
|
+
const el = screen.getByTestId('fs');
|
|
52
|
+
expect(el).not.toHaveAttribute('role');
|
|
53
|
+
expect(el).not.toHaveAttribute('aria-live');
|
|
37
54
|
});
|
|
38
55
|
|
|
39
|
-
//
|
|
40
|
-
it('
|
|
41
|
-
render(<FieldStatus type="
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
// Errors are urgent — they interrupt via the assertive channel.
|
|
57
|
+
it('announces error messages assertively, including on first mount', async () => {
|
|
58
|
+
render(<FieldStatus type="error" message="This field is required" />);
|
|
59
|
+
await waitFor(() => {
|
|
60
|
+
expect(assertiveRegion()).toHaveTextContent('This field is required');
|
|
61
|
+
});
|
|
62
|
+
expect(politeRegion()).toHaveTextContent('');
|
|
44
63
|
});
|
|
45
64
|
|
|
46
|
-
it('
|
|
47
|
-
render(<FieldStatus type="
|
|
48
|
-
|
|
65
|
+
it('announces warning messages politely', async () => {
|
|
66
|
+
render(<FieldStatus type="warning" message="Check this value" />);
|
|
67
|
+
await waitFor(() => {
|
|
68
|
+
expect(politeRegion()).toHaveTextContent('Check this value');
|
|
69
|
+
});
|
|
70
|
+
expect(assertiveRegion()).toHaveTextContent('');
|
|
49
71
|
});
|
|
50
|
-
});
|
|
51
72
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
'aria-live',
|
|
58
|
-
'assertive',
|
|
59
|
-
);
|
|
73
|
+
it('announces success messages politely', async () => {
|
|
74
|
+
render(<FieldStatus type="success" message="Looks good" />);
|
|
75
|
+
await waitFor(() => {
|
|
76
|
+
expect(politeRegion()).toHaveTextContent('Looks good');
|
|
77
|
+
});
|
|
60
78
|
});
|
|
61
79
|
|
|
62
|
-
it('
|
|
63
|
-
render(<FieldStatus type="
|
|
64
|
-
|
|
80
|
+
it('announces message changes', async () => {
|
|
81
|
+
const {rerender} = render(<FieldStatus type="error" message="First" />);
|
|
82
|
+
await waitFor(() => {
|
|
83
|
+
expect(assertiveRegion()).toHaveTextContent('First');
|
|
84
|
+
});
|
|
85
|
+
rerender(<FieldStatus type="error" message="Second" />);
|
|
86
|
+
await waitFor(() => {
|
|
87
|
+
expect(assertiveRegion()).toHaveTextContent('Second');
|
|
88
|
+
});
|
|
65
89
|
});
|
|
66
90
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
91
|
+
// Severity changes re-route the announcement to the matching channel.
|
|
92
|
+
it('re-routes to the polite channel when type changes from error', async () => {
|
|
93
|
+
const {rerender} = render(<FieldStatus type="error" message="msg" />);
|
|
94
|
+
await waitFor(() => {
|
|
95
|
+
expect(assertiveRegion()).toHaveTextContent('msg');
|
|
96
|
+
});
|
|
97
|
+
rerender(<FieldStatus type="success" message="msg" />);
|
|
98
|
+
await waitFor(() => {
|
|
99
|
+
expect(politeRegion()).toHaveTextContent('msg');
|
|
100
|
+
});
|
|
70
101
|
});
|
|
71
102
|
|
|
72
|
-
|
|
103
|
+
it('does not announce an empty message', () => {
|
|
104
|
+
render(<FieldStatus type="error" message="" />);
|
|
105
|
+
// The live regions are created lazily on first announce; an empty
|
|
106
|
+
// message must not trigger one.
|
|
107
|
+
expect(assertiveRegion()).toBeNull();
|
|
108
|
+
expect(politeRegion()).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// The visible message stays perceivable by assistive tech (it is the
|
|
112
|
+
// aria-describedby target for the input).
|
|
73
113
|
it('does not mark itself aria-hidden', () => {
|
|
74
114
|
render(<FieldStatus type="error" message="msg" data-testid="fs" />);
|
|
75
115
|
expect(screen.getByTestId('fs')).not.toHaveAttribute('aria-hidden');
|
|
@@ -245,20 +285,18 @@ describe('FieldStatus', () => {
|
|
|
245
285
|
expect(screen.getByTestId('fs')).toHaveTextContent('Second');
|
|
246
286
|
});
|
|
247
287
|
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
it('
|
|
288
|
+
// The element must never regain live-region semantics when the type
|
|
289
|
+
// changes — announcements always flow through the persistent regions.
|
|
290
|
+
it('keeps the element role-free when type changes from error', () => {
|
|
251
291
|
const {rerender} = render(
|
|
252
292
|
<FieldStatus type="error" message="msg" data-testid="fs" />,
|
|
253
293
|
);
|
|
254
|
-
|
|
255
|
-
expect(el).toHaveAttribute('role', 'alert');
|
|
256
|
-
expect(el).toHaveAttribute('aria-live', 'assertive');
|
|
294
|
+
expect(screen.getByTestId('fs')).not.toHaveAttribute('role');
|
|
257
295
|
|
|
258
296
|
rerender(<FieldStatus type="success" message="msg" data-testid="fs" />);
|
|
259
|
-
el = screen.getByTestId('fs');
|
|
260
|
-
expect(el).toHaveAttribute('role'
|
|
261
|
-
expect(el).toHaveAttribute('aria-live'
|
|
297
|
+
const el = screen.getByTestId('fs');
|
|
298
|
+
expect(el).not.toHaveAttribute('role');
|
|
299
|
+
expect(el).not.toHaveAttribute('aria-live');
|
|
262
300
|
});
|
|
263
301
|
});
|
|
264
302
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file FieldStatus.tsx
|
|
5
|
-
* @input Uses React, stylex, theme tokens
|
|
5
|
+
* @input Uses React, stylex, theme tokens, useAnnounce
|
|
6
6
|
* @output Exports FieldStatus component, FieldStatusProps
|
|
7
7
|
* @position Core implementation; consumed by Field, Switch, CheckboxInput, and the FieldStatus entrypoint
|
|
8
8
|
*
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
'use client';
|
|
18
18
|
|
|
19
|
-
import React from 'react';
|
|
19
|
+
import React, {useEffect} from 'react';
|
|
20
20
|
import * as stylex from '@stylexjs/stylex';
|
|
21
21
|
import type {BaseProps} from '../BaseProps';
|
|
22
|
+
import {useAnnounce} from '../hooks/useAnnounce';
|
|
22
23
|
import {mergeProps} from '../utils';
|
|
23
24
|
import {
|
|
24
25
|
colorVars,
|
|
@@ -113,6 +114,14 @@ export interface FieldStatusProps extends BaseProps<HTMLDivElement> {
|
|
|
113
114
|
/**
|
|
114
115
|
* A status message component for form fields.
|
|
115
116
|
*
|
|
117
|
+
* Screen-reader announcements go through the persistent `useAnnounce` live
|
|
118
|
+
* regions (assertive for errors, polite otherwise) rather than `role`/
|
|
119
|
+
* `aria-live` on the rendered element. Live regions that mount together with
|
|
120
|
+
* their content are not reliably announced by assistive technology, and
|
|
121
|
+
* FieldStatus is almost always conditionally rendered by its callers. The
|
|
122
|
+
* message is announced whenever it appears — including on first mount — and
|
|
123
|
+
* whenever it changes.
|
|
124
|
+
*
|
|
116
125
|
* @example
|
|
117
126
|
* ```
|
|
118
127
|
* <FieldStatus
|
|
@@ -138,13 +147,22 @@ export function FieldStatus({
|
|
|
138
147
|
...rest
|
|
139
148
|
}: FieldStatusProps) {
|
|
140
149
|
const entryStyle = useEntryAnimation('slideDown');
|
|
150
|
+
const announce = useAnnounce();
|
|
151
|
+
|
|
152
|
+
// Announce the message through the persistently-mounted live regions.
|
|
153
|
+
// Announce-on-mount is intentional: callers conditionally mount FieldStatus
|
|
154
|
+
// when a status appears (and whole forms can mount with a server-side
|
|
155
|
+
// validation error already present), and both cases must be heard.
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (message) {
|
|
158
|
+
announce(message, type === 'error' ? 'assertive' : 'polite');
|
|
159
|
+
}
|
|
160
|
+
}, [announce, message, type]);
|
|
141
161
|
|
|
142
162
|
return (
|
|
143
163
|
<div
|
|
144
164
|
ref={ref}
|
|
145
165
|
id={id}
|
|
146
|
-
role={type === 'error' ? 'alert' : 'status'}
|
|
147
|
-
aria-live={type === 'error' ? 'assertive' : 'polite'}
|
|
148
166
|
{...rest}
|
|
149
167
|
{...mergeProps(
|
|
150
168
|
themeProps('field-status', {type, variant}),
|
|
@@ -348,9 +348,10 @@ describe('FileInput', () => {
|
|
|
348
348
|
fireEvent.change(fileInputEl(), {
|
|
349
349
|
target: {files: [createFile('note.txt', 100)]},
|
|
350
350
|
});
|
|
351
|
-
// A rejected selection
|
|
352
|
-
// the
|
|
353
|
-
|
|
351
|
+
// A rejected selection announces nothing politely. (The region pair may
|
|
352
|
+
// exist because the validation error itself is announced assertively via
|
|
353
|
+
// FieldStatus, but the polite channel must stay empty.)
|
|
354
|
+
expect(politeRegion()?.textContent ?? '').toBe('');
|
|
354
355
|
});
|
|
355
356
|
});
|
|
356
357
|
|
|
@@ -641,6 +642,54 @@ describe('FileInput', () => {
|
|
|
641
642
|
});
|
|
642
643
|
});
|
|
643
644
|
|
|
645
|
+
describe('trigger accessible name', () => {
|
|
646
|
+
it('includes the selected file name in the trigger name', () => {
|
|
647
|
+
const file = createFile('report.pdf', 1024, 'application/pdf');
|
|
648
|
+
render(<FileInput label="Document" value={file} onChange={() => {}} />);
|
|
649
|
+
expect(
|
|
650
|
+
screen.getByRole('button', {name: 'Document, report.pdf'}),
|
|
651
|
+
).toBeInTheDocument();
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it('includes all selected file names when multiple files are selected', () => {
|
|
655
|
+
const files = [createFile('a.txt', 100), createFile('b.txt', 200)];
|
|
656
|
+
render(
|
|
657
|
+
<FileInput
|
|
658
|
+
label="Files"
|
|
659
|
+
value={files}
|
|
660
|
+
onChange={() => {}}
|
|
661
|
+
isMultiple
|
|
662
|
+
/>,
|
|
663
|
+
);
|
|
664
|
+
expect(
|
|
665
|
+
screen.getByRole('button', {name: 'Files, a.txt, b.txt'}),
|
|
666
|
+
).toBeInTheDocument();
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it('uses exactly the label when no files are selected', () => {
|
|
670
|
+
render(<FileInput label="Document" value={null} onChange={() => {}} />);
|
|
671
|
+
expect(screen.getByRole('button', {name: 'Document'})).toHaveAttribute(
|
|
672
|
+
'aria-label',
|
|
673
|
+
'Document',
|
|
674
|
+
);
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it('includes the selected file name in dropzone mode', () => {
|
|
678
|
+
const file = createFile('doc.pdf', 100, 'application/pdf');
|
|
679
|
+
render(
|
|
680
|
+
<FileInput
|
|
681
|
+
label="Upload"
|
|
682
|
+
value={file}
|
|
683
|
+
onChange={() => {}}
|
|
684
|
+
mode="dropzone"
|
|
685
|
+
/>,
|
|
686
|
+
);
|
|
687
|
+
expect(
|
|
688
|
+
screen.getByRole('button', {name: 'Upload, doc.pdf'}),
|
|
689
|
+
).toBeInTheDocument();
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
|
|
644
693
|
describe('data-testid', () => {
|
|
645
694
|
it('passes data-testid to native input', () => {
|
|
646
695
|
render(
|
|
@@ -745,7 +745,15 @@ export function FileInput({
|
|
|
745
745
|
aria-disabled={showsDisabledMessage ? 'true' : undefined}
|
|
746
746
|
onClick={handleClick}
|
|
747
747
|
onKeyDown={handleKeyDown}
|
|
748
|
-
aria-label
|
|
748
|
+
// aria-label suppresses child content from the accessible name, so
|
|
749
|
+
// compose the selected filenames into it — otherwise a screen-reader
|
|
750
|
+
// user refocusing the control hears only the field label and cannot
|
|
751
|
+
// tell what (if anything) is attached.
|
|
752
|
+
aria-label={
|
|
753
|
+
hasFiles && fileNames
|
|
754
|
+
? t('@astryx.fileInput.triggerWithFiles', {label, fileNames})
|
|
755
|
+
: label
|
|
756
|
+
}
|
|
749
757
|
aria-busy={isLoading || undefined}
|
|
750
758
|
// These describe the operable control, so they belong on the focusable
|
|
751
759
|
// role="button" wrapper — not the hidden tabIndex={-1} file input the
|
|
@@ -785,7 +793,10 @@ export function FileInput({
|
|
|
785
793
|
/>
|
|
786
794
|
{isDropzone ? renderDropzoneContent() : renderCompactContent()}
|
|
787
795
|
{hasFiles && !isDisabled && !isLoading && (
|
|
788
|
-
<InputClearButton
|
|
796
|
+
<InputClearButton
|
|
797
|
+
label={t('@astryx.fileInput.clearLabel', {label})}
|
|
798
|
+
onClick={handleClear}
|
|
799
|
+
/>
|
|
789
800
|
)}
|
|
790
801
|
</div>
|
|
791
802
|
<div
|
|
@@ -79,6 +79,12 @@ export const docs = {
|
|
|
79
79
|
description: 'Enables or disables the hover and focus triggers.',
|
|
80
80
|
default: 'true',
|
|
81
81
|
},
|
|
82
|
+
{
|
|
83
|
+
name: 'label',
|
|
84
|
+
type: 'string',
|
|
85
|
+
description:
|
|
86
|
+
'Accessible name for the hover card popup. When provided, the popup is exposed as a named role="dialog"; when omitted, it falls back to role="group" (a group may validly be unnamed).',
|
|
87
|
+
},
|
|
82
88
|
{
|
|
83
89
|
name: 'onOpenChange',
|
|
84
90
|
type: '(isOpen: boolean) => void',
|
|
@@ -188,6 +194,12 @@ export const docsZh = {
|
|
|
188
194
|
description: '启用或禁用悬停和聚焦触发器。',
|
|
189
195
|
default: 'true',
|
|
190
196
|
},
|
|
197
|
+
{
|
|
198
|
+
name: 'label',
|
|
199
|
+
type: 'string',
|
|
200
|
+
description:
|
|
201
|
+
'悬浮卡片弹出层的无障碍名称。提供时,弹出层以具名的 role="dialog" 暴露;省略时,回退为 role="group"(group 可以合法地没有名称)。',
|
|
202
|
+
},
|
|
191
203
|
{
|
|
192
204
|
name: 'onOpenChange',
|
|
193
205
|
type: '(isOpen: boolean) => void',
|
|
@@ -252,6 +264,7 @@ export const docsDense = {
|
|
|
252
264
|
hideDelay: 'Hide delay in ms.',
|
|
253
265
|
focusTrigger: 'Controls when focus events trigger hover card.',
|
|
254
266
|
isEnabled: 'Enable/disable hover + focus triggers.',
|
|
267
|
+
label: 'Accessible name for the popup. With label: named role="dialog"; without: role="group".',
|
|
255
268
|
onOpenChange: 'Callback when visibility changes; true=shown, false=hidden.',
|
|
256
269
|
hasHoverIndication: 'Dashed underline on trigger element.',
|
|
257
270
|
isDefaultOpen: 'Show hover card on mount. Still dismissible.',
|
|
@@ -58,15 +58,34 @@ describe('HoverCard', () => {
|
|
|
58
58
|
expect(screen.getByRole('button', {name: 'Trigger'})).toBeInTheDocument();
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
it('
|
|
61
|
+
it('exposes the floating layer as role="group" when no label is provided', () => {
|
|
62
62
|
render(
|
|
63
63
|
<HoverCard content={<span>Card content</span>}>
|
|
64
64
|
<button type="button">Trigger</button>
|
|
65
65
|
</HoverCard>,
|
|
66
66
|
);
|
|
67
|
-
|
|
67
|
+
// A group may validly be unnamed; an unnamed dialog may not. Without a
|
|
68
|
+
// label the layer must not claim the dialog role.
|
|
69
|
+
expect(screen.getByRole('group', {hidden: true})).toHaveTextContent(
|
|
68
70
|
'Card content',
|
|
69
71
|
);
|
|
72
|
+
expect(screen.queryByRole('dialog', {hidden: true})).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('exposes the floating layer as a named dialog when label is provided', () => {
|
|
76
|
+
render(
|
|
77
|
+
<HoverCard content={<span>Card content</span>} label="Profile preview">
|
|
78
|
+
<button type="button">Trigger</button>
|
|
79
|
+
</HoverCard>,
|
|
80
|
+
);
|
|
81
|
+
// The layer is hidden while closed, so assert the accessible name via the
|
|
82
|
+
// aria-label attribute on the role-carrying element (same pattern as the
|
|
83
|
+
// Popover dialogLabel test) — accname computation returns '' for hidden
|
|
84
|
+
// elements.
|
|
85
|
+
const dialog = screen.getByRole('dialog', {hidden: true});
|
|
86
|
+
expect(dialog).toHaveAttribute('aria-label', 'Profile preview');
|
|
87
|
+
expect(dialog).toHaveTextContent('Card content');
|
|
88
|
+
expect(screen.queryByRole('group', {hidden: true})).toBeNull();
|
|
70
89
|
});
|
|
71
90
|
|
|
72
91
|
it('wraps element children in an inline-safe span', () => {
|
|
@@ -96,6 +96,15 @@ export interface HoverCardProps extends Pick<
|
|
|
96
96
|
*/
|
|
97
97
|
isEnabled?: boolean;
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Accessible name for the hover card popup.
|
|
101
|
+
*
|
|
102
|
+
* When provided, the popup is exposed to assistive technology as a named
|
|
103
|
+
* `role="dialog"`. When omitted, the popup falls back to `role="group"` —
|
|
104
|
+
* a group may validly be unnamed, an unnamed dialog may not.
|
|
105
|
+
*/
|
|
106
|
+
label?: string;
|
|
107
|
+
|
|
99
108
|
/**
|
|
100
109
|
* Callback fired when hover card visibility changes.
|
|
101
110
|
* Called with `true` when shown and `false` when hidden.
|
|
@@ -166,6 +175,7 @@ export function HoverCard({
|
|
|
166
175
|
hideDelay = 200,
|
|
167
176
|
focusTrigger = 'auto',
|
|
168
177
|
isEnabled = true,
|
|
178
|
+
label,
|
|
169
179
|
onOpenChange,
|
|
170
180
|
hasHoverIndication = 'auto',
|
|
171
181
|
isOpen,
|
|
@@ -197,6 +207,7 @@ export function HoverCard({
|
|
|
197
207
|
hideDelay,
|
|
198
208
|
focusTrigger,
|
|
199
209
|
isEnabled,
|
|
210
|
+
label,
|
|
200
211
|
isOpen,
|
|
201
212
|
isDefaultOpen,
|
|
202
213
|
onShow: handleShow,
|
|
@@ -13,6 +13,7 @@ export const docs = {
|
|
|
13
13
|
{name: 'hideDelay', type: 'number', description: 'Delay before hiding after mouse or focus leaves, in milliseconds.', default: '200'},
|
|
14
14
|
{name: 'focusTrigger', type: "'auto' | 'always' | 'never'", description: 'When focus should open the hover card. auto only attaches focus listeners to naturally focusable elements.', default: "'auto'"},
|
|
15
15
|
{name: 'isEnabled', type: 'boolean', description: 'Whether hover and focus triggers are enabled.', default: 'true'},
|
|
16
|
+
{name: 'label', type: 'string', description: 'Accessible name for the hover card popup. When provided, the popup is exposed as a named role="dialog"; when omitted, it falls back to role="group" (a group may validly be unnamed).'},
|
|
16
17
|
{name: 'isOpen', type: 'boolean', description: 'Controlled open state. true force-shows, false force-hides, undefined lets hover/focus manage visibility.'},
|
|
17
18
|
{name: 'isDefaultOpen', type: 'boolean', description: 'Whether the hover card should be shown on mount.', default: 'false'},
|
|
18
19
|
{name: 'onShow', type: '() => void', description: 'Callback fired when the hover card becomes visible.'},
|
|
@@ -52,6 +53,7 @@ export const docsDense = {
|
|
|
52
53
|
hideDelay: 'hide delay after mouse/focus leave in ms.',
|
|
53
54
|
focusTrigger: 'when focus opens hover card; auto = naturally focusable elements only.',
|
|
54
55
|
isEnabled: 'whether hover/focus triggers are enabled.',
|
|
56
|
+
label: 'accessible name for popup. with label: named role="dialog"; without: role="group".',
|
|
55
57
|
isOpen: 'controlled open state: true force-show, false force-hide, undefined unmanaged.',
|
|
56
58
|
isDefaultOpen: 'show on mount.',
|
|
57
59
|
onShow: 'fires when hover card becomes visible.',
|