@astryxdesign/core 0.7.1-canary.ff40b78 → 0.7.1
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 +116 -0
- package/dist/Button/Button.d.ts +1 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +5 -2
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +191 -32
- package/dist/Chat/ChatComposerInput.d.ts +9 -0
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +5 -0
- package/dist/Chat/ChatMessage.d.ts +11 -0
- package/dist/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/Chat/ChatMessage.js +18 -9
- package/dist/Chat/useTriggerMenu.d.ts +9 -2
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +222 -50
- package/dist/CodeBlock/CodeBlock.d.ts +10 -1
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +158 -38
- package/dist/CodeBlock/tokenizer.d.ts +39 -0
- package/dist/CodeBlock/tokenizer.d.ts.map +1 -1
- package/dist/CodeBlock/tokenizer.js +108 -0
- package/dist/CommandPalette/CommandPalette.d.ts +9 -5
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +38 -20
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
- package/dist/CommandPalette/CommandPaletteInput.d.ts +5 -4
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +35 -18
- package/dist/CommandPalette/CommandPaletteItem.d.ts +11 -6
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +8 -9
- package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
- package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteList.js +10 -5
- package/dist/ComplexSelector/ComplexSelector.d.ts +8 -5
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +12 -3
- package/dist/ContextMenu/ContextMenu.d.ts +3 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +62 -48
- package/dist/DateInput/DateInput.js +1 -1
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +12 -4
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +12 -2
- package/dist/Dialog/Dialog.d.ts +1 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +16 -5
- package/dist/Dialog/DialogHeader.d.ts +1 -1
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +7 -7
- package/dist/Divider/Divider.d.ts +1 -0
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +5 -4
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +161 -65
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +7 -5
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +12 -5
- package/dist/Field/InputClearButton.d.ts +8 -4
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +23 -7
- package/dist/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/FormLayout/FormLayout.js +1 -0
- package/dist/Grid/Grid.d.ts +13 -4
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +7 -1
- package/dist/HoverCard/useHoverCard.d.ts +1 -1
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +67 -49
- package/dist/InputGroup/InputGroup.d.ts +2 -4
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +2 -4
- package/dist/InputGroup/groupStyles.d.ts +10 -2
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +19 -2
- package/dist/Layer/useLayerDismissal.d.ts +9 -9
- package/dist/Layer/useLayerDismissal.js +9 -9
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +25 -4
- package/dist/MobileNav/MobileNav.d.ts +7 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +45 -2
- package/dist/MultiSelector/MultiSelector.d.ts +7 -4
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +24 -3
- package/dist/NumberInput/NumberInput.js +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +6 -0
- package/dist/Resizable/ResizeHandle.d.ts +1 -1
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +83 -15
- package/dist/Resizable/useResizable.d.ts +23 -0
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +78 -0
- package/dist/Selector/Selector.d.ts +6 -3
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +28 -6
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +10 -2
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +18 -7
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +16 -4
- package/dist/Toolbar/Toolbar.d.ts +2 -1
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +3 -1
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +6 -3
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +7 -1
- package/dist/astryx.css +22 -0
- package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
- package/dist/hooks/useScrollOverflow.js +55 -12
- package/dist/hooks/useTreeFocus.d.ts +21 -0
- package/dist/hooks/useTreeFocus.d.ts.map +1 -1
- package/dist/hooks/useTreeFocus.js +40 -2
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +3 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
- package/dist/reset.a11y.chromium.spec.js +6 -1
- package/dist/reset.until-found.a11y.browser.spec.d.ts +2 -0
- package/dist/reset.until-found.a11y.browser.spec.d.ts.map +1 -0
- package/dist/reset.until-found.a11y.browser.spec.js +71 -0
- package/dist/theme/syntax/SyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/SyntaxTheme.js +43 -2
- package/dist/theme/syntax/defineSyntaxTheme.d.ts +18 -5
- package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.js +38 -9
- package/dist/theme/syntax/index.d.ts +1 -1
- package/dist/theme/syntax/index.d.ts.map +1 -1
- package/dist/theme/syntax/tokens.d.ts +12 -2
- package/dist/theme/syntax/tokens.d.ts.map +1 -1
- package/dist/theme/syntax/tokens.js +25 -4
- package/dist/utils/plainDate.d.ts +12 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +63 -10
- package/locales/en.json +13 -1
- package/locales/pseudo.json +9 -0
- package/package.json +4 -4
- package/src/AlertDialog/AlertDialog.doc.mjs +6 -0
- package/src/AlertDialog/AlertDialog.spec.md +205 -0
- package/src/AlertDialog/AlertDialog.test.tsx +3 -14
- package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +115 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +52 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +68 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +144 -0
- package/src/Button/Button.test.tsx +37 -0
- package/src/Button/Button.tsx +5 -1
- package/src/Calendar/Calendar.doc.mjs +234 -60
- package/src/Calendar/Calendar.spec.md +209 -0
- package/src/Calendar/Calendar.test.tsx +559 -20
- package/src/Calendar/Calendar.tsx +258 -41
- package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +126 -0
- package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +12 -0
- package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +57 -0
- package/src/Calendar/__tests__/Calendar.a11y.states.ts +139 -0
- package/src/Calendar/__tests__/Calendar.a11y.test.tsx +137 -0
- package/src/Carousel/Carousel.test.tsx +54 -71
- package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +192 -0
- package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +27 -0
- package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +39 -0
- package/src/Carousel/__tests__/Carousel.a11y.states.ts +139 -0
- package/src/Carousel/__tests__/Carousel.a11y.test.tsx +127 -0
- package/src/Chat/ChatComposerInput.doc.mjs +1 -1
- package/src/Chat/ChatComposerInput.spec.md +94 -55
- package/src/Chat/ChatComposerInput.test.tsx +202 -1
- package/src/Chat/ChatComposerInput.tsx +13 -0
- package/src/Chat/ChatMessage.doc.mjs +31 -16
- package/src/Chat/ChatMessage.spec.md +265 -0
- package/src/Chat/ChatMessage.test.tsx +246 -0
- package/src/Chat/ChatMessage.tsx +30 -7
- package/src/Chat/ChatSystemMessage.spec.md +36 -37
- package/src/Chat/ChatSystemMessage.test.tsx +4 -4
- package/src/Chat/useTriggerMenu.test.tsx +994 -0
- package/src/Chat/useTriggerMenu.tsx +278 -46
- package/src/CodeBlock/CodeBlock.doc.mjs +7 -0
- package/src/CodeBlock/CodeBlock.spec.md +40 -41
- package/src/CodeBlock/CodeBlock.test.tsx +89 -9
- package/src/CodeBlock/CodeBlock.tsx +205 -25
- package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +125 -0
- package/src/CodeBlock/__tests__/DiffRowPaint.a11y.chromium.spec.ts +93 -0
- package/src/CodeBlock/tokenizer.test.ts +112 -0
- package/src/CodeBlock/tokenizer.ts +106 -0
- package/src/Collapsible/CollapsibleGroup.spec.md +17 -8
- package/src/Collapsible/__tests__/CollapsibleGroup.a11y.renders.tsx +45 -0
- package/src/Collapsible/__tests__/CollapsibleGroup.a11y.states.ts +139 -0
- package/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx +135 -0
- package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +128 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +126 -23
- package/src/CommandPalette/CommandPalette.spec.md +100 -49
- package/src/CommandPalette/CommandPalette.test.tsx +241 -2
- package/src/CommandPalette/CommandPalette.tsx +47 -21
- package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +61 -4
- package/src/CommandPalette/CommandPaletteInput.spec.md +215 -0
- package/src/CommandPalette/CommandPaletteInput.test.tsx +375 -51
- package/src/CommandPalette/CommandPaletteInput.tsx +74 -21
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +73 -22
- package/src/CommandPalette/CommandPaletteItem.spec.md +223 -0
- package/src/CommandPalette/CommandPaletteItem.test.tsx +200 -5
- package/src/CommandPalette/CommandPaletteItem.tsx +16 -11
- package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
- package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
- package/src/CommandPalette/CommandPaletteList.test.tsx +80 -0
- package/src/CommandPalette/CommandPaletteList.tsx +10 -5
- package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
- package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -8
- package/src/ComplexSelector/ComplexSelector.spec.md +14 -9
- package/src/ComplexSelector/ComplexSelector.test.tsx +64 -16
- package/src/ComplexSelector/ComplexSelector.tsx +37 -8
- package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
- package/src/ContextMenu/ContextMenu.spec.md +51 -53
- package/src/ContextMenu/ContextMenu.test.tsx +132 -1
- package/src/ContextMenu/ContextMenu.tsx +78 -48
- package/src/DateInput/DateInput.doc.mjs +52 -0
- package/src/DateInput/DateInput.spec.md +27 -15
- package/src/DateInput/DateInput.test.tsx +38 -0
- package/src/DateInput/DateInput.tsx +1 -1
- package/src/DateInput/DateInputTouch.test.tsx +22 -0
- package/src/DateInput/MonthScroller.tsx +12 -4
- package/src/DateRangeInput/DateRangeInput.test.tsx +49 -0
- package/src/DateRangeInput/DateRangeInput.tsx +20 -2
- package/src/Dialog/Dialog.test.tsx +27 -1
- package/src/Dialog/Dialog.tsx +16 -5
- package/src/Dialog/DialogHeader.test.tsx +27 -2
- package/src/Dialog/DialogHeader.tsx +7 -8
- package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +21 -14
- package/src/Divider/Divider.doc.mjs +5 -3
- package/src/Divider/Divider.spec.md +140 -54
- package/src/Divider/Divider.test.tsx +15 -0
- package/src/Divider/Divider.tsx +6 -4
- package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -2
- package/src/DropdownMenu/DropdownMenu.spec.md +121 -45
- package/src/DropdownMenu/DropdownMenu.test.tsx +288 -23
- package/src/DropdownMenu/DropdownMenu.tsx +212 -81
- package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +38 -2
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +32 -18
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +146 -1
- package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +136 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +12 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +67 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +141 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +138 -0
- package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +40 -5
- package/src/Field/InputClearButton.test.tsx +29 -4
- package/src/Field/InputClearButton.tsx +26 -8
- package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +21 -14
- package/src/FormLayout/FormLayout.tsx +1 -0
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +1 -1
- package/src/Grid/Grid.doc.mjs +14 -11
- package/src/Grid/Grid.spec.md +50 -32
- package/src/Grid/Grid.tsx +13 -4
- package/src/HoverCard/HoverCard.test.tsx +46 -1
- package/src/HoverCard/useHoverCard.tsx +68 -48
- package/src/InputGroup/InputGroup.doc.mjs +11 -1
- package/src/InputGroup/InputGroup.tsx +2 -4
- package/src/InputGroup/groupStyles.ts +40 -2
- package/src/Layer/useLayerDismissal.ts +9 -9
- package/src/Link/__tests__/Link.a11y.chromium.spec.ts +142 -0
- package/src/Link/__tests__/Link.a11y.renders.tsx +97 -0
- package/src/Link/__tests__/Link.a11y.states.ts +104 -0
- package/src/Link/__tests__/Link.a11y.test.tsx +127 -0
- package/src/Markdown/Markdown.doc.mjs +1 -1
- package/src/Markdown/Markdown.fr23.perf.test.ts +161 -40
- package/src/Markdown/Markdown.spec.md +45 -23
- package/src/Markdown/Markdown.test.tsx +38 -0
- package/src/Markdown/Markdown.tsx +24 -2
- package/src/Markdown/plugins/semanticFence.test.tsx +3 -2
- package/src/MobileNav/MobileNav.tsx +50 -2
- package/src/MobileNav/MobileNavFocus.test.tsx +553 -0
- package/src/MoreMenu/MoreMenu.spec.md +18 -7
- package/src/MoreMenu/MoreMenu.test.tsx +38 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +8 -8
- package/src/MultiSelector/MultiSelector.spec.md +1 -1
- package/src/MultiSelector/MultiSelector.test.tsx +188 -21
- package/src/MultiSelector/MultiSelector.tsx +50 -11
- package/src/NumberInput/NumberInput.test.tsx +24 -0
- package/src/NumberInput/NumberInput.tsx +1 -1
- package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +22 -15
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +78 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -0
- package/src/Resizable/Resizable.spec.md +41 -9
- package/src/Resizable/ResizeHandle.test.tsx +565 -2
- package/src/Resizable/ResizeHandle.tsx +126 -21
- package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +119 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +39 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +80 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +197 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +123 -0
- package/src/Resizable/useResizable.ts +95 -0
- package/src/Selector/Selector.doc.mjs +7 -6
- package/src/Selector/Selector.spec.md +37 -24
- package/src/Selector/Selector.test.tsx +176 -10
- package/src/Selector/Selector.tsx +59 -14
- package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +27 -0
- package/src/Selector/__tests__/Combobox.a11y.states.ts +145 -0
- package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +108 -0
- package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +126 -0
- package/src/SideNav/SideNav.test.tsx +35 -0
- package/src/SideNav/SideNav.tsx +16 -2
- package/src/Slider/Slider.spec.md +43 -17
- package/src/Slider/Slider.test.tsx +134 -0
- package/src/Slider/Slider.tsx +29 -6
- package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +112 -0
- package/src/Slider/__tests__/Slider.a11y.known-failures.ts +12 -0
- package/src/Slider/__tests__/Slider.a11y.renders.tsx +42 -0
- package/src/Slider/__tests__/Slider.a11y.states.ts +106 -0
- package/src/Slider/__tests__/Slider.a11y.test.tsx +105 -0
- package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +25 -18
- package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +19 -12
- package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +21 -14
- package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +18 -11
- package/src/Tokenizer/Tokenizer.test.tsx +87 -0
- package/src/Tokenizer/Tokenizer.tsx +31 -3
- package/src/Toolbar/Toolbar.test.tsx +131 -4
- package/src/Toolbar/Toolbar.tsx +3 -1
- package/src/TreeList/TreeList.spec.md +33 -8
- package/src/TreeList/TreeList.test.tsx +72 -0
- package/src/TreeList/TreeList.tsx +10 -6
- package/src/TreeList/TreeListItem.tsx +5 -0
- package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +125 -0
- package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +44 -0
- package/src/TreeList/__tests__/TreeList.a11y.states.ts +164 -0
- package/src/TreeList/__tests__/TreeList.a11y.test.tsx +138 -0
- package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +82 -0
- package/src/hooks/useScrollOverflow.test.tsx +222 -0
- package/src/hooks/useScrollOverflow.ts +55 -13
- package/src/hooks/useTreeFocus.test.tsx +37 -0
- package/src/hooks/useTreeFocus.ts +55 -2
- package/src/i18n/generated-locales/en.generated.ts +3 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +3 -0
- package/src/reset.a11y.chromium.spec.ts +9 -1
- package/src/reset.css +8 -3
- package/src/reset.until-found.a11y.browser.spec.ts +75 -0
- package/src/reset.until-found.test.ts +32 -0
- package/src/stylexValidation.source-build.test.mjs +42 -0
- package/src/theme/syntax/SyntaxTheme.tsx +46 -5
- package/src/theme/syntax/defineSyntaxTheme.diff.test.ts +91 -0
- package/src/theme/syntax/defineSyntaxTheme.ts +75 -15
- package/src/theme/syntax/index.ts +5 -1
- package/src/theme/syntax/tokens.ts +32 -2
- package/src/theme/syntax/useSyntaxTheme.diff.test.tsx +40 -0
- package/src/utils/plainDate.test.ts +147 -0
- package/src/utils/plainDate.ts +75 -10
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useHoverCard.ts
|
|
7
7
|
* @input Uses useLayer, useTouchTrigger, React hooks
|
|
8
|
-
* @output Exports useHoverCard hook for hover/focus/tap
|
|
8
|
+
* @output Exports useHoverCard hook with stable trigger refs for hover/focus/tap layers
|
|
9
9
|
* @position Layer hook; builds on useLayer for hover card behavior
|
|
10
10
|
*
|
|
11
11
|
* SYNC: When modified, update:
|
|
@@ -295,6 +295,21 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
295
295
|
onShow,
|
|
296
296
|
onHide,
|
|
297
297
|
});
|
|
298
|
+
// Destructure the layer and touch objects once: both are rebuilt every
|
|
299
|
+
// render, but their members are stable. Depending on the members instead of
|
|
300
|
+
// the enclosing objects keeps the refs below identity-stable across
|
|
301
|
+
// unchanged rerenders, so React never detaches and re-attaches the trigger
|
|
302
|
+
// (and never rewrites the trigger's `anchor-name`) while the card streams
|
|
303
|
+
// alongside it.
|
|
304
|
+
const {
|
|
305
|
+
ref: layerRef,
|
|
306
|
+
anchorId: layerAnchorId,
|
|
307
|
+
show: showLayer,
|
|
308
|
+
hide: hideLayer,
|
|
309
|
+
isOpen: isLayerOpen,
|
|
310
|
+
id: layerId,
|
|
311
|
+
render: renderLayer,
|
|
312
|
+
} = layer;
|
|
298
313
|
|
|
299
314
|
const popoverXstyle = styles.container;
|
|
300
315
|
|
|
@@ -321,21 +336,27 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
321
336
|
// passing across the trigger, and a tap is never that.
|
|
322
337
|
const showNow = useCallback(() => {
|
|
323
338
|
clearTimeouts();
|
|
324
|
-
|
|
325
|
-
}, [clearTimeouts,
|
|
339
|
+
showLayer();
|
|
340
|
+
}, [clearTimeouts, showLayer]);
|
|
326
341
|
|
|
327
342
|
const hideNow = useCallback(() => {
|
|
328
343
|
clearTimeouts();
|
|
329
344
|
isHoveringContentRef.current = false;
|
|
330
|
-
|
|
331
|
-
}, [clearTimeouts,
|
|
345
|
+
hideLayer();
|
|
346
|
+
}, [clearTimeouts, hideLayer]);
|
|
332
347
|
|
|
333
|
-
const
|
|
348
|
+
const {
|
|
349
|
+
isTouchPointerRef,
|
|
350
|
+
isTouchInteraction,
|
|
351
|
+
handlePointerEnter,
|
|
352
|
+
handlePointerDown: handleTouchPointerDown,
|
|
353
|
+
clearTapOpen,
|
|
354
|
+
} = useTouchTrigger({
|
|
334
355
|
touchTrigger,
|
|
335
356
|
isEnabled,
|
|
336
357
|
isControlled: isOpen !== undefined,
|
|
337
|
-
isOpen:
|
|
338
|
-
layerId:
|
|
358
|
+
isOpen: isLayerOpen,
|
|
359
|
+
layerId: layerId,
|
|
339
360
|
triggerRef,
|
|
340
361
|
show: showNow,
|
|
341
362
|
hide: hideNow,
|
|
@@ -348,9 +369,9 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
348
369
|
}
|
|
349
370
|
clearTimeouts();
|
|
350
371
|
showTimeoutRef.current = setTimeout(() => {
|
|
351
|
-
|
|
372
|
+
showLayer();
|
|
352
373
|
}, delay);
|
|
353
|
-
}, [isEnabled, isOpen, clearTimeouts,
|
|
374
|
+
}, [isEnabled, isOpen, clearTimeouts, showLayer, delay]);
|
|
354
375
|
|
|
355
376
|
// Schedule hide with delay (suppressed when isOpen is true)
|
|
356
377
|
const scheduleHide = useCallback(() => {
|
|
@@ -361,37 +382,37 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
361
382
|
hideTimeoutRef.current = setTimeout(() => {
|
|
362
383
|
// Don't hide if hovering content
|
|
363
384
|
if (!isHoveringContentRef.current) {
|
|
364
|
-
|
|
385
|
+
hideLayer();
|
|
365
386
|
}
|
|
366
387
|
}, hideDelay);
|
|
367
|
-
}, [isOpen, clearTimeouts,
|
|
388
|
+
}, [isOpen, clearTimeouts, hideLayer, hideDelay]);
|
|
368
389
|
|
|
369
390
|
// Event handlers
|
|
370
391
|
const handleMouseEnter = useCallback(() => {
|
|
371
392
|
// A tap synthesizes mouseenter. On touch the tap path owns the decision,
|
|
372
393
|
// so hover must not also fire — without this a hover card opens on every
|
|
373
394
|
// tap of its trigger and has nothing to close it.
|
|
374
|
-
if (
|
|
395
|
+
if (isTouchPointerRef.current) {
|
|
375
396
|
return;
|
|
376
397
|
}
|
|
377
398
|
scheduleShow();
|
|
378
|
-
}, [
|
|
399
|
+
}, [isTouchPointerRef, scheduleShow]);
|
|
379
400
|
|
|
380
401
|
const handleMouseLeave = useCallback(() => {
|
|
381
402
|
// On touch the synthesized mouseleave arrives with the next tap elsewhere,
|
|
382
403
|
// which the outside-tap dismissal already handles.
|
|
383
|
-
if (
|
|
404
|
+
if (isTouchPointerRef.current) {
|
|
384
405
|
return;
|
|
385
406
|
}
|
|
386
407
|
scheduleHide();
|
|
387
|
-
}, [
|
|
408
|
+
}, [isTouchPointerRef, scheduleHide]);
|
|
388
409
|
|
|
389
410
|
// Tap-to-open on touch; on a mouse this does nothing and hover still rules.
|
|
390
411
|
const handlePointerDown = useCallback(
|
|
391
412
|
(event: PointerEvent) => {
|
|
392
|
-
|
|
413
|
+
handleTouchPointerDown(event);
|
|
393
414
|
},
|
|
394
|
-
[
|
|
415
|
+
[handleTouchPointerDown],
|
|
395
416
|
);
|
|
396
417
|
|
|
397
418
|
const handleFocusIn = useCallback(() => {
|
|
@@ -401,7 +422,7 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
401
422
|
// A tap focuses the trigger it activates. Opening on that focus would
|
|
402
423
|
// reinstate exactly the behavior the touch path just decided against —
|
|
403
424
|
// and on an action trigger it covers the thing the user tapped.
|
|
404
|
-
if (
|
|
425
|
+
if (isTouchInteraction()) {
|
|
405
426
|
return;
|
|
406
427
|
}
|
|
407
428
|
// Skip showing if we're in the middle of an Escape dismiss
|
|
@@ -410,14 +431,14 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
410
431
|
return;
|
|
411
432
|
}
|
|
412
433
|
clearTimeouts();
|
|
413
|
-
|
|
414
|
-
}, [isEnabled,
|
|
434
|
+
showLayer();
|
|
435
|
+
}, [isEnabled, isTouchInteraction, clearTimeouts, showLayer]);
|
|
415
436
|
|
|
416
437
|
const handleFocusOut = useCallback(
|
|
417
438
|
(e: FocusEvent) => {
|
|
418
439
|
// Check if focus is moving to the hover card content
|
|
419
440
|
const relatedTarget = e.relatedTarget as HTMLElement | null;
|
|
420
|
-
const popoverElement = document.getElementById(
|
|
441
|
+
const popoverElement = document.getElementById(layerId);
|
|
421
442
|
|
|
422
443
|
if (popoverElement?.contains(relatedTarget)) {
|
|
423
444
|
// Focus moving into hover card, keep it open
|
|
@@ -426,7 +447,7 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
426
447
|
|
|
427
448
|
scheduleHide();
|
|
428
449
|
},
|
|
429
|
-
[
|
|
450
|
+
[layerId, scheduleHide],
|
|
430
451
|
);
|
|
431
452
|
|
|
432
453
|
// Escape dismissal (WCAG 1.4.13) goes through the shared layer stack: a
|
|
@@ -453,7 +474,7 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
453
474
|
const el =
|
|
454
475
|
typeof document === 'undefined'
|
|
455
476
|
? null
|
|
456
|
-
: document.getElementById(
|
|
477
|
+
: document.getElementById(layerId);
|
|
457
478
|
if (el == null) {
|
|
458
479
|
return false;
|
|
459
480
|
}
|
|
@@ -462,12 +483,12 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
462
483
|
} catch {
|
|
463
484
|
// Browsers without the Popover API (and some test environments) cannot
|
|
464
485
|
// answer the selector; fall back to the hook's own state.
|
|
465
|
-
return
|
|
486
|
+
return isLayerOpen;
|
|
466
487
|
}
|
|
467
488
|
},
|
|
468
489
|
onDismiss: () => {
|
|
469
490
|
clearTimeouts();
|
|
470
|
-
|
|
491
|
+
clearTapOpen();
|
|
471
492
|
// Controlled: report and stop. The close — and the focus restore that
|
|
472
493
|
// goes with it — happens in the controlled effect if and when the
|
|
473
494
|
// consumer flips `isOpen`.
|
|
@@ -485,9 +506,9 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
485
506
|
const card =
|
|
486
507
|
typeof document === 'undefined'
|
|
487
508
|
? null
|
|
488
|
-
: document.getElementById(
|
|
509
|
+
: document.getElementById(layerId);
|
|
489
510
|
const hadFocus = card?.contains(document.activeElement) ?? false;
|
|
490
|
-
|
|
511
|
+
hideLayer();
|
|
491
512
|
if (hadFocus) {
|
|
492
513
|
isEscapeDismissingRef.current = true;
|
|
493
514
|
triggerRef.current?.focus();
|
|
@@ -496,7 +517,6 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
496
517
|
});
|
|
497
518
|
|
|
498
519
|
// Interaction ref that handles event listeners only
|
|
499
|
-
const {handlePointerEnter} = touch;
|
|
500
520
|
const interactionRef: RefCallback<HTMLElement> = useCallback(
|
|
501
521
|
(el: HTMLElement | null) => {
|
|
502
522
|
// Cleanup previous element
|
|
@@ -554,10 +574,10 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
554
574
|
// Combined ref - shorthand for calling both positionRef and interactionRef
|
|
555
575
|
const ref: RefCallback<HTMLElement> = useCallback(
|
|
556
576
|
(el: HTMLElement | null) => {
|
|
557
|
-
|
|
577
|
+
layerRef(el);
|
|
558
578
|
interactionRef(el);
|
|
559
579
|
},
|
|
560
|
-
[
|
|
580
|
+
[layerRef, interactionRef],
|
|
561
581
|
);
|
|
562
582
|
|
|
563
583
|
// Cleanup on unmount
|
|
@@ -570,7 +590,7 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
570
590
|
// Show on mount when isDefaultOpen is true
|
|
571
591
|
useEffect(() => {
|
|
572
592
|
if (isDefaultOpen) {
|
|
573
|
-
|
|
593
|
+
showLayer();
|
|
574
594
|
}
|
|
575
595
|
// eslint-disable-next-line @eslint-react/exhaustive-deps -- intentionally only on mount
|
|
576
596
|
}, []);
|
|
@@ -582,7 +602,7 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
582
602
|
}
|
|
583
603
|
if (isOpen) {
|
|
584
604
|
clearTimeouts();
|
|
585
|
-
|
|
605
|
+
showLayer();
|
|
586
606
|
} else {
|
|
587
607
|
clearTimeouts();
|
|
588
608
|
// A consumer closing the card while it holds focus would strand focus on
|
|
@@ -591,15 +611,15 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
591
611
|
const card =
|
|
592
612
|
typeof document === 'undefined'
|
|
593
613
|
? null
|
|
594
|
-
: document.getElementById(
|
|
614
|
+
: document.getElementById(layerId);
|
|
595
615
|
const hadFocus = card?.contains(document.activeElement) ?? false;
|
|
596
|
-
|
|
616
|
+
hideLayer();
|
|
597
617
|
if (hadFocus) {
|
|
598
618
|
isEscapeDismissingRef.current = true;
|
|
599
619
|
triggerRef.current?.focus();
|
|
600
620
|
}
|
|
601
621
|
}
|
|
602
|
-
}, [isOpen, clearTimeouts,
|
|
622
|
+
}, [isOpen, clearTimeouts, showLayer, hideLayer, layerId]);
|
|
603
623
|
|
|
604
624
|
// Render function that wraps layer.render with hover card behavior
|
|
605
625
|
const renderHoverCard = useCallback(
|
|
@@ -636,20 +656,20 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
636
656
|
as: 'div' as const,
|
|
637
657
|
};
|
|
638
658
|
|
|
639
|
-
return
|
|
659
|
+
return renderLayer(
|
|
640
660
|
<div
|
|
641
661
|
{...stylex.props(styles.content)}
|
|
642
662
|
onMouseEnter={() => {
|
|
643
663
|
// Touch synthesizes these over the card too; letting a tap inside
|
|
644
664
|
// register as "hovering content" would block every later hide.
|
|
645
|
-
if (
|
|
665
|
+
if (isTouchPointerRef.current) {
|
|
646
666
|
return;
|
|
647
667
|
}
|
|
648
668
|
isHoveringContentRef.current = true;
|
|
649
669
|
clearTimeouts();
|
|
650
670
|
}}
|
|
651
671
|
onMouseLeave={() => {
|
|
652
|
-
if (
|
|
672
|
+
if (isTouchPointerRef.current) {
|
|
653
673
|
return;
|
|
654
674
|
}
|
|
655
675
|
isHoveringContentRef.current = false;
|
|
@@ -679,27 +699,27 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
679
699
|
);
|
|
680
700
|
},
|
|
681
701
|
[
|
|
682
|
-
|
|
702
|
+
renderLayer,
|
|
683
703
|
placement,
|
|
684
704
|
alignment,
|
|
685
705
|
label,
|
|
686
706
|
clearTimeouts,
|
|
687
707
|
scheduleHide,
|
|
688
708
|
popoverXstyle,
|
|
689
|
-
|
|
709
|
+
isTouchPointerRef,
|
|
690
710
|
],
|
|
691
711
|
);
|
|
692
712
|
|
|
693
713
|
return {
|
|
694
714
|
ref,
|
|
695
|
-
positionRef:
|
|
715
|
+
positionRef: layerRef,
|
|
696
716
|
interactionRef,
|
|
697
|
-
anchorId:
|
|
698
|
-
id:
|
|
699
|
-
describedBy:
|
|
700
|
-
isOpen:
|
|
717
|
+
anchorId: layerAnchorId,
|
|
718
|
+
id: layerId,
|
|
719
|
+
describedBy: layerId,
|
|
720
|
+
isOpen: isLayerOpen,
|
|
701
721
|
renderHoverCard,
|
|
702
|
-
show:
|
|
703
|
-
hide:
|
|
722
|
+
show: showLayer,
|
|
723
|
+
hide: hideLayer,
|
|
704
724
|
};
|
|
705
725
|
}
|
|
@@ -30,7 +30,7 @@ export const docs = {
|
|
|
30
30
|
name: 'children',
|
|
31
31
|
type: 'ReactNode',
|
|
32
32
|
description:
|
|
33
|
-
'InputGroupText and compatible input children: TextInput, NumberInput, TimeInput, DateInput, Typeahead, Selector, or MultiSelector.',
|
|
33
|
+
'InputGroupText, Button addons, and compatible input children: TextInput, NumberInput, TimeInput, DateInput, Typeahead, Selector, or MultiSelector.',
|
|
34
34
|
required: true,
|
|
35
35
|
},
|
|
36
36
|
{
|
|
@@ -110,6 +110,11 @@ export const docs = {
|
|
|
110
110
|
description:
|
|
111
111
|
'Use InputGroupText for static prefixes/suffixes like "$", "kg", or "https://".',
|
|
112
112
|
},
|
|
113
|
+
{
|
|
114
|
+
guidance: true,
|
|
115
|
+
description:
|
|
116
|
+
'Use Button for an action attached to the input; InputGroup connects its corners automatically.',
|
|
117
|
+
},
|
|
113
118
|
{
|
|
114
119
|
guidance: true,
|
|
115
120
|
description:
|
|
@@ -181,6 +186,11 @@ export const docsDense = {
|
|
|
181
186
|
description:
|
|
182
187
|
'Use InputGroupText for static prefixes/suffixes like "$", "kg", or "https://".',
|
|
183
188
|
},
|
|
189
|
+
{
|
|
190
|
+
guidance: true,
|
|
191
|
+
description:
|
|
192
|
+
'Use Button for an action attached to the input; InputGroup connects its corners automatically.',
|
|
193
|
+
},
|
|
184
194
|
{
|
|
185
195
|
guidance: true,
|
|
186
196
|
description:
|
|
@@ -8,10 +8,8 @@
|
|
|
8
8
|
* @output Exports InputGroup component with group label/description ARIA wiring
|
|
9
9
|
* @position Groups input with prefix/suffix addons; consumed by index.ts
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* to remove their own border/radius so the group container provides
|
|
14
|
-
* the unified border treatment.
|
|
11
|
+
* Compatible input children consume InputGroup context to remove competing
|
|
12
|
+
* border/radius geometry. Button addons consume it to join the same outer shape.
|
|
15
13
|
*
|
|
16
14
|
* SYNC: When modified, update these files to stay in sync:
|
|
17
15
|
* - /packages/core/src/InputGroup/InputGroup.doc.mjs
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file groupStyles.ts
|
|
5
5
|
* @input Uses StyleX, theme tokens, and layer-aware group end-cap selectors
|
|
6
|
-
* @output Exports shared group-aware styles for
|
|
7
|
-
* @position Shared styles consumed by InputGroup-compatible controls
|
|
6
|
+
* @output Exports shared group-aware styles for InputGroup children
|
|
7
|
+
* @position Shared styles consumed by InputGroup-compatible controls and addons
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import * as stylex from '@stylexjs/stylex';
|
|
@@ -15,7 +15,45 @@ import {radiusVars, borderVars} from '../theme/tokens.stylex';
|
|
|
15
15
|
// are not visual group members; skip them when finding the trailing edge.
|
|
16
16
|
const IS_LAST_ITEM = ':not(:has(~ *:not([popover]):not(template):not(dialog)))';
|
|
17
17
|
|
|
18
|
+
// Button consumes InputGroup context anywhere below the group, including buttons
|
|
19
|
+
// inside an input's own implementation. Keep the visual adaptation on direct
|
|
20
|
+
// children so internal clear, calendar, and disclosure actions stay unchanged.
|
|
21
|
+
const DIRECT_INPUT_GROUP_ADDON = ':where(.astryx-input-group > *)';
|
|
22
|
+
const DIRECT_INPUT_GROUP_ADDON_FOCUS_VISIBLE =
|
|
23
|
+
':where(.astryx-input-group > *):focus-visible';
|
|
24
|
+
const DIRECT_INPUT_GROUP_ADDON_NOT_FIRST =
|
|
25
|
+
':where(.astryx-input-group > *):not(:first-child)';
|
|
26
|
+
const DIRECT_INPUT_GROUP_ADDON_WITH_MEMBER_AFTER =
|
|
27
|
+
':where(.astryx-input-group > *):has(~ *:not([popover]):not(template):not(dialog))';
|
|
28
|
+
|
|
18
29
|
export const groupStyles = stylex.create({
|
|
30
|
+
buttonAddon: {
|
|
31
|
+
borderStartStartRadius: {
|
|
32
|
+
default: null,
|
|
33
|
+
[DIRECT_INPUT_GROUP_ADDON_NOT_FIRST]: 0,
|
|
34
|
+
},
|
|
35
|
+
borderEndStartRadius: {
|
|
36
|
+
default: null,
|
|
37
|
+
[DIRECT_INPUT_GROUP_ADDON_NOT_FIRST]: 0,
|
|
38
|
+
},
|
|
39
|
+
borderStartEndRadius: {
|
|
40
|
+
default: null,
|
|
41
|
+
[DIRECT_INPUT_GROUP_ADDON_WITH_MEMBER_AFTER]: 0,
|
|
42
|
+
},
|
|
43
|
+
borderEndEndRadius: {
|
|
44
|
+
default: null,
|
|
45
|
+
[DIRECT_INPUT_GROUP_ADDON_WITH_MEMBER_AFTER]: 0,
|
|
46
|
+
},
|
|
47
|
+
height: {
|
|
48
|
+
default: null,
|
|
49
|
+
[DIRECT_INPUT_GROUP_ADDON]: '100%',
|
|
50
|
+
},
|
|
51
|
+
// Keep the keyboard ring above the adjacent group member at the shared edge.
|
|
52
|
+
zIndex: {
|
|
53
|
+
default: null,
|
|
54
|
+
[DIRECT_INPUT_GROUP_ADDON_FOCUS_VISIBLE]: 2,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
19
57
|
inGroup: {
|
|
20
58
|
flex: 1,
|
|
21
59
|
minWidth: 0,
|
|
@@ -96,16 +96,16 @@ export interface UseLayerDismissalReturn {
|
|
|
96
96
|
*
|
|
97
97
|
* The layer does NOT attach a key listener — the stack owns one listener and
|
|
98
98
|
* routes each Escape press to the top-most REGISTERED layer, so one press
|
|
99
|
-
* dismisses exactly one of them. Dialog (and what is built on it
|
|
100
|
-
* the menus built on it,
|
|
101
|
-
* BottomSheetSwitcher and lab's
|
|
99
|
+
* dismisses exactly one of them. Dialog (and what is built on it, including
|
|
100
|
+
* CommandPalette), Popover and the menus built on it, ContextMenu, Tooltip,
|
|
101
|
+
* HoverCard, Lightbox, MobileNav, BottomSheet, BottomSheetSwitcher and lab's
|
|
102
|
+
* `Drawer` register today.
|
|
102
103
|
*
|
|
103
|
-
* `
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* fix.
|
|
104
|
+
* `DropdownMenuSubMenu` and `PowerSearchEditPopover` still run their own Escape
|
|
105
|
+
* listeners. They stay safe next to the stack only because each claims the press
|
|
106
|
+
* at element level, and the stack stands down on an already-`defaultPrevented`
|
|
107
|
+
* press — but a registered layer opened inside one of them does not get the
|
|
108
|
+
* press. Migrating them is the fix.
|
|
109
109
|
*
|
|
110
110
|
* Wrap the layer's own content in `LayerDepthProvider` so anything opened from
|
|
111
111
|
* inside it registers as nested.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Real-browser and accessibility-tree binding evidence for Link.
|
|
5
|
+
*
|
|
6
|
+
* A link's activation leaves no trace on the link, so the binding counts every
|
|
7
|
+
* navigation the story requests: native navigations through the browser's
|
|
8
|
+
* Navigation API (cancelled, so the story stays mounted), router navigations
|
|
9
|
+
* the story's router component records, and new tabs the page opens.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {expect, test, type CDPSession, type Page} from '@playwright/test';
|
|
13
|
+
import {
|
|
14
|
+
LINK_PATTERN,
|
|
15
|
+
blockingResults,
|
|
16
|
+
checkAccessibilitySpec,
|
|
17
|
+
formatFailures,
|
|
18
|
+
neverExercised,
|
|
19
|
+
summarize,
|
|
20
|
+
type BindingResult,
|
|
21
|
+
} from '@astryxdesign/a11y-spec';
|
|
22
|
+
import {
|
|
23
|
+
createChromiumHarness,
|
|
24
|
+
holdMotionStill,
|
|
25
|
+
} from '@astryxdesign/a11y-spec/chromium';
|
|
26
|
+
import {
|
|
27
|
+
DEFAULT_STORYBOOK_DIR,
|
|
28
|
+
serveStorybook,
|
|
29
|
+
type StaticServer,
|
|
30
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
31
|
+
import {LINK_A11Y_STATES, type LinkA11yRow} from './Link.a11y.states';
|
|
32
|
+
|
|
33
|
+
const SUBJECT_SELECTOR = '[data-a11y-link]';
|
|
34
|
+
const NEW_TAB_SETTLE_MS = 500;
|
|
35
|
+
let storybook: StaticServer;
|
|
36
|
+
|
|
37
|
+
interface NavigationWindow extends Window {
|
|
38
|
+
__a11yLinkNavigations?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test.beforeAll(async () => {
|
|
42
|
+
storybook = await serveStorybook(
|
|
43
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test.afterAll(async () => storybook?.close());
|
|
48
|
+
|
|
49
|
+
function storyUrl(id: string): string {
|
|
50
|
+
return `${storybook.origin}/iframe.html?id=${id}&viewMode=story`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function countNavigations(
|
|
54
|
+
page: Page,
|
|
55
|
+
opensNewTab: boolean,
|
|
56
|
+
): Promise<() => Promise<number>> {
|
|
57
|
+
let newTabs = 0;
|
|
58
|
+
page.context().on('page', opened => {
|
|
59
|
+
newTabs += 1;
|
|
60
|
+
void opened.close();
|
|
61
|
+
});
|
|
62
|
+
await page.evaluate(() => {
|
|
63
|
+
const target = window as NavigationWindow & {navigation: EventTarget};
|
|
64
|
+
target.__a11yLinkNavigations = 0;
|
|
65
|
+
target.navigation.addEventListener('navigate', event => {
|
|
66
|
+
target.__a11yLinkNavigations = (target.__a11yLinkNavigations ?? 0) + 1;
|
|
67
|
+
if (event.cancelable) {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
return async () => {
|
|
73
|
+
// A new tab reaches the test through the browser's target events, after
|
|
74
|
+
// the click or key that opened it has returned. Give it time to arrive so
|
|
75
|
+
// a later reading cannot miss a navigation an earlier one started.
|
|
76
|
+
if (opensNewTab) {
|
|
77
|
+
await page.waitForTimeout(NEW_TAB_SETTLE_MS);
|
|
78
|
+
}
|
|
79
|
+
const inPage = await page.evaluate(
|
|
80
|
+
() => (window as NavigationWindow).__a11yLinkNavigations ?? 0,
|
|
81
|
+
);
|
|
82
|
+
return inPage + newTabs;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function runState(
|
|
87
|
+
page: Page,
|
|
88
|
+
cdp: CDPSession,
|
|
89
|
+
state: LinkA11yRow,
|
|
90
|
+
): Promise<BindingResult> {
|
|
91
|
+
let navigations: () => Promise<number> = async () => {
|
|
92
|
+
throw new Error('the Link story was not mounted');
|
|
93
|
+
};
|
|
94
|
+
return checkAccessibilitySpec({
|
|
95
|
+
spec: LINK_PATTERN,
|
|
96
|
+
binding: 'Link',
|
|
97
|
+
state: state.id,
|
|
98
|
+
facts: state.facts,
|
|
99
|
+
mount: async () => {
|
|
100
|
+
await page.goto(storyUrl(state.storyId), {waitUntil: 'load'});
|
|
101
|
+
await holdMotionStill(page);
|
|
102
|
+
const subject = page.locator(SUBJECT_SELECTOR);
|
|
103
|
+
await subject.waitFor({state: 'visible'});
|
|
104
|
+
await expect(subject).toHaveCount(1);
|
|
105
|
+
navigations = await countNavigations(
|
|
106
|
+
page,
|
|
107
|
+
state.id === 'external-new-tab',
|
|
108
|
+
);
|
|
109
|
+
return createChromiumHarness({page, cdp, subject});
|
|
110
|
+
},
|
|
111
|
+
activations: async () => navigations(),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
test('every Link expectation is exercised by at least one Link state', async ({
|
|
116
|
+
page,
|
|
117
|
+
}) => {
|
|
118
|
+
// Mounts every state in one test, so it gets every state's time budget.
|
|
119
|
+
test.setTimeout(30_000 * LINK_A11Y_STATES.length);
|
|
120
|
+
const cdp = await page.context().newCDPSession(page);
|
|
121
|
+
const results: BindingResult[] = [];
|
|
122
|
+
for (const state of LINK_A11Y_STATES) {
|
|
123
|
+
results.push(await runState(page, cdp, state));
|
|
124
|
+
}
|
|
125
|
+
expect(neverExercised(results)).toEqual([]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
for (const state of LINK_A11Y_STATES) {
|
|
129
|
+
test(`Link [${state.id}] — ${state.summary}`, async ({page}) => {
|
|
130
|
+
const cdp = await page.context().newCDPSession(page);
|
|
131
|
+
const result = await runState(page, cdp, state);
|
|
132
|
+
const report = summarize(LINK_PATTERN, [result]);
|
|
133
|
+
expect(formatFailures(blockingResults([result]))).toBe('');
|
|
134
|
+
expect(
|
|
135
|
+
result.results
|
|
136
|
+
.filter(row => row.status === 'fail')
|
|
137
|
+
.map(row => `${row.expectation}: ${row.detail ?? ''}`),
|
|
138
|
+
).toEqual([]);
|
|
139
|
+
expect(report.unrunLayers).toEqual([]);
|
|
140
|
+
expect(report.counts.unexpectedPass).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/** Stable Link renders shared by jsdom, Storybook, and Chromium. */
|
|
6
|
+
|
|
7
|
+
import type {ComponentProps, MouseEvent, ReactElement} from 'react';
|
|
8
|
+
import {Icon} from '../../Icon';
|
|
9
|
+
import {Link} from '../Link';
|
|
10
|
+
import {LinkProvider} from '../LinkProvider';
|
|
11
|
+
import {LINK_A11Y_DESTINATION, type LinkA11yRow} from './Link.a11y.states';
|
|
12
|
+
|
|
13
|
+
interface NavigationWindow extends Window {
|
|
14
|
+
__a11yLinkNavigations?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A minimal client-side router link. It takes over activation the way router
|
|
19
|
+
* links do — cancel the browser navigation, then navigate itself — and counts
|
|
20
|
+
* each navigation on the window, where the browser binding reads it.
|
|
21
|
+
*/
|
|
22
|
+
function RouterLink({onClick, children, ...props}: ComponentProps<'a'>) {
|
|
23
|
+
const navigate = (event: MouseEvent<HTMLAnchorElement>) => {
|
|
24
|
+
onClick?.(event);
|
|
25
|
+
if (event.defaultPrevented) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
const target = window as NavigationWindow;
|
|
30
|
+
target.__a11yLinkNavigations = (target.__a11yLinkNavigations ?? 0) + 1;
|
|
31
|
+
};
|
|
32
|
+
return (
|
|
33
|
+
// eslint-disable-next-line @astryx/no-hardcoded-anchor -- this stands in for a third-party router's own anchor, which Link receives through LinkProvider and `as`.
|
|
34
|
+
<a {...props} data-a11y-link-router onClick={navigate}>
|
|
35
|
+
{children}
|
|
36
|
+
</a>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A tab stop after the link, so focus has somewhere to go when it leaves. */
|
|
41
|
+
function Frame({children}: {children: ReactElement}) {
|
|
42
|
+
return (
|
|
43
|
+
<div>
|
|
44
|
+
{children}
|
|
45
|
+
<button type="button">After</button>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const LINK_A11Y_RENDERS: Readonly<
|
|
51
|
+
Record<LinkA11yRow['id'], () => ReactElement>
|
|
52
|
+
> = {
|
|
53
|
+
'text-native': () => (
|
|
54
|
+
<Frame>
|
|
55
|
+
<Link data-a11y-link href={LINK_A11Y_DESTINATION}>
|
|
56
|
+
Documentation
|
|
57
|
+
</Link>
|
|
58
|
+
</Frame>
|
|
59
|
+
),
|
|
60
|
+
'icon-labelled': () => (
|
|
61
|
+
<Frame>
|
|
62
|
+
<Link data-a11y-link href={LINK_A11Y_DESTINATION} label="Close dialog">
|
|
63
|
+
<Icon icon="close" />
|
|
64
|
+
</Link>
|
|
65
|
+
</Frame>
|
|
66
|
+
),
|
|
67
|
+
'current-page': () => (
|
|
68
|
+
<Frame>
|
|
69
|
+
<Link aria-current="page" data-a11y-link href={LINK_A11Y_DESTINATION}>
|
|
70
|
+
Overview
|
|
71
|
+
</Link>
|
|
72
|
+
</Frame>
|
|
73
|
+
),
|
|
74
|
+
'external-new-tab': () => (
|
|
75
|
+
<Frame>
|
|
76
|
+
<Link data-a11y-link href={LINK_A11Y_DESTINATION} isExternalLink>
|
|
77
|
+
GitHub
|
|
78
|
+
</Link>
|
|
79
|
+
</Frame>
|
|
80
|
+
),
|
|
81
|
+
'provider-router': () => (
|
|
82
|
+
<LinkProvider component={RouterLink}>
|
|
83
|
+
<Frame>
|
|
84
|
+
<Link data-a11y-link href="/projects">
|
|
85
|
+
Projects
|
|
86
|
+
</Link>
|
|
87
|
+
</Frame>
|
|
88
|
+
</LinkProvider>
|
|
89
|
+
),
|
|
90
|
+
'as-router': () => (
|
|
91
|
+
<Frame>
|
|
92
|
+
<Link as={RouterLink} data-a11y-link href="/settings">
|
|
93
|
+
Settings
|
|
94
|
+
</Link>
|
|
95
|
+
</Frame>
|
|
96
|
+
),
|
|
97
|
+
};
|