@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
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file tooltipEntries.test.ts
|
|
5
|
+
* @input formatTooltipLines and its entry types
|
|
6
|
+
* @output Coverage for zone/format/label resolution of Timestamp tooltip lines
|
|
7
|
+
* @position Unit tests for the pure tooltip-line formatter
|
|
8
|
+
*
|
|
9
|
+
* These tests must pass under ANY machine timezone. The repo pins TZ neither in
|
|
10
|
+
* vitest.config.ts nor in CI, so a developer machine (e.g. America/New_York) and
|
|
11
|
+
* CI (typically UTC) run the same assertions. Every exact-string expectation
|
|
12
|
+
* therefore names an explicit zone and uses a `system_*` format, which is built
|
|
13
|
+
* from numeric parts and is both timezone- and locale-independent. Assertions
|
|
14
|
+
* about locale-formatted output stay structural.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {describe, it, expect, vi} from 'vitest';
|
|
18
|
+
import {SHARED_DATE_FORMAT_OPTIONS} from '../utils/plainDate';
|
|
19
|
+
import {formatTooltipLines} from './tooltipEntries';
|
|
20
|
+
|
|
21
|
+
/** 2026-02-19T17:00:00Z — chosen so several zones land on different dates. */
|
|
22
|
+
const INSTANT = new Date('2026-02-19T17:00:00Z');
|
|
23
|
+
|
|
24
|
+
const LOCAL_ZONE = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
25
|
+
|
|
26
|
+
describe('formatTooltipLines', () => {
|
|
27
|
+
// --- Explicit zones: exact, locale-free, TZ-agnostic ---
|
|
28
|
+
|
|
29
|
+
it('renders system_date_time in an explicit zone', () => {
|
|
30
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
31
|
+
{timezoneID: 'UTC', format: 'system_date_time'},
|
|
32
|
+
]);
|
|
33
|
+
expect(lines).toHaveLength(1);
|
|
34
|
+
expect(lines[0].value).toBe('2026-02-19 17:00:00');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('crosses the date line for a zone that is a day ahead', () => {
|
|
38
|
+
// 17:00Z is 02:00 the NEXT day in Tokyo. A naive local-getter
|
|
39
|
+
// implementation would return the viewer's date here.
|
|
40
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
41
|
+
{timezoneID: 'Asia/Tokyo', format: 'system_date_time'},
|
|
42
|
+
]);
|
|
43
|
+
expect(lines[0].value).toBe('2026-02-20 02:00:00');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('handles a half-hour offset zone', () => {
|
|
47
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
48
|
+
{timezoneID: 'Asia/Kolkata', format: 'system_date_time'},
|
|
49
|
+
]);
|
|
50
|
+
expect(lines[0].value).toBe('2026-02-19 22:30:00');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('renders system_date and system_time in an explicit zone', () => {
|
|
54
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
55
|
+
{timezoneID: 'America/Los_Angeles', format: 'system_date'},
|
|
56
|
+
{timezoneID: 'America/Los_Angeles', format: 'system_time'},
|
|
57
|
+
]);
|
|
58
|
+
expect(lines[0].value).toBe('2026-02-19');
|
|
59
|
+
expect(lines[1].value).toBe('09:00:00');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('honors the zone across a DST transition rather than a fixed offset', () => {
|
|
63
|
+
// Los Angeles is UTC-8 in January and UTC-7 in July. A fixed-offset
|
|
64
|
+
// implementation would get one of these wrong.
|
|
65
|
+
const winter = formatTooltipLines(new Date('2026-01-15T20:00:00Z'), [
|
|
66
|
+
{timezoneID: 'America/Los_Angeles', format: 'system_date_time'},
|
|
67
|
+
]);
|
|
68
|
+
const summer = formatTooltipLines(new Date('2026-07-15T20:00:00Z'), [
|
|
69
|
+
{timezoneID: 'America/Los_Angeles', format: 'system_date_time'},
|
|
70
|
+
]);
|
|
71
|
+
expect(winter[0].value).toBe('2026-01-15 12:00:00');
|
|
72
|
+
expect(summer[0].value).toBe('2026-07-15 13:00:00');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// --- The shared date vocabulary ---
|
|
76
|
+
|
|
77
|
+
it.each(['date', 'date_long', 'date_weekday'] as const)(
|
|
78
|
+
'renders %s from the shared option bag, in the requested zone',
|
|
79
|
+
format => {
|
|
80
|
+
// These three members are owned by SHARED_DATE_FORMAT_OPTIONS so that
|
|
81
|
+
// Timestamp's rendered text and DateInput's display path cannot drift.
|
|
82
|
+
// A tooltip line must come from the same bag: rebuilding the expected
|
|
83
|
+
// string from the shared constant here fails the moment this file
|
|
84
|
+
// inlines its own options again. Locale-agnostic — both sides format
|
|
85
|
+
// under whatever locale the machine has.
|
|
86
|
+
for (const timezoneID of ['UTC', 'Asia/Tokyo']) {
|
|
87
|
+
const [line] = formatTooltipLines(INSTANT, [{timezoneID, format}]);
|
|
88
|
+
expect(line.value).toBe(
|
|
89
|
+
new Intl.DateTimeFormat(undefined, {
|
|
90
|
+
...SHARED_DATE_FORMAT_OPTIONS[format],
|
|
91
|
+
timeZone: timezoneID,
|
|
92
|
+
}).format(INSTANT),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 17:00Z is already the next calendar day in Tokyo, so the two zones
|
|
97
|
+
// must disagree — proof the zone reached Intl rather than being dropped.
|
|
98
|
+
const [utc] = formatTooltipLines(INSTANT, [{timezoneID: 'UTC', format}]);
|
|
99
|
+
const [tokyo] = formatTooltipLines(INSTANT, [
|
|
100
|
+
{timezoneID: 'Asia/Tokyo', format},
|
|
101
|
+
]);
|
|
102
|
+
expect(tokyo.value).not.toBe(utc.value);
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// --- Equivalence with the untouched local implementation ---
|
|
107
|
+
|
|
108
|
+
it('matches the local-getter output character-for-character for the viewer zone', () => {
|
|
109
|
+
// The existing formatTimestamp() builds system_* from date.getFullYear()/
|
|
110
|
+
// getHours()/... Naming the viewer's own zone explicitly must produce the
|
|
111
|
+
// identical string, or the two code paths have drifted.
|
|
112
|
+
const pad = (n: number) => String(n).padStart(2, '0');
|
|
113
|
+
const expected = `${INSTANT.getFullYear()}-${pad(INSTANT.getMonth() + 1)}-${pad(
|
|
114
|
+
INSTANT.getDate(),
|
|
115
|
+
)} ${pad(INSTANT.getHours())}:${pad(INSTANT.getMinutes())}:${pad(
|
|
116
|
+
INSTANT.getSeconds(),
|
|
117
|
+
)}`;
|
|
118
|
+
|
|
119
|
+
const explicit = formatTooltipLines(INSTANT, [
|
|
120
|
+
{timezoneID: LOCAL_ZONE, format: 'system_date_time'},
|
|
121
|
+
]);
|
|
122
|
+
const implicit = formatTooltipLines(INSTANT, [
|
|
123
|
+
{format: 'system_date_time'},
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
expect(explicit[0].value).toBe(expected);
|
|
127
|
+
expect(implicit[0].value).toBe(expected);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// --- The 'local' sentinel ---
|
|
131
|
+
|
|
132
|
+
it("treats timezoneID 'local' as an alias for the viewer's zone", () => {
|
|
133
|
+
const omitted = formatTooltipLines(INSTANT, [{format: 'system_date_time'}]);
|
|
134
|
+
const sentinel = formatTooltipLines(INSTANT, [
|
|
135
|
+
{timezoneID: 'local', format: 'system_date_time'},
|
|
136
|
+
]);
|
|
137
|
+
expect(sentinel[0].value).toBe(omitted[0].value);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("does not treat 'local' as an IANA id to be looked up", () => {
|
|
141
|
+
// 'local' is not a real zone; it must never reach Intl, which would throw.
|
|
142
|
+
expect(() =>
|
|
143
|
+
formatTooltipLines(INSTANT, [{timezoneID: 'local'}]),
|
|
144
|
+
).not.toThrow();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// --- Defaults ---
|
|
148
|
+
|
|
149
|
+
it("defaults an entry's format to the full absolute style", () => {
|
|
150
|
+
const [line] = formatTooltipLines(INSTANT, [{timezoneID: 'UTC'}]);
|
|
151
|
+
// The full style carries date, time-with-seconds and a zone name.
|
|
152
|
+
expect(line.value).toContain('2026');
|
|
153
|
+
expect(line.value).toMatch(/\d{1,2}:00:00/);
|
|
154
|
+
expect(line.value).toContain('UTC');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('defaults an entry to the viewer zone when no timezoneID is given', () => {
|
|
158
|
+
const [line] = formatTooltipLines(INSTANT, [{}]);
|
|
159
|
+
expect(line.value).toContain('2026');
|
|
160
|
+
expect(line.label).toBeUndefined();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// --- Labels ---
|
|
164
|
+
|
|
165
|
+
it('passes a consumer label through verbatim', () => {
|
|
166
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
167
|
+
{label: 'Your time'},
|
|
168
|
+
{timezoneID: 'UTC', label: 'UTC'},
|
|
169
|
+
]);
|
|
170
|
+
expect(lines[0].label).toBe('Your time');
|
|
171
|
+
expect(lines[1].label).toBe('UTC');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('never invents a label when none is supplied', () => {
|
|
175
|
+
const lines = formatTooltipLines(INSTANT, [{}, {timezoneID: 'Asia/Tokyo'}]);
|
|
176
|
+
expect(lines[0].label).toBeUndefined();
|
|
177
|
+
expect(lines[1].label).toBeUndefined();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// --- Auto zone abbreviation (the disambiguation rule) ---
|
|
181
|
+
|
|
182
|
+
it('appends a zone abbreviation to date_time lines when zones differ', () => {
|
|
183
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
184
|
+
{format: 'date_time'},
|
|
185
|
+
{timezoneID: 'UTC', format: 'date_time'},
|
|
186
|
+
]);
|
|
187
|
+
// Two distinct zones were asked for, so each line must say which it is.
|
|
188
|
+
expect(lines[1].value).toContain('UTC');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('marks even the unnamed local line once a second zone is on screen', () => {
|
|
192
|
+
// This is the case only the multiple-zones half of the rule can reach: the
|
|
193
|
+
// first entry names nothing, so without that clause it would sit unmarked
|
|
194
|
+
// beside a marked foreign line and read as though it were the foreign one.
|
|
195
|
+
const alone = formatTooltipLines(INSTANT, [{format: 'date_time'}]);
|
|
196
|
+
const beside = formatTooltipLines(INSTANT, [
|
|
197
|
+
{format: 'date_time'},
|
|
198
|
+
{timezoneID: 'UTC', format: 'date_time'},
|
|
199
|
+
]);
|
|
200
|
+
expect(beside[0].value).not.toBe(alone[0].value);
|
|
201
|
+
expect(beside[0].value.startsWith(alone[0].value)).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('omits the zone abbreviation when one unnamed zone is shown twice', () => {
|
|
205
|
+
// Same viewer zone rendered in two formats, neither of them steered by the
|
|
206
|
+
// consumer — a zone marker here would be noise.
|
|
207
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
208
|
+
{format: 'date_time'},
|
|
209
|
+
{format: 'system_date_time'},
|
|
210
|
+
]);
|
|
211
|
+
const localName = new Intl.DateTimeFormat(undefined, {
|
|
212
|
+
timeZoneName: 'short',
|
|
213
|
+
})
|
|
214
|
+
.formatToParts(INSTANT)
|
|
215
|
+
.find(part => part.type === 'timeZoneName')?.value;
|
|
216
|
+
expect(lines[0].value).not.toContain(localName);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('never appends a zone abbreviation to system_* lines', () => {
|
|
220
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
221
|
+
{format: 'system_date_time'},
|
|
222
|
+
{timezoneID: 'Asia/Tokyo', format: 'system_date_time'},
|
|
223
|
+
]);
|
|
224
|
+
// Two distinct zones, but system_* output stays machine-clean.
|
|
225
|
+
expect(lines[1].value).toBe('2026-02-20 02:00:00');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('marks a single explicitly-named zone so it cannot be read as local', () => {
|
|
229
|
+
// The tooltip is never read in isolation: it is the <time>'s
|
|
230
|
+
// aria-describedby, and that element's accessible name is the LOCAL
|
|
231
|
+
// absolute time. A lone foreign-zone line with no marker is therefore
|
|
232
|
+
// announced right after a local time, with nothing to say it is not one.
|
|
233
|
+
const zoneName = new Intl.DateTimeFormat(undefined, {
|
|
234
|
+
timeZone: 'Asia/Tokyo',
|
|
235
|
+
timeZoneName: 'short',
|
|
236
|
+
})
|
|
237
|
+
.formatToParts(INSTANT)
|
|
238
|
+
.find(part => part.type === 'timeZoneName')?.value;
|
|
239
|
+
|
|
240
|
+
const [line] = formatTooltipLines(INSTANT, [
|
|
241
|
+
{timezoneID: 'Asia/Tokyo', format: 'date_time'},
|
|
242
|
+
]);
|
|
243
|
+
expect(zoneName).toBeTruthy();
|
|
244
|
+
expect(line.value).toContain(zoneName);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('leaves an unnamed local line unmarked', () => {
|
|
248
|
+
// The consumer never named a zone, so there is nothing to disambiguate
|
|
249
|
+
// against — this must stay as bare as the visible text is by default.
|
|
250
|
+
const [line] = formatTooltipLines(INSTANT, [{format: 'date_time'}]);
|
|
251
|
+
const localName = new Intl.DateTimeFormat(undefined, {
|
|
252
|
+
timeZoneName: 'short',
|
|
253
|
+
})
|
|
254
|
+
.formatToParts(INSTANT)
|
|
255
|
+
.find(part => part.type === 'timeZoneName')?.value;
|
|
256
|
+
expect(line.value).not.toContain(localName);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('always carries a zone name on the full style, even for a single line', () => {
|
|
260
|
+
const [line] = formatTooltipLines(INSTANT, [{timezoneID: 'UTC'}]);
|
|
261
|
+
expect(line.value).toContain('UTC');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// --- Invalid zone handling ---
|
|
265
|
+
|
|
266
|
+
it('falls back to the viewer zone instead of throwing on an invalid zone id', () => {
|
|
267
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
268
|
+
try {
|
|
269
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
270
|
+
{timezoneID: 'Not/AZone', format: 'system_date_time'},
|
|
271
|
+
]);
|
|
272
|
+
const local = formatTooltipLines(INSTANT, [{format: 'system_date_time'}]);
|
|
273
|
+
expect(lines[0].value).toBe(local[0].value);
|
|
274
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining('Not/AZone'));
|
|
275
|
+
} finally {
|
|
276
|
+
warn.mockRestore();
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('warns once per unknown zone, however many times it is used', () => {
|
|
281
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
282
|
+
try {
|
|
283
|
+
// The same bad id twice in one call, then again on a re-render. A live
|
|
284
|
+
// timestamp re-renders every second, so one typo must not turn into an
|
|
285
|
+
// unbounded stream of identical console warnings.
|
|
286
|
+
formatTooltipLines(INSTANT, [
|
|
287
|
+
{timezoneID: 'Bad/RepeatedZone'},
|
|
288
|
+
{timezoneID: 'Bad/RepeatedZone'},
|
|
289
|
+
]);
|
|
290
|
+
formatTooltipLines(INSTANT, [{timezoneID: 'Bad/RepeatedZone'}]);
|
|
291
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
292
|
+
} finally {
|
|
293
|
+
warn.mockRestore();
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('falls back for an empty or whitespace-only zone id', () => {
|
|
298
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
299
|
+
try {
|
|
300
|
+
const local = formatTooltipLines(INSTANT, [{format: 'system_date_time'}]);
|
|
301
|
+
// Intl rejects both of these, so they must take the same degraded path
|
|
302
|
+
// as any other unknown identifier rather than reaching the formatter.
|
|
303
|
+
for (const timezoneID of ['', ' ']) {
|
|
304
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
305
|
+
{timezoneID, format: 'system_date_time'},
|
|
306
|
+
]);
|
|
307
|
+
expect(lines[0].value).toBe(local[0].value);
|
|
308
|
+
}
|
|
309
|
+
expect(warn).toHaveBeenCalledTimes(2);
|
|
310
|
+
} finally {
|
|
311
|
+
warn.mockRestore();
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('rejects a zone id padded with whitespace rather than trimming it', () => {
|
|
316
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
317
|
+
try {
|
|
318
|
+
const local = formatTooltipLines(INSTANT, [{format: 'system_date_time'}]);
|
|
319
|
+
// ' UTC ' is not a valid identifier. Silently trimming would be a guess
|
|
320
|
+
// about intent; degrading to the viewer's zone with a warning is not.
|
|
321
|
+
const padded = formatTooltipLines(INSTANT, [
|
|
322
|
+
{timezoneID: ' UTC ', format: 'system_date_time'},
|
|
323
|
+
]);
|
|
324
|
+
expect(padded[0].value).toBe(local[0].value);
|
|
325
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining(' UTC '));
|
|
326
|
+
} finally {
|
|
327
|
+
warn.mockRestore();
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('accepts a legacy link id and resolves it to the modern zone', () => {
|
|
332
|
+
// Intl accepts the old US/* names; they must not be mistaken for junk.
|
|
333
|
+
const legacy = formatTooltipLines(INSTANT, [
|
|
334
|
+
{timezoneID: 'US/Pacific', format: 'system_date_time'},
|
|
335
|
+
]);
|
|
336
|
+
expect(legacy[0].value).toBe('2026-02-19 09:00:00');
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('treats a fixed-offset id like EST as fixed, not as US Eastern', () => {
|
|
340
|
+
// Intl ACCEPTS 'EST', so no warning fires — but it is a fixed UTC-5 zone
|
|
341
|
+
// that never observes daylight saving. A consumer who writes 'EST' meaning
|
|
342
|
+
// "US Eastern" is silently an hour out for half the year. Pinned here so
|
|
343
|
+
// the trap is visible in the tests, and steered against in the docs.
|
|
344
|
+
const summer = new Date('2026-07-19T17:00:00Z');
|
|
345
|
+
const fixed = formatTooltipLines(summer, [
|
|
346
|
+
{timezoneID: 'EST', format: 'system_date_time'},
|
|
347
|
+
]);
|
|
348
|
+
const region = formatTooltipLines(summer, [
|
|
349
|
+
{timezoneID: 'America/New_York', format: 'system_date_time'},
|
|
350
|
+
]);
|
|
351
|
+
expect(fixed[0].value).toBe('2026-07-19 12:00:00');
|
|
352
|
+
expect(region[0].value).toBe('2026-07-19 13:00:00');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('keeps the remaining lines intact when one entry has a bad zone', () => {
|
|
356
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
357
|
+
try {
|
|
358
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
359
|
+
{timezoneID: 'Not/AZone', format: 'system_date_time'},
|
|
360
|
+
{timezoneID: 'UTC', format: 'system_date_time'},
|
|
361
|
+
]);
|
|
362
|
+
expect(lines).toHaveLength(2);
|
|
363
|
+
expect(lines[1].value).toBe('2026-02-19 17:00:00');
|
|
364
|
+
} finally {
|
|
365
|
+
warn.mockRestore();
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// --- Wall-clock edges ---
|
|
370
|
+
|
|
371
|
+
it('renders midnight as 00, never 24', () => {
|
|
372
|
+
// The parts are read under hourCycle 'h23'. Under 'h24' the same instant
|
|
373
|
+
// comes back as hour 24 and every midnight timestamp renders wrong.
|
|
374
|
+
const lines = formatTooltipLines(new Date('2026-02-19T00:00:00Z'), [
|
|
375
|
+
{timezoneID: 'UTC', format: 'system_date_time'},
|
|
376
|
+
]);
|
|
377
|
+
expect(lines[0].value).toBe('2026-02-19 00:00:00');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('rolls midnight into the next day for a zone that is ahead', () => {
|
|
381
|
+
// 15:00Z is exactly 00:00 the following day in Tokyo — midnight and a
|
|
382
|
+
// date rollover in the same assertion.
|
|
383
|
+
const lines = formatTooltipLines(new Date('2026-02-19T15:00:00Z'), [
|
|
384
|
+
{timezoneID: 'Asia/Tokyo', format: 'system_date_time'},
|
|
385
|
+
]);
|
|
386
|
+
expect(lines[0].value).toBe('2026-02-20 00:00:00');
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('handles a 45-minute offset zone', () => {
|
|
390
|
+
// Nepal is UTC+5:45. Anything that assumes whole- or half-hour offsets
|
|
391
|
+
// gets this wrong.
|
|
392
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
393
|
+
{timezoneID: 'Asia/Kathmandu', format: 'system_date_time'},
|
|
394
|
+
]);
|
|
395
|
+
expect(lines[0].value).toBe('2026-02-19 22:45:00');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('spans the full range either side of the date line', () => {
|
|
399
|
+
// +14 and -11 are the extremes in use; the same instant is 25 hours and
|
|
400
|
+
// two calendar days apart between them.
|
|
401
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
402
|
+
{timezoneID: 'Pacific/Kiritimati', format: 'system_date_time'},
|
|
403
|
+
{timezoneID: 'Pacific/Niue', format: 'system_date_time'},
|
|
404
|
+
]);
|
|
405
|
+
expect(lines.map(l => l.value)).toEqual([
|
|
406
|
+
'2026-02-20 07:00:00',
|
|
407
|
+
'2026-02-19 06:00:00',
|
|
408
|
+
]);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('carries the year across a new-year boundary', () => {
|
|
412
|
+
const lines = formatTooltipLines(new Date('2025-12-31T23:30:00Z'), [
|
|
413
|
+
{timezoneID: 'Asia/Tokyo', format: 'system_date_time'},
|
|
414
|
+
]);
|
|
415
|
+
expect(lines[0].value).toBe('2026-01-01 08:30:00');
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('renders a leap day', () => {
|
|
419
|
+
const lines = formatTooltipLines(new Date('2028-02-29T12:00:00Z'), [
|
|
420
|
+
{timezoneID: 'UTC', format: 'system_date'},
|
|
421
|
+
]);
|
|
422
|
+
expect(lines[0].value).toBe('2028-02-29');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('renders both halves of a repeated DST hour identically', () => {
|
|
426
|
+
// On the fall-back date, 01:30 local happens twice. Mapping an instant to
|
|
427
|
+
// a wall clock is many-to-one, so both instants legitimately render the
|
|
428
|
+
// same machine string — which is exactly why system_* output is not
|
|
429
|
+
// sufficient on its own to identify an instant. Pinned so nobody "fixes"
|
|
430
|
+
// it into a fake distinction.
|
|
431
|
+
const beforeShift = formatTooltipLines(new Date('2026-11-01T05:30:00Z'), [
|
|
432
|
+
{timezoneID: 'America/New_York', format: 'system_date_time'},
|
|
433
|
+
]);
|
|
434
|
+
const afterShift = formatTooltipLines(new Date('2026-11-01T06:30:00Z'), [
|
|
435
|
+
{timezoneID: 'America/New_York', format: 'system_date_time'},
|
|
436
|
+
]);
|
|
437
|
+
expect(beforeShift[0].value).toBe('2026-11-01 01:30:00');
|
|
438
|
+
expect(afterShift[0].value).toBe('2026-11-01 01:30:00');
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// --- Distinct-zone counting drives the abbreviation rule ---
|
|
442
|
+
|
|
443
|
+
it('counts a zone that fell back to local as the local zone', () => {
|
|
444
|
+
// The bad entry degrades to the viewer's zone, so the tooltip is really
|
|
445
|
+
// showing ONE zone and must not start labelling lines as if it showed two.
|
|
446
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
447
|
+
try {
|
|
448
|
+
const single = formatTooltipLines(INSTANT, [{format: 'date_time'}]);
|
|
449
|
+
const withBad = formatTooltipLines(INSTANT, [
|
|
450
|
+
{format: 'date_time'},
|
|
451
|
+
{timezoneID: 'Bad/CountingZone', format: 'date_time'},
|
|
452
|
+
]);
|
|
453
|
+
expect(withBad[0].value).toBe(single[0].value);
|
|
454
|
+
expect(withBad[1].value).toBe(single[0].value);
|
|
455
|
+
} finally {
|
|
456
|
+
warn.mockRestore();
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('renders two alias spellings of one zone identically to a single one', () => {
|
|
461
|
+
// 'UTC' and 'GMT' are aliases, so the distinctness key counts them as two
|
|
462
|
+
// zones. That can never show, because a line whose zone the consumer named
|
|
463
|
+
// is marked whether or not a second zone is present — all three renderings
|
|
464
|
+
// below must agree. If the marker rule ever narrows back to "more than one
|
|
465
|
+
// zone", this pair would start disagreeing with the single entry and read
|
|
466
|
+
// as two lines that need telling apart while being character-identical.
|
|
467
|
+
const pair = formatTooltipLines(INSTANT, [
|
|
468
|
+
{timezoneID: 'UTC', format: 'date_time'},
|
|
469
|
+
{timezoneID: 'GMT', format: 'date_time'},
|
|
470
|
+
]);
|
|
471
|
+
const solo = formatTooltipLines(INSTANT, [
|
|
472
|
+
{timezoneID: 'UTC', format: 'date_time'},
|
|
473
|
+
]);
|
|
474
|
+
expect(pair[0].value).toBe(solo[0].value);
|
|
475
|
+
expect(pair[1].value).toBe(solo[0].value);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('renders the same zone spelled in different casing identically', () => {
|
|
479
|
+
// Zone ids are case-insensitive per ECMA-402, and the distinctness key
|
|
480
|
+
// lowercases to match, so neither line can drift from the other.
|
|
481
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
482
|
+
{timezoneID: 'UTC', format: 'date_time'},
|
|
483
|
+
{timezoneID: 'utc', format: 'date_time'},
|
|
484
|
+
]);
|
|
485
|
+
expect(lines[0].value).toBe(lines[1].value);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it("counts an omitted zone and 'local' as one zone", () => {
|
|
489
|
+
const single = formatTooltipLines(INSTANT, [{format: 'date_time'}]);
|
|
490
|
+
const both = formatTooltipLines(INSTANT, [
|
|
491
|
+
{format: 'date_time'},
|
|
492
|
+
{timezoneID: 'local', format: 'date_time'},
|
|
493
|
+
]);
|
|
494
|
+
expect(both[0].value).toBe(single[0].value);
|
|
495
|
+
expect(both[1].value).toBe(single[0].value);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// --- Entry shape ---
|
|
499
|
+
|
|
500
|
+
it('treats an explicitly undefined format as absent', () => {
|
|
501
|
+
const explicit = formatTooltipLines(INSTANT, [
|
|
502
|
+
{timezoneID: 'UTC', format: undefined},
|
|
503
|
+
]);
|
|
504
|
+
const omitted = formatTooltipLines(INSTANT, [{timezoneID: 'UTC'}]);
|
|
505
|
+
expect(explicit[0].value).toBe(omitted[0].value);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('keeps an empty-string label rather than dropping it', () => {
|
|
509
|
+
// '' is a deliberate "this line has no label" that still occupies the
|
|
510
|
+
// label column; only an omitted label is truly absent.
|
|
511
|
+
const [line] = formatTooltipLines(INSTANT, [{label: ''}]);
|
|
512
|
+
expect(line.label).toBe('');
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// --- Ordering and arity ---
|
|
516
|
+
|
|
517
|
+
it('preserves entry order', () => {
|
|
518
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
519
|
+
{timezoneID: 'Asia/Tokyo', format: 'system_date', label: 'Tokyo'},
|
|
520
|
+
{timezoneID: 'UTC', format: 'system_date', label: 'UTC'},
|
|
521
|
+
{timezoneID: 'America/Los_Angeles', format: 'system_date', label: 'LA'},
|
|
522
|
+
]);
|
|
523
|
+
expect(lines.map(l => l.label)).toEqual(['Tokyo', 'UTC', 'LA']);
|
|
524
|
+
expect(lines.map(l => l.value)).toEqual([
|
|
525
|
+
'2026-02-20',
|
|
526
|
+
'2026-02-19',
|
|
527
|
+
'2026-02-19',
|
|
528
|
+
]);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('returns one line per entry, including duplicates', () => {
|
|
532
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
533
|
+
{timezoneID: 'UTC'},
|
|
534
|
+
{timezoneID: 'UTC'},
|
|
535
|
+
]);
|
|
536
|
+
expect(lines).toHaveLength(2);
|
|
537
|
+
expect(lines[0].value).toBe(lines[1].value);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('returns an empty array for no entries', () => {
|
|
541
|
+
expect(formatTooltipLines(INSTANT, [])).toEqual([]);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// --- Zone ids are case-insensitive per ECMA-402 ---
|
|
545
|
+
|
|
546
|
+
it('accepts a lowercase IANA id', () => {
|
|
547
|
+
const lines = formatTooltipLines(INSTANT, [
|
|
548
|
+
{timezoneID: 'asia/tokyo', format: 'system_date_time'},
|
|
549
|
+
]);
|
|
550
|
+
expect(lines[0].value).toBe('2026-02-20 02:00:00');
|
|
551
|
+
});
|
|
552
|
+
});
|