@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
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
getAllInjectedCss,
|
|
19
19
|
} from '../__tests__/forcedColors';
|
|
20
20
|
import * as stylex from '@stylexjs/stylex';
|
|
21
|
+
import {useState} from 'react';
|
|
21
22
|
import {Calendar} from './Calendar';
|
|
22
23
|
import type {CalendarHandle, ISODateString, DateRange} from './Calendar';
|
|
23
24
|
import {calendarStyles} from './styles';
|
|
@@ -39,6 +40,24 @@ function politeRegion(): HTMLElement | null {
|
|
|
39
40
|
return document.querySelector('[data-astryx-live-region="polite"]');
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/** A controlled Calendar whose owner follows the focus-date changes. */
|
|
44
|
+
function ControlledFocusCalendar({
|
|
45
|
+
onFocusDateChange,
|
|
46
|
+
}: {
|
|
47
|
+
onFocusDateChange: (iso: string) => void;
|
|
48
|
+
}) {
|
|
49
|
+
const [focusDate, setFocusDate] = useState<ISODateString>('2026-01-01');
|
|
50
|
+
return (
|
|
51
|
+
<Calendar
|
|
52
|
+
focusDate={focusDate}
|
|
53
|
+
onFocusDateChange={iso => {
|
|
54
|
+
onFocusDateChange(iso);
|
|
55
|
+
setFocusDate(iso);
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
42
61
|
/**
|
|
43
62
|
* Helper to find a day button by its day number.
|
|
44
63
|
* Day buttons are native <button> elements with aria-labels like
|
|
@@ -52,6 +71,20 @@ function getDayButton(day: number, month = 'January', year = 2026) {
|
|
|
52
71
|
}
|
|
53
72
|
|
|
54
73
|
describe('Calendar', () => {
|
|
74
|
+
it("with week numbers, the header row's corner cell is a named columnheader, so every row child is a grid cell", () => {
|
|
75
|
+
render(<Calendar focusDate="2026-01-01" hasWeekNumbers />);
|
|
76
|
+
const grid = screen.getByRole('grid');
|
|
77
|
+
const stray = grid.querySelectorAll(
|
|
78
|
+
'[role="row"] > :not([role="gridcell"]):not([role="columnheader"]):not([role="rowheader"])',
|
|
79
|
+
);
|
|
80
|
+
expect(stray).toHaveLength(0);
|
|
81
|
+
// Named from CLDR ("week" in en-US), as visually hidden text.
|
|
82
|
+
expect(
|
|
83
|
+
within(grid).getByRole('columnheader', {name: 'week'}),
|
|
84
|
+
).toBeInTheDocument();
|
|
85
|
+
expect(within(grid).getAllByRole('rowheader').length).toBeGreaterThan(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
55
88
|
// ─── Basic Rendering ─────────────────────────────────────────
|
|
56
89
|
it('forwards ref to the calendar root element', () => {
|
|
57
90
|
let root: HTMLDivElement | null = null;
|
|
@@ -219,6 +252,31 @@ describe('Calendar', () => {
|
|
|
219
252
|
expect(handleChange).toHaveBeenCalledWith('2026-01-15', expect.any(Date));
|
|
220
253
|
});
|
|
221
254
|
|
|
255
|
+
it('shows years 1-99 as themselves, not as 1900-1999', async () => {
|
|
256
|
+
const user = userEvent.setup();
|
|
257
|
+
const handleChange = vi.fn();
|
|
258
|
+
|
|
259
|
+
render(
|
|
260
|
+
<Calendar
|
|
261
|
+
value="0001-01-15"
|
|
262
|
+
onChange={handleChange}
|
|
263
|
+
focusDate="0001-01-01"
|
|
264
|
+
/>,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
expect(screen.getByRole('grid', {name: 'January 1'})).toBeInTheDocument();
|
|
268
|
+
const day15 = getDayButton(15, 'January', 1);
|
|
269
|
+
expect(day15).toHaveAccessibleName(/^Monday, January 15, 1\b/);
|
|
270
|
+
expect(day15.closest('[role="gridcell"]')).toHaveAttribute(
|
|
271
|
+
'aria-selected',
|
|
272
|
+
'true',
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
await user.click(getDayButton(20, 'January', 1));
|
|
276
|
+
expect(handleChange).toHaveBeenCalledWith('0001-01-20', expect.any(Date));
|
|
277
|
+
expect(handleChange.mock.calls[0][1].getFullYear()).toBe(1);
|
|
278
|
+
});
|
|
279
|
+
|
|
222
280
|
it('keeps null single selection empty until the parent accepts a date', async () => {
|
|
223
281
|
const user = userEvent.setup();
|
|
224
282
|
const onChange = vi.fn();
|
|
@@ -932,6 +990,493 @@ describe('Calendar', () => {
|
|
|
932
990
|
});
|
|
933
991
|
});
|
|
934
992
|
|
|
993
|
+
it('moves the focused day by a month with PageDown and PageUp, into the month now shown', async () => {
|
|
994
|
+
const user = userEvent.setup();
|
|
995
|
+
render(<Calendar focusDate="2026-01-31" />);
|
|
996
|
+
|
|
997
|
+
getDayButton(31).focus();
|
|
998
|
+
await user.keyboard('{PageDown}');
|
|
999
|
+
await waitFor(() => {
|
|
1000
|
+
expect(politeRegion()).toHaveTextContent('February 2026');
|
|
1001
|
+
});
|
|
1002
|
+
// A month, not a week: Jan 31 lands on the last day of February.
|
|
1003
|
+
await waitFor(() => {
|
|
1004
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1005
|
+
'aria-label',
|
|
1006
|
+
expect.stringMatching(/February 28, 2026/),
|
|
1007
|
+
);
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
await user.keyboard('{PageUp}');
|
|
1011
|
+
await waitFor(() => {
|
|
1012
|
+
expect(politeRegion()).toHaveTextContent('January 2026');
|
|
1013
|
+
});
|
|
1014
|
+
await waitFor(() => {
|
|
1015
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1016
|
+
'aria-label',
|
|
1017
|
+
expect.stringMatching(/January 28, 2026/),
|
|
1018
|
+
);
|
|
1019
|
+
});
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
it("pages from the first of two months onto the right day, not the second grid's fallback", async () => {
|
|
1023
|
+
const user = userEvent.setup();
|
|
1024
|
+
render(<Calendar focusDate="2026-01-01" numberOfMonths={2} />);
|
|
1025
|
+
|
|
1026
|
+
getDayButton(15).focus();
|
|
1027
|
+
await user.keyboard('{PageDown}');
|
|
1028
|
+
await waitFor(() => {
|
|
1029
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1030
|
+
'aria-label',
|
|
1031
|
+
expect.stringMatching(/February 15, 2026/),
|
|
1032
|
+
);
|
|
1033
|
+
});
|
|
1034
|
+
expect(document.activeElement).not.toHaveAttribute('aria-disabled');
|
|
1035
|
+
|
|
1036
|
+
await user.keyboard('{PageUp}');
|
|
1037
|
+
await waitFor(() => {
|
|
1038
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1039
|
+
'aria-label',
|
|
1040
|
+
expect.stringMatching(/January 15, 2026/),
|
|
1041
|
+
);
|
|
1042
|
+
});
|
|
1043
|
+
expect(document.activeElement).not.toHaveAttribute('aria-disabled');
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
it('skips borrowed adjacent-month days and crosses into their month instead', async () => {
|
|
1047
|
+
const user = userEvent.setup();
|
|
1048
|
+
render(<Calendar focusDate="2026-01-01" />);
|
|
1049
|
+
|
|
1050
|
+
// January 1, 2026 is a Thursday: the first row borrows December 28–31.
|
|
1051
|
+
getDayButton(1).focus();
|
|
1052
|
+
await user.keyboard('{ArrowLeft}');
|
|
1053
|
+
await waitFor(() => {
|
|
1054
|
+
expect(politeRegion()).toHaveTextContent('December 2025');
|
|
1055
|
+
});
|
|
1056
|
+
await waitFor(() => {
|
|
1057
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1058
|
+
'data-date',
|
|
1059
|
+
'2025-12-31',
|
|
1060
|
+
);
|
|
1061
|
+
});
|
|
1062
|
+
expect(document.activeElement).not.toHaveAttribute('aria-disabled');
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
it('in a two-month view an arrow from the first month lands on the real day, never a borrowed one', async () => {
|
|
1066
|
+
const user = userEvent.setup();
|
|
1067
|
+
render(<Calendar focusDate="2026-01-01" numberOfMonths={2} />);
|
|
1068
|
+
|
|
1069
|
+
// January 28 (Wednesday) → ArrowDown → February 4, shown in the second
|
|
1070
|
+
// grid: no page, and never the first day of a new month.
|
|
1071
|
+
getDayButton(28).focus();
|
|
1072
|
+
await user.keyboard('{ArrowDown}');
|
|
1073
|
+
await waitFor(() => {
|
|
1074
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1075
|
+
'data-date',
|
|
1076
|
+
'2026-02-04',
|
|
1077
|
+
);
|
|
1078
|
+
});
|
|
1079
|
+
expect(document.activeElement).not.toHaveAttribute('aria-disabled');
|
|
1080
|
+
// No page happened: January is still the first grid.
|
|
1081
|
+
expect(screen.getAllByRole('grid')).toHaveLength(2);
|
|
1082
|
+
expect(getDayButton(28)).toBeInTheDocument();
|
|
1083
|
+
|
|
1084
|
+
// February 24 (Tuesday) → ArrowDown → March 3: the view pages so March
|
|
1085
|
+
// is the second month, and focus lands on the real March 3.
|
|
1086
|
+
getDayButton(24, 'February').focus();
|
|
1087
|
+
await user.keyboard('{ArrowDown}');
|
|
1088
|
+
await waitFor(() => {
|
|
1089
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1090
|
+
'data-date',
|
|
1091
|
+
'2026-03-03',
|
|
1092
|
+
);
|
|
1093
|
+
});
|
|
1094
|
+
expect(document.activeElement).not.toHaveAttribute('aria-disabled');
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
it('pages from a borrowed adjacent-month day onto the real day in the month already shown', async () => {
|
|
1098
|
+
const user = userEvent.setup();
|
|
1099
|
+
render(<Calendar focusDate="2026-01-01" />);
|
|
1100
|
+
|
|
1101
|
+
// A pointer can still land on the borrowed December 28 in the first
|
|
1102
|
+
// row (the keyboard skips it).
|
|
1103
|
+
const borrowed = document.querySelector<HTMLElement>(
|
|
1104
|
+
'button[data-date="2025-12-28"]',
|
|
1105
|
+
)!;
|
|
1106
|
+
borrowed.focus();
|
|
1107
|
+
expect(document.activeElement).toBe(borrowed);
|
|
1108
|
+
|
|
1109
|
+
// PageDown targets January 28, which January's grid already shows: no
|
|
1110
|
+
// month change, focus moves there and never to a borrowed copy.
|
|
1111
|
+
await user.keyboard('{PageDown}');
|
|
1112
|
+
await waitFor(() => {
|
|
1113
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1114
|
+
'data-date',
|
|
1115
|
+
'2026-01-28',
|
|
1116
|
+
);
|
|
1117
|
+
});
|
|
1118
|
+
expect(screen.getByRole('grid')).toHaveAttribute(
|
|
1119
|
+
'aria-label',
|
|
1120
|
+
'January 2026',
|
|
1121
|
+
);
|
|
1122
|
+
expect(document.activeElement).not.toHaveAttribute('aria-disabled');
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
it('an arrow past max changes nothing and leaves no pending target behind', async () => {
|
|
1126
|
+
const user = userEvent.setup();
|
|
1127
|
+
const {rerender} = render(
|
|
1128
|
+
<Calendar focusDate="2026-01-01" max="2026-01-31" />,
|
|
1129
|
+
);
|
|
1130
|
+
const last = getDayButton(31);
|
|
1131
|
+
last.focus();
|
|
1132
|
+
await user.keyboard('{ArrowRight}');
|
|
1133
|
+
// January stays, focus stays on the max day.
|
|
1134
|
+
expect(screen.getByRole('button', {name: 'Next month'})).toBeDisabled();
|
|
1135
|
+
expect(document.activeElement).toBe(getDayButton(31));
|
|
1136
|
+
|
|
1137
|
+
// Later the range widens and the user clicks Next: no stale target
|
|
1138
|
+
// pulls focus into the grid.
|
|
1139
|
+
rerender(<Calendar focusDate="2026-01-01" max="2026-02-28" />);
|
|
1140
|
+
const next = screen.getByRole('button', {name: 'Next month'});
|
|
1141
|
+
await user.click(next);
|
|
1142
|
+
await waitFor(() => {
|
|
1143
|
+
expect(politeRegion()).toHaveTextContent('February 2026');
|
|
1144
|
+
});
|
|
1145
|
+
expect(document.activeElement).toBe(next);
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
it('a page into a month with no enabled day changes nothing and keeps focus', async () => {
|
|
1149
|
+
const user = userEvent.setup();
|
|
1150
|
+
// Only January 1–15 is enabled: February has no enabled day at all.
|
|
1151
|
+
const earlyJanuaryOnly = (date: Date) =>
|
|
1152
|
+
date.getFullYear() === 2026 &&
|
|
1153
|
+
date.getMonth() === 0 &&
|
|
1154
|
+
date.getDate() <= 15;
|
|
1155
|
+
render(
|
|
1156
|
+
<Calendar
|
|
1157
|
+
focusDate="2026-01-01"
|
|
1158
|
+
dateConstraints={[earlyJanuaryOnly]}
|
|
1159
|
+
/>,
|
|
1160
|
+
);
|
|
1161
|
+
getDayButton(10).focus();
|
|
1162
|
+
await user.keyboard('{PageDown}');
|
|
1163
|
+
expect(screen.getByRole('grid')).toHaveAttribute(
|
|
1164
|
+
'aria-label',
|
|
1165
|
+
'January 2026',
|
|
1166
|
+
);
|
|
1167
|
+
expect(document.activeElement).toBe(getDayButton(10));
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
it('a page onto a day disabled by a constraint moves the month and focuses its first enabled day', async () => {
|
|
1171
|
+
const user = userEvent.setup();
|
|
1172
|
+
// February 1–14 disabled; February 15 onwards enabled.
|
|
1173
|
+
const noEarlyFebruary = (date: Date) =>
|
|
1174
|
+
!(date.getMonth() === 1 && date.getDate() < 15);
|
|
1175
|
+
render(
|
|
1176
|
+
<Calendar focusDate="2026-01-01" dateConstraints={[noEarlyFebruary]} />,
|
|
1177
|
+
);
|
|
1178
|
+
getDayButton(10).focus();
|
|
1179
|
+
await user.keyboard('{PageDown}');
|
|
1180
|
+
await waitFor(() => {
|
|
1181
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1182
|
+
'data-date',
|
|
1183
|
+
'2026-02-15',
|
|
1184
|
+
);
|
|
1185
|
+
});
|
|
1186
|
+
expect(screen.getByRole('grid')).toHaveAttribute(
|
|
1187
|
+
'aria-label',
|
|
1188
|
+
'February 2026',
|
|
1189
|
+
);
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
it('in a two-month view PageDown from the second month moves both months and a later Next click selects no stale day', async () => {
|
|
1193
|
+
const user = userEvent.setup();
|
|
1194
|
+
const onChange = vi.fn();
|
|
1195
|
+
render(
|
|
1196
|
+
<Calendar
|
|
1197
|
+
focusDate="2026-01-01"
|
|
1198
|
+
numberOfMonths={2}
|
|
1199
|
+
onChange={onChange}
|
|
1200
|
+
/>,
|
|
1201
|
+
);
|
|
1202
|
+
getDayButton(10, 'February').focus();
|
|
1203
|
+
await user.keyboard('{PageDown}');
|
|
1204
|
+
// March 10 is in no shown grid, so the months move (Feb + Mar) and
|
|
1205
|
+
// focus lands on March 10.
|
|
1206
|
+
await waitFor(() => {
|
|
1207
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1208
|
+
'data-date',
|
|
1209
|
+
'2026-03-10',
|
|
1210
|
+
);
|
|
1211
|
+
});
|
|
1212
|
+
expect(
|
|
1213
|
+
screen.getAllByRole('grid').map(g => g.getAttribute('aria-label')),
|
|
1214
|
+
).toEqual(['February 2026', 'March 2026']);
|
|
1215
|
+
// A later Next click pages again without selecting anything.
|
|
1216
|
+
await user.click(getButton('Next month'));
|
|
1217
|
+
await waitFor(() => {
|
|
1218
|
+
expect(
|
|
1219
|
+
screen.getAllByRole('grid').map(g => g.getAttribute('aria-label')),
|
|
1220
|
+
).toEqual(['March 2026', 'April 2026']);
|
|
1221
|
+
});
|
|
1222
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
it('an arrow toward a direction where every remaining day is disabled changes nothing', async () => {
|
|
1226
|
+
const user = userEvent.setup();
|
|
1227
|
+
// Only January 2026 is enabled: nothing after January 31 can take focus
|
|
1228
|
+
// (the walk looks a year ahead, so the constraint pins the year too).
|
|
1229
|
+
const januaryOnly = (date: Date) =>
|
|
1230
|
+
date.getFullYear() === 2026 && date.getMonth() === 0;
|
|
1231
|
+
render(
|
|
1232
|
+
<Calendar focusDate="2026-01-01" dateConstraints={[januaryOnly]} />,
|
|
1233
|
+
);
|
|
1234
|
+
getDayButton(28).focus();
|
|
1235
|
+
await user.keyboard('{ArrowDown}');
|
|
1236
|
+
// No month change, no focus drop: still January 28.
|
|
1237
|
+
expect(screen.getByRole('grid')).toHaveAttribute(
|
|
1238
|
+
'aria-label',
|
|
1239
|
+
'January 2026',
|
|
1240
|
+
);
|
|
1241
|
+
expect(document.activeElement).toBe(getDayButton(28));
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
it('an arrow resolves the next enabled day in its direction first: weekdays-only ArrowRight from Friday January 30 reaches Monday February 2', async () => {
|
|
1245
|
+
const user = userEvent.setup();
|
|
1246
|
+
const weekdaysOnly = (date: Date) =>
|
|
1247
|
+
date.getDay() !== 0 && date.getDay() !== 6;
|
|
1248
|
+
render(
|
|
1249
|
+
<Calendar focusDate="2026-01-01" dateConstraints={[weekdaysOnly]} />,
|
|
1250
|
+
);
|
|
1251
|
+
getDayButton(30).focus();
|
|
1252
|
+
await user.keyboard('{ArrowRight}');
|
|
1253
|
+
await waitFor(() => {
|
|
1254
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1255
|
+
'data-date',
|
|
1256
|
+
'2026-02-02',
|
|
1257
|
+
);
|
|
1258
|
+
});
|
|
1259
|
+
// The month paged with it: February's grid is the one shown.
|
|
1260
|
+
expect(screen.getByRole('grid')).toHaveAttribute(
|
|
1261
|
+
'aria-label',
|
|
1262
|
+
'February 2026',
|
|
1263
|
+
);
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
it('an arrow whose next day in range does not exist changes nothing: max January 25, ArrowDown from January 21', async () => {
|
|
1267
|
+
const user = userEvent.setup();
|
|
1268
|
+
render(<Calendar focusDate="2026-01-01" max="2026-01-25" />);
|
|
1269
|
+
getDayButton(21).focus();
|
|
1270
|
+
await user.keyboard('{ArrowDown}');
|
|
1271
|
+
expect(politeRegion()?.textContent ?? '').not.toContain('February 2026');
|
|
1272
|
+
expect(document.activeElement).toBe(getDayButton(21));
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
it('a controlled owner that refuses the month change leaves no stale target to steal focus or Enter later', async () => {
|
|
1276
|
+
const user = userEvent.setup();
|
|
1277
|
+
const onChange = vi.fn();
|
|
1278
|
+
function RefusingOwner() {
|
|
1279
|
+
const [focusDate, setFocusDate] = useState<ISODateString>('2026-01-01');
|
|
1280
|
+
const [allow, setAllow] = useState(false);
|
|
1281
|
+
return (
|
|
1282
|
+
<>
|
|
1283
|
+
<button type="button" onClick={() => setAllow(true)}>
|
|
1284
|
+
allow
|
|
1285
|
+
</button>
|
|
1286
|
+
<Calendar
|
|
1287
|
+
focusDate={focusDate}
|
|
1288
|
+
onFocusDateChange={next => {
|
|
1289
|
+
if (allow) {
|
|
1290
|
+
setFocusDate(next);
|
|
1291
|
+
}
|
|
1292
|
+
}}
|
|
1293
|
+
onChange={onChange}
|
|
1294
|
+
/>
|
|
1295
|
+
</>
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
render(<RefusingOwner />);
|
|
1299
|
+
getDayButton(15).focus();
|
|
1300
|
+
// PageDown asks for February; the owner refuses, so January stays and
|
|
1301
|
+
// focus does not move.
|
|
1302
|
+
await user.keyboard('{PageDown}');
|
|
1303
|
+
expect(screen.getByRole('grid')).toHaveAttribute(
|
|
1304
|
+
'aria-label',
|
|
1305
|
+
'January 2026',
|
|
1306
|
+
);
|
|
1307
|
+
expect(document.activeElement).toBe(getDayButton(15));
|
|
1308
|
+
// Later the owner lets February show (through the Next button): the
|
|
1309
|
+
// old February 15 target must not grab focus, and Enter on whatever is
|
|
1310
|
+
// focused must not pick it.
|
|
1311
|
+
await user.click(screen.getByRole('button', {name: 'allow'}));
|
|
1312
|
+
await user.click(getButton('Next month'));
|
|
1313
|
+
await waitFor(() => {
|
|
1314
|
+
expect(screen.getByRole('grid')).toHaveAttribute(
|
|
1315
|
+
'aria-label',
|
|
1316
|
+
'February 2026',
|
|
1317
|
+
);
|
|
1318
|
+
});
|
|
1319
|
+
expect(document.activeElement).not.toBe(getDayButton(15, 'February'));
|
|
1320
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
it('a refused target is dropped when focus leaves the day grid, so a later Next click plus Enter picks nothing', async () => {
|
|
1324
|
+
const user = userEvent.setup();
|
|
1325
|
+
const onChange = vi.fn();
|
|
1326
|
+
function Owner({allow}: {allow: boolean}) {
|
|
1327
|
+
const [focusDate, setFocusDate] = useState<ISODateString>('2026-01-01');
|
|
1328
|
+
return (
|
|
1329
|
+
<Calendar
|
|
1330
|
+
focusDate={focusDate}
|
|
1331
|
+
onFocusDateChange={next => {
|
|
1332
|
+
if (allow) {
|
|
1333
|
+
setFocusDate(next);
|
|
1334
|
+
}
|
|
1335
|
+
}}
|
|
1336
|
+
onChange={onChange}
|
|
1337
|
+
/>
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
const {rerender} = render(<Owner allow={false} />);
|
|
1341
|
+
getDayButton(15).focus();
|
|
1342
|
+
// Refused: January stays, focus stays on January 15.
|
|
1343
|
+
await user.keyboard('{PageDown}');
|
|
1344
|
+
expect(document.activeElement).toBe(getDayButton(15));
|
|
1345
|
+
// Focus moves off the day grid onto the Next button; the owner then
|
|
1346
|
+
// allows month changes without any focus change of its own.
|
|
1347
|
+
act(() => getButton('Next month').focus());
|
|
1348
|
+
rerender(<Owner allow />);
|
|
1349
|
+
await user.click(getButton('Next month'));
|
|
1350
|
+
await waitFor(() => {
|
|
1351
|
+
expect(screen.getByRole('grid')).toHaveAttribute(
|
|
1352
|
+
'aria-label',
|
|
1353
|
+
'February 2026',
|
|
1354
|
+
);
|
|
1355
|
+
});
|
|
1356
|
+
// The stale February 15 target must not have taken focus; Enter on
|
|
1357
|
+
// whatever is focused picks nothing.
|
|
1358
|
+
expect(document.activeElement).not.toBe(getDayButton(15, 'February'));
|
|
1359
|
+
await user.keyboard('{Enter}');
|
|
1360
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
it('a refused page target is dropped by an arrow to another day, so showing the months later does not select it', async () => {
|
|
1364
|
+
const user = userEvent.setup();
|
|
1365
|
+
const onChange = vi.fn();
|
|
1366
|
+
function Owner({focusDate}: {focusDate: ISODateString}) {
|
|
1367
|
+
// A controlled owner that ignores every requested change: the shown
|
|
1368
|
+
// months move only when the test re-renders it with a new prop, with
|
|
1369
|
+
// no focus change of its own.
|
|
1370
|
+
return (
|
|
1371
|
+
<Calendar
|
|
1372
|
+
focusDate={focusDate}
|
|
1373
|
+
numberOfMonths={2}
|
|
1374
|
+
onFocusDateChange={() => {}}
|
|
1375
|
+
onChange={onChange}
|
|
1376
|
+
/>
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
const {rerender} = render(<Owner focusDate="2026-01-01" />);
|
|
1380
|
+
getDayButton(10, 'February').focus();
|
|
1381
|
+
// Declined: Jan–Feb stay; focus stays on February 10.
|
|
1382
|
+
await user.keyboard('{PageDown}');
|
|
1383
|
+
expect(document.activeElement).toBe(getDayButton(10, 'February'));
|
|
1384
|
+
// An arrow to February 11 is a focus move to a day other than the
|
|
1385
|
+
// target, so the stale March 10 target is dropped.
|
|
1386
|
+
await user.keyboard('{ArrowRight}');
|
|
1387
|
+
expect(document.activeElement).toBe(getDayButton(11, 'February'));
|
|
1388
|
+
// The owner now shows February–March without touching focus.
|
|
1389
|
+
rerender(<Owner focusDate="2026-02-01" />);
|
|
1390
|
+
await waitFor(() => {
|
|
1391
|
+
expect(
|
|
1392
|
+
screen.getAllByRole('grid').map(g => g.getAttribute('aria-label')),
|
|
1393
|
+
).toEqual(['February 2026', 'March 2026']);
|
|
1394
|
+
});
|
|
1395
|
+
expect(document.activeElement).toBe(getDayButton(11, 'February'));
|
|
1396
|
+
await user.keyboard('{Enter}');
|
|
1397
|
+
expect(onChange).not.toHaveBeenCalledWith('2026-03-10');
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
it("the pending target becomes the grid's Tab stop: after a page into the second grid and after a page that moves the months, only it has tabindex 0", async () => {
|
|
1401
|
+
const user = userEvent.setup();
|
|
1402
|
+
render(<Calendar focusDate="2026-01-01" numberOfMonths={2} />);
|
|
1403
|
+
// Per grid: the day buttons with tabindex 0 (each grid keeps at most one;
|
|
1404
|
+
// a borrowed copy in the other grid may share the date).
|
|
1405
|
+
const stops = (gridName: string) =>
|
|
1406
|
+
Array.from(
|
|
1407
|
+
screen
|
|
1408
|
+
.getByRole('grid', {name: gridName})
|
|
1409
|
+
.querySelectorAll<HTMLElement>('button[data-date]'),
|
|
1410
|
+
).filter(button => button.tabIndex === 0);
|
|
1411
|
+
// PageDown from January 15 lands on February 15 in the second grid —
|
|
1412
|
+
// not February's default stop (its first day), so the stop must move.
|
|
1413
|
+
getDayButton(15).focus();
|
|
1414
|
+
await user.keyboard('{PageDown}');
|
|
1415
|
+
await waitFor(() => {
|
|
1416
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1417
|
+
'data-date',
|
|
1418
|
+
'2026-02-15',
|
|
1419
|
+
);
|
|
1420
|
+
});
|
|
1421
|
+
expect(
|
|
1422
|
+
stops('February 2026').map(b => b.getAttribute('data-date')),
|
|
1423
|
+
).toEqual(['2026-02-15']);
|
|
1424
|
+
expect(stops('January 2026')).toHaveLength(1);
|
|
1425
|
+
// PageDown again moves both months; March 15 is March's one stop.
|
|
1426
|
+
await user.keyboard('{PageDown}');
|
|
1427
|
+
await waitFor(() => {
|
|
1428
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1429
|
+
'data-date',
|
|
1430
|
+
'2026-03-15',
|
|
1431
|
+
);
|
|
1432
|
+
});
|
|
1433
|
+
expect(stops('March 2026').map(b => b.getAttribute('data-date'))).toEqual(
|
|
1434
|
+
['2026-03-15'],
|
|
1435
|
+
);
|
|
1436
|
+
expect(stops('February 2026')).toHaveLength(1);
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
it('a page whose target lies past max changes nothing and keeps focus', async () => {
|
|
1440
|
+
const user = userEvent.setup();
|
|
1441
|
+
render(<Calendar focusDate="2026-01-01" max="2026-02-10" />);
|
|
1442
|
+
getDayButton(15).focus();
|
|
1443
|
+
// February 15 is past max: no page, no focus drop.
|
|
1444
|
+
await user.keyboard('{PageDown}');
|
|
1445
|
+
// No announcement was made (the live region never mounted) and January
|
|
1446
|
+
// is still the month shown.
|
|
1447
|
+
expect(politeRegion()?.textContent ?? '').not.toContain('February 2026');
|
|
1448
|
+
expect(getDayButton(15)).toHaveAttribute('data-date', '2026-01-15');
|
|
1449
|
+
expect(document.activeElement).toBe(getDayButton(15));
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
it('in a two-month view a boundary blocks only out-of-range targets, not month movement', async () => {
|
|
1453
|
+
const user = userEvent.setup();
|
|
1454
|
+
render(
|
|
1455
|
+
<Calendar focusDate="2026-01-01" min="2026-01-01" numberOfMonths={2} />,
|
|
1456
|
+
);
|
|
1457
|
+
// ArrowUp from February 3 reaches January 27 — in range, in the first
|
|
1458
|
+
// grid — even though Previous is disabled at the January minimum.
|
|
1459
|
+
getDayButton(3, 'February').focus();
|
|
1460
|
+
await user.keyboard('{ArrowUp}');
|
|
1461
|
+
await waitFor(() => {
|
|
1462
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1463
|
+
'data-date',
|
|
1464
|
+
'2026-01-27',
|
|
1465
|
+
);
|
|
1466
|
+
});
|
|
1467
|
+
// PageDown from January 15 reaches February 15, already visible: no
|
|
1468
|
+
// month change, focus moves to it.
|
|
1469
|
+
getDayButton(15).focus();
|
|
1470
|
+
await user.keyboard('{PageDown}');
|
|
1471
|
+
await waitFor(() => {
|
|
1472
|
+
expect(document.activeElement).toHaveAttribute(
|
|
1473
|
+
'data-date',
|
|
1474
|
+
'2026-02-15',
|
|
1475
|
+
);
|
|
1476
|
+
});
|
|
1477
|
+
expect(getDayButton(15)).toBeInTheDocument();
|
|
1478
|
+
});
|
|
1479
|
+
|
|
935
1480
|
it('announces the next month when paging the grid with PageDown', async () => {
|
|
936
1481
|
const user = userEvent.setup();
|
|
937
1482
|
render(<Calendar focusDate="2026-01-01" />);
|
|
@@ -1106,15 +1651,6 @@ describe('Calendar', () => {
|
|
|
1106
1651
|
});
|
|
1107
1652
|
|
|
1108
1653
|
describe('aria-multiselectable', () => {
|
|
1109
|
-
it('sets aria-multiselectable="true" on the grid in range mode', () => {
|
|
1110
|
-
render(<Calendar mode="range" focusDate="2026-01-01" />);
|
|
1111
|
-
|
|
1112
|
-
expect(screen.getByRole('grid')).toHaveAttribute(
|
|
1113
|
-
'aria-multiselectable',
|
|
1114
|
-
'true',
|
|
1115
|
-
);
|
|
1116
|
-
});
|
|
1117
|
-
|
|
1118
1654
|
it('does not set aria-multiselectable in single mode', () => {
|
|
1119
1655
|
render(<Calendar focusDate="2026-01-01" />);
|
|
1120
1656
|
|
|
@@ -1149,21 +1685,23 @@ describe('Calendar', () => {
|
|
|
1149
1685
|
const user = userEvent.setup();
|
|
1150
1686
|
const handleFocusChange = vi.fn();
|
|
1151
1687
|
|
|
1152
|
-
render(
|
|
1153
|
-
<Calendar focusDate="2026-01-01" onFocusDateChange={handleFocusChange} />,
|
|
1154
|
-
);
|
|
1688
|
+
render(<ControlledFocusCalendar onFocusDateChange={handleFocusChange} />);
|
|
1155
1689
|
|
|
1156
1690
|
// Focus Jan 28
|
|
1157
1691
|
const day28 = getDayButton(28);
|
|
1158
1692
|
await user.click(day28);
|
|
1159
1693
|
day28.focus();
|
|
1160
1694
|
|
|
1161
|
-
// Press ArrowDown — should move to Feb 4 (+7 days), not Feb 28
|
|
1695
|
+
// Press ArrowDown — should move to Feb 4 (+7 days), not Feb 28. The
|
|
1696
|
+
// borrowed February row in January's grid is skipped; the grid navigates
|
|
1697
|
+
// to February and focuses the real day there.
|
|
1162
1698
|
await user.keyboard('{ArrowDown}');
|
|
1163
1699
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1700
|
+
expect(handleFocusChange).toHaveBeenCalledWith('2026-02-01');
|
|
1701
|
+
await waitFor(() => {
|
|
1702
|
+
expect(document.activeElement).toHaveAttribute('data-date', '2026-02-04');
|
|
1703
|
+
});
|
|
1704
|
+
expect(document.activeElement).not.toHaveAttribute('aria-disabled');
|
|
1167
1705
|
});
|
|
1168
1706
|
|
|
1169
1707
|
it('ArrowDown lands on the same weekday +7 days even when earlier days are disabled (complex-2)', async () => {
|
|
@@ -1222,9 +1760,7 @@ describe('Calendar', () => {
|
|
|
1222
1760
|
const user = userEvent.setup();
|
|
1223
1761
|
const handleFocusChange = vi.fn();
|
|
1224
1762
|
|
|
1225
|
-
render(
|
|
1226
|
-
<Calendar focusDate="2026-01-01" onFocusDateChange={handleFocusChange} />,
|
|
1227
|
-
);
|
|
1763
|
+
render(<ControlledFocusCalendar onFocusDateChange={handleFocusChange} />);
|
|
1228
1764
|
|
|
1229
1765
|
const day28 = getDayButton(28);
|
|
1230
1766
|
await user.click(day28);
|
|
@@ -1235,7 +1771,10 @@ describe('Calendar', () => {
|
|
|
1235
1771
|
await user.keyboard('{ArrowDown}');
|
|
1236
1772
|
|
|
1237
1773
|
// Feb 4 (+7 days) — resolved via data-date despite the unparseable label.
|
|
1238
|
-
expect(
|
|
1774
|
+
expect(handleFocusChange).toHaveBeenCalledWith('2026-02-01');
|
|
1775
|
+
await waitFor(() => {
|
|
1776
|
+
expect(document.activeElement).toHaveAttribute('data-date', '2026-02-04');
|
|
1777
|
+
});
|
|
1239
1778
|
});
|
|
1240
1779
|
|
|
1241
1780
|
it('prev button is disabled when focusDate month contains min', () => {
|