@astryxdesign/core 0.1.9 → 0.2.0-canary.136a00f
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 +107 -0
- package/dist/Avatar/Avatar.d.ts +42 -3
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +190 -59
- package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
- package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.d.ts +7 -1
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.js +58 -6
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -2
- package/dist/Banner/Banner.js +2 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +11 -4
- package/dist/Breadcrumbs/index.d.ts +1 -0
- package/dist/Breadcrumbs/index.d.ts.map +1 -1
- package/dist/Breadcrumbs/index.js +2 -1
- package/dist/Button/Button.d.ts +1 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +6 -4
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +50 -9
- package/dist/Calendar/dayCellUtils.d.ts +4 -4
- package/dist/Calendar/dayCellUtils.js +2 -2
- package/dist/Calendar/styles.d.ts +30 -31
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +30 -31
- package/dist/Card/Card.js +1 -1
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +22 -12
- package/dist/Chat/ChatComposerInput.js +1 -1
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +3 -1
- package/dist/Chat/ChatLayout.js +4 -4
- package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
- package/dist/Chat/ChatMessageBubble.js +8 -8
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +10 -3
- package/dist/Chat/useTriggerMenu.js +2 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +6 -6
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +19 -4
- package/dist/Citation/Citation.d.ts +22 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +20 -6
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +16 -10
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +3 -1
- package/dist/CommandPalette/CommandPalette.d.ts +1 -1
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +42 -3
- package/dist/CommandPalette/CommandPaletteItem.js +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +23 -5
- package/dist/ContextMenu/index.d.ts +1 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/ContextMenu/index.js +3 -1
- package/dist/DateInput/DateInput.d.ts +10 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +33 -19
- package/dist/DateRangeInput/DateRangeInput.d.ts +10 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +23 -20
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +23 -18
- package/dist/Divider/Divider.d.ts +2 -2
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +13 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts +6 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +22 -5
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +5 -2
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +8 -11
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioGroup.js +3 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +5 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +427 -0
- package/dist/DropdownMenu/index.d.ts +10 -0
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/index.js +13 -0
- package/dist/DropdownMenu/menuItemHover.d.ts +23 -0
- package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
- package/dist/DropdownMenu/menuItemHover.js +33 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts +8 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -1
- package/dist/DropdownMenu/menuItemRoles.js +10 -1
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
- package/dist/DropdownMenu/renderDropdownItems.js +21 -6
- package/dist/Field/Field.d.ts +3 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +4 -1
- package/dist/Field/FieldLabel.d.ts +2 -1
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +9 -5
- package/dist/Field/inputStyles.stylex.d.ts +1 -0
- package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
- package/dist/Field/inputStyles.stylex.js +1 -0
- package/dist/FieldStatus/FieldStatus.d.ts +10 -0
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +39 -3
- package/dist/FileInput/FileInput.d.ts +10 -2
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +93 -65
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +10 -4
- package/dist/Icon/Icon.d.ts +15 -1
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +19 -3
- package/dist/Item/Item.d.ts +16 -3
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +31 -6
- package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
- package/dist/Layer/layerAnimations.stylex.js +10 -2
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +19 -13
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +6 -3
- package/dist/List/ListItem.d.ts +11 -1
- package/dist/List/ListItem.d.ts.map +1 -1
- package/dist/List/ListItem.js +2 -0
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +7 -0
- package/dist/MetadataList/MetadataList.d.ts +1 -1
- package/dist/MetadataList/MetadataList.d.ts.map +1 -1
- package/dist/MetadataList/MetadataList.js +4 -2
- package/dist/MultiSelector/MultiSelector.d.ts +9 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +80 -42
- package/dist/NavMenu/NavHeadingMenuItem.js +1 -1
- package/dist/NumberInput/NumberInput.d.ts +10 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +22 -17
- package/dist/Outline/Outline.js +2 -2
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +11 -3
- package/dist/PowerSearch/PowerSearch.d.ts +9 -2
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +2 -0
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +6 -1
- package/dist/RadioList/RadioList.d.ts +2 -2
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +5 -5
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +49 -19
- package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +5 -3
- package/dist/Selector/Selector.d.ts +8 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +66 -17
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +5 -5
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +13 -10
- package/dist/SideNav/SideNavItem.d.ts +1 -1
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +12 -10
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +2 -2
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +87 -34
- package/dist/Spinner/Spinner.d.ts +3 -2
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +11 -2
- package/dist/Switch/Switch.d.ts +8 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +171 -91
- package/dist/TabList/Tab.js +2 -2
- package/dist/TabList/TabList.d.ts +2 -11
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +5 -9
- package/dist/TabList/TabListContext.d.ts +0 -5
- package/dist/TabList/TabListContext.d.ts.map +1 -1
- package/dist/TabList/TabListContext.js +0 -5
- package/dist/TabList/TabMenu.js +1 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +30 -4
- package/dist/Table/TableCell.js +3 -3
- package/dist/Table/TableHeaderCell.js +3 -3
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +37 -27
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +3 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +15 -7
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeData.js +29 -23
- package/dist/Table/types.d.ts +32 -0
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/TextArea/TextArea.d.ts +10 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +18 -19
- package/dist/TextInput/TextInput.d.ts +10 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +20 -17
- package/dist/Thumbnail/Thumbnail.js +2 -2
- package/dist/TimeInput/TimeInput.d.ts +10 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +17 -17
- package/dist/Timestamp/Timestamp.d.ts +31 -3
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +47 -76
- package/dist/Timestamp/formatInstant.d.ts +41 -0
- package/dist/Timestamp/formatInstant.d.ts.map +1 -0
- package/dist/Timestamp/formatInstant.js +177 -0
- package/dist/Timestamp/index.d.ts +1 -0
- package/dist/Timestamp/index.d.ts.map +1 -1
- package/dist/Timestamp/tooltipEntries.d.ts +56 -0
- package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
- package/dist/Timestamp/tooltipEntries.js +163 -0
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +61 -9
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +3 -1
- package/dist/Token/Token.d.ts +34 -4
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +85 -45
- package/dist/Token/TokenLink.d.ts +62 -0
- package/dist/Token/TokenLink.d.ts.map +1 -0
- package/dist/Token/TokenLink.js +76 -0
- package/dist/Token/index.d.ts +1 -1
- package/dist/Token/index.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts +9 -2
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +4 -2
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +17 -1
- package/dist/TopNav/TopNavItem.d.ts +3 -1
- package/dist/TopNav/TopNavItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavItem.js +30 -3
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +1 -0
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +8 -6
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +32 -14
- package/dist/Typeahead/BaseTypeahead.d.ts +9 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +22 -11
- package/dist/Typeahead/Typeahead.d.ts +9 -2
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +11 -2
- package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
- package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
- package/dist/Typeahead/TypeaheadItem.js +20 -10
- package/dist/astryx.css +70 -30
- package/dist/astryx.umd.js +51 -51
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/isRtlElement.d.ts +18 -0
- package/dist/hooks/isRtlElement.d.ts.map +1 -0
- package/dist/hooks/isRtlElement.js +27 -0
- package/dist/hooks/useAnnounce.d.ts +3 -0
- package/dist/hooks/useAnnounce.d.ts.map +1 -1
- package/dist/hooks/useAnnounce.js +39 -1
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +52 -11
- package/dist/hooks/useGridFocus.d.ts +6 -1
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useGridFocus.js +12 -9
- package/dist/hooks/useHotkeys.d.ts +9 -0
- package/dist/hooks/useHotkeys.d.ts.map +1 -1
- package/dist/hooks/useHotkeys.js +9 -0
- package/dist/hooks/useInputStatusIcon.d.ts +38 -0
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
- package/dist/hooks/useInputStatusIcon.js +158 -0
- package/dist/hooks/useKeyboardHint.d.ts +1 -1
- package/dist/hooks/useKeyboardHint.d.ts.map +1 -1
- package/dist/hooks/useKeyboardHint.js +4 -2
- package/dist/hooks/useListFocus.d.ts +39 -1
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +54 -9
- package/dist/i18n/InternationalizationContext.d.ts +1 -0
- package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
- package/dist/i18n/InternationalizationContext.js +3 -0
- package/dist/i18n/InternationalizationProvider.d.ts +8 -1
- package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
- package/dist/i18n/InternationalizationProvider.js +5 -1
- package/dist/i18n/getLocaleDirection.d.ts +18 -0
- package/dist/i18n/getLocaleDirection.d.ts.map +1 -0
- package/dist/i18n/getLocaleDirection.js +29 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +7 -1
- package/dist/i18n/useDirection.d.ts +2 -0
- package/dist/i18n/useDirection.d.ts.map +1 -0
- package/dist/i18n/useDirection.js +27 -0
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +13 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/plainDate.d.ts +17 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +23 -4
- package/dist/utils/rtlStyles.d.ts +24 -0
- package/dist/utils/rtlStyles.d.ts.map +1 -0
- package/dist/utils/rtlStyles.js +27 -0
- package/locales/en.json +112 -0
- package/locales/pseudo.json +84 -0
- package/package.json +6 -1
- package/src/Avatar/Avatar.doc.mjs +50 -3
- package/src/Avatar/Avatar.test.tsx +218 -0
- package/src/Avatar/Avatar.tsx +285 -79
- package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
- package/src/Avatar/AvatarStatusDot.tsx +6 -4
- package/src/AvatarGroup/AvatarGroup.doc.mjs +2 -0
- package/src/AvatarGroup/AvatarGroup.test.tsx +162 -2
- package/src/AvatarGroup/AvatarGroup.tsx +58 -3
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +5 -2
- package/src/Banner/Banner.tsx +6 -6
- package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +52 -1
- package/src/Breadcrumbs/index.ts +4 -0
- package/src/Button/Button.doc.mjs +0 -4
- package/src/Button/Button.test.tsx +16 -0
- package/src/Button/Button.tsx +6 -4
- package/src/Calendar/Calendar.test.tsx +170 -20
- package/src/Calendar/Calendar.tsx +73 -16
- package/src/Calendar/dayCellUtils.test.ts +18 -18
- package/src/Calendar/dayCellUtils.ts +2 -2
- package/src/Calendar/styles.ts +33 -39
- package/src/Card/Card.tsx +1 -1
- package/src/Carousel/Carousel.tsx +25 -6
- package/src/Chat/ChatComposer.doc.mjs +2 -2
- package/src/Chat/ChatComposerInput.doc.mjs +1 -1
- package/src/Chat/ChatComposerInput.tsx +2 -2
- package/src/Chat/ChatDictationButton.tsx +7 -1
- package/src/Chat/ChatLayout.tsx +4 -4
- package/src/Chat/ChatMessageBubble.tsx +15 -10
- package/src/Chat/ChatSystemMessage.test.tsx +5 -0
- package/src/Chat/ChatToolCalls.tsx +9 -2
- package/src/Chat/useTriggerMenu.tsx +1 -1
- package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +23 -0
- package/src/CheckboxInput/CheckboxInput.tsx +21 -6
- package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
- package/src/CheckboxList/CheckboxList.test.tsx +94 -3
- package/src/CheckboxList/CheckboxListItem.tsx +17 -10
- package/src/Citation/Citation.doc.mjs +4 -3
- package/src/Citation/Citation.test.tsx +97 -0
- package/src/Citation/Citation.tsx +43 -11
- package/src/CodeBlock/CodeBlock.test.tsx +17 -0
- package/src/CodeBlock/CodeBlock.tsx +38 -11
- package/src/Collapsible/Collapsible.doc.mjs +1 -0
- package/src/Collapsible/Collapsible.test.tsx +7 -0
- package/src/Collapsible/Collapsible.tsx +16 -6
- package/src/CommandPalette/CommandPalette.test.tsx +95 -1
- package/src/CommandPalette/CommandPalette.tsx +41 -2
- package/src/CommandPalette/CommandPaletteItem.tsx +1 -1
- package/src/ContextMenu/ContextMenu.test.tsx +59 -2
- package/src/ContextMenu/ContextMenu.tsx +22 -11
- package/src/ContextMenu/index.ts +5 -0
- package/src/DateInput/DateInput.doc.mjs +25 -0
- package/src/DateInput/DateInput.test.tsx +230 -0
- package/src/DateInput/DateInput.tsx +51 -29
- package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +140 -0
- package/src/DateRangeInput/DateRangeInput.tsx +42 -30
- package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
- package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
- package/src/DateTimeInput/DateTimeInput.tsx +44 -39
- package/src/Divider/Divider.test.tsx +32 -8
- package/src/Divider/Divider.tsx +14 -1
- package/src/DropdownMenu/DropdownMenu.test.tsx +60 -0
- package/src/DropdownMenu/DropdownMenu.tsx +31 -12
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuItem.tsx +9 -3
- package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +4 -12
- package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +9 -10
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +9 -3
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +20 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +635 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +553 -0
- package/src/DropdownMenu/index.ts +16 -0
- package/src/DropdownMenu/menuItemHover.ts +39 -0
- package/src/DropdownMenu/menuItemRoles.ts +9 -0
- package/src/DropdownMenu/renderDropdownItems.tsx +29 -15
- package/src/Field/Field.tsx +14 -9
- package/src/Field/FieldLabel.test.tsx +25 -0
- package/src/Field/FieldLabel.tsx +12 -3
- package/src/Field/inputStyles.stylex.ts +2 -0
- package/src/FieldStatus/FieldStatus.test.tsx +72 -0
- package/src/FieldStatus/FieldStatus.tsx +48 -2
- package/src/FileInput/FileInput.doc.mjs +23 -2
- package/src/FileInput/FileInput.test.tsx +104 -13
- package/src/FileInput/FileInput.tsx +106 -79
- package/src/HoverCard/useHoverCard.tsx +15 -3
- package/src/Icon/Icon.doc.mjs +13 -0
- package/src/Icon/Icon.test.tsx +89 -0
- package/src/Icon/Icon.tsx +51 -3
- package/src/Item/Item.doc.mjs +5 -0
- package/src/Item/Item.test.tsx +88 -0
- package/src/Item/Item.tsx +57 -11
- package/src/Layer/layerAnimations.stylex.ts +23 -0
- package/src/Lightbox/Lightbox.tsx +15 -7
- package/src/List/List.test.tsx +17 -29
- package/src/List/List.tsx +6 -1
- package/src/List/ListItem.doc.mjs +12 -0
- package/src/List/ListItem.tsx +13 -0
- package/src/Markdown/Markdown.test.tsx +21 -0
- package/src/Markdown/Markdown.tsx +4 -0
- package/src/MetadataList/MetadataList.test.tsx +24 -0
- package/src/MetadataList/MetadataList.tsx +6 -2
- package/src/MultiSelector/MultiSelector.doc.mjs +20 -0
- package/src/MultiSelector/MultiSelector.test.tsx +402 -0
- package/src/MultiSelector/MultiSelector.tsx +120 -59
- package/src/NavMenu/NavHeadingMenuItem.tsx +1 -1
- package/src/NumberInput/NumberInput.doc.mjs +21 -0
- package/src/NumberInput/NumberInput.test.tsx +71 -0
- package/src/NumberInput/NumberInput.tsx +35 -26
- package/src/Outline/Outline.test.tsx +12 -12
- package/src/Outline/Outline.tsx +2 -2
- package/src/Pagination/Pagination.tsx +11 -3
- package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
- package/src/PowerSearch/PowerSearch.test.tsx +23 -0
- package/src/PowerSearch/PowerSearch.tsx +10 -1
- package/src/ProgressBar/ProgressBar.test.tsx +34 -0
- package/src/ProgressBar/ProgressBar.tsx +16 -1
- package/src/RadioList/RadioList.doc.mjs +6 -0
- package/src/RadioList/RadioList.test.tsx +26 -4
- package/src/RadioList/RadioList.tsx +2 -2
- package/src/RadioList/RadioListItem.tsx +12 -5
- package/src/Resizable/ResizeHandle.test.tsx +94 -0
- package/src/Resizable/ResizeHandle.tsx +70 -20
- package/src/SegmentedControl/SegmentedControl.test.tsx +28 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +22 -2
- package/src/Selector/Selector.doc.mjs +15 -0
- package/src/Selector/Selector.test.tsx +370 -0
- package/src/Selector/Selector.tsx +88 -20
- package/src/SideNav/SideNav.tsx +1 -8
- package/src/SideNav/SideNavCollapseButton.tsx +9 -7
- package/src/SideNav/SideNavItem.tsx +5 -3
- package/src/Skeleton/Skeleton.test.tsx +16 -0
- package/src/Skeleton/Skeleton.tsx +13 -1
- package/src/Slider/Slider.doc.mjs +6 -0
- package/src/Slider/Slider.test.tsx +240 -12
- package/src/Slider/Slider.tsx +96 -23
- package/src/Spinner/Spinner.test.tsx +10 -8
- package/src/Spinner/Spinner.tsx +15 -5
- package/src/Switch/Switch.doc.mjs +142 -42
- package/src/Switch/Switch.test.tsx +111 -0
- package/src/Switch/Switch.tsx +149 -32
- package/src/TabList/Tab.tsx +2 -2
- package/src/TabList/TabList.doc.mjs +0 -6
- package/src/TabList/TabList.test.tsx +3 -15
- package/src/TabList/TabList.tsx +6 -17
- package/src/TabList/TabListContext.ts +0 -6
- package/src/TabList/TabMenu.tsx +2 -2
- package/src/Table/BaseTable.tsx +34 -2
- package/src/Table/Table.doc.mjs +11 -0
- package/src/Table/Table.test.tsx +85 -0
- package/src/Table/TableCell.tsx +3 -3
- package/src/Table/TableHeaderCell.tsx +3 -3
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +19 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +27 -18
- package/src/Table/plugins/selection/useTableSelection.test.tsx +61 -3
- package/src/Table/plugins/selection/useTableSelection.tsx +3 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +38 -9
- package/src/Table/plugins/tree/useTableTreeData.tsx +17 -13
- package/src/Table/types.ts +33 -0
- package/src/Table/useTableRowStatus.doc.mjs +1 -1
- package/src/TextArea/TextArea.doc.mjs +21 -0
- package/src/TextArea/TextArea.test.tsx +23 -0
- package/src/TextArea/TextArea.tsx +27 -26
- package/src/TextInput/TextInput.doc.mjs +21 -0
- package/src/TextInput/TextInput.test.tsx +150 -0
- package/src/TextInput/TextInput.tsx +28 -25
- package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
- package/src/Thumbnail/Thumbnail.tsx +1 -1
- package/src/TimeInput/TimeInput.doc.mjs +21 -0
- package/src/TimeInput/TimeInput.test.tsx +23 -0
- package/src/TimeInput/TimeInput.tsx +24 -25
- package/src/Timestamp/Timestamp.doc.mjs +32 -6
- package/src/Timestamp/Timestamp.test.tsx +371 -0
- package/src/Timestamp/Timestamp.tsx +96 -93
- package/src/Timestamp/formatInstant.ts +218 -0
- package/src/Timestamp/index.ts +4 -0
- package/src/Timestamp/tooltipEntries.test.ts +552 -0
- package/src/Timestamp/tooltipEntries.ts +210 -0
- package/src/Toast/ToastViewport.test.tsx +218 -8
- package/src/Toast/ToastViewport.tsx +71 -12
- package/src/ToggleButton/ToggleButton.test.tsx +24 -0
- package/src/ToggleButton/ToggleButton.tsx +19 -1
- package/src/Token/Token.test.tsx +166 -15
- package/src/Token/Token.tsx +104 -47
- package/src/Token/TokenLink.tsx +107 -0
- package/src/Token/index.ts +1 -1
- package/src/Tokenizer/Tokenizer.doc.mjs +23 -1
- package/src/Tokenizer/Tokenizer.test.tsx +23 -0
- package/src/Tokenizer/Tokenizer.tsx +14 -2
- package/src/Tooltip/Tooltip.test.tsx +46 -0
- package/src/Tooltip/useTooltip.tsx +20 -0
- package/src/TopNav/TopNav.test.tsx +93 -2
- package/src/TopNav/TopNavItem.doc.mjs +3 -3
- package/src/TopNav/TopNavItem.tsx +35 -6
- package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
- package/src/TreeList/TreeList.doc.mjs +6 -0
- package/src/TreeList/TreeList.test.tsx +50 -10
- package/src/TreeList/TreeList.tsx +5 -0
- package/src/TreeList/TreeListBranches.tsx +8 -6
- package/src/TreeList/TreeListItem.tsx +33 -11
- package/src/Typeahead/BaseTypeahead.tsx +42 -15
- package/src/Typeahead/Typeahead.doc.mjs +13 -0
- package/src/Typeahead/Typeahead.test.tsx +112 -0
- package/src/Typeahead/Typeahead.tsx +17 -1
- package/src/Typeahead/TypeaheadItem.tsx +2 -1
- package/src/__tests__/forcedColors.ts +60 -0
- package/src/__tests__/inputWidthContract.test.tsx +62 -0
- package/src/doc-mjs.d.ts +19 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/isRtlElement.ts +27 -0
- package/src/hooks/useAnnounce.test.tsx +75 -2
- package/src/hooks/useAnnounce.ts +47 -1
- package/src/hooks/useFocusTrap.test.tsx +104 -0
- package/src/hooks/useFocusTrap.ts +61 -12
- package/src/hooks/useGridFocus.doc.mjs +3 -3
- package/src/hooks/useGridFocus.test.tsx +41 -0
- package/src/hooks/useGridFocus.ts +21 -10
- package/src/hooks/useHotkeys.ts +9 -0
- package/src/hooks/useInputStatusIcon.test.tsx +162 -0
- package/src/hooks/useInputStatusIcon.tsx +224 -0
- package/src/hooks/useKeyboardHint.test.tsx +22 -0
- package/src/hooks/useKeyboardHint.tsx +6 -2
- package/src/hooks/useListFocus.doc.mjs +25 -3
- package/src/hooks/useListFocus.test.tsx +153 -1
- package/src/hooks/useListFocus.ts +102 -9
- package/src/i18n/InternationalizationContext.ts +4 -0
- package/src/i18n/InternationalizationProvider.tsx +12 -2
- package/src/i18n/__tests__/getLocaleDirection.test.ts +40 -0
- package/src/i18n/__tests__/useDirection.test.tsx +66 -0
- package/src/i18n/getLocaleDirection.ts +29 -0
- package/src/i18n/index.ts +6 -0
- package/src/i18n/useDirection.ts +29 -0
- package/src/reset.css +0 -26
- package/src/reset.test.ts +60 -0
- package/src/theme/derivedVarRegistry.test.ts +1 -0
- package/src/theme/derivedVarRegistry.ts +6 -0
- package/src/theme/generateThemeRules.test.ts +42 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/plainDate.test.ts +24 -6
- package/src/utils/plainDate.ts +18 -4
- package/src/utils/rtlStyles.ts +27 -0
|
@@ -4,16 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useListFocus.ts
|
|
7
|
-
* @input Uses React useCallback, useRef, useIsomorphicLayoutEffect
|
|
7
|
+
* @input Uses React useCallback, useRef, useIsomorphicLayoutEffect,
|
|
8
|
+
* isRtlElement
|
|
8
9
|
* @output Exports useListFocus hook for linear list keyboard navigation
|
|
9
10
|
* @position Core hook; used by TabMenu for dropdown menu navigation, Toolbar
|
|
10
11
|
* for roving tabindex, ButtonGroup, ContextMenu, NavHeadingMenu, and more.
|
|
12
|
+
* Auto-detects RTL from the container's computed direction so horizontal
|
|
13
|
+
* arrow keys follow visual direction (WCAG 1.3.2).
|
|
11
14
|
*
|
|
12
15
|
* SYNC: When modified, update:
|
|
13
16
|
* - /packages/core/src/hooks/index.ts
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
19
|
import {useCallback, useRef} from 'react';
|
|
20
|
+
import {isRtlElement} from './isRtlElement';
|
|
17
21
|
import {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect';
|
|
18
22
|
|
|
19
23
|
/**
|
|
@@ -34,6 +38,25 @@ export interface UseListFocusOptions {
|
|
|
34
38
|
*/
|
|
35
39
|
itemSelector?: string;
|
|
36
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Selector identifying a list boundary — used to scope a list that contains
|
|
43
|
+
* *nested* lists of the same kind (e.g. a menu with submenu flyouts).
|
|
44
|
+
*
|
|
45
|
+
* Overlays like submenu flyouts render inline (native popover, not a
|
|
46
|
+
* portal), so a nested list's items are DOM descendants of the parent list.
|
|
47
|
+
* Without scoping, the parent's `querySelectorAll` sweeps the nested items
|
|
48
|
+
* into its roving order (leaving hidden items `.focus()` can't land on), and
|
|
49
|
+
* key events from the nested list bubble into the parent's handler (moving
|
|
50
|
+
* focus twice). When set, `useListFocus`:
|
|
51
|
+
* - counts an item as its own only when the item's nearest
|
|
52
|
+
* `boundarySelector` ancestor is this list's container, and
|
|
53
|
+
* - ignores key events whose nearest `boundarySelector` ancestor is not
|
|
54
|
+
* this list's container (i.e. they originated in a nested list).
|
|
55
|
+
*
|
|
56
|
+
* Typically `'[role="menu"]'` for menus. Omit for flat lists.
|
|
57
|
+
*/
|
|
58
|
+
boundarySelector?: string;
|
|
59
|
+
|
|
37
60
|
/**
|
|
38
61
|
* Whether arrow navigation wraps around at the ends.
|
|
39
62
|
* @default true
|
|
@@ -62,7 +85,12 @@ export interface UseListFocusOptions {
|
|
|
62
85
|
* Whether the list is in a right-to-left context. When true, ArrowLeft and
|
|
63
86
|
* ArrowRight are swapped so horizontal navigation follows visual direction.
|
|
64
87
|
* Only affects horizontal (`'horizontal'`/`'both'`) navigation.
|
|
65
|
-
*
|
|
88
|
+
*
|
|
89
|
+
* When omitted, the direction is auto-detected from the container's computed
|
|
90
|
+
* `direction` (read lazily on keydown, only for horizontal arrow keys), so
|
|
91
|
+
* lists inside `dir="rtl"` subtrees flip automatically. Pass an explicit
|
|
92
|
+
* boolean to override detection.
|
|
93
|
+
* @default undefined (auto-detect from the container)
|
|
66
94
|
*/
|
|
67
95
|
isRtl?: boolean;
|
|
68
96
|
|
|
@@ -128,6 +156,23 @@ export interface UseListFocusReturn<T extends HTMLElement = HTMLElement> {
|
|
|
128
156
|
* Focus the last enabled item. Returns true when an item was focused.
|
|
129
157
|
*/
|
|
130
158
|
focusLast: () => boolean;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Whether a key event belongs to this list level (vs. a nested list that
|
|
162
|
+
* shares the same `boundarySelector`). Useful when a consumer wraps
|
|
163
|
+
* `handleKeyDown` with extra behavior (Enter/Space activation, typeahead)
|
|
164
|
+
* and must apply it only to events this level owns. Always true when no
|
|
165
|
+
* `boundarySelector` is configured.
|
|
166
|
+
*/
|
|
167
|
+
ownsEvent: (e: React.KeyboardEvent) => boolean;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The current, in-DOM-order list of this level's focusable items (already
|
|
171
|
+
* scoped by `boundarySelector`). Exposed so consumers can build typeahead
|
|
172
|
+
* targets from the same source of truth as roving focus, rather than
|
|
173
|
+
* re-querying and re-filtering.
|
|
174
|
+
*/
|
|
175
|
+
getItems: () => HTMLElement[];
|
|
131
176
|
}
|
|
132
177
|
|
|
133
178
|
// Text-editing inputs whose caret must not be hijacked by arrow navigation.
|
|
@@ -271,11 +316,12 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
|
|
|
271
316
|
): UseListFocusReturn<T> {
|
|
272
317
|
const {
|
|
273
318
|
itemSelector = '[role="menuitem"]',
|
|
319
|
+
boundarySelector,
|
|
274
320
|
wrap = true,
|
|
275
321
|
onEscape,
|
|
276
322
|
orientation = 'vertical',
|
|
277
323
|
hasHomeEnd = true,
|
|
278
|
-
isRtl
|
|
324
|
+
isRtl,
|
|
279
325
|
hasRovingTabIndex = false,
|
|
280
326
|
hasCaretGuard = false,
|
|
281
327
|
} = options;
|
|
@@ -299,13 +345,43 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
|
|
|
299
345
|
* Get all focusable items in the list.
|
|
300
346
|
*/
|
|
301
347
|
const getItems = useCallback((): HTMLElement[] => {
|
|
302
|
-
|
|
348
|
+
const listEl = listRef.current;
|
|
349
|
+
if (!listEl) {
|
|
303
350
|
return [];
|
|
304
351
|
}
|
|
305
|
-
|
|
306
|
-
|
|
352
|
+
const matched = Array.from(
|
|
353
|
+
listEl.querySelectorAll<HTMLElement>(itemSelector),
|
|
307
354
|
);
|
|
308
|
-
|
|
355
|
+
// When a boundary is set, keep only items that belong to THIS list level —
|
|
356
|
+
// i.e. whose nearest boundary ancestor is our own container. This excludes
|
|
357
|
+
// items inside nested lists (e.g. inline submenu flyouts) that would
|
|
358
|
+
// otherwise be swept in by querySelectorAll.
|
|
359
|
+
if (!boundarySelector) {
|
|
360
|
+
return matched;
|
|
361
|
+
}
|
|
362
|
+
return matched.filter(el => el.closest(boundarySelector) === listEl);
|
|
363
|
+
}, [itemSelector, boundarySelector]);
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Whether a key event belongs to this list level rather than a nested list.
|
|
367
|
+
* With a boundary set, an event that originated inside a nested boundary
|
|
368
|
+
* (e.g. a submenu flyout) has bubbled up to us and must be ignored so we
|
|
369
|
+
* don't double-handle navigation. Without a boundary, every event is ours.
|
|
370
|
+
*/
|
|
371
|
+
const ownsEvent = useCallback(
|
|
372
|
+
(e: React.KeyboardEvent): boolean => {
|
|
373
|
+
const listEl = listRef.current;
|
|
374
|
+
if (!listEl || !boundarySelector) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
const target = e.target as HTMLElement | null;
|
|
378
|
+
if (!target) {
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
return target.closest(boundarySelector) === listEl;
|
|
382
|
+
},
|
|
383
|
+
[boundarySelector],
|
|
384
|
+
);
|
|
309
385
|
|
|
310
386
|
/**
|
|
311
387
|
* Find the next enabled item index from `start`, moving by `step`, optionally
|
|
@@ -473,6 +549,13 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
|
|
|
473
549
|
return;
|
|
474
550
|
}
|
|
475
551
|
|
|
552
|
+
// Ignore events that bubbled up from a nested list (e.g. a submenu
|
|
553
|
+
// flyout, which renders inline inside this container). That level owns
|
|
554
|
+
// and already handled them; re-handling here would move focus twice.
|
|
555
|
+
if (!ownsEvent(e)) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
476
559
|
// Escape is handled regardless of orientation. Preserve the historical
|
|
477
560
|
// behavior of always consuming Escape here (preventDefault) so consumers
|
|
478
561
|
// that relied on it are unaffected.
|
|
@@ -486,11 +569,18 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
|
|
|
486
569
|
const vertical = orientation === 'vertical' || orientation === 'both';
|
|
487
570
|
|
|
488
571
|
// Resolve which keys advance vs retreat, honoring RTL for horizontal.
|
|
572
|
+
// Direction is resolved lazily — getComputedStyle runs only when a
|
|
573
|
+
// horizontal arrow key is actually pressed (SSR-safe, no layout thrash
|
|
574
|
+
// on unrelated keys) — and an explicit `isRtl` always wins.
|
|
489
575
|
const nextKeys: string[] = [];
|
|
490
576
|
const prevKeys: string[] = [];
|
|
491
577
|
if (horizontal) {
|
|
492
|
-
|
|
493
|
-
|
|
578
|
+
const rtl =
|
|
579
|
+
e.key === 'ArrowLeft' || e.key === 'ArrowRight'
|
|
580
|
+
? (isRtl ?? isRtlElement(listRef.current))
|
|
581
|
+
: false;
|
|
582
|
+
nextKeys.push(rtl ? 'ArrowLeft' : 'ArrowRight');
|
|
583
|
+
prevKeys.push(rtl ? 'ArrowRight' : 'ArrowLeft');
|
|
494
584
|
}
|
|
495
585
|
if (vertical) {
|
|
496
586
|
nextKeys.push('ArrowDown');
|
|
@@ -553,6 +643,7 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
|
|
|
553
643
|
focusFirst,
|
|
554
644
|
focusLast,
|
|
555
645
|
onEscape,
|
|
646
|
+
ownsEvent,
|
|
556
647
|
],
|
|
557
648
|
);
|
|
558
649
|
|
|
@@ -563,5 +654,7 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
|
|
|
563
654
|
focusItem,
|
|
564
655
|
focusFirst,
|
|
565
656
|
focusLast,
|
|
657
|
+
ownsEvent,
|
|
658
|
+
getItems,
|
|
566
659
|
};
|
|
567
660
|
}
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* SYNC: When modified, update these files to stay in sync:
|
|
16
16
|
* - /packages/core/src/i18n/InternationalizationProvider.tsx
|
|
17
17
|
* - /packages/core/src/i18n/t.client.ts
|
|
18
|
+
* - /packages/core/src/i18n/useDirection.ts
|
|
19
|
+
* - /packages/core/src/i18n/getLocaleDirection.ts
|
|
18
20
|
* - /packages/core/src/i18n/index.ts
|
|
19
21
|
*/
|
|
20
22
|
|
|
@@ -23,6 +25,7 @@ import type {Locale, MessagesByLocale, Overrides} from './types';
|
|
|
23
25
|
|
|
24
26
|
export interface InternationalizationContextValue {
|
|
25
27
|
locale: Locale;
|
|
28
|
+
direction: 'ltr' | 'rtl';
|
|
26
29
|
messages: MessagesByLocale;
|
|
27
30
|
overrides?: Overrides;
|
|
28
31
|
}
|
|
@@ -34,6 +37,7 @@ export interface InternationalizationContextValue {
|
|
|
34
37
|
export const InternationalizationContext =
|
|
35
38
|
createContext<InternationalizationContextValue>({
|
|
36
39
|
locale: 'en',
|
|
40
|
+
direction: 'ltr',
|
|
37
41
|
messages: {},
|
|
38
42
|
});
|
|
39
43
|
InternationalizationContext.displayName = 'InternationalizationContext';
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import {useMemo, type ReactNode} from 'react';
|
|
23
23
|
import {InternationalizationContext} from './InternationalizationContext';
|
|
24
|
+
import {getLocaleDirection} from './getLocaleDirection';
|
|
24
25
|
import type {Locale, MessagesByLocale, Overrides} from './types';
|
|
25
26
|
|
|
26
27
|
export interface InternationalizationProviderProps {
|
|
@@ -55,6 +56,13 @@ export interface InternationalizationProviderProps {
|
|
|
55
56
|
* ```
|
|
56
57
|
*/
|
|
57
58
|
overrides?: Overrides;
|
|
59
|
+
/**
|
|
60
|
+
* Optional explicit text direction override. When omitted, direction is derived
|
|
61
|
+
* from `locale` via `Intl.Locale.getTextInfo()`. Provide this to force a
|
|
62
|
+
* direction (e.g. RTL layout testing under an English catalog) or to skip the
|
|
63
|
+
* derivation when you already know the direction.
|
|
64
|
+
*/
|
|
65
|
+
dir?: 'ltr' | 'rtl';
|
|
58
66
|
children: ReactNode;
|
|
59
67
|
}
|
|
60
68
|
|
|
@@ -66,11 +74,13 @@ export function InternationalizationProvider({
|
|
|
66
74
|
locale,
|
|
67
75
|
messages,
|
|
68
76
|
overrides,
|
|
77
|
+
dir,
|
|
69
78
|
children,
|
|
70
79
|
}: InternationalizationProviderProps) {
|
|
80
|
+
const direction = dir ?? getLocaleDirection(locale);
|
|
71
81
|
const value = useMemo(
|
|
72
|
-
() => ({locale, messages: messages ?? {}, overrides}),
|
|
73
|
-
[locale, messages, overrides],
|
|
82
|
+
() => ({locale, direction, messages: messages ?? {}, overrides}),
|
|
83
|
+
[locale, direction, messages, overrides],
|
|
74
84
|
);
|
|
75
85
|
return (
|
|
76
86
|
<InternationalizationContext value={value}>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file getLocaleDirection.test.ts
|
|
5
|
+
* @input packages/core/src/i18n/getLocaleDirection.ts
|
|
6
|
+
* @output Unit tests for the server-safe locale → direction helper
|
|
7
|
+
* @position Colocated tests; targets LTR/RTL classification and the
|
|
8
|
+
* try/catch fallback for malformed input.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {describe, expect, test} from 'vitest';
|
|
12
|
+
import {getLocaleDirection} from '../getLocaleDirection';
|
|
13
|
+
|
|
14
|
+
describe('getLocaleDirection', () => {
|
|
15
|
+
test('returns ltr for English', () => {
|
|
16
|
+
expect(getLocaleDirection('en')).toBe('ltr');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('returns rtl for RTL languages', () => {
|
|
20
|
+
expect(getLocaleDirection('ar')).toBe('rtl');
|
|
21
|
+
expect(getLocaleDirection('he')).toBe('rtl');
|
|
22
|
+
expect(getLocaleDirection('fa')).toBe('rtl');
|
|
23
|
+
expect(getLocaleDirection('ur')).toBe('rtl');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('returns ltr for regional LTR tags', () => {
|
|
27
|
+
expect(getLocaleDirection('pt-BR')).toBe('ltr');
|
|
28
|
+
expect(getLocaleDirection('zh-CN')).toBe('ltr');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('falls back to ltr for a malformed locale without throwing', () => {
|
|
32
|
+
expect(() => getLocaleDirection('not_a_locale')).not.toThrow();
|
|
33
|
+
expect(getLocaleDirection('not_a_locale')).toBe('ltr');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('falls back to ltr for an empty string without throwing', () => {
|
|
37
|
+
expect(() => getLocaleDirection('')).not.toThrow();
|
|
38
|
+
expect(getLocaleDirection('')).toBe('ltr');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file useDirection.test.tsx
|
|
5
|
+
* @input packages/core/src/i18n/useDirection.ts, InternationalizationProvider
|
|
6
|
+
* @output Unit tests for the ambient text-direction hook
|
|
7
|
+
* @position Colocated tests; targets the default (no-provider) fallback,
|
|
8
|
+
* locale-derived direction, and the explicit `dir` override.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {ReactNode} from 'react';
|
|
12
|
+
import {describe, expect, test} from 'vitest';
|
|
13
|
+
import {renderHook} from '@testing-library/react';
|
|
14
|
+
import {useDirection} from '../useDirection';
|
|
15
|
+
import {InternationalizationProvider} from '../InternationalizationProvider';
|
|
16
|
+
|
|
17
|
+
describe('useDirection', () => {
|
|
18
|
+
test('returns ltr when rendered without a provider', () => {
|
|
19
|
+
const {result} = renderHook(() => useDirection());
|
|
20
|
+
expect(result.current).toBe('ltr');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('returns ltr under an English provider', () => {
|
|
24
|
+
const {result} = renderHook(() => useDirection(), {
|
|
25
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
26
|
+
<InternationalizationProvider locale="en">
|
|
27
|
+
{children}
|
|
28
|
+
</InternationalizationProvider>
|
|
29
|
+
),
|
|
30
|
+
});
|
|
31
|
+
expect(result.current).toBe('ltr');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('returns rtl under an Arabic provider', () => {
|
|
35
|
+
const {result} = renderHook(() => useDirection(), {
|
|
36
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
37
|
+
<InternationalizationProvider locale="ar">
|
|
38
|
+
{children}
|
|
39
|
+
</InternationalizationProvider>
|
|
40
|
+
),
|
|
41
|
+
});
|
|
42
|
+
expect(result.current).toBe('rtl');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('explicit dir="rtl" wins over an LTR locale', () => {
|
|
46
|
+
const {result} = renderHook(() => useDirection(), {
|
|
47
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
48
|
+
<InternationalizationProvider locale="en" dir="rtl">
|
|
49
|
+
{children}
|
|
50
|
+
</InternationalizationProvider>
|
|
51
|
+
),
|
|
52
|
+
});
|
|
53
|
+
expect(result.current).toBe('rtl');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('explicit dir="ltr" wins over an RTL locale', () => {
|
|
57
|
+
const {result} = renderHook(() => useDirection(), {
|
|
58
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
59
|
+
<InternationalizationProvider locale="ar" dir="ltr">
|
|
60
|
+
{children}
|
|
61
|
+
</InternationalizationProvider>
|
|
62
|
+
),
|
|
63
|
+
});
|
|
64
|
+
expect(result.current).toBe('ltr');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file getLocaleDirection.ts
|
|
5
|
+
* @input BCP 47 locale tag
|
|
6
|
+
* @output 'ltr' | 'rtl'
|
|
7
|
+
* @position Server-safe helper for computing text direction from a locale.
|
|
8
|
+
* Callable in React Server Components / Next.js layouts to set <html dir>.
|
|
9
|
+
*
|
|
10
|
+
* Uses Intl.Locale.getTextInfo() (CLDR-backed) with a try/catch fallback to
|
|
11
|
+
* 'ltr'. React 19 baseline covers all target browsers + Node ≥20; the older
|
|
12
|
+
* accessor form (`.textInfo`) is handled as a fallback for defensive parity.
|
|
13
|
+
*
|
|
14
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
15
|
+
* - /packages/core/src/i18n/InternationalizationProvider.tsx (uses this to derive default)
|
|
16
|
+
* - /packages/core/src/i18n/useDirection.ts (client-side hook counterpart)
|
|
17
|
+
* - /packages/core/src/i18n/index.ts (public export)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export function getLocaleDirection(locale: string): 'ltr' | 'rtl' {
|
|
21
|
+
try {
|
|
22
|
+
const loc = new Intl.Locale(locale);
|
|
23
|
+
// @ts-expect-error — older engines expose accessor form `.textInfo`
|
|
24
|
+
const info = typeof loc.getTextInfo === 'function' ? loc.getTextInfo() : loc.textInfo;
|
|
25
|
+
return info?.direction === 'rtl' ? 'rtl' : 'ltr';
|
|
26
|
+
} catch {
|
|
27
|
+
return 'ltr';
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/i18n/index.ts
CHANGED
|
@@ -10,10 +10,14 @@
|
|
|
10
10
|
* The public API is deliberately small:
|
|
11
11
|
* - InternationalizationProvider — provider component
|
|
12
12
|
* - useTranslator — hook returning a translator function
|
|
13
|
+
* - useDirection — hook returning the ambient text direction
|
|
14
|
+
* - getLocaleDirection — server-safe locale → direction helper
|
|
13
15
|
* - Translator — interface for consumer-injected runtimes
|
|
14
16
|
*
|
|
15
17
|
* SYNC: When modified, update these files to stay in sync:
|
|
16
18
|
* - /packages/core/src/index.ts
|
|
19
|
+
* - /packages/core/src/i18n/useDirection.ts
|
|
20
|
+
* - /packages/core/src/i18n/getLocaleDirection.ts
|
|
17
21
|
*/
|
|
18
22
|
|
|
19
23
|
export {InternationalizationProvider} from './InternationalizationProvider';
|
|
@@ -22,6 +26,8 @@ export {InternationalizationContext} from './InternationalizationContext';
|
|
|
22
26
|
export type {InternationalizationContextValue} from './InternationalizationContext';
|
|
23
27
|
export {useTranslator} from './useTranslator';
|
|
24
28
|
export type {TranslatorFn} from './useTranslator';
|
|
29
|
+
export {useDirection} from './useDirection';
|
|
30
|
+
export {getLocaleDirection} from './getLocaleDirection';
|
|
25
31
|
export type {Translator} from './translator';
|
|
26
32
|
export type {
|
|
27
33
|
Locale,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useDirection.ts
|
|
7
|
+
* @input InternationalizationContext (via use())
|
|
8
|
+
* @output 'ltr' | 'rtl'
|
|
9
|
+
* @position Client hook for reading the ambient text direction. Returns 'ltr'
|
|
10
|
+
* when called outside a provider (matches the silent-en-fallback pattern of
|
|
11
|
+
* useTranslator).
|
|
12
|
+
*
|
|
13
|
+
* Use this in components that need to swap directional icons, mirror
|
|
14
|
+
* behavioral logic (slider math, keyboard nav), or apply direction-specific
|
|
15
|
+
* styling that CSS logical properties can't express.
|
|
16
|
+
*
|
|
17
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
18
|
+
* - /packages/core/src/i18n/InternationalizationContext.ts
|
|
19
|
+
* - /packages/core/src/i18n/getLocaleDirection.ts (server-safe counterpart)
|
|
20
|
+
* - /packages/core/src/i18n/index.ts
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import {use} from 'react';
|
|
24
|
+
import {InternationalizationContext} from './InternationalizationContext';
|
|
25
|
+
|
|
26
|
+
export function useDirection(): 'ltr' | 'rtl' {
|
|
27
|
+
const ctx = use(InternationalizationContext);
|
|
28
|
+
return ctx.direction;
|
|
29
|
+
}
|
package/src/reset.css
CHANGED
|
@@ -360,32 +360,6 @@
|
|
|
360
360
|
-webkit-tap-highlight-color: transparent;
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
-
/* ===========================================================================
|
|
364
|
-
Touch Devices
|
|
365
|
-
=========================================================================== */
|
|
366
|
-
|
|
367
|
-
/*
|
|
368
|
-
* Suppress focus-visible outlines on touch-only devices.
|
|
369
|
-
*
|
|
370
|
-
* Focus rings exist to show keyboard users where focus is. On devices
|
|
371
|
-
* with no hover capability and a coarse pointer (phones, tablets),
|
|
372
|
-
* there's no keyboard navigation — focus rings are visual noise that
|
|
373
|
-
* can flash on tap, on dialog open (showModal auto-focus), or on
|
|
374
|
-
* programmatic focus.
|
|
375
|
-
*
|
|
376
|
-
* This targets touch-only devices specifically:
|
|
377
|
-
* - (hover: none) — no hover capability (not a mouse)
|
|
378
|
-
* - (pointer: coarse) — imprecise pointer (finger, not stylus)
|
|
379
|
-
*
|
|
380
|
-
* Hybrid devices (laptop with touchscreen) are NOT affected because
|
|
381
|
-
* their primary pointer is fine (trackpad/mouse) and hover is available.
|
|
382
|
-
*/
|
|
383
|
-
@media (hover: none) and (pointer: coarse) {
|
|
384
|
-
:where(:focus-visible) {
|
|
385
|
-
outline: none;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
363
|
/* ===========================================================================
|
|
390
364
|
Color Scheme
|
|
391
365
|
=========================================================================== */
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests for reset.css invariants that components rely on.
|
|
5
|
+
*
|
|
6
|
+
* reset.css is a plain stylesheet, so these tests assert on its source
|
|
7
|
+
* text (same approach as the reset.css baseline checks in
|
|
8
|
+
* theme/onMediaTokens.test.ts and AspectRatio/AspectRatio.test.tsx).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
|
|
14
|
+
import {beforeAll, describe, expect, it} from 'vitest';
|
|
15
|
+
|
|
16
|
+
describe('reset.css accessibility invariants', () => {
|
|
17
|
+
let ruleBlocks: {selector: string; body: string}[];
|
|
18
|
+
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
const resetCSS = fs.readFileSync(
|
|
21
|
+
path.resolve(__dirname, './reset.css'),
|
|
22
|
+
'utf-8',
|
|
23
|
+
);
|
|
24
|
+
// Strip comments, then collect every innermost `selector { body }`
|
|
25
|
+
// block. `[^{}]` keeps at-rule preludes (@media, @layer) out of the
|
|
26
|
+
// selector capture, since their own `{` terminates any earlier match.
|
|
27
|
+
const stripped = resetCSS.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
28
|
+
ruleBlocks = [...stripped.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map(
|
|
29
|
+
([, selector, body]) => ({selector: selector.trim(), body}),
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('parses rule blocks out of reset.css', () => {
|
|
34
|
+
// Guards the WCAG assertion below against silently passing if the
|
|
35
|
+
// block parsing ever breaks.
|
|
36
|
+
expect(ruleBlocks.length).toBeGreaterThan(20);
|
|
37
|
+
expect(
|
|
38
|
+
ruleBlocks.some(({body}) => body.includes('box-sizing: border-box')),
|
|
39
|
+
).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* WCAG 2.4.7 (Focus Visible, Level AA): keyboard focus indicators must
|
|
44
|
+
* not be suppressed. `:focus-visible` only matches keyboard-style focus,
|
|
45
|
+
* so any `outline: none` on it removes the focus indicator for keyboard
|
|
46
|
+
* users — and a `@media (hover: none) and (pointer: coarse)` guard does
|
|
47
|
+
* not exempt keyboard users on coarse-pointer devices (iPad/phone with a
|
|
48
|
+
* Bluetooth keyboard, switch-control users).
|
|
49
|
+
*/
|
|
50
|
+
it('does not suppress :focus-visible outlines (WCAG 2.4.7)', () => {
|
|
51
|
+
const focusVisibleBlocks = ruleBlocks.filter(({selector}) =>
|
|
52
|
+
selector.includes(':focus-visible'),
|
|
53
|
+
);
|
|
54
|
+
for (const {selector, body} of focusVisibleBlocks) {
|
|
55
|
+
expect
|
|
56
|
+
.soft(body, `":focus-visible" rule "${selector}" must keep the outline`)
|
|
57
|
+
.not.toMatch(/outline(?:-style|-width)?\s*:\s*(?:none|0)/);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -186,6 +186,7 @@ function discoverComponents(): ComponentInfo[] {
|
|
|
186
186
|
// ---------------------------------------------------------------------------
|
|
187
187
|
|
|
188
188
|
const DIR_TO_REGISTRY_KEY: Record<string, string> = {
|
|
189
|
+
Avatar: 'avatar',
|
|
189
190
|
Banner: 'banner',
|
|
190
191
|
Button: 'button',
|
|
191
192
|
Card: 'card',
|
|
@@ -35,6 +35,12 @@ export interface DerivedVarEntry {
|
|
|
35
35
|
* entries share the same property.
|
|
36
36
|
*/
|
|
37
37
|
export const derivedVarRegistry: Record<string, DerivedVarEntry[]> = {
|
|
38
|
+
avatar: [
|
|
39
|
+
{property: 'fontSize', vars: ['--_avatar-fallback-font-size']},
|
|
40
|
+
{property: 'fontWeight', vars: ['--_avatar-fallback-font-weight']},
|
|
41
|
+
{property: 'color', vars: ['--_avatar-fallback-color']},
|
|
42
|
+
{property: 'backgroundColor', vars: ['--_avatar-fallback-background']},
|
|
43
|
+
],
|
|
38
44
|
banner: [{property: 'borderRadius', vars: ['--_banner-radius']}],
|
|
39
45
|
button: [{property: 'borderRadius', vars: ['--_button-radius']}],
|
|
40
46
|
card: [
|
|
@@ -385,6 +385,48 @@ describe('derived var expansion', () => {
|
|
|
385
385
|
expect(rule).toContain('--_button-radius: 8px');
|
|
386
386
|
});
|
|
387
387
|
|
|
388
|
+
it('emits internal vars for avatar fallback typography (base)', () => {
|
|
389
|
+
const theme = defineTheme({
|
|
390
|
+
name: 'test-derived-avatar',
|
|
391
|
+
components: {
|
|
392
|
+
avatar: {
|
|
393
|
+
base: {
|
|
394
|
+
fontWeight: 'var(--font-weight-normal)',
|
|
395
|
+
color: 'var(--color-text-secondary)',
|
|
396
|
+
backgroundColor: 'var(--color-accent-muted)',
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
const rules = generateThemeRules(theme);
|
|
402
|
+
const rule = rules.find(r => r.includes('.astryx-avatar'));
|
|
403
|
+
expect(rule).toBeDefined();
|
|
404
|
+
expect(rule).toContain(
|
|
405
|
+
'--_avatar-fallback-font-weight: var(--font-weight-normal)',
|
|
406
|
+
);
|
|
407
|
+
expect(rule).toContain(
|
|
408
|
+
'--_avatar-fallback-color: var(--color-text-secondary)',
|
|
409
|
+
);
|
|
410
|
+
expect(rule).toContain(
|
|
411
|
+
'--_avatar-fallback-background: var(--color-accent-muted)',
|
|
412
|
+
);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('emits a per-size fallback font-size for avatar (size:sm)', () => {
|
|
416
|
+
const theme = defineTheme({
|
|
417
|
+
name: 'test-derived-avatar-size',
|
|
418
|
+
components: {
|
|
419
|
+
avatar: {
|
|
420
|
+
'size:sm': {fontSize: '9px'},
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
const rules = generateThemeRules(theme);
|
|
425
|
+
const rule = rules.find(r => r.includes('.astryx-avatar.sm'));
|
|
426
|
+
expect(rule).toBeDefined();
|
|
427
|
+
expect(rule).toContain('--_avatar-fallback-font-size: 9px');
|
|
428
|
+
});
|
|
429
|
+
|
|
388
430
|
it('does not emit derived vars for components without registry entries', () => {
|
|
389
431
|
const theme = defineTheme({
|
|
390
432
|
name: 'test-no-derived',
|
package/src/utils/index.ts
CHANGED
|
@@ -235,19 +235,37 @@ describe('plainDateAddMonths', () => {
|
|
|
235
235
|
});
|
|
236
236
|
});
|
|
237
237
|
|
|
238
|
-
it('
|
|
238
|
+
it('clamps to the last day when the target month is shorter', () => {
|
|
239
|
+
// Jan 31 + 1 month lands in February (clamped), not overflowed into
|
|
240
|
+
// March — matching Temporal.PlainDate.add and date-fns addMonths.
|
|
239
241
|
expect(plainDateAddMonths({year: 2026, month: 1, day: 31}, 1)).toEqual({
|
|
240
242
|
year: 2026,
|
|
241
|
-
month:
|
|
242
|
-
day:
|
|
243
|
+
month: 2,
|
|
244
|
+
day: 28,
|
|
243
245
|
});
|
|
244
246
|
});
|
|
245
247
|
|
|
246
|
-
it('
|
|
248
|
+
it('clamps to Feb 29 in a leap year', () => {
|
|
247
249
|
expect(plainDateAddMonths({year: 2024, month: 1, day: 31}, 1)).toEqual({
|
|
248
250
|
year: 2024,
|
|
249
|
-
month:
|
|
250
|
-
day:
|
|
251
|
+
month: 2,
|
|
252
|
+
day: 29,
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('clamps when subtracting into a shorter month', () => {
|
|
257
|
+
expect(plainDateAddMonths({year: 2026, month: 3, day: 31}, -1)).toEqual({
|
|
258
|
+
year: 2026,
|
|
259
|
+
month: 2,
|
|
260
|
+
day: 28,
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('clamps a 31st onto a 30-day month', () => {
|
|
265
|
+
expect(plainDateAddMonths({year: 2026, month: 5, day: 31}, 1)).toEqual({
|
|
266
|
+
year: 2026,
|
|
267
|
+
month: 6,
|
|
268
|
+
day: 30,
|
|
251
269
|
});
|
|
252
270
|
});
|
|
253
271
|
});
|