@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
|
@@ -214,9 +214,9 @@ export const docs = {
|
|
|
214
214
|
},
|
|
215
215
|
{
|
|
216
216
|
name: 'variant',
|
|
217
|
-
type: "'input' | 'ghost'",
|
|
217
|
+
type: "'input' | 'secondary' | 'ghost'",
|
|
218
218
|
description:
|
|
219
|
-
'Visual trigger style. input is the bordered input treatment for forms; ghost is borderless and
|
|
219
|
+
'Visual trigger style. input is the bordered input treatment for forms; secondary matches the secondary buttons of the active theme (borderless and neutral-filled by default) for navs and toolbars; ghost is borderless and transparent, matching ghost buttons for toolbar usage.',
|
|
220
220
|
default: "'input'",
|
|
221
221
|
},
|
|
222
222
|
{
|
|
@@ -340,9 +340,9 @@ export const docs = {
|
|
|
340
340
|
name: 'statusVariant',
|
|
341
341
|
type: "'attached' | 'detached' | 'tooltip'",
|
|
342
342
|
description:
|
|
343
|
-
'How the status message is placed relative to the input. attached overlaps directly below the bordered input and is only valid for the input variant; ghost selectors detach attached status messages by default. Use tooltip for compact toolbar controls.',
|
|
343
|
+
'How the status message is placed relative to the input. attached overlaps directly below the bordered input and is only valid for the input variant; secondary and ghost selectors detach attached status messages by default. Use tooltip for compact toolbar controls.',
|
|
344
344
|
default:
|
|
345
|
-
"'attached' for input selectors; 'detached' for ghost selectors",
|
|
345
|
+
"'attached' for input selectors; 'detached' for secondary and ghost selectors",
|
|
346
346
|
},
|
|
347
347
|
{
|
|
348
348
|
name: 'renderOption',
|
|
@@ -493,7 +493,7 @@ export const docs = {
|
|
|
493
493
|
{
|
|
494
494
|
guidance: true,
|
|
495
495
|
description:
|
|
496
|
-
'Use variant="
|
|
496
|
+
'Use variant="secondary" when a multi-selector sits beside secondary buttons or segmented controls, and variant="ghost" when it sits beside ghost buttons. If validation status is needed there, prefer statusVariant="tooltip" so the toolbar height stays compact.',
|
|
497
497
|
},
|
|
498
498
|
{
|
|
499
499
|
guidance: true,
|
|
@@ -661,7 +661,7 @@ export const docsDense = {
|
|
|
661
661
|
{
|
|
662
662
|
guidance: true,
|
|
663
663
|
description:
|
|
664
|
-
'Use variant="
|
|
664
|
+
'Use variant="secondary" beside secondary buttons or segmented controls and variant="ghost" beside ghost buttons; prefer statusVariant="tooltip" for compact validation status.',
|
|
665
665
|
},
|
|
666
666
|
{
|
|
667
667
|
guidance: false,
|
|
@@ -693,7 +693,7 @@ export const docsDense = {
|
|
|
693
693
|
placeholder: 'text when nothing selected',
|
|
694
694
|
size: 'size variant',
|
|
695
695
|
variant:
|
|
696
|
-
'visual trigger style: input bordered control or ghost
|
|
696
|
+
'visual trigger style: input bordered control, secondary control matching the theme secondary buttons, or ghost transparent control',
|
|
697
697
|
triggerDisplay: 'how to show selected in trigger',
|
|
698
698
|
formatValue:
|
|
699
699
|
'formats count/labels trigger text; receives selected items',
|
|
@@ -719,7 +719,7 @@ export const docsDense = {
|
|
|
719
719
|
isLoading: 'spinner in trigger',
|
|
720
720
|
status: 'validation status w/ optional message',
|
|
721
721
|
statusVariant:
|
|
722
|
-
'status message placement; ghost
|
|
722
|
+
'status message placement; secondary and ghost detach attached by default; use tooltip for compact toolbars.',
|
|
723
723
|
renderOption:
|
|
724
724
|
'custom render fn per selectable option; not dividers/sections/select-all',
|
|
725
725
|
renderTrigger:
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
type MultiSelectorHandle,
|
|
26
26
|
type MultiSelectorChange,
|
|
27
27
|
} from './MultiSelector';
|
|
28
|
-
import {useRef} from 'react';
|
|
28
|
+
import {useRef, useState} from 'react';
|
|
29
29
|
import {Icon} from '../Icon';
|
|
30
30
|
import {InternationalizationProvider} from '../i18n';
|
|
31
31
|
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
@@ -33,6 +33,8 @@ import {__resetInteractionModalityForTest} from '../utils/interactionModality';
|
|
|
33
33
|
import {defineTheme} from '../theme/defineTheme';
|
|
34
34
|
import {generateThemeCSS} from '../theme/generateThemeRules';
|
|
35
35
|
import {selectorPresentationStyles} from '../Selector/selectorPresentation.stylex';
|
|
36
|
+
import {groupStyles} from '../InputGroup/groupStyles';
|
|
37
|
+
import {InputGroup, InputGroupText} from '../InputGroup';
|
|
36
38
|
|
|
37
39
|
function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
|
|
38
40
|
const {prose, component} = generateThemeCSS(theme);
|
|
@@ -219,6 +221,54 @@ describe('MultiSelector', () => {
|
|
|
219
221
|
expect(screen.getByLabelText('Fruit')).toBeInTheDocument();
|
|
220
222
|
});
|
|
221
223
|
|
|
224
|
+
it('applies distinct trigger chrome to the secondary variant', () => {
|
|
225
|
+
// data-testid reaches the native control; the variant is on the trigger root.
|
|
226
|
+
const triggerRoot = () =>
|
|
227
|
+
screen
|
|
228
|
+
.getByTestId('selector')
|
|
229
|
+
.closest('.astryx-multi-selector') as HTMLElement;
|
|
230
|
+
const {rerender} = render(
|
|
231
|
+
<MultiSelector
|
|
232
|
+
label="Fruit"
|
|
233
|
+
options={defaultOptions}
|
|
234
|
+
value={[]}
|
|
235
|
+
onChange={() => {}}
|
|
236
|
+
variant="input"
|
|
237
|
+
data-testid="selector"
|
|
238
|
+
/>,
|
|
239
|
+
);
|
|
240
|
+
const inputClasses = triggerRoot().className;
|
|
241
|
+
|
|
242
|
+
rerender(
|
|
243
|
+
<MultiSelector
|
|
244
|
+
label="Fruit"
|
|
245
|
+
options={defaultOptions}
|
|
246
|
+
value={[]}
|
|
247
|
+
onChange={() => {}}
|
|
248
|
+
variant="secondary"
|
|
249
|
+
data-testid="selector"
|
|
250
|
+
/>,
|
|
251
|
+
);
|
|
252
|
+
const secondary = triggerRoot();
|
|
253
|
+
const secondaryClasses = secondary.className;
|
|
254
|
+
expect(secondary).toHaveAttribute('data-variant', 'secondary');
|
|
255
|
+
|
|
256
|
+
rerender(
|
|
257
|
+
<MultiSelector
|
|
258
|
+
label="Fruit"
|
|
259
|
+
options={defaultOptions}
|
|
260
|
+
value={[]}
|
|
261
|
+
onChange={() => {}}
|
|
262
|
+
variant="ghost"
|
|
263
|
+
data-testid="selector"
|
|
264
|
+
/>,
|
|
265
|
+
);
|
|
266
|
+
const ghostClasses = triggerRoot().className;
|
|
267
|
+
|
|
268
|
+
expect(secondaryClasses).not.toBe(inputClasses);
|
|
269
|
+
expect(secondaryClasses).not.toBe(ghostClasses);
|
|
270
|
+
});
|
|
271
|
+
|
|
222
272
|
it('renders custom option content with renderOption', async () => {
|
|
223
273
|
const user = userEvent.setup();
|
|
224
274
|
render(
|
|
@@ -1595,6 +1645,55 @@ describe('MultiSelector', () => {
|
|
|
1595
1645
|
expect(clear).not.toHaveAttribute('tabIndex', '-1');
|
|
1596
1646
|
});
|
|
1597
1647
|
|
|
1648
|
+
it('returns focus to the trigger when the clear button is activated by keyboard', async () => {
|
|
1649
|
+
function Controlled() {
|
|
1650
|
+
const [value, setValue] = useState(['Apple', 'Banana']);
|
|
1651
|
+
return (
|
|
1652
|
+
<MultiSelector
|
|
1653
|
+
label="Fruit"
|
|
1654
|
+
options={defaultOptions}
|
|
1655
|
+
value={value}
|
|
1656
|
+
onChange={setValue}
|
|
1657
|
+
hasClear
|
|
1658
|
+
/>
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
const user = userEvent.setup();
|
|
1662
|
+
render(<Controlled />);
|
|
1663
|
+
screen.getByRole('button', {name: 'Clear all Fruit'}).focus();
|
|
1664
|
+
await user.keyboard('{Enter}');
|
|
1665
|
+
expect(
|
|
1666
|
+
screen.queryByRole('button', {name: 'Clear all Fruit'}),
|
|
1667
|
+
).not.toBeInTheDocument();
|
|
1668
|
+
expect(screen.getByRole('combobox')).toHaveFocus();
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
it('leaves focus in the search input when the clear button is clicked with the list open', async () => {
|
|
1672
|
+
function Controlled() {
|
|
1673
|
+
const [value, setValue] = useState(['Apple', 'Banana']);
|
|
1674
|
+
return (
|
|
1675
|
+
<MultiSelector
|
|
1676
|
+
label="Fruit"
|
|
1677
|
+
options={defaultOptions}
|
|
1678
|
+
value={value}
|
|
1679
|
+
onChange={setValue}
|
|
1680
|
+
hasClear
|
|
1681
|
+
hasSearch
|
|
1682
|
+
/>
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
const user = userEvent.setup();
|
|
1686
|
+
const {container} = render(<Controlled />);
|
|
1687
|
+
await user.click(container.querySelector('[aria-haspopup]') as Element);
|
|
1688
|
+
const search = screen.getByRole('combobox', h);
|
|
1689
|
+
search.focus();
|
|
1690
|
+
await user.click(screen.getByRole('button', {name: 'Clear all Fruit'}));
|
|
1691
|
+
expect(
|
|
1692
|
+
screen.queryByRole('button', {name: 'Clear all Fruit'}),
|
|
1693
|
+
).not.toBeInTheDocument();
|
|
1694
|
+
expect(search).toHaveFocus();
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1598
1697
|
it('scrolls the highlighted option into view during arrow navigation', async () => {
|
|
1599
1698
|
const scrollIntoView = vi.fn();
|
|
1600
1699
|
Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', {
|
|
@@ -2396,26 +2495,29 @@ describe('MultiSelector statusVariant forwarding', () => {
|
|
|
2396
2495
|
).not.toBeNull();
|
|
2397
2496
|
});
|
|
2398
2497
|
|
|
2399
|
-
it('
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
'
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
'
|
|
2417
|
-
|
|
2418
|
-
|
|
2498
|
+
it.each(['secondary', 'ghost'] as const)(
|
|
2499
|
+
'detaches attached status by default for the %s variant',
|
|
2500
|
+
variant => {
|
|
2501
|
+
const {container} = render(
|
|
2502
|
+
<MultiSelector
|
|
2503
|
+
label="Fruit"
|
|
2504
|
+
options={['Apple', 'Banana']}
|
|
2505
|
+
value={[]}
|
|
2506
|
+
onChange={() => {}}
|
|
2507
|
+
variant={variant}
|
|
2508
|
+
status={{type: 'error', message: 'Required'}}
|
|
2509
|
+
/>,
|
|
2510
|
+
);
|
|
2511
|
+
expect(container.querySelector('.astryx-multi-selector')).toHaveAttribute(
|
|
2512
|
+
'data-variant',
|
|
2513
|
+
variant,
|
|
2514
|
+
);
|
|
2515
|
+
expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
|
|
2516
|
+
'data-variant',
|
|
2517
|
+
'detached',
|
|
2518
|
+
);
|
|
2519
|
+
},
|
|
2520
|
+
);
|
|
2419
2521
|
|
|
2420
2522
|
it('uses a status tooltip for ghost multi-selectors when requested', () => {
|
|
2421
2523
|
const {container} = render(
|
|
@@ -4206,3 +4308,68 @@ describe('MultiSelector create row inside a grid', () => {
|
|
|
4206
4308
|
});
|
|
4207
4309
|
});
|
|
4208
4310
|
});
|
|
4311
|
+
|
|
4312
|
+
describe('MultiSelector in an InputGroup', () => {
|
|
4313
|
+
const groupClasses = () =>
|
|
4314
|
+
stylex.props(groupStyles.inGroup).className!.split(' ');
|
|
4315
|
+
const triggerBox = (name: RegExp) =>
|
|
4316
|
+
screen
|
|
4317
|
+
.getByRole('combobox', {name})
|
|
4318
|
+
.closest('.astryx-multi-selector') as HTMLElement;
|
|
4319
|
+
|
|
4320
|
+
it.each(['input', 'secondary'] as const)(
|
|
4321
|
+
'gives a grouped %s trigger the connected group geometry in every position',
|
|
4322
|
+
variant => {
|
|
4323
|
+
const selector = (label: string) => (
|
|
4324
|
+
<MultiSelector
|
|
4325
|
+
label={label}
|
|
4326
|
+
isLabelHidden
|
|
4327
|
+
variant={variant}
|
|
4328
|
+
options={['Apple', 'Banana', 'Cherry']}
|
|
4329
|
+
value={EMPTY_VALUE}
|
|
4330
|
+
onChange={() => {}}
|
|
4331
|
+
/>
|
|
4332
|
+
);
|
|
4333
|
+
render(
|
|
4334
|
+
<>
|
|
4335
|
+
<InputGroup label="First">
|
|
4336
|
+
{selector('Fruit first')}
|
|
4337
|
+
<InputGroupText>kg</InputGroupText>
|
|
4338
|
+
</InputGroup>
|
|
4339
|
+
<InputGroup label="Middle">
|
|
4340
|
+
<InputGroupText>#</InputGroupText>
|
|
4341
|
+
{selector('Fruit middle')}
|
|
4342
|
+
<InputGroupText>kg</InputGroupText>
|
|
4343
|
+
</InputGroup>
|
|
4344
|
+
<InputGroup label="Last">
|
|
4345
|
+
<InputGroupText>#</InputGroupText>
|
|
4346
|
+
{selector('Fruit last')}
|
|
4347
|
+
</InputGroup>
|
|
4348
|
+
</>,
|
|
4349
|
+
);
|
|
4350
|
+
|
|
4351
|
+
// Row height, seam radii, and focus stacking come from these classes;
|
|
4352
|
+
// a secondary trigger keeps its fill but not its standalone edges.
|
|
4353
|
+
for (const position of ['first', 'middle', 'last']) {
|
|
4354
|
+
const classes = new Set(
|
|
4355
|
+
triggerBox(new RegExp(`Fruit ${position}`)).className.split(' '),
|
|
4356
|
+
);
|
|
4357
|
+
expect(groupClasses().every(c => classes.has(c))).toBe(true);
|
|
4358
|
+
}
|
|
4359
|
+
},
|
|
4360
|
+
);
|
|
4361
|
+
|
|
4362
|
+
it('keeps a standalone secondary trigger out of the group geometry', () => {
|
|
4363
|
+
render(
|
|
4364
|
+
<MultiSelector
|
|
4365
|
+
label="Fruit"
|
|
4366
|
+
variant="secondary"
|
|
4367
|
+
options={['Apple', 'Banana', 'Cherry']}
|
|
4368
|
+
value={EMPTY_VALUE}
|
|
4369
|
+
onChange={() => {}}
|
|
4370
|
+
/>,
|
|
4371
|
+
);
|
|
4372
|
+
const classes = new Set(triggerBox(/Fruit/).className.split(' '));
|
|
4373
|
+
expect(groupClasses().some(c => classes.has(c))).toBe(false);
|
|
4374
|
+
});
|
|
4375
|
+
});
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file MultiSelector.tsx
|
|
7
7
|
* @input Uses React, StyleX, usePopover, useTooltip, CheckboxInput, Field, Badge, Icon, InputGroupContext
|
|
8
|
-
* @output Exports MultiSelector
|
|
8
|
+
* @output Exports MultiSelector with input, secondary, and ghost trigger variants
|
|
9
9
|
* @position Core implementation; consumed by index.ts
|
|
10
10
|
*
|
|
11
11
|
* SYNC: When modified, update:
|
|
12
12
|
* - /packages/core/src/MultiSelector/MultiSelector.doc.mjs
|
|
13
13
|
* - /packages/core/src/MultiSelector/MultiSelector.test.tsx
|
|
14
14
|
* - /packages/core/src/MultiSelector/index.ts
|
|
15
|
+
* - /apps/storybook/stories/MultiSelector.stories.tsx
|
|
15
16
|
* - /apps/storybook/stories/InputGroup.stories.tsx
|
|
16
17
|
* - /packages/cli/assets/templates/blocks/components/MultiSelector/ (showcase blocks)
|
|
17
18
|
*/
|
|
@@ -241,6 +242,31 @@ const styles = stylex.create({
|
|
|
241
242
|
'[data-astryx-press="on"]': 'scale(0.98)',
|
|
242
243
|
},
|
|
243
244
|
},
|
|
245
|
+
// Keep the input-family width contract while reusing ghost's borderless paint
|
|
246
|
+
// and the neutral resting fill used by secondary buttons.
|
|
247
|
+
triggerSecondary: {
|
|
248
|
+
borderWidth: 0,
|
|
249
|
+
backgroundColor: colorVars['--color-neutral'],
|
|
250
|
+
boxShadow: {
|
|
251
|
+
default: 'none',
|
|
252
|
+
':hover:not(:focus-within):where(:not(:disabled,[aria-disabled="true"]))':
|
|
253
|
+
{
|
|
254
|
+
'@media (hover: hover)': 'none',
|
|
255
|
+
},
|
|
256
|
+
':focus-within': 'none',
|
|
257
|
+
},
|
|
258
|
+
fontWeight: fontWeightVars['--font-weight-medium'],
|
|
259
|
+
transitionProperty:
|
|
260
|
+
'background-image, background-color, color, opacity, transform',
|
|
261
|
+
transform: {
|
|
262
|
+
default: 'scale(1)',
|
|
263
|
+
':active': {
|
|
264
|
+
default: 'scale(0.98)',
|
|
265
|
+
'@media (pointer: coarse)': 'scale(1)',
|
|
266
|
+
},
|
|
267
|
+
'[data-astryx-press="on"]': 'scale(0.98)',
|
|
268
|
+
},
|
|
269
|
+
},
|
|
244
270
|
triggerGhostDisabled: {
|
|
245
271
|
backgroundImage: 'none',
|
|
246
272
|
transform: {
|
|
@@ -494,7 +520,7 @@ const STATUS_BUTTON_LABEL_KEY: Record<MultiSelectorStatusType, string> = {
|
|
|
494
520
|
|
|
495
521
|
export type MultiSelectorSize = 'sm' | 'md' | 'lg';
|
|
496
522
|
|
|
497
|
-
export type MultiSelectorVariant = 'input' | 'ghost';
|
|
523
|
+
export type MultiSelectorVariant = 'input' | 'secondary' | 'ghost';
|
|
498
524
|
|
|
499
525
|
export type MultiSelectorPresentation = AdaptivePresentation;
|
|
500
526
|
|
|
@@ -736,7 +762,9 @@ export interface MultiSelectorProps<
|
|
|
736
762
|
/**
|
|
737
763
|
* Visual style of the selector trigger.
|
|
738
764
|
* - 'input': bordered input-style trigger for forms
|
|
739
|
-
* - '
|
|
765
|
+
* - 'secondary': matches the theme's secondary buttons (borderless and
|
|
766
|
+
* neutral-filled by default)
|
|
767
|
+
* - 'ghost': borderless, transparent trigger matching ghost buttons
|
|
740
768
|
* @default 'input'
|
|
741
769
|
*/
|
|
742
770
|
variant?: MultiSelectorVariant;
|
|
@@ -750,7 +778,7 @@ export interface MultiSelectorProps<
|
|
|
750
778
|
* - 'attached': message overlaps directly below the bordered input (input variant only)
|
|
751
779
|
* - 'detached': message floats below as a separate element with spacing
|
|
752
780
|
* - 'tooltip': message is exposed from the on-field status icon
|
|
753
|
-
* @default 'attached' for input selectors; 'detached' for ghost selectors
|
|
781
|
+
* @default 'attached' for input selectors; 'detached' for secondary and ghost selectors
|
|
754
782
|
*/
|
|
755
783
|
statusVariant?: FieldStatusVariant;
|
|
756
784
|
|
|
@@ -1114,7 +1142,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1114
1142
|
emptySearchTextFromProps ?? t('@astryx.multiSelector.emptySearchResults');
|
|
1115
1143
|
const size = useSize(sizeProp, 'md');
|
|
1116
1144
|
const effectiveStatusVariant =
|
|
1117
|
-
variant
|
|
1145
|
+
variant !== 'input' && statusVariant === 'attached'
|
|
1118
1146
|
? 'detached'
|
|
1119
1147
|
: statusVariant;
|
|
1120
1148
|
const isEffectivelyReadOnly = isReadOnly && !isDisabled;
|
|
@@ -1569,6 +1597,14 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1569
1597
|
(e: React.MouseEvent) => {
|
|
1570
1598
|
e.stopPropagation(); // Don't open dropdown
|
|
1571
1599
|
clearValues();
|
|
1600
|
+
// A keyboard activation (detail 0) leaves focus on the clear button,
|
|
1601
|
+
// which unmounts once the value is empty and would drop focus to <body>;
|
|
1602
|
+
// hand it to the trigger. A pointer press never focuses the button, so
|
|
1603
|
+
// there is no focus to repair, and moving it would pull focus out of an
|
|
1604
|
+
// open list's search input.
|
|
1605
|
+
if (e.detail === 0) {
|
|
1606
|
+
triggerRef.current?.focus();
|
|
1607
|
+
}
|
|
1572
1608
|
},
|
|
1573
1609
|
[clearValues],
|
|
1574
1610
|
);
|
|
@@ -2465,25 +2501,28 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
2465
2501
|
inputWrapperStyles.base,
|
|
2466
2502
|
styles.triggerContainer,
|
|
2467
2503
|
sizeStyles[size],
|
|
2504
|
+
variant === 'secondary' && styles.triggerSecondary,
|
|
2468
2505
|
variant === 'ghost' && styles.triggerGhost,
|
|
2469
|
-
variant
|
|
2470
|
-
variant
|
|
2506
|
+
variant !== 'input' && interactionOverlayStyles.backgroundImage,
|
|
2507
|
+
variant !== 'input' && focusOutlineStyles.focusWithin,
|
|
2471
2508
|
surface.isTriggerFocusRingSuppressed &&
|
|
2472
2509
|
selectorPresentationStyles.pointerRestoredFocus,
|
|
2473
2510
|
isDisabled && inputWrapperStyles.disabled,
|
|
2474
2511
|
isEffectivelyReadOnly && styles.triggerReadOnly,
|
|
2475
|
-
variant
|
|
2476
|
-
variant
|
|
2512
|
+
variant !== 'input' && isDisabled && styles.triggerGhostDisabled,
|
|
2513
|
+
variant !== 'input' &&
|
|
2477
2514
|
isEffectivelyReadOnly &&
|
|
2478
2515
|
styles.triggerGhostReadOnly,
|
|
2479
2516
|
optimisticValue.length === 0 && styles.triggerPlaceholder,
|
|
2480
|
-
variant
|
|
2517
|
+
variant === 'input' &&
|
|
2481
2518
|
status &&
|
|
2482
2519
|
inputStatusBorderStyles[status.type],
|
|
2483
|
-
variant
|
|
2520
|
+
variant === 'input' &&
|
|
2484
2521
|
status &&
|
|
2485
2522
|
!isDisabled &&
|
|
2486
2523
|
inputStatusHoverShadowStyles[status.type],
|
|
2524
|
+
// Grouped secondary triggers keep their neutral fill but take the
|
|
2525
|
+
// group's row height, connected edge radii, and focus stacking.
|
|
2487
2526
|
variant !== 'ghost' && inputGroup && groupStyles.inGroup,
|
|
2488
2527
|
controlXstyle,
|
|
2489
2528
|
),
|
|
@@ -1099,6 +1099,30 @@ describe('NumberInput', () => {
|
|
|
1099
1099
|
expect(screen.getByRole('alert')).toHaveTextContent('Invalid number');
|
|
1100
1100
|
});
|
|
1101
1101
|
|
|
1102
|
+
it('resolves the invalid-number announcement from the i18n catalog', async () => {
|
|
1103
|
+
const user = userEvent.setup();
|
|
1104
|
+
render(
|
|
1105
|
+
<InternationalizationProvider
|
|
1106
|
+
locale="ar-SA"
|
|
1107
|
+
overrides={{
|
|
1108
|
+
'ar-SA': {'@astryx.numberInput.invalidNumber': 'رقم غير صالح'},
|
|
1109
|
+
}}>
|
|
1110
|
+
<NumberInput
|
|
1111
|
+
label="Count"
|
|
1112
|
+
value={null}
|
|
1113
|
+
onChange={() => {}}
|
|
1114
|
+
isIntegerOnly
|
|
1115
|
+
/>
|
|
1116
|
+
</InternationalizationProvider>,
|
|
1117
|
+
);
|
|
1118
|
+
|
|
1119
|
+
const input = screen.getByRole('spinbutton');
|
|
1120
|
+
await user.click(input);
|
|
1121
|
+
await user.type(input, '3.5');
|
|
1122
|
+
|
|
1123
|
+
expect(screen.getByRole('alert')).toHaveTextContent('رقم غير صالح');
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1102
1126
|
it('does not announce an alert message when input is valid', async () => {
|
|
1103
1127
|
const user = userEvent.setup();
|
|
1104
1128
|
render(<NumberInput label="Count" value={null} onChange={() => {}} />);
|
|
@@ -977,7 +977,7 @@ export function NumberInput({
|
|
|
977
977
|
user would get no feedback that their entry was rejected (WCAG 3.3.1).
|
|
978
978
|
*/}
|
|
979
979
|
<VisuallyHidden as="div" role="alert" aria-live="assertive">
|
|
980
|
-
{!isInputValid ? '
|
|
980
|
+
{!isInputValid ? t('@astryx.numberInput.invalidNumber') : ''}
|
|
981
981
|
</VisuallyHidden>
|
|
982
982
|
{hasClear && value != null && !isDisabled && !isReadOnly && (
|
|
983
983
|
<InputClearButton
|
|
@@ -71,21 +71,6 @@ async function runState(
|
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
test('every expectation is exercised by at least one NumberInput state', async ({
|
|
75
|
-
page,
|
|
76
|
-
}) => {
|
|
77
|
-
test.setTimeout(4 * 60 * 1000);
|
|
78
|
-
const cdp = await page.context().newCDPSession(page);
|
|
79
|
-
const results: BindingResult[] = [];
|
|
80
|
-
for (const state of NUMBER_INPUT_A11Y_STATES) {
|
|
81
|
-
results.push(await runState(page, cdp, state));
|
|
82
|
-
}
|
|
83
|
-
expect(neverExercised(results)).toEqual([]);
|
|
84
|
-
expect(unmatchedKnownFailures(NUMBER_INPUT_KNOWN_FAILURES, results)).toEqual(
|
|
85
|
-
[],
|
|
86
|
-
);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
74
|
test('the Chromium read-only omission remains exact visible debt', async ({
|
|
90
75
|
page,
|
|
91
76
|
}) => {
|
|
@@ -104,13 +89,35 @@ test('the Chromium read-only omission remains exact visible debt', async ({
|
|
|
104
89
|
expect(readOnly?.knownFailure).toBe(NUMBER_INPUT_KNOWN_FAILURES[0]);
|
|
105
90
|
});
|
|
106
91
|
|
|
92
|
+
// Each state's result, recorded by its own test below so the whole-suite check
|
|
93
|
+
// at the end reads it instead of measuring every state a second time. A state
|
|
94
|
+
// whose test did not run in this worker (a filtered run, or a worker restarted
|
|
95
|
+
// after a failure) is measured by the whole-suite check itself.
|
|
96
|
+
const recorded = new Map<string, BindingResult>();
|
|
97
|
+
|
|
107
98
|
for (const state of NUMBER_INPUT_A11Y_STATES) {
|
|
108
99
|
test(`NumberInput [${state.id}] — ${state.summary}`, async ({page}) => {
|
|
109
100
|
const cdp = await page.context().newCDPSession(page);
|
|
110
101
|
const result = await runState(page, cdp, state);
|
|
102
|
+
recorded.set(state.id, result);
|
|
111
103
|
const report = summarize(SPINBUTTON_PATTERN, [result]);
|
|
112
104
|
expect(formatFailures(blockingResults([result]))).toBe('');
|
|
113
105
|
expect(report.unrunLayers).toEqual([]);
|
|
114
106
|
expect(report.counts.unexpectedPass).toBe(0);
|
|
115
107
|
});
|
|
116
108
|
}
|
|
109
|
+
|
|
110
|
+
test('every expectation is exercised by at least one NumberInput state', async ({
|
|
111
|
+
page,
|
|
112
|
+
}) => {
|
|
113
|
+
test.setTimeout(4 * 60 * 1000);
|
|
114
|
+
const cdp = await page.context().newCDPSession(page);
|
|
115
|
+
const results: BindingResult[] = [];
|
|
116
|
+
for (const state of NUMBER_INPUT_A11Y_STATES) {
|
|
117
|
+
results.push(recorded.get(state.id) ?? (await runState(page, cdp, state)));
|
|
118
|
+
}
|
|
119
|
+
expect(neverExercised(results)).toEqual([]);
|
|
120
|
+
expect(unmatchedKnownFailures(NUMBER_INPUT_KNOWN_FAILURES, results)).toEqual(
|
|
121
|
+
[],
|
|
122
|
+
);
|
|
123
|
+
});
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
waitFor,
|
|
19
19
|
within,
|
|
20
20
|
} from '@testing-library/react';
|
|
21
|
+
import userEvent from '@testing-library/user-event';
|
|
21
22
|
import React, {useState} from 'react';
|
|
22
23
|
import * as stylex from '@stylexjs/stylex';
|
|
23
24
|
import {layerViewportInset} from '../Layer/layerViewportInset.stylex';
|
|
@@ -1110,3 +1111,80 @@ describe('empty-operator token with an Editor override', () => {
|
|
|
1110
1111
|
expect(onChange).not.toHaveBeenCalled();
|
|
1111
1112
|
});
|
|
1112
1113
|
});
|
|
1114
|
+
|
|
1115
|
+
describe('Enter on a focused footer button (#7437)', () => {
|
|
1116
|
+
const validFilter: PowerSearchFilter = {
|
|
1117
|
+
field: 'status',
|
|
1118
|
+
operator: 'is',
|
|
1119
|
+
value: {type: 'string', value: 'example'},
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
function Harness({
|
|
1123
|
+
onSave,
|
|
1124
|
+
onCancel,
|
|
1125
|
+
}: {
|
|
1126
|
+
onSave: (filter: PowerSearchFilter | null) => void;
|
|
1127
|
+
onCancel: () => void;
|
|
1128
|
+
}) {
|
|
1129
|
+
const internalConfig = useInternalConfig(testConfig);
|
|
1130
|
+
return (
|
|
1131
|
+
<PowerSearchEditPopover
|
|
1132
|
+
config={internalConfig}
|
|
1133
|
+
filter={validFilter}
|
|
1134
|
+
mode="edit"
|
|
1135
|
+
onSave={onSave}
|
|
1136
|
+
onCancel={onCancel}
|
|
1137
|
+
/>
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
it('cancels without saving when Cancel has focus', async () => {
|
|
1142
|
+
const onSave = vi.fn();
|
|
1143
|
+
const onCancel = vi.fn();
|
|
1144
|
+
const user = userEvent.setup();
|
|
1145
|
+
render(<Harness onSave={onSave} onCancel={onCancel} />);
|
|
1146
|
+
|
|
1147
|
+
screen.getByRole('button', {name: 'Cancel'}).focus();
|
|
1148
|
+
await user.keyboard('{Enter}');
|
|
1149
|
+
|
|
1150
|
+
expect(onCancel).toHaveBeenCalledTimes(1);
|
|
1151
|
+
expect(onSave).not.toHaveBeenCalled();
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
it('deletes the filter when Delete has focus', async () => {
|
|
1155
|
+
const onSave = vi.fn();
|
|
1156
|
+
const user = userEvent.setup();
|
|
1157
|
+
render(<Harness onSave={onSave} onCancel={() => {}} />);
|
|
1158
|
+
|
|
1159
|
+
screen.getByRole('button', {name: 'Delete'}).focus();
|
|
1160
|
+
await user.keyboard('{Enter}');
|
|
1161
|
+
|
|
1162
|
+
expect(onSave).toHaveBeenCalledTimes(1);
|
|
1163
|
+
expect(onSave).toHaveBeenCalledWith(null);
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
it('saves once when Apply has focus', async () => {
|
|
1167
|
+
const onSave = vi.fn();
|
|
1168
|
+
const user = userEvent.setup();
|
|
1169
|
+
render(<Harness onSave={onSave} onCancel={() => {}} />);
|
|
1170
|
+
|
|
1171
|
+
screen.getByRole('button', {name: 'Apply'}).focus();
|
|
1172
|
+
await user.keyboard('{Enter}');
|
|
1173
|
+
|
|
1174
|
+
expect(onSave).toHaveBeenCalledTimes(1);
|
|
1175
|
+
expect(onSave).toHaveBeenCalledWith(validFilter);
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
it('still cancels on Escape when a button has focus', async () => {
|
|
1179
|
+
const onSave = vi.fn();
|
|
1180
|
+
const onCancel = vi.fn();
|
|
1181
|
+
const user = userEvent.setup();
|
|
1182
|
+
render(<Harness onSave={onSave} onCancel={onCancel} />);
|
|
1183
|
+
|
|
1184
|
+
screen.getByRole('button', {name: 'Delete'}).focus();
|
|
1185
|
+
await user.keyboard('{Escape}');
|
|
1186
|
+
|
|
1187
|
+
expect(onCancel).toHaveBeenCalledTimes(1);
|
|
1188
|
+
expect(onSave).not.toHaveBeenCalled();
|
|
1189
|
+
});
|
|
1190
|
+
});
|
|
@@ -793,6 +793,12 @@ export function PowerSearchEditPopover({
|
|
|
793
793
|
if (isImeKeyEvent(e.nativeEvent)) {
|
|
794
794
|
return;
|
|
795
795
|
}
|
|
796
|
+
// A focused button owns its Enter: Cancel cancels, Delete deletes, and
|
|
797
|
+
// the date picker's calendar button opens the calendar. Enter in a
|
|
798
|
+
// text or number value field still saves.
|
|
799
|
+
if (e.key === 'Enter' && e.target instanceof HTMLButtonElement) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
796
802
|
if (e.key === 'Enter' && !isSaveDisabled && !e.defaultPrevented) {
|
|
797
803
|
e.preventDefault();
|
|
798
804
|
handleSave();
|