@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
|
@@ -6,10 +6,22 @@
|
|
|
6
6
|
* @file useScrollOverflow.ts
|
|
7
7
|
* @input Uses React useRef, useState, useCallback, useEffect
|
|
8
8
|
* @output Exports useScrollOverflow hook for tracking scroll position edges
|
|
9
|
-
* @position Core hook; used by Carousel for fade-edge and button
|
|
9
|
+
* @position Core hook; used by Carousel and TabList for fade-edge and button
|
|
10
|
+
* state
|
|
10
11
|
*
|
|
11
12
|
* Observes a scroll container and reports whether content overflows at the
|
|
12
|
-
* start, end, or both edges. Updates on scroll and resize
|
|
13
|
+
* start, end, or both edges. Updates on every scroll, and on a resize while
|
|
14
|
+
* the container is rendered: a layout read on an element the browser is not
|
|
15
|
+
* rendering (an ancestor with `display: none`, or a `content-visibility:
|
|
16
|
+
* hidden` subtree) either answers zeros or makes the browser lay out that
|
|
17
|
+
* whole hidden subtree for the answer, so a resize observation delivered for
|
|
18
|
+
* a hidden container, and the measurement at registration inside one, are
|
|
19
|
+
* skipped; the hook keeps its last state until the resize observation that
|
|
20
|
+
* follows the reveal (one frame after it paints). A scroll is measured
|
|
21
|
+
* whether or not the container is rendered: a scroll of a hidden container
|
|
22
|
+
* is script's (a selected tab revealed under a hidden panel), its layout is
|
|
23
|
+
* already the scroll's, and a reveal at the same size brings no resize to
|
|
24
|
+
* measure it with.
|
|
13
25
|
*
|
|
14
26
|
* SYNC: When modified, update:
|
|
15
27
|
* - /packages/core/src/hooks/index.ts
|
|
@@ -27,6 +39,19 @@ export interface ScrollOverflowState {
|
|
|
27
39
|
hasOverflow: boolean;
|
|
28
40
|
}
|
|
29
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Whether the browser is rendering `el`, so a layout read on it answers for a
|
|
44
|
+
* real box. `checkVisibility()` is false under a `display: none` ancestor and
|
|
45
|
+
* inside a `content-visibility: hidden` subtree, and it answers without
|
|
46
|
+
* rendering that subtree. Not `contentVisibilityAuto`: a freshly inserted,
|
|
47
|
+
* on-screen `content-visibility: auto` section counts as skipped until the
|
|
48
|
+
* next rendering update, and a strip mounted into one would paint its first
|
|
49
|
+
* frame unmeasured. A browser without `checkVisibility` measures as before.
|
|
50
|
+
*/
|
|
51
|
+
function isRendered(el: HTMLElement): boolean {
|
|
52
|
+
return typeof el.checkVisibility !== 'function' || el.checkVisibility();
|
|
53
|
+
}
|
|
54
|
+
|
|
30
55
|
/**
|
|
31
56
|
* Tracks scroll overflow state for a horizontally scrollable container.
|
|
32
57
|
*
|
|
@@ -49,12 +74,7 @@ export function useScrollOverflow() {
|
|
|
49
74
|
hasOverflow: false,
|
|
50
75
|
});
|
|
51
76
|
|
|
52
|
-
const
|
|
53
|
-
const el = elRef.current;
|
|
54
|
-
if (!el) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
77
|
+
const measureNow = useCallback((el: HTMLElement) => {
|
|
58
78
|
const tolerance = 1;
|
|
59
79
|
const {scrollLeft, scrollWidth, clientWidth} = el;
|
|
60
80
|
const maxScroll = scrollWidth - clientWidth;
|
|
@@ -75,22 +95,44 @@ export function useScrollOverflow() {
|
|
|
75
95
|
});
|
|
76
96
|
}, []);
|
|
77
97
|
|
|
98
|
+
// Registration and resize: only a rendered container. A hidden one has no
|
|
99
|
+
// edges to report, and reading its layout would make the browser render
|
|
100
|
+
// the hidden subtree it sits in; the resize observation that follows the
|
|
101
|
+
// reveal measures again.
|
|
102
|
+
const measure = useCallback(() => {
|
|
103
|
+
const el = elRef.current;
|
|
104
|
+
if (el && isRendered(el)) {
|
|
105
|
+
measureNow(el);
|
|
106
|
+
}
|
|
107
|
+
}, [measureNow]);
|
|
108
|
+
|
|
109
|
+
// A scroll: measured hidden or not. Script scrolled the hidden container
|
|
110
|
+
// (its layout is already the scroll's), and a reveal at the same size
|
|
111
|
+
// brings no resize observation, so this is the read that keeps the edges
|
|
112
|
+
// right for it.
|
|
113
|
+
const measureOnScroll = useCallback(() => {
|
|
114
|
+
const el = elRef.current;
|
|
115
|
+
if (el) {
|
|
116
|
+
measureNow(el);
|
|
117
|
+
}
|
|
118
|
+
}, [measureNow]);
|
|
119
|
+
|
|
78
120
|
const scrollRef = useCallback(
|
|
79
121
|
(el: HTMLElement | null) => {
|
|
80
122
|
if (elRef.current) {
|
|
81
123
|
unobserveRef.current?.();
|
|
82
124
|
unobserveRef.current = null;
|
|
83
|
-
elRef.current.removeEventListener('scroll',
|
|
125
|
+
elRef.current.removeEventListener('scroll', measureOnScroll);
|
|
84
126
|
}
|
|
85
127
|
|
|
86
128
|
elRef.current = el;
|
|
87
129
|
|
|
88
130
|
if (el) {
|
|
89
|
-
el.addEventListener('scroll',
|
|
131
|
+
el.addEventListener('scroll', measureOnScroll, {passive: true});
|
|
90
132
|
unobserveRef.current = observeResize(el, measure);
|
|
91
133
|
}
|
|
92
134
|
},
|
|
93
|
-
[measure],
|
|
135
|
+
[measure, measureOnScroll],
|
|
94
136
|
);
|
|
95
137
|
|
|
96
138
|
useEffect(() => {
|
|
@@ -98,10 +140,10 @@ export function useScrollOverflow() {
|
|
|
98
140
|
if (elRef.current) {
|
|
99
141
|
unobserveRef.current?.();
|
|
100
142
|
unobserveRef.current = null;
|
|
101
|
-
elRef.current.removeEventListener('scroll',
|
|
143
|
+
elRef.current.removeEventListener('scroll', measureOnScroll);
|
|
102
144
|
}
|
|
103
145
|
};
|
|
104
|
-
}, [
|
|
146
|
+
}, [measureOnScroll]);
|
|
105
147
|
|
|
106
148
|
return {
|
|
107
149
|
scrollRef,
|
|
@@ -262,6 +262,43 @@ describe('useTreeFocus activation + typeahead', () => {
|
|
|
262
262
|
expect(screen.getByTestId('c')).toHaveFocus();
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
+
it('typeahead matches whole text for a direct caller, even when an item carries aria-labelledby and the options smuggle the internal flag', () => {
|
|
266
|
+
function LabelledTree() {
|
|
267
|
+
// An untyped options object trying to reach the internal flag through
|
|
268
|
+
// the public hook: it must have no effect.
|
|
269
|
+
const smuggled = {
|
|
270
|
+
hasRovingTabIndex: true,
|
|
271
|
+
matchLabelledBy: true,
|
|
272
|
+
} as Record<string, unknown>;
|
|
273
|
+
const {treeRef, handleKeyDown, handleFocus} =
|
|
274
|
+
useTreeFocus<HTMLUListElement>(smuggled);
|
|
275
|
+
return (
|
|
276
|
+
<ul
|
|
277
|
+
ref={treeRef}
|
|
278
|
+
role="tree"
|
|
279
|
+
onKeyDown={handleKeyDown}
|
|
280
|
+
onFocus={handleFocus}>
|
|
281
|
+
<li role="treeitem" tabIndex={0} data-testid="first">
|
|
282
|
+
First
|
|
283
|
+
</li>
|
|
284
|
+
<li
|
|
285
|
+
role="treeitem"
|
|
286
|
+
tabIndex={-1}
|
|
287
|
+
aria-labelledby="lbl"
|
|
288
|
+
data-testid="labelled">
|
|
289
|
+
<span>zz</span> <span id="lbl">Budget</span>
|
|
290
|
+
</li>
|
|
291
|
+
</ul>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
render(<LabelledTree />);
|
|
295
|
+
const tree = screen.getByRole('tree');
|
|
296
|
+
screen.getByTestId('first').focus();
|
|
297
|
+
// Released behavior: the text content ("zz Budget") is what matches.
|
|
298
|
+
fireEvent.keyDown(tree, {key: 'z'});
|
|
299
|
+
expect(screen.getByTestId('labelled')).toHaveFocus();
|
|
300
|
+
});
|
|
301
|
+
|
|
265
302
|
it('typeahead cycles through same-letter matches on repeated presses', () => {
|
|
266
303
|
const nodes: Node[] = [
|
|
267
304
|
{id: 'apple', label: 'Apple', level: 1},
|
|
@@ -176,6 +176,28 @@ export interface UseTreeFocusReturn<T extends HTMLElement = HTMLElement> {
|
|
|
176
176
|
focusLast: () => void;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
/**
|
|
180
|
+
* The text typeahead matches against. With `matchLabelledBy`, the row's
|
|
181
|
+
* `aria-labelledby` text (the label alone) when it has one; otherwise, and by
|
|
182
|
+
* default, its text content.
|
|
183
|
+
*/
|
|
184
|
+
function itemLabelText(item: HTMLElement, matchLabelledBy: boolean): string {
|
|
185
|
+
const labelledBy = matchLabelledBy
|
|
186
|
+
? item.getAttribute('aria-labelledby')
|
|
187
|
+
: null;
|
|
188
|
+
if (labelledBy != null) {
|
|
189
|
+
const label = labelledBy
|
|
190
|
+
.split(/\s+/)
|
|
191
|
+
.map(id => item.ownerDocument.getElementById(id)?.textContent ?? '')
|
|
192
|
+
.join(' ')
|
|
193
|
+
.trim();
|
|
194
|
+
if (label !== '') {
|
|
195
|
+
return label;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return (item.textContent ?? '').trim();
|
|
199
|
+
}
|
|
200
|
+
|
|
179
201
|
/**
|
|
180
202
|
* Hook for managing roving-tabindex focus + the WAI-ARIA tree keyboard model.
|
|
181
203
|
*
|
|
@@ -208,8 +230,38 @@ export interface UseTreeFocusReturn<T extends HTMLElement = HTMLElement> {
|
|
|
208
230
|
* </ul>
|
|
209
231
|
* ```
|
|
210
232
|
*/
|
|
233
|
+
|
|
211
234
|
export function useTreeFocus<T extends HTMLElement = HTMLElement>(
|
|
212
235
|
options: UseTreeFocusOptions = {},
|
|
236
|
+
): UseTreeFocusReturn<T> {
|
|
237
|
+
// The flag is pinned off here so an untyped options object cannot smuggle
|
|
238
|
+
// it through the public hook.
|
|
239
|
+
return useTreeFocusInternal<T>({...options, matchLabelledBy: false});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Options the package's own components can pass beyond the public hook.
|
|
244
|
+
*
|
|
245
|
+
* Not exported from the package: TreeList imports this module directly, so
|
|
246
|
+
* `UseTreeFocusOptions` stays exactly as released.
|
|
247
|
+
*/
|
|
248
|
+
export type UseTreeFocusInternalOptions = UseTreeFocusOptions & {
|
|
249
|
+
/**
|
|
250
|
+
* Match typeahead against the item's `aria-labelledby` text (its label
|
|
251
|
+
* alone) instead of its whole text content, so start content, a chevron
|
|
252
|
+
* toggle or a description never takes part. TreeList passes this; the
|
|
253
|
+
* released text match stays the default for direct callers.
|
|
254
|
+
* @default false
|
|
255
|
+
*/
|
|
256
|
+
matchLabelledBy?: boolean;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* The implementation behind {@link useTreeFocus}. Not exported from the
|
|
261
|
+
* package; the public hook calls it without the internal options.
|
|
262
|
+
*/
|
|
263
|
+
export function useTreeFocusInternal<T extends HTMLElement = HTMLElement>(
|
|
264
|
+
options: UseTreeFocusInternalOptions = {},
|
|
213
265
|
): UseTreeFocusReturn<T> {
|
|
214
266
|
const {
|
|
215
267
|
itemSelector = '[role="treeitem"]',
|
|
@@ -222,6 +274,7 @@ export function useTreeFocus<T extends HTMLElement = HTMLElement>(
|
|
|
222
274
|
onActivate,
|
|
223
275
|
typeahead = true,
|
|
224
276
|
typeaheadResetMs = DEFAULT_TYPEAHEAD_RESET_MS,
|
|
277
|
+
matchLabelledBy = false,
|
|
225
278
|
onActiveChange,
|
|
226
279
|
hasRovingTabIndex = false,
|
|
227
280
|
} = options;
|
|
@@ -399,14 +452,14 @@ export function useTreeFocus<T extends HTMLElement = HTMLElement>(
|
|
|
399
452
|
const match = ordered.find(
|
|
400
453
|
item =>
|
|
401
454
|
!itemDisabled(item) &&
|
|
402
|
-
(item
|
|
455
|
+
itemLabelText(item, matchLabelledBy).toLowerCase().startsWith(query),
|
|
403
456
|
);
|
|
404
457
|
if (match != null) {
|
|
405
458
|
e.preventDefault();
|
|
406
459
|
focusItem(match);
|
|
407
460
|
}
|
|
408
461
|
},
|
|
409
|
-
[typeaheadResetMs, itemDisabled, focusItem],
|
|
462
|
+
[typeaheadResetMs, itemDisabled, focusItem, matchLabelledBy],
|
|
410
463
|
);
|
|
411
464
|
|
|
412
465
|
const handleKeyDown = useCallback(
|
|
@@ -246,6 +246,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
246
246
|
'@astryx.chat.speechRecognition.noSpeechDetected': 'No speech was detected.',
|
|
247
247
|
'@astryx.commandPalette.label': 'Command palette',
|
|
248
248
|
'@astryx.commandPalette.input.placeholder': 'Search…',
|
|
249
|
+
'@astryx.commandPalette.input.label': 'Search commands',
|
|
249
250
|
'@astryx.commandPalette.list.label': 'Commands',
|
|
250
251
|
'@astryx.contextMenu.label': 'Context menu',
|
|
251
252
|
'@astryx.dateInput.placeholder': 'Select a date',
|
|
@@ -353,6 +354,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
353
354
|
'@astryx.numberInput.clearLabel': 'Clear {label}',
|
|
354
355
|
'@astryx.numberInput.decrementLabel': 'Decrement {label}',
|
|
355
356
|
'@astryx.numberInput.incrementLabel': 'Increment {label}',
|
|
357
|
+
'@astryx.numberInput.invalidNumber': 'Invalid number',
|
|
356
358
|
'@astryx.selector.clearLabel': 'Clear {label}',
|
|
357
359
|
'@astryx.sideNavCollapseButton.expandSidebar': 'Expand sidebar',
|
|
358
360
|
'@astryx.sideNavCollapseButton.collapseSidebar': 'Collapse sidebar',
|
|
@@ -386,6 +388,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
386
388
|
'{count, number} {count, plural, one {character} other {characters}} remaining',
|
|
387
389
|
'@astryx.textArea.charactersOverLimit':
|
|
388
390
|
'{count, number} {count, plural, one {character} other {characters}} over the limit',
|
|
391
|
+
'@astryx.inputClearButton.tooltip': 'Clear',
|
|
389
392
|
'@astryx.textInput.clearLabel': 'Clear {label}',
|
|
390
393
|
'@astryx.thumbnail.remove': 'Remove {accessibleName}',
|
|
391
394
|
'@astryx.thumbnail.open': 'Open {accessibleName}',
|
|
@@ -253,6 +253,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
253
253
|
'⟦Ñó šþééçĥ ŵàš ðéţéçţéð.⟧',
|
|
254
254
|
'@astryx.commandPalette.label': '⟦Çóɱɱàñð þàłéţţé⟧',
|
|
255
255
|
'@astryx.commandPalette.input.placeholder': '⟦Šéàřçĥ…⟧',
|
|
256
|
+
'@astryx.commandPalette.input.label': '⟦Šéàřçĥ çóɱɱàñðš⟧',
|
|
256
257
|
'@astryx.commandPalette.list.label': '⟦Çóɱɱàñðš⟧',
|
|
257
258
|
'@astryx.contextMenu.label': '⟦Çóñţéẋţ ɱéñú⟧',
|
|
258
259
|
'@astryx.dateInput.placeholder': '⟦Šéłéçţ à ðàţé⟧',
|
|
@@ -360,6 +361,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
360
361
|
'@astryx.numberInput.clearLabel': '⟦Çłéàř {label}⟧',
|
|
361
362
|
'@astryx.numberInput.decrementLabel': '⟦Ðéçřéɱéñţ {label}⟧',
|
|
362
363
|
'@astryx.numberInput.incrementLabel': '⟦Íñçřéɱéñţ {label}⟧',
|
|
364
|
+
'@astryx.numberInput.invalidNumber': '⟦Íñṽàłíð ñúɱƀéř⟧',
|
|
363
365
|
'@astryx.selector.clearLabel': '⟦Çłéàř {label}⟧',
|
|
364
366
|
'@astryx.sideNavCollapseButton.expandSidebar': '⟦Éẋþàñð šíðéƀàř⟧',
|
|
365
367
|
'@astryx.sideNavCollapseButton.collapseSidebar': '⟦Çółłàþšé šíðéƀàř⟧',
|
|
@@ -393,6 +395,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
393
395
|
'⟦{count, number} {count, plural, one {character} other {characters}} řéɱàíñíñĝ⟧',
|
|
394
396
|
'@astryx.textArea.charactersOverLimit':
|
|
395
397
|
'⟦{count, number} {count, plural, one {character} other {characters}} óṽéř ţĥé łíɱíţ⟧',
|
|
398
|
+
'@astryx.inputClearButton.tooltip': '⟦Çłéàř⟧',
|
|
396
399
|
'@astryx.textInput.clearLabel': '⟦Çłéàř {label}⟧',
|
|
397
400
|
'@astryx.thumbnail.remove': '⟦Řéɱóṽé {accessibleName}⟧',
|
|
398
401
|
'@astryx.thumbnail.open': '⟦Óþéñ {accessibleName}⟧',
|
|
@@ -72,7 +72,15 @@ test('Chromium keeps every selector the reset writes', async ({page}) => {
|
|
|
72
72
|
// A forgiving list drops an invalid entry instead of the whole rule, so a
|
|
73
73
|
// dropped pseudo-element shows up as a shorter serialized selector.
|
|
74
74
|
const parsed = await page.evaluate(() => {
|
|
75
|
-
|
|
75
|
+
// The sheet opens with the `@layer` order statement; the rules live in
|
|
76
|
+
// the `reset` layer block after it.
|
|
77
|
+
const layer = [...document.styleSheets[0].cssRules].find(
|
|
78
|
+
(rule): rule is CSSLayerBlockRule =>
|
|
79
|
+
rule instanceof CSSLayerBlockRule && rule.name === 'reset',
|
|
80
|
+
);
|
|
81
|
+
if (layer == null) {
|
|
82
|
+
throw new Error('reset.css has no @layer reset block');
|
|
83
|
+
}
|
|
76
84
|
return [...layer.cssRules].map(rule => (rule as CSSStyleRule).selectorText);
|
|
77
85
|
});
|
|
78
86
|
expect(parsed.map(normalize)).toEqual(
|
package/src/reset.css
CHANGED
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
* import '@astryxdesign/core/astryx.css'; // @layer astryx-base — component styles
|
|
26
26
|
* import '@astryxdesign/theme-neutral/theme.css'; // @layer astryx-theme — theme overrides
|
|
27
27
|
*
|
|
28
|
-
* Layer order: reset → astryx-base → astryx-theme
|
|
28
|
+
* Layer order: reset → astryx-base → astryx-theme. This file registers that
|
|
29
|
+
* order itself, like every Astryx stylesheet, so it holds in any load order.
|
|
29
30
|
* Consumer styles (unlayered) always override all Astryx layers.
|
|
30
31
|
* The reset has the lowest priority and can be overridden by any other layer.
|
|
31
32
|
*
|
|
@@ -36,6 +37,8 @@
|
|
|
36
37
|
* - modern-normalize
|
|
37
38
|
*/
|
|
38
39
|
|
|
40
|
+
@layer reset, astryx-base, astryx-theme;
|
|
41
|
+
|
|
39
42
|
@layer reset {
|
|
40
43
|
|
|
41
44
|
/* ===========================================================================
|
|
@@ -354,9 +357,11 @@
|
|
|
354
357
|
}
|
|
355
358
|
|
|
356
359
|
/*
|
|
357
|
-
* Ensure hidden elements are actually hidden.
|
|
360
|
+
* Ensure hidden elements are actually hidden. `hidden="until-found"` keeps
|
|
361
|
+
* the browser's own rendering, `content-visibility: hidden`, so find in page
|
|
362
|
+
* and text fragment links can still find its content and reveal it.
|
|
358
363
|
*/
|
|
359
|
-
:where([hidden]) {
|
|
364
|
+
:where([hidden]:not([hidden='until-found' i])) {
|
|
360
365
|
display: none;
|
|
361
366
|
}
|
|
362
367
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file reset.until-found.a11y.browser.spec.ts
|
|
5
|
+
* @input packages/core/src/reset.css, alone on a blank page
|
|
6
|
+
* @output Browser evidence that `hidden="until-found"` content stays findable
|
|
7
|
+
* @position reset.css integration proof. The browser hides until-found content
|
|
8
|
+
* with `content-visibility: hidden`, which find in page and text fragments
|
|
9
|
+
* can search and reveal; `display: none` takes the content out of both, and
|
|
10
|
+
* only a shipping engine shows which one applies.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import {fileURLToPath} from 'node:url';
|
|
15
|
+
import {expect, test} from '@playwright/test';
|
|
16
|
+
|
|
17
|
+
const RESET_CSS = fs.readFileSync(
|
|
18
|
+
fileURLToPath(new URL('./reset.css', import.meta.url)),
|
|
19
|
+
'utf8',
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Served at a real URL so a text fragment navigation can run against it.
|
|
23
|
+
const ORIGIN = 'http://reset.test';
|
|
24
|
+
const PAGE = `<!doctype html>
|
|
25
|
+
<style>${RESET_CSS}</style>
|
|
26
|
+
<div style="height: 3000px">Above the fold</div>
|
|
27
|
+
<div id="answer" hidden="until-found">The hidden answer</div>
|
|
28
|
+
<div id="closed" hidden>Plain hidden text</div>
|
|
29
|
+
<script>
|
|
30
|
+
document.getElementById('answer').addEventListener('beforematch', () => {
|
|
31
|
+
document.body.dataset.beforematch = 'fired';
|
|
32
|
+
});
|
|
33
|
+
</script>`;
|
|
34
|
+
|
|
35
|
+
test.beforeEach(async ({page}) => {
|
|
36
|
+
await page.route(`${ORIGIN}/**`, async route =>
|
|
37
|
+
route.fulfill({contentType: 'text/html', body: PAGE}),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('until-found content keeps the browser rendering', async ({page}) => {
|
|
42
|
+
await page.goto(`${ORIGIN}/`);
|
|
43
|
+
const styles = await page.evaluate(() =>
|
|
44
|
+
['answer', 'closed'].map(id => {
|
|
45
|
+
const element = document.getElementById(id);
|
|
46
|
+
if (!element) {
|
|
47
|
+
throw new Error(`missing #${id}`);
|
|
48
|
+
}
|
|
49
|
+
const style = getComputedStyle(element);
|
|
50
|
+
return {
|
|
51
|
+
display: style.display,
|
|
52
|
+
contentVisibility: style.contentVisibility,
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
expect(styles).toEqual([
|
|
57
|
+
{display: 'block', contentVisibility: 'hidden'},
|
|
58
|
+
{display: 'none', contentVisibility: 'visible'},
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('a text fragment finds until-found content and reveals it', async ({
|
|
63
|
+
page,
|
|
64
|
+
}) => {
|
|
65
|
+
await page.goto(`${ORIGIN}/#:~:text=hidden%20answer`);
|
|
66
|
+
// The browser removes the attribute and fires beforematch when it reveals.
|
|
67
|
+
await expect(page.locator('#answer')).not.toHaveAttribute('hidden');
|
|
68
|
+
await expect(page.locator('#answer')).toBeInViewport();
|
|
69
|
+
await expect(page.locator('body')).toHaveAttribute(
|
|
70
|
+
'data-beforematch',
|
|
71
|
+
'fired',
|
|
72
|
+
);
|
|
73
|
+
// Plain hidden content stays out of reach.
|
|
74
|
+
await expect(page.locator('#closed')).toBeHidden();
|
|
75
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Static guard for the reset's `[hidden]` rule and `hidden="until-found"`.
|
|
5
|
+
*
|
|
6
|
+
* The browser hides until-found content with `content-visibility: hidden`,
|
|
7
|
+
* which find in page and text fragments can search and reveal. A
|
|
8
|
+
* `display: none` that also matches it takes the content out of both.
|
|
9
|
+
* reset.until-found.a11y.browser.spec.ts measures the result in a browser.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
|
|
15
|
+
import {describe, expect, it} from 'vitest';
|
|
16
|
+
|
|
17
|
+
describe('reset.css and hidden="until-found"', () => {
|
|
18
|
+
it('sets display: none only on hidden elements that are not until-found', () => {
|
|
19
|
+
const stripped = fs
|
|
20
|
+
.readFileSync(path.resolve(__dirname, './reset.css'), 'utf-8')
|
|
21
|
+
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
22
|
+
const hiddenRules = [...stripped.matchAll(/([^{}]+)\{([^{}]*)\}/g)]
|
|
23
|
+
.map(([, selector, body]) => ({selector: selector.trim(), body}))
|
|
24
|
+
.filter(({selector}) => selector.includes('[hidden'));
|
|
25
|
+
expect(hiddenRules.length).toBeGreaterThan(0);
|
|
26
|
+
for (const {selector, body} of hiddenRules) {
|
|
27
|
+
if (/display:\s*none/.test(body)) {
|
|
28
|
+
expect(selector).toContain(":not([hidden='until-found' i])");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file stylexValidation.source-build.test.mjs
|
|
5
|
+
* @input Uses Babel and the core StyleX build config
|
|
6
|
+
* @output Verifies unsupported StyleX declarations fail the core build
|
|
7
|
+
* @position Regression test for the core build's propertyValidationMode
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import {fileURLToPath} from 'node:url';
|
|
12
|
+
import {transformAsync} from '@babel/core';
|
|
13
|
+
import {describe, expect, it} from 'vitest';
|
|
14
|
+
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const CORE_ROOT = path.resolve(__dirname, '..');
|
|
17
|
+
|
|
18
|
+
function compile(declaration) {
|
|
19
|
+
const source = `import * as stylex from '@stylexjs/stylex';
|
|
20
|
+
export const styles = stylex.create({root: {${declaration}}});`;
|
|
21
|
+
return transformAsync(source, {
|
|
22
|
+
babelrc: false,
|
|
23
|
+
configFile: path.join(CORE_ROOT, 'babel.config.json'),
|
|
24
|
+
filename: path.join(__dirname, 'unsupported-declaration.tsx'),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('core StyleX property validation', () => {
|
|
29
|
+
// In StyleX's default `silent` mode each of these compiles successfully and
|
|
30
|
+
// the declaration never reaches the shipped CSS, so a green build proves
|
|
31
|
+
// nothing. The core build must fail instead.
|
|
32
|
+
it.each([
|
|
33
|
+
['border', "border: 'none'", /border is not supported/],
|
|
34
|
+
['background', "background: 'none'", /background is not supported/],
|
|
35
|
+
['all', "all: 'unset'", /all is not supported/],
|
|
36
|
+
])(
|
|
37
|
+
'rejects the %s shorthand instead of dropping it',
|
|
38
|
+
async (_name, declaration, message) => {
|
|
39
|
+
await expect(compile(declaration)).rejects.toThrow(message);
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
});
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
type SyntaxThemeTokenKey,
|
|
21
21
|
} from './defineSyntaxTheme';
|
|
22
22
|
import {useMediaQuery} from '../../hooks/useMediaQuery';
|
|
23
|
+
import {useTheme} from '../useTheme';
|
|
24
|
+
import {formatColor, parseColor} from '../../utils/color';
|
|
23
25
|
|
|
24
26
|
// =============================================================================
|
|
25
27
|
// Context
|
|
@@ -59,6 +61,22 @@ export interface UseSyntaxThemeReturn {
|
|
|
59
61
|
// Hook
|
|
60
62
|
// =============================================================================
|
|
61
63
|
|
|
64
|
+
/** The strength of the default diff row paints: the muted tokens at 25%. */
|
|
65
|
+
const DIFF_TINT_STRENGTH = 0.25;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The colour `color-mix(in srgb, <colour> <strength>, transparent)` paints, as
|
|
69
|
+
* a value a canvas can use. Returns undefined for a colour the shared parser
|
|
70
|
+
* does not read (the CSS default then stands).
|
|
71
|
+
*/
|
|
72
|
+
function mixWithTransparent(
|
|
73
|
+
colour: string,
|
|
74
|
+
strength: number,
|
|
75
|
+
): string | undefined {
|
|
76
|
+
const parsed = parseColor(colour);
|
|
77
|
+
return parsed ? formatColor({...parsed, a: parsed.a * strength}) : undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
62
80
|
/**
|
|
63
81
|
* Access the current syntax theme's token values, resolved for the active
|
|
64
82
|
* color mode. Returns null if no SyntaxTheme provider is present.
|
|
@@ -74,6 +92,7 @@ export interface UseSyntaxThemeReturn {
|
|
|
74
92
|
*/
|
|
75
93
|
export function useSyntaxTheme(): UseSyntaxThemeReturn | null {
|
|
76
94
|
const ctx = use(SyntaxThemeContext);
|
|
95
|
+
const page = useTheme();
|
|
77
96
|
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
|
|
78
97
|
const effectiveMode: 'light' | 'dark' = prefersDark ? 'dark' : 'light';
|
|
79
98
|
|
|
@@ -83,13 +102,35 @@ export function useSyntaxTheme(): UseSyntaxThemeReturn | null {
|
|
|
83
102
|
}
|
|
84
103
|
const resolved: Partial<Record<SyntaxThemeTokenKey, string>> = {};
|
|
85
104
|
for (const key of ALL_SYNTAX_KEYS) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
effectiveMode
|
|
89
|
-
|
|
105
|
+
const input = ctx.theme.__inputTokens[key];
|
|
106
|
+
if (input !== undefined) {
|
|
107
|
+
resolved[key] = resolveSyntaxTokenForMode(input, effectiveMode);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// A diff row slot the theme did not set follows its CSS default; a canvas
|
|
111
|
+
// consumer needs a colour it can paint, so the marker slots take the
|
|
112
|
+
// theme's own resolved punctuation colour and the row paints derive from
|
|
113
|
+
// the enclosing page theme's resolved tokens (the same mix the CSS
|
|
114
|
+
// default makes), never from a probe outside the nearest Theme.
|
|
115
|
+
for (const key of ALL_SYNTAX_KEYS) {
|
|
116
|
+
if (resolved[key] !== undefined) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const fallback = ctx.theme.tokens[key] ?? '';
|
|
120
|
+
if (key === 'added-marker' || key === 'removed-marker') {
|
|
121
|
+
resolved[key] = resolved.punctuation ?? fallback;
|
|
122
|
+
} else if (key === 'added' || key === 'removed') {
|
|
123
|
+
const base = page.token(
|
|
124
|
+
key === 'added' ? '--color-success-muted' : '--color-error-muted',
|
|
125
|
+
);
|
|
126
|
+
resolved[key] =
|
|
127
|
+
mixWithTransparent(base, DIFF_TINT_STRENGTH) ?? fallback;
|
|
128
|
+
} else {
|
|
129
|
+
resolved[key] = fallback;
|
|
130
|
+
}
|
|
90
131
|
}
|
|
91
132
|
return resolved as Record<SyntaxThemeTokenKey, string>;
|
|
92
|
-
}, [ctx, effectiveMode]);
|
|
133
|
+
}, [ctx, effectiveMode, page]);
|
|
93
134
|
|
|
94
135
|
if (!ctx || !tokens) {
|
|
95
136
|
return null;
|