@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
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CodeBlockOverflowStop.a11y.chromium.spec.ts
|
|
5
|
+
* @input Built CodeBlock story and real Chromium layout
|
|
6
|
+
* @output Browser evidence that the code viewport is a Tab stop only while it overflows
|
|
7
|
+
* @position CodeBlock FR8 / AST-025 FR12–FR13 proof over the shared scroll capability
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {expect, test, type Page} from '@playwright/test';
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_STORYBOOK_DIR,
|
|
13
|
+
serveStorybook,
|
|
14
|
+
type StaticServer,
|
|
15
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
16
|
+
|
|
17
|
+
let storybook: StaticServer;
|
|
18
|
+
test.beforeAll(async () => {
|
|
19
|
+
storybook = await serveStorybook(
|
|
20
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
test.afterAll(async () => {
|
|
24
|
+
await storybook?.close();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const STORY = 'core-codeblock--overflow-stop';
|
|
28
|
+
const WIDE = 'const wide = "' + 'x'.repeat(400) + '";';
|
|
29
|
+
const TALL = Array.from({length: 40}, (_, i) => `const line${i} = ${i};`).join(
|
|
30
|
+
'\n',
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
async function mount(page: Page) {
|
|
34
|
+
await page.setViewportSize({width: 600, height: 500});
|
|
35
|
+
await page.goto(`${storybook.origin}/iframe.html?id=${STORY}&viewMode=story`);
|
|
36
|
+
await expect(page.getByRole('group', {name: 'tsx'})).toBeVisible();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function setArgs(page: Page, args: Record<string, unknown>) {
|
|
40
|
+
await page.evaluate(
|
|
41
|
+
async ({storyId, updatedArgs}) =>
|
|
42
|
+
new Promise<void>(resolve => {
|
|
43
|
+
const channel = (
|
|
44
|
+
window as unknown as {
|
|
45
|
+
__STORYBOOK_ADDONS_CHANNEL__: {
|
|
46
|
+
emit: (event: string, payload: unknown) => void;
|
|
47
|
+
once: (event: string, handler: () => void) => void;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
).__STORYBOOK_ADDONS_CHANNEL__;
|
|
51
|
+
channel.once('storyRendered', () => resolve());
|
|
52
|
+
channel.emit('updateStoryArgs', {storyId, updatedArgs});
|
|
53
|
+
setTimeout(resolve, 1500);
|
|
54
|
+
}),
|
|
55
|
+
{storyId: STORY, updatedArgs: args},
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function viewport(page: Page) {
|
|
60
|
+
return page.getByRole('group', {name: 'tsx'});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test('a fitting viewport is not a Tab stop', async ({page}) => {
|
|
64
|
+
await mount(page);
|
|
65
|
+
await expect(viewport(page)).not.toHaveAttribute('tabindex', /.*/);
|
|
66
|
+
// Tab from the document start lands on the copy button, never the viewport.
|
|
67
|
+
await page.keyboard.press('Tab');
|
|
68
|
+
const active = await page.evaluate(() =>
|
|
69
|
+
document.activeElement?.getAttribute('role'),
|
|
70
|
+
);
|
|
71
|
+
expect(active).not.toBe('group');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('inline overflow adds the stop; block overflow adds it too', async ({
|
|
75
|
+
page,
|
|
76
|
+
}) => {
|
|
77
|
+
await mount(page);
|
|
78
|
+
await setArgs(page, {code: WIDE});
|
|
79
|
+
await expect(viewport(page)).toHaveAttribute('tabindex', '0');
|
|
80
|
+
await expect(viewport(page)).toHaveAttribute(
|
|
81
|
+
'data-scrollable-inline',
|
|
82
|
+
'true',
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
await setArgs(page, {code: TALL, maxHeight: 160});
|
|
86
|
+
await expect(viewport(page)).toHaveAttribute('tabindex', '0');
|
|
87
|
+
await expect(viewport(page)).toHaveAttribute('data-scrollable-block', 'true');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('losing overflow removes the stop without moving focus', async ({
|
|
91
|
+
page,
|
|
92
|
+
}) => {
|
|
93
|
+
await mount(page);
|
|
94
|
+
await setArgs(page, {code: WIDE});
|
|
95
|
+
await expect(viewport(page)).toHaveAttribute('tabindex', '0');
|
|
96
|
+
await viewport(page).focus();
|
|
97
|
+
await expect(viewport(page)).toBeFocused();
|
|
98
|
+
|
|
99
|
+
await setArgs(page, {code: 'const fits = true;'});
|
|
100
|
+
// Focus stays on the element that had it; the stop is gone for the next Tab.
|
|
101
|
+
await expect(viewport(page)).toBeFocused();
|
|
102
|
+
await expect(viewport(page)).toHaveAttribute('tabindex', '-1');
|
|
103
|
+
// Leaving and coming back by keyboard skips it: it is out of the Tab order.
|
|
104
|
+
await page.keyboard.press('Shift+Tab');
|
|
105
|
+
await page.keyboard.press('Tab');
|
|
106
|
+
await expect(viewport(page)).not.toBeFocused();
|
|
107
|
+
await expect(viewport(page)).not.toHaveAttribute('tabindex', '0');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('a root height set from outside bounds the viewport, which then scrolls', async ({
|
|
111
|
+
page,
|
|
112
|
+
}) => {
|
|
113
|
+
await mount(page);
|
|
114
|
+
// The IDE template's pattern: the block fills a parent of fixed height.
|
|
115
|
+
await setArgs(page, {code: TALL, style: {height: 160}});
|
|
116
|
+
const vp = viewport(page);
|
|
117
|
+
await expect(vp).toHaveAttribute('tabindex', '0');
|
|
118
|
+
await expect(vp).toHaveAttribute('data-scrollable-block', 'true');
|
|
119
|
+
const box = await vp.boundingBox();
|
|
120
|
+
expect(box?.height ?? 0).toBeLessThan(160);
|
|
121
|
+
const scrollable = await vp.evaluate(
|
|
122
|
+
node => node.scrollHeight > node.clientHeight + 1,
|
|
123
|
+
);
|
|
124
|
+
expect(scrollable).toBe(true);
|
|
125
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file DiffRowPaint.a11y.chromium.spec.ts
|
|
5
|
+
* @input Built CodeBlock diff story with lines longer than a phone viewport
|
|
6
|
+
* @output Browser evidence that a changed row's paint spans the full scroll width
|
|
7
|
+
* @position CodeBlock FR9 proof: row paint follows a scrolled line, and wraps
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {expect, test, type Page} from '@playwright/test';
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_STORYBOOK_DIR,
|
|
13
|
+
serveStorybook,
|
|
14
|
+
type StaticServer,
|
|
15
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
16
|
+
|
|
17
|
+
let storybook: StaticServer;
|
|
18
|
+
test.beforeAll(async () => {
|
|
19
|
+
storybook = await serveStorybook(
|
|
20
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
test.afterAll(async () => {
|
|
24
|
+
await storybook?.close();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
async function mount(page: Page, wrapped: boolean) {
|
|
28
|
+
await page.setViewportSize({width: 390, height: 700});
|
|
29
|
+
await page.goto(
|
|
30
|
+
`${storybook.origin}/iframe.html?id=core-codeblock--diff-long-lines&viewMode=story&args=isWrapped:${wrapped}`,
|
|
31
|
+
);
|
|
32
|
+
await expect(page.locator('[data-diff="added"]')).toHaveCount(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The painted rows, the scroll container, and the widths that matter. */
|
|
36
|
+
async function measure(page: Page) {
|
|
37
|
+
return page.evaluate(() => {
|
|
38
|
+
const scroller = document.querySelector('[role="group"]') as HTMLElement;
|
|
39
|
+
const rows = ['added', 'removed'].map(kind => {
|
|
40
|
+
const row = document.querySelector(
|
|
41
|
+
`[data-diff="${kind}"]`,
|
|
42
|
+
) as HTMLElement;
|
|
43
|
+
const rect = row.getBoundingClientRect();
|
|
44
|
+
return {
|
|
45
|
+
kind,
|
|
46
|
+
width: Math.round(rect.width),
|
|
47
|
+
lines: Math.round(
|
|
48
|
+
rect.height / parseFloat(getComputedStyle(row).lineHeight),
|
|
49
|
+
),
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
clientWidth: scroller.clientWidth,
|
|
54
|
+
scrollWidth: scroller.scrollWidth,
|
|
55
|
+
rows,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
test("a changed row's paint spans the full scroll width of a long line", async ({
|
|
61
|
+
page,
|
|
62
|
+
}) => {
|
|
63
|
+
await mount(page, false);
|
|
64
|
+
const before = await measure(page);
|
|
65
|
+
// The line really overflows a phone viewport.
|
|
66
|
+
expect(before.scrollWidth).toBeGreaterThan(before.clientWidth + 100);
|
|
67
|
+
// Each painted row is as wide as the scrollable content, not the viewport.
|
|
68
|
+
for (const row of before.rows) {
|
|
69
|
+
expect(row.width).toBeGreaterThanOrEqual(before.scrollWidth - 1);
|
|
70
|
+
}
|
|
71
|
+
// Scrolled to the end, the rows still cover what is on screen.
|
|
72
|
+
await page.evaluate(() => {
|
|
73
|
+
const scroller = document.querySelector('[role="group"]') as HTMLElement;
|
|
74
|
+
scroller.scrollLeft = scroller.scrollWidth;
|
|
75
|
+
});
|
|
76
|
+
const after = await measure(page);
|
|
77
|
+
for (const row of after.rows) {
|
|
78
|
+
expect(row.width).toBeGreaterThanOrEqual(after.scrollWidth - 1);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('in wrapped mode the rows wrap and still paint their full width', async ({
|
|
83
|
+
page,
|
|
84
|
+
}) => {
|
|
85
|
+
await mount(page, true);
|
|
86
|
+
const m = await measure(page);
|
|
87
|
+
// Nothing scrolls: the long line wrapped.
|
|
88
|
+
expect(m.scrollWidth).toBeLessThanOrEqual(m.clientWidth + 1);
|
|
89
|
+
for (const row of m.rows) {
|
|
90
|
+
expect(row.lines).toBeGreaterThan(1);
|
|
91
|
+
expect(row.width).toBeGreaterThanOrEqual(m.clientWidth - 1);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
tokenizeAsync,
|
|
7
7
|
tokenizeStreaming,
|
|
8
8
|
flatTokensToLines,
|
|
9
|
+
diffLineKind,
|
|
10
|
+
parseFenceLanguage,
|
|
11
|
+
splitDiffLines,
|
|
9
12
|
} from './tokenizer';
|
|
10
13
|
import type {TokenLine} from './tokenizer';
|
|
11
14
|
|
|
@@ -166,3 +169,112 @@ describe('flatTokensToLines', () => {
|
|
|
166
169
|
expect(result[1]).toContainEqual({type: 'number', start: 8, end: 9});
|
|
167
170
|
});
|
|
168
171
|
});
|
|
172
|
+
|
|
173
|
+
describe('diffLineKind', () => {
|
|
174
|
+
it('classifies unified-diff lines by their first character', () => {
|
|
175
|
+
expect(diffLineKind('+added line')).toBe('added');
|
|
176
|
+
expect(diffLineKind('-removed line')).toBe('removed');
|
|
177
|
+
expect(diffLineKind(' context line')).toBe('context');
|
|
178
|
+
expect(diffLineKind('')).toBe('context');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('keeps hunk headers out of the change rows; a lone --- or +++ is a change', () => {
|
|
182
|
+
expect(diffLineKind('@@ -1,3 +1,4 @@')).toBe('header');
|
|
183
|
+
expect(diffLineKind('--- a/file.ts')).toBe('removed');
|
|
184
|
+
expect(diffLineKind('+++ b/file.ts')).toBe('added');
|
|
185
|
+
// Lines with no glyph are context, whatever tool wrote them.
|
|
186
|
+
expect(diffLineKind('diff --git a/x b/x')).toBe('context');
|
|
187
|
+
expect(diffLineKind('index 83db48f..bf269f4 100644')).toBe('context');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('splits the glyph column from the code the base language sees', () => {
|
|
191
|
+
const {kinds, glyphs, content} = splitDiffLines([
|
|
192
|
+
'@@ -1 +1 @@',
|
|
193
|
+
'- const a = 1;',
|
|
194
|
+
'+ const a = 2;',
|
|
195
|
+
' const b = 3;',
|
|
196
|
+
'',
|
|
197
|
+
]);
|
|
198
|
+
expect(kinds).toEqual(['header', 'removed', 'added', 'context', 'context']);
|
|
199
|
+
expect(glyphs).toEqual(['', '-', '+', ' ', '']);
|
|
200
|
+
expect(content).toEqual([
|
|
201
|
+
'',
|
|
202
|
+
' const a = 1;',
|
|
203
|
+
' const a = 2;',
|
|
204
|
+
' const b = 3;',
|
|
205
|
+
'',
|
|
206
|
+
]);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('treats a ---/+++ pair directly before @@ as a file header anywhere, and keeps unindented context whole', () => {
|
|
210
|
+
const {kinds, glyphs, content} = splitDiffLines([
|
|
211
|
+
'--- a/x.ts',
|
|
212
|
+
'+++ b/x.ts',
|
|
213
|
+
'@@ -1 +1 @@',
|
|
214
|
+
'--x;',
|
|
215
|
+
'---',
|
|
216
|
+
'+++y;',
|
|
217
|
+
'plain',
|
|
218
|
+
'--- a/y.ts',
|
|
219
|
+
'+++ b/y.ts',
|
|
220
|
+
'@@ -1 +1 @@',
|
|
221
|
+
'-old',
|
|
222
|
+
]);
|
|
223
|
+
expect(kinds).toEqual([
|
|
224
|
+
'header',
|
|
225
|
+
'header',
|
|
226
|
+
'header',
|
|
227
|
+
'removed',
|
|
228
|
+
'removed',
|
|
229
|
+
'added',
|
|
230
|
+
'context',
|
|
231
|
+
'header',
|
|
232
|
+
'header',
|
|
233
|
+
'header',
|
|
234
|
+
'removed',
|
|
235
|
+
]);
|
|
236
|
+
expect(glyphs.slice(0, 7)).toEqual(['', '', '', '-', '-', '+', '']);
|
|
237
|
+
expect(content.slice(3, 7)).toEqual(['-x;', '--', '++y;', 'plain']);
|
|
238
|
+
// No hunk after the pair: both lines are changes, not headers.
|
|
239
|
+
expect(splitDiffLines(['--- gone', '+++ here', ' same']).kinds).toEqual([
|
|
240
|
+
'removed',
|
|
241
|
+
'added',
|
|
242
|
+
'context',
|
|
243
|
+
]);
|
|
244
|
+
// A hunk ending in `---i;` / `+++i;` right before the next `@@` is code,
|
|
245
|
+
// not a file header: the header form has a space after the signs.
|
|
246
|
+
expect(
|
|
247
|
+
splitDiffLines(['@@ -1 +1 @@', '---i;', '+++i;', '@@ -9 +9 @@', ' x'])
|
|
248
|
+
.kinds,
|
|
249
|
+
).toEqual(['header', 'removed', 'added', 'header', 'context']);
|
|
250
|
+
expect(diffLineKind('--- a/x.ts')).toBe('removed');
|
|
251
|
+
expect(diffLineKind('--- a/x.ts', {isFileHeader: true})).toBe('header');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('tokenizes the code past the glyph with the base language', () => {
|
|
255
|
+
const {content} = splitDiffLines(['-const a = 1;', '+const a = 2;']);
|
|
256
|
+
const lines = tokenize(content.join('\n'), 'typescript');
|
|
257
|
+
expect(lines[0][0]).toEqual({type: 'keyword', start: 0, end: 5});
|
|
258
|
+
expect(lines[1][0]).toEqual({type: 'keyword', start: 0, end: 5});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('reads fence info strings: diff, diff-<lang>, and a plain language (patch is just a language)', () => {
|
|
262
|
+
expect(parseFenceLanguage('diff')).toEqual({
|
|
263
|
+
language: 'plaintext',
|
|
264
|
+
isDiff: true,
|
|
265
|
+
});
|
|
266
|
+
expect(parseFenceLanguage('patch')).toEqual({
|
|
267
|
+
language: 'patch',
|
|
268
|
+
isDiff: false,
|
|
269
|
+
});
|
|
270
|
+
expect(parseFenceLanguage('diff-tsx')).toEqual({
|
|
271
|
+
language: 'tsx',
|
|
272
|
+
isDiff: true,
|
|
273
|
+
});
|
|
274
|
+
expect(parseFenceLanguage('tsx')).toEqual({language: 'tsx', isDiff: false});
|
|
275
|
+
expect(parseFenceLanguage(undefined)).toEqual({
|
|
276
|
+
language: 'plaintext',
|
|
277
|
+
isDiff: false,
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
});
|
|
@@ -313,6 +313,112 @@ function buildLanguagePatterns(lang: string): {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
// Diff lines
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
/** How a unified-diff line reads: what it adds, removes, frames, or keeps. */
|
|
321
|
+
export type DiffLineKind = 'added' | 'removed' | 'header' | 'context';
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Read a Markdown fence info string the way CodeBlock renders it: `diff` is
|
|
325
|
+
* the diff layer over plaintext, `diff-<lang>` (the common convention) is the
|
|
326
|
+
* diff layer over `<lang>`, anything else is a language.
|
|
327
|
+
*/
|
|
328
|
+
export function parseFenceLanguage(info: string | undefined): {
|
|
329
|
+
language: string;
|
|
330
|
+
isDiff: boolean;
|
|
331
|
+
} {
|
|
332
|
+
const value = (info ?? '').trim();
|
|
333
|
+
if (value === 'diff') {
|
|
334
|
+
return {language: 'plaintext', isDiff: true};
|
|
335
|
+
}
|
|
336
|
+
const layered = /^diff-(.+)$/.exec(value);
|
|
337
|
+
if (layered) {
|
|
338
|
+
return {language: layered[1], isDiff: true};
|
|
339
|
+
}
|
|
340
|
+
return {language: value || 'plaintext', isDiff: false};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Split a diff into the glyph column and the code the base language sees:
|
|
345
|
+
* every added, removed, or space-led context line loses its first character
|
|
346
|
+
* (the `+`, `-`, or space); a context line with no leading space keeps all
|
|
347
|
+
* of it; header lines contribute an empty line so offsets stay per line.
|
|
348
|
+
*/
|
|
349
|
+
export function splitDiffLines(lines: ReadonlyArray<string>): {
|
|
350
|
+
kinds: DiffLineKind[];
|
|
351
|
+
glyphs: string[];
|
|
352
|
+
content: string[];
|
|
353
|
+
} {
|
|
354
|
+
const kinds: DiffLineKind[] = [];
|
|
355
|
+
const glyphs: string[] = [];
|
|
356
|
+
const content: string[] = [];
|
|
357
|
+
const fileHeaders = fileHeaderLines(lines);
|
|
358
|
+
lines.forEach((line, index) => {
|
|
359
|
+
const kind = diffLineKind(line, {isFileHeader: fileHeaders.has(index)});
|
|
360
|
+
kinds.push(kind);
|
|
361
|
+
if (kind === 'header') {
|
|
362
|
+
glyphs.push('');
|
|
363
|
+
content.push('');
|
|
364
|
+
} else if (kind === 'context' && !line.startsWith(' ')) {
|
|
365
|
+
// A context line without its leading space (an unindented diff, or
|
|
366
|
+
// an empty line) has no glyph: the marker column is empty and the
|
|
367
|
+
// whole line is code.
|
|
368
|
+
glyphs.push('');
|
|
369
|
+
content.push(line);
|
|
370
|
+
} else {
|
|
371
|
+
glyphs.push(line.slice(0, 1));
|
|
372
|
+
content.push(line.slice(1));
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
return {kinds, glyphs, content};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Classify one line of a unified diff by its first character: `+` added,
|
|
380
|
+
* `-` removed, `@@` header, anything else context. A `--- ` line (with its
|
|
381
|
+
* space) directly followed by a `+++ ` line and then an `@@` line is a file
|
|
382
|
+
* header (both lines), wherever it occurs; every other `---`/`+++` line is
|
|
383
|
+
* removed or added, so a removed line that starts with `--` — `---i;`
|
|
384
|
+
* included — is never misread. The glyph is part of the text and is never
|
|
385
|
+
* removed.
|
|
386
|
+
*/
|
|
387
|
+
export function diffLineKind(
|
|
388
|
+
line: string,
|
|
389
|
+
{isFileHeader = false}: {isFileHeader?: boolean} = {},
|
|
390
|
+
): DiffLineKind {
|
|
391
|
+
if (line.startsWith('@@') || isFileHeader) {
|
|
392
|
+
return 'header';
|
|
393
|
+
}
|
|
394
|
+
if (line.startsWith('+')) {
|
|
395
|
+
return 'added';
|
|
396
|
+
}
|
|
397
|
+
if (line.startsWith('-')) {
|
|
398
|
+
return 'removed';
|
|
399
|
+
}
|
|
400
|
+
return 'context';
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* The indexes of the `--- `/`+++ ` lines (unified-diff form, with the space)
|
|
405
|
+
* that form a file header directly before an `@@`; `---i;` is a change.
|
|
406
|
+
*/
|
|
407
|
+
export function fileHeaderLines(lines: ReadonlyArray<string>): Set<number> {
|
|
408
|
+
const headers = new Set<number>();
|
|
409
|
+
for (let i = 0; i + 2 < lines.length; i++) {
|
|
410
|
+
if (
|
|
411
|
+
lines[i].startsWith('--- ') &&
|
|
412
|
+
lines[i + 1].startsWith('+++ ') &&
|
|
413
|
+
lines[i + 2].startsWith('@@')
|
|
414
|
+
) {
|
|
415
|
+
headers.add(i);
|
|
416
|
+
headers.add(i + 1);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return headers;
|
|
420
|
+
}
|
|
421
|
+
|
|
316
422
|
// ---------------------------------------------------------------------------
|
|
317
423
|
// Core tokenizer
|
|
318
424
|
// ---------------------------------------------------------------------------
|
|
@@ -133,6 +133,7 @@ Consumer migration instructions belong in consumer docs and release notes.
|
|
|
133
133
|
- **AR1 — Child semantics remain primary.** Group coordination MUST preserve each Collapsible button's accessible name, `aria-expanded`, `aria-controls`, controlled region, and keyboard activation.
|
|
134
134
|
- **AR2 — Presentation adds no competing interaction.** The divider wrapper MUST NOT add a role, focus target, accessible name, or keyboard behavior.
|
|
135
135
|
- **AR3 — Direction stays with Collapsible.** Group chevron defaults MUST preserve `component:Collapsible/AR1–AR3`, including the inward leading cue under LTR and RTL.
|
|
136
|
+
- **AR4 — The group adopts the APG Accordion pattern's required parts** (`spec:AST-020/FR15`): each direct item's header is a `button` whose accessible name is the item's title, with `aria-expanded` reflecting its panel, `aria-controls` naming the panel it reveals, and `aria-disabled` when it cannot be toggled; every enabled header sits in the page Tab order and Enter or Space toggles its panel, in `single` mode collapsing the sibling that was open; a disabled header carries `aria-disabled`, drops out of the Tab order, and cannot be toggled. Not adopted: a heading element around each header (this record requires none; the caller's document structure supplies headings), the optional `region` role on panels, and arrow-key or Home/End movement between headers — the group is a set of independent disclosures, not one composite control.
|
|
136
137
|
|
|
137
138
|
## Design relationships
|
|
138
139
|
|
|
@@ -171,17 +172,25 @@ The Group container is CollapsibleGroup's conditional aggregate wrapper. The oth
|
|
|
171
172
|
|
|
172
173
|
## Verification map
|
|
173
174
|
|
|
174
|
-
| Contract | Verification
|
|
175
|
-
| ---------------------- |
|
|
176
|
-
| FR1–FR4, ORD1 | `CollapsibleGroup.test.tsx`; `useCollapsible.test.tsx`
|
|
177
|
-
| FR5–FR6, ORD2–ORD3 | `CollapsibleGroup.test.tsx`; `Collapsible.test.tsx`; `themingTargets.test.ts`
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
175
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
176
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
|
|
177
|
+
| FR1–FR4, ORD1 | `CollapsibleGroup.test.tsx`; `useCollapsible.test.tsx` | single/multiple; controlled/uncontrolled; valued/unvalued | A sibling stays open in single mode, closes in multiple mode, controlled state mutates internally, or an unvalued item loses local state. | `audit:CollapsibleGroup/behavior` |
|
|
178
|
+
| FR5–FR6, ORD2–ORD3 | `CollapsibleGroup.test.tsx`; `Collapsible.test.tsx`; `themingTargets.test.ts` | plain/divided; each density; group/item/nested position | Wrapper/target reachability changes, defaults stop reaching direct items, or presentation leaks into nested content. | `audit:CollapsibleGroup/theming` |
|
|
179
|
+
| AR4 | `Collapsible.test.tsx` (disabled trigger: `aria-disabled`, out of the Tab order, no toggle); `CollapsibleGroup.test.tsx` (single/multiple coordination, `aria-expanded`, Enter activation); `Collapsible.a11y.test.tsx` and `Collapsible.a11y.chromium.spec.ts` (disclosure semantics, Space, Tab reachability for a lone item); grouped Tab order, Space in a group, and no arrow-key movement under the Accordion contract (`internal/a11y-spec`, pending) | single/multiple; open/closed; a disabled item, open or closed; Tab order; Enter/Space round trip | A header loses its button role, name, `aria-expanded`, or `aria-controls`; Enter or Space stops toggling; arrow keys move focus between headers | `audit:CollapsibleGroup/accessibility` |
|
|
180
|
+
| AR1–AR3 | DOM and Chromium accessibility suites; component-owned Storybook fixture; RTL audit | open/closed; single/multiple; leading/trailing; LTR/RTL | Disclosure semantics diverge from state, the wrapper becomes interactive, or leading direction stops mirroring. | `audit:CollapsibleGroup/accessibility` |
|
|
181
|
+
| PR1–PR2, FR7 | source review plus context and interaction tests | unchanged and changed state/presentation inputs | An Effect/resource appears, unchanged payload broadcasts a new identity, or changed group behavior fails to reach consumers. | `audit:CollapsibleGroup/code-health` |
|
|
182
|
+
| Consumer documentation | CLI component projection, docs checks, and `CollapsibleGroup.stories.tsx` | every public concept and representative composition | A released prop/default is undiscoverable or the owned browser route loses representative state coverage. | `audit:CollapsibleGroup/docs` |
|
|
181
183
|
|
|
182
184
|
## Decision log
|
|
183
185
|
|
|
184
|
-
|
|
186
|
+
### DEC-1 — Adopt Accordion's required parts; keep the headers independent
|
|
187
|
+
|
|
188
|
+
**Reference:** `component:CollapsibleGroup/DEC-1`
|
|
189
|
+
**Decider:** `cixzhang`, `2026-10-13`
|
|
190
|
+
|
|
191
|
+
The group's headers already play the accordion header's role, so they carry its name, state, relationship, and Enter/Space activation (AR4). They stay in the page Tab order without arrow-key or Home/End movement because the group coordinates independent disclosures rather than acting as one composite control; a heading wrapper is left to the caller's document structure.
|
|
192
|
+
|
|
193
|
+
Rejected: arrow-key movement between headers; a required heading element around each header.
|
|
185
194
|
|
|
186
195
|
## Open questions
|
|
187
196
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CollapsibleGroup.a11y.renders.tsx
|
|
5
|
+
* @input One CollapsibleGroup accordion state from CollapsibleGroup.a11y.states.ts
|
|
6
|
+
* @output The React tree every lane renders for that state (the jsdom test directly, the story through Storybook)
|
|
7
|
+
* @position Shared fixture for the CollapsibleGroup accordion binding
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {ReactElement} from 'react';
|
|
11
|
+
import {Collapsible, CollapsibleGroup} from '../index';
|
|
12
|
+
import {
|
|
13
|
+
COLLAPSIBLE_GROUP_ITEMS,
|
|
14
|
+
type CollapsibleGroupAccordionState,
|
|
15
|
+
} from './CollapsibleGroup.a11y.states';
|
|
16
|
+
|
|
17
|
+
export function renderAccordionState(
|
|
18
|
+
state: Pick<
|
|
19
|
+
CollapsibleGroupAccordionState,
|
|
20
|
+
'type' | 'defaultValue' | 'disabled'
|
|
21
|
+
>,
|
|
22
|
+
): ReactElement {
|
|
23
|
+
return (
|
|
24
|
+
<div>
|
|
25
|
+
<CollapsibleGroup
|
|
26
|
+
type={state.type}
|
|
27
|
+
defaultValue={
|
|
28
|
+
state.type === 'single'
|
|
29
|
+
? state.defaultValue[0]
|
|
30
|
+
: [...state.defaultValue]
|
|
31
|
+
}>
|
|
32
|
+
{COLLAPSIBLE_GROUP_ITEMS.map(item => (
|
|
33
|
+
<Collapsible
|
|
34
|
+
key={item.value}
|
|
35
|
+
value={item.value}
|
|
36
|
+
trigger={item.label}
|
|
37
|
+
isDisabled={state.disabled?.includes(item.value)}>
|
|
38
|
+
{item.label} content
|
|
39
|
+
</Collapsible>
|
|
40
|
+
))}
|
|
41
|
+
</CollapsibleGroup>
|
|
42
|
+
<button type="button">After the group</button>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|