@astryxdesign/core 0.4.7 → 0.5.0-canary.32a62fd
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 +161 -1
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +3 -2
- package/dist/AspectRatio/AspectRatio.d.ts +24 -0
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +23 -4
- package/dist/Avatar/Avatar.d.ts +25 -1
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +58 -19
- package/dist/Avatar/index.d.ts +2 -2
- package/dist/Avatar/index.d.ts.map +1 -1
- package/dist/Avatar/index.js +1 -1
- package/dist/AvatarGroup/AvatarGroup.d.ts +9 -2
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.js +8 -4
- package/dist/AvatarGroup/AvatarGroupContext.d.ts +2 -1
- package/dist/AvatarGroup/AvatarGroupContext.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +10 -6
- package/dist/Banner/Banner.d.ts +40 -15
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +51 -18
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +17 -4
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +3 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +11 -10
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +5 -4
- package/dist/ButtonGroup/ButtonGroup.d.ts +5 -1
- package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
- package/dist/ButtonGroup/ButtonGroup.js +26 -7
- package/dist/Calendar/Calendar.d.ts +3 -1
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +13 -14
- package/dist/Calendar/getInitialFocusDate.d.ts +46 -0
- package/dist/Calendar/getInitialFocusDate.d.ts.map +1 -0
- package/dist/Calendar/getInitialFocusDate.js +62 -0
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +3 -3
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +2 -3
- package/dist/Chat/ChatLayout.d.ts.map +1 -1
- package/dist/Chat/ChatLayout.js +3 -3
- package/dist/Chat/ChatMessageList.d.ts +22 -3
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +6 -3
- package/dist/Chat/ChatTokenizedText.d.ts +2 -0
- package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
- package/dist/Chat/ChatTokenizedText.js +11 -6
- package/dist/Chat/useChatComposerTokens.js +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +10 -3
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +3 -3
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +15 -4
- package/dist/CodeBlock/highlightRanges.js +1 -1
- package/dist/CodeBlock/highlightStyles.js +13 -13
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +3 -2
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +3 -2
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +4 -3
- package/dist/DateInput/DateInput.d.ts +43 -0
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +22 -2
- package/dist/DateInput/NativeDateField.d.ts +10 -0
- package/dist/DateInput/NativeDateField.d.ts.map +1 -0
- package/dist/DateInput/NativeDateField.js +477 -0
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +38 -4
- package/dist/DateInput/index.d.ts +1 -1
- package/dist/DateInput/index.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts +21 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +406 -12
- package/dist/DateTimeInput/index.d.ts +1 -1
- package/dist/DateTimeInput/index.d.ts.map +1 -1
- package/dist/Dialog/Dialog.d.ts +7 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +89 -58
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +6 -5
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +9 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +3 -2
- package/dist/Heading/Heading.d.ts.map +1 -1
- package/dist/Heading/Heading.js +6 -2
- package/dist/HoverCard/HoverCard.d.ts +6 -0
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +52 -3
- package/dist/HoverCard/useHoverCard.d.ts +16 -2
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +74 -26
- package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
- package/dist/Indicator/CheckboxIndicator.js +6 -2
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +4 -2
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +2 -19
- package/dist/Layer/LayerDepthContext.d.ts +52 -0
- package/dist/Layer/LayerDepthContext.d.ts.map +1 -0
- package/dist/Layer/LayerDepthContext.js +66 -0
- package/dist/Layer/index.d.ts +3 -0
- package/dist/Layer/index.d.ts.map +1 -1
- package/dist/Layer/index.js +4 -0
- package/dist/Layer/layerStack.d.ts +98 -0
- package/dist/Layer/layerStack.d.ts.map +1 -0
- package/dist/Layer/layerStack.js +306 -0
- package/dist/Layer/useLayerDismissal.d.ts +93 -0
- package/dist/Layer/useLayerDismissal.d.ts.map +1 -0
- package/dist/Layer/useLayerDismissal.js +91 -0
- package/dist/Lightbox/Lightbox.d.ts +1 -0
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +103 -81
- package/dist/Markdown/index.d.ts +1 -1
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/parser.d.ts +38 -1
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +177 -29
- package/dist/MobileNav/MobileNav.d.ts +6 -1
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +61 -36
- package/dist/MultiSelector/MultiSelector.d.ts +15 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +17 -6
- package/dist/MultiSelector/index.d.ts +1 -1
- package/dist/MultiSelector/index.d.ts.map +1 -1
- package/dist/NavIcon/NavIcon.d.ts.map +1 -1
- package/dist/NavIcon/NavIcon.js +5 -1
- package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
- package/dist/NavMenu/NavHeadingMenu.js +3 -2
- package/dist/NumberInput/NumberInput.d.ts +1 -0
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +27 -37
- package/dist/NumberInput/numberParser.d.ts +43 -0
- package/dist/NumberInput/numberParser.d.ts.map +1 -0
- package/dist/NumberInput/numberParser.js +293 -0
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +3 -2
- package/dist/OverflowList/OverflowList.d.ts.map +1 -1
- package/dist/OverflowList/OverflowList.js +3 -2
- package/dist/Overlay/Overlay.d.ts.map +1 -1
- package/dist/Overlay/Overlay.js +3 -2
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +20 -17
- package/dist/PowerSearch/PowerSearch.d.ts +8 -2
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +19 -6
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts +3 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +28 -6
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts +3 -1
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +19 -9
- package/dist/PowerSearch/types.d.ts +2 -0
- package/dist/PowerSearch/types.d.ts.map +1 -1
- package/dist/PowerSearch/usePowerSearchSource.d.ts +6 -1
- package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
- package/dist/PowerSearch/usePowerSearchSource.js +14 -5
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +18 -5
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +3 -2
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +3 -2
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +3 -3
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +5 -0
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +4 -3
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +8 -7
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +4 -3
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +3 -3
- package/dist/Spinner/Spinner.d.ts +3 -3
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +130 -78
- package/dist/StatusDot/StatusDot.d.ts.map +1 -1
- package/dist/StatusDot/StatusDot.js +6 -1
- package/dist/Stepper/Step.d.ts +140 -0
- package/dist/Stepper/Step.d.ts.map +1 -0
- package/dist/Stepper/Step.js +1045 -0
- package/dist/Stepper/StepStatus.d.ts +24 -0
- package/dist/Stepper/StepStatus.d.ts.map +1 -0
- package/dist/Stepper/StepStatus.js +1 -0
- package/dist/Stepper/Stepper.d.ts +93 -0
- package/dist/Stepper/Stepper.d.ts.map +1 -0
- package/dist/Stepper/Stepper.js +184 -0
- package/dist/Stepper/StepperContext.d.ts +41 -0
- package/dist/Stepper/StepperContext.d.ts.map +1 -0
- package/dist/Stepper/StepperContext.js +34 -0
- package/dist/Stepper/index.d.ts +8 -0
- package/dist/Stepper/index.d.ts.map +1 -0
- package/dist/Stepper/index.js +7 -0
- package/dist/Stepper/stepper.stylex.d.ts +15 -0
- package/dist/Stepper/stepper.stylex.d.ts.map +1 -0
- package/dist/Stepper/stepper.stylex.js +20 -0
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +8 -3
- package/dist/TabList/Tab.d.ts +15 -2
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +32 -7
- package/dist/TabList/TabList.d.ts +46 -7
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +353 -23
- package/dist/TabList/TabListContext.d.ts +11 -1
- package/dist/TabList/TabListContext.d.ts.map +1 -1
- package/dist/TabList/TabListContext.js +11 -2
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +3 -2
- package/dist/TabList/index.d.ts +2 -2
- package/dist/TabList/index.d.ts.map +1 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +10 -2
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +3 -6
- package/dist/Table/TableRow.d.ts +16 -5
- package/dist/Table/TableRow.d.ts.map +1 -1
- package/dist/Table/TableRow.js +16 -5
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +20 -8
- package/dist/Table/plugins/pagination/useTablePagination.d.ts +3 -0
- package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/useTablePagination.js +21 -4
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
- package/dist/Table/plugins/rowStatus/useTableRowStatus.js +10 -3
- package/dist/Table/plugins/selection/useTableSelection.d.ts +16 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +19 -5
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +3 -4
- package/dist/Table/types.d.ts +22 -4
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +5 -0
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +6 -2
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +9 -3
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +3 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +3 -2
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +5 -3
- package/dist/Token/TokenLink.d.ts.map +1 -1
- package/dist/Token/TokenLink.js +2 -2
- package/dist/Tokenizer/Tokenizer.d.ts +3 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +2 -0
- package/dist/Tooltip/Tooltip.d.ts +5 -0
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.d.ts +5 -0
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +39 -24
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +5 -4
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +3 -3
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +4 -3
- package/dist/Typeahead/BaseTypeahead.d.ts +2 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +68 -29
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +3 -2
- package/dist/astryx.css +94 -7
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useFocusTrap.d.ts +7 -4
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +60 -95
- package/dist/hooks/useHotkeys.d.ts.map +1 -1
- package/dist/hooks/useHotkeys.js +3 -18
- package/dist/hooks/useMergedRefs.d.ts +18 -0
- package/dist/hooks/useMergedRefs.d.ts.map +1 -0
- package/dist/hooks/useMergedRefs.js +25 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +24 -4
- package/dist/theme/syntax/tokens.d.ts +1 -1
- package/dist/theme/syntax/tokens.d.ts.map +1 -1
- package/dist/theme/syntax/tokens.js +5 -2
- package/dist/utils/groupItems.d.ts +5 -1
- package/dist/utils/groupItems.d.ts.map +1 -1
- package/dist/utils/groupItems.js +9 -9
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/isApplePlatform.d.ts +18 -0
- package/dist/utils/isApplePlatform.d.ts.map +1 -0
- package/dist/utils/isApplePlatform.js +37 -0
- package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
- package/dist/utils/sharedResizeObserver.js +10 -1
- package/dist/utils/timeParser.d.ts +1 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +26 -12
- package/locales/en.json +31 -7
- package/locales/pseudo.json +18 -0
- package/package.json +8 -3
- package/src/AppShell/AppShell.tsx +3 -2
- package/src/AspectRatio/AspectRatio.doc.mjs +6 -3
- package/src/AspectRatio/AspectRatio.test.tsx +57 -10
- package/src/AspectRatio/AspectRatio.tsx +40 -1
- package/src/Avatar/Avatar.doc.mjs +14 -3
- package/src/Avatar/Avatar.test.tsx +97 -0
- package/src/Avatar/Avatar.tsx +125 -52
- package/src/Avatar/index.ts +2 -2
- package/src/AvatarGroup/AvatarGroup.doc.mjs +8 -2
- package/src/AvatarGroup/AvatarGroup.test.tsx +24 -0
- package/src/AvatarGroup/AvatarGroup.tsx +15 -6
- package/src/AvatarGroup/AvatarGroupContext.ts +5 -3
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +10 -4
- package/src/Banner/Banner.doc.mjs +13 -11
- package/src/Banner/Banner.test.tsx +102 -15
- package/src/Banner/Banner.tsx +72 -25
- package/src/BottomSheet/BottomSheet.test.tsx +123 -0
- package/src/BottomSheet/BottomSheet.tsx +17 -4
- package/src/BottomSheet/BottomSheetSwitcher.tsx +3 -2
- package/src/Breadcrumbs/BreadcrumbItem.tsx +9 -5
- package/src/Button/Button.doc.mjs +22 -0
- package/src/Button/Button.test.tsx +13 -0
- package/src/Button/Button.tsx +5 -4
- package/src/ButtonGroup/ButtonGroup.doc.mjs +10 -1
- package/src/ButtonGroup/ButtonGroup.test.tsx +391 -1
- package/src/ButtonGroup/ButtonGroup.tsx +23 -5
- package/src/Calendar/Calendar.doc.mjs +4 -3
- package/src/Calendar/Calendar.test.tsx +52 -0
- package/src/Calendar/Calendar.tsx +18 -15
- package/src/Calendar/getInitialFocusDate.test.ts +94 -0
- package/src/Calendar/getInitialFocusDate.ts +93 -0
- package/src/Carousel/Carousel.tsx +3 -2
- package/src/Chat/ChatComposerInput.tsx +5 -1
- package/src/Chat/ChatLayout.tsx +3 -2
- package/src/Chat/ChatMessageList.doc.mjs +9 -1
- package/src/Chat/ChatMessageList.test.tsx +46 -0
- package/src/Chat/ChatMessageList.tsx +28 -4
- package/src/Chat/ChatTokenizedText.tsx +17 -10
- package/src/Chat/useChatComposerTokens.ts +1 -1
- package/src/CheckboxInput/CheckboxInput.doc.mjs +2 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +15 -0
- package/src/CheckboxInput/CheckboxInput.tsx +8 -2
- package/src/ClickableCard/ClickableCard.tsx +3 -2
- package/src/CodeBlock/CodeBlock.doc.mjs +16 -4
- package/src/CodeBlock/CodeBlock.test.tsx +37 -0
- package/src/CodeBlock/CodeBlock.tsx +20 -4
- package/src/CodeBlock/highlightRanges.ts +1 -1
- package/src/CodeBlock/highlightStyles.ts +13 -13
- package/src/CommandPalette/CommandPaletteInput.tsx +3 -2
- package/src/CommandPalette/CommandPaletteItem.tsx +3 -2
- package/src/ContextMenu/ContextMenu.doc.mjs +5 -0
- package/src/ContextMenu/ContextMenu.tsx +4 -3
- package/src/DateInput/DateInput.doc.mjs +16 -0
- package/src/DateInput/DateInput.tsx +55 -2
- package/src/DateInput/DateInputTouch.test.tsx +68 -9
- package/src/DateInput/NativeDateField.test.tsx +565 -0
- package/src/DateInput/NativeDateField.tsx +623 -0
- package/src/DateInput/TouchDateField.tsx +38 -4
- package/src/DateInput/index.ts +1 -0
- package/src/DateTimeInput/DateTimeInput.doc.mjs +18 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +1088 -0
- package/src/DateTimeInput/DateTimeInput.tsx +556 -24
- package/src/DateTimeInput/index.ts +1 -0
- package/src/Dialog/Dialog.doc.mjs +8 -3
- package/src/Dialog/Dialog.test.tsx +261 -1
- package/src/Dialog/Dialog.tsx +109 -58
- package/src/DropdownMenu/DropdownMenu.doc.mjs +6 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +8 -5
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +8 -11
- package/src/Field/Field.doc.mjs +13 -2
- package/src/Field/FieldLabel.test.tsx +29 -0
- package/src/Field/FieldLabel.tsx +7 -0
- package/src/FileInput/FileInput.tsx +3 -2
- package/src/Heading/Heading.test.tsx +23 -1
- package/src/Heading/Heading.tsx +6 -2
- package/src/HoverCard/HoverCard.doc.mjs +13 -2
- package/src/HoverCard/HoverCard.test.tsx +101 -4
- package/src/HoverCard/HoverCard.tsx +71 -3
- package/src/HoverCard/useHoverCard.tsx +101 -33
- package/src/Indicator/CheckboxIndicator.tsx +14 -8
- package/src/Indicator/Indicator.doc.mjs +2 -0
- package/src/Item/Item.tsx +5 -4
- package/src/Kbd/Kbd.tsx +2 -19
- package/src/Layer/LayerDepthContext.tsx +62 -0
- package/src/Layer/index.ts +9 -0
- package/src/Layer/layerDismissalFamilies.test.tsx +359 -0
- package/src/Layer/layerDismissalInvariants.test.tsx +417 -0
- package/src/Layer/layerStack.ts +362 -0
- package/src/Layer/useLayerDismissal.test.tsx +469 -0
- package/src/Layer/useLayerDismissal.ts +173 -0
- package/src/Layout/Layout.doc.mjs +2 -2
- package/src/Lightbox/Lightbox.doc.mjs +12 -0
- package/src/Lightbox/Lightbox.tsx +124 -102
- package/src/Link/Link.doc.mjs +39 -0
- package/src/Markdown/Markdown.doc.mjs +5 -0
- package/src/Markdown/incremental.test.ts +16 -2
- package/src/Markdown/index.ts +1 -0
- package/src/Markdown/parser.perf.test.ts +96 -1
- package/src/Markdown/parser.test.ts +145 -2
- package/src/Markdown/parser.ts +244 -29
- package/src/MobileNav/MobileNav.tsx +62 -36
- package/src/MoreMenu/MoreMenu.doc.mjs +7 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +27 -0
- package/src/MultiSelector/MultiSelector.test.tsx +106 -0
- package/src/MultiSelector/MultiSelector.tsx +50 -6
- package/src/MultiSelector/index.ts +1 -0
- package/src/NavIcon/NavIcon.doc.mjs +8 -2
- package/src/NavIcon/NavIcon.test.tsx +9 -0
- package/src/NavIcon/NavIcon.tsx +5 -1
- package/src/NavMenu/NavHeadingMenu.tsx +3 -2
- package/src/NumberInput/NumberInput.doc.mjs +20 -0
- package/src/NumberInput/NumberInput.tsx +28 -29
- package/src/NumberInput/numberParser.test.ts +137 -0
- package/src/NumberInput/numberParser.ts +346 -0
- package/src/Outline/Outline.tsx +3 -2
- package/src/OverflowList/OverflowList.tsx +3 -2
- package/src/Overlay/Overlay.tsx +3 -2
- package/src/Popover/usePopover.tsx +31 -28
- package/src/PowerSearch/PowerSearch.doc.mjs +160 -28
- package/src/PowerSearch/PowerSearch.test.tsx +240 -1
- package/src/PowerSearch/PowerSearch.tsx +34 -5
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +201 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +29 -2
- package/src/PowerSearch/PowerSearchValueEditor.test.tsx +47 -1
- package/src/PowerSearch/PowerSearchValueEditor.tsx +15 -0
- package/src/PowerSearch/types.ts +2 -0
- package/src/PowerSearch/usePowerSearchSource.test.ts +82 -2
- package/src/PowerSearch/usePowerSearchSource.ts +13 -3
- package/src/ProgressBar/ProgressBar.doc.mjs +42 -6
- package/src/ProgressBar/ProgressBar.test.tsx +24 -0
- package/src/ProgressBar/ProgressBar.tsx +28 -8
- package/src/Resizable/ResizeHandle.tsx +3 -2
- package/src/SegmentedControl/SegmentedControl.tsx +3 -2
- package/src/SelectableCard/SelectableCard.tsx +3 -2
- package/src/Selector/Selector.doc.mjs +12 -1
- package/src/Selector/Selector.test.tsx +45 -1
- package/src/Selector/Selector.tsx +11 -1
- package/src/SideNav/SideNav.doc.mjs +6 -6
- package/src/SideNav/SideNav.tsx +4 -2
- package/src/SideNav/SideNavHeading.tsx +12 -12
- package/src/SideNav/SideNavItem.tsx +4 -2
- package/src/Slider/Slider.tsx +3 -2
- package/src/Spinner/Spinner.test.tsx +147 -1
- package/src/Spinner/Spinner.tsx +120 -101
- package/src/StatusDot/StatusDot.doc.mjs +8 -2
- package/src/StatusDot/StatusDot.test.tsx +9 -0
- package/src/StatusDot/StatusDot.tsx +7 -1
- package/src/Stepper/Step.doc.mjs +98 -0
- package/src/Stepper/Step.tsx +1638 -0
- package/src/Stepper/StepStatus.ts +26 -0
- package/src/Stepper/Stepper.doc.mjs +370 -0
- package/src/Stepper/Stepper.test.tsx +1024 -0
- package/src/Stepper/Stepper.tsx +258 -0
- package/src/Stepper/StepperContext.ts +71 -0
- package/src/Stepper/index.ts +18 -0
- package/src/Stepper/stepper.stylex.ts +19 -0
- package/src/Switch/Switch.doc.mjs +2 -0
- package/src/Switch/Switch.test.tsx +10 -0
- package/src/Switch/Switch.tsx +6 -2
- package/src/TabList/Tab.doc.mjs +13 -1
- package/src/TabList/Tab.tsx +67 -7
- package/src/TabList/TabList.doc.mjs +22 -6
- package/src/TabList/TabList.test.tsx +559 -0
- package/src/TabList/TabList.tsx +490 -22
- package/src/TabList/TabListContext.ts +13 -2
- package/src/TabList/TabMenu.tsx +3 -2
- package/src/TabList/index.ts +6 -2
- package/src/Table/BaseTable.tsx +12 -4
- package/src/Table/Table.doc.mjs +9 -2
- package/src/Table/Table.test.tsx +132 -14
- package/src/Table/Table.tsx +3 -8
- package/src/Table/TableBody.doc.mjs +40 -0
- package/src/Table/TableFooter.doc.mjs +40 -0
- package/src/Table/TableHeader.doc.mjs +40 -0
- package/src/Table/TableRow.tsx +16 -5
- package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +112 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +100 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +17 -8
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +34 -4
- package/src/Table/plugins/pagination/useTablePagination.tsx +35 -3
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +9 -2
- package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +13 -7
- package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +12 -3
- package/src/Table/plugins/selection/useTableSelection.test.tsx +76 -0
- package/src/Table/plugins/selection/useTableSelection.tsx +40 -7
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +1 -2
- package/src/Table/types.ts +22 -4
- package/src/Table/useBaseTablePlugins.ts +5 -0
- package/src/Table/useTableGroupedRows.doc.mjs +5 -4
- package/src/Table/useTablePagination.doc.mjs +7 -0
- package/src/Table/useTableRowStatus.doc.mjs +1 -1
- package/src/Table/useTableSelection.doc.mjs +31 -0
- package/src/Text/Text.test.tsx +23 -1
- package/src/Text/Text.tsx +6 -2
- package/src/TextArea/TextArea.doc.mjs +8 -2
- package/src/TextArea/TextArea.test.tsx +11 -0
- package/src/TextArea/TextArea.tsx +15 -8
- package/src/TextInput/TextInput.doc.mjs +10 -0
- package/src/TextInput/TextInput.tsx +3 -2
- package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
- package/src/TimeInput/TimeInput.tsx +2 -2
- package/src/Timestamp/Timestamp.tsx +4 -2
- package/src/Toast/Toast.doc.mjs +6 -0
- package/src/Token/TokenLink.tsx +2 -2
- package/src/Tokenizer/Tokenizer.doc.mjs +27 -0
- package/src/Tokenizer/Tokenizer.tsx +4 -0
- package/src/Toolbar/Toolbar.doc.mjs +5 -0
- package/src/Tooltip/Tooltip.doc.mjs +5 -0
- package/src/Tooltip/Tooltip.test.tsx +32 -0
- package/src/Tooltip/Tooltip.tsx +5 -0
- package/src/Tooltip/useTooltip.tsx +47 -24
- package/src/TopNav/TopNavHeading.tsx +5 -4
- package/src/TopNav/TopNavMegaMenu.tsx +3 -2
- package/src/TopNav/TopNavMenu.tsx +4 -3
- package/src/Typeahead/BaseTypeahead.doc.mjs +11 -0
- package/src/Typeahead/BaseTypeahead.tsx +85 -33
- package/src/Typeahead/Typeahead.doc.mjs +5 -0
- package/src/Typeahead/Typeahead.test.tsx +49 -0
- package/src/Typeahead/Typeahead.tsx +3 -2
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useContainerReveal.doc.mjs +2 -2
- package/src/hooks/useFocusTrap.test.tsx +14 -0
- package/src/hooks/useFocusTrap.ts +62 -111
- package/src/hooks/useFocusTrapEscapeShim.test.tsx +165 -0
- package/src/hooks/useHotkeys.ts +3 -18
- package/src/hooks/useMergedRefs.doc.mjs +79 -0
- package/src/hooks/useMergedRefs.test.tsx +62 -0
- package/src/hooks/useMergedRefs.ts +36 -0
- package/src/index.ts +1 -0
- package/src/theme/MediaTheme.doc.mjs +5 -5
- package/src/theme/derivedVarRegistry.test.ts +19 -3
- package/src/theme/derivedVarRegistry.ts +23 -4
- package/src/theme/generateThemeRules.test.ts +37 -0
- package/src/theme/syntax/tokens.ts +5 -2
- package/src/utils/groupItems.ts +12 -6
- package/src/utils/index.ts +1 -1
- package/src/utils/isApplePlatform.test.ts +61 -0
- package/src/utils/isApplePlatform.ts +37 -0
- package/src/utils/sharedResizeObserver.ts +10 -2
- package/src/utils/timeParser.test.ts +6 -1
- package/src/utils/timeParser.ts +35 -13
- package/src/__tests__/inputWidthContract.test.tsx +0 -69
|
@@ -19,80 +19,26 @@
|
|
|
19
19
|
import {useCallback, useEffect, useRef} from 'react';
|
|
20
20
|
|
|
21
21
|
import {FOCUSABLE_SELECTOR} from './focusableSelector';
|
|
22
|
-
import {
|
|
22
|
+
import {useLayerDismissal} from '../Layer/useLayerDismissal';
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* traps in a shared stack lets only the top-most trap respond to Escape.
|
|
31
|
-
*
|
|
32
|
-
* "Top-most" is resolved by DOM containment first, push order second. Push
|
|
33
|
-
* order alone is not reliable: React runs child effects before parent
|
|
34
|
-
* effects, so when an outer and an inner (DOM-nested) trap mount in the SAME
|
|
35
|
-
* commit, the inner trap pushes first and the outer trap would wrongly win a
|
|
36
|
-
* pure last-pushed comparison.
|
|
37
|
-
*/
|
|
38
|
-
interface EscapeStackEntry {
|
|
39
|
-
handler: () => void;
|
|
40
|
-
getContainer: () => HTMLElement | null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const escapeStack: EscapeStackEntry[] = [];
|
|
44
|
-
|
|
45
|
-
function pushEscapeHandler(entry: EscapeStackEntry): void {
|
|
46
|
-
escapeStack.push(entry);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function removeEscapeHandler(handler: () => void): void {
|
|
50
|
-
for (let i = escapeStack.length - 1; i >= 0; i--) {
|
|
51
|
-
if (escapeStack[i].handler === handler) {
|
|
52
|
-
escapeStack.splice(i, 1);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
24
|
+
// Escape-dismissible focus traps currently mounted. This is the whole state
|
|
25
|
+
// behind `hasActiveFocusTrapEscape`, which predates the shared stack and must
|
|
26
|
+
// keep answering about focus traps alone — the stack now carries families that
|
|
27
|
+
// never trapped focus (tooltips, hover cards), and counting those would tell
|
|
28
|
+
// callers a trap is above them when none is.
|
|
29
|
+
let activeEscapeTrapCount = 0;
|
|
57
30
|
|
|
58
31
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (escapeStack.length === 0) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
let top = escapeStack[0];
|
|
70
|
-
for (let i = 1; i < escapeStack.length; i++) {
|
|
71
|
-
const entry = escapeStack[i];
|
|
72
|
-
const topContainer = top.getContainer();
|
|
73
|
-
const entryContainer = entry.getContainer();
|
|
74
|
-
if (
|
|
75
|
-
topContainer != null &&
|
|
76
|
-
entryContainer != null &&
|
|
77
|
-
entryContainer !== topContainer &&
|
|
78
|
-
entryContainer.contains(topContainer)
|
|
79
|
-
) {
|
|
80
|
-
// The current top is nested inside this entry — it stays on top.
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
top = entry;
|
|
84
|
-
}
|
|
85
|
-
return top.handler === handler;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Whether any focus-trap Escape handler is currently active (i.e. a popover
|
|
90
|
-
* layer is open). Other overlay primitives that manage their own Escape (e.g.
|
|
91
|
-
* Dialog) can consult this to defer to a popover layered on top of them,
|
|
92
|
-
* giving topmost-only dismissal until a full layer stack exists.
|
|
32
|
+
* Whether an Escape-dismissible focus trap is currently active — a Popover,
|
|
33
|
+
* menu or other trapped layer that would take an Escape press.
|
|
34
|
+
*
|
|
35
|
+
* @deprecated The focus trap no longer owns Escape coordination — every overlay
|
|
36
|
+
* family shares one stack (`useLayerDismissal`), which routes each press to
|
|
37
|
+
* the top-most layer. A layer that wants the same ordering should join the
|
|
38
|
+
* stack rather than ask whether a trap exists.
|
|
93
39
|
*/
|
|
94
40
|
export function hasActiveFocusTrapEscape(): boolean {
|
|
95
|
-
return
|
|
41
|
+
return activeEscapeTrapCount > 0;
|
|
96
42
|
}
|
|
97
43
|
|
|
98
44
|
/**
|
|
@@ -243,6 +189,38 @@ export function useFocusTrap<T extends HTMLElement = HTMLElement>(
|
|
|
243
189
|
// Track if focus change was triggered by keyboard (Tab key)
|
|
244
190
|
const isKeyboardNavigationRef = useRef(false);
|
|
245
191
|
|
|
192
|
+
// Join the shared layer dismissal stack. The trap no longer listens for
|
|
193
|
+
// Escape itself: the stack owns one listener and routes each press to the
|
|
194
|
+
// top-most layer, so a popover inside a Dialog, a submenu inside a menu, and
|
|
195
|
+
// a modal inside a modal all peel off one at a time. A trap with no
|
|
196
|
+
// `onEscape` is not dismissible and stays off the stack, so a press flows
|
|
197
|
+
// past it to whatever is underneath.
|
|
198
|
+
// One expression drives both the stack registration and the deprecated
|
|
199
|
+
// `hasActiveFocusTrapEscape` count, so the two can never disagree about
|
|
200
|
+
// whether this trap is active.
|
|
201
|
+
const isEscapeTrap = isActive && onEscape != null;
|
|
202
|
+
|
|
203
|
+
useLayerDismissal({
|
|
204
|
+
isActive: isEscapeTrap,
|
|
205
|
+
onDismiss: () => {
|
|
206
|
+
onEscape?.();
|
|
207
|
+
},
|
|
208
|
+
// The trap renders nothing, so it cannot push a depth provider around its
|
|
209
|
+
// content; hand the stack the container instead so two DOM-nested traps
|
|
210
|
+
// still resolve in the right order.
|
|
211
|
+
getContainer: () => containerRef.current,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
if (!isEscapeTrap) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
activeEscapeTrapCount += 1;
|
|
219
|
+
return () => {
|
|
220
|
+
activeEscapeTrapCount -= 1;
|
|
221
|
+
};
|
|
222
|
+
}, [isEscapeTrap]);
|
|
223
|
+
|
|
246
224
|
/**
|
|
247
225
|
* Focus the first focusable element.
|
|
248
226
|
*/
|
|
@@ -353,67 +331,43 @@ export function useFocusTrap<T extends HTMLElement = HTMLElement>(
|
|
|
353
331
|
}, [isActive]);
|
|
354
332
|
|
|
355
333
|
/**
|
|
356
|
-
* Handle Tab key to wrap focus at boundaries
|
|
357
|
-
*
|
|
334
|
+
* Handle Tab key to wrap focus at boundaries. Also tracks that keyboard
|
|
335
|
+
* navigation is occurring.
|
|
336
|
+
*
|
|
337
|
+
* No Escape here, and no IME guard: the shared stack owns the press, claims
|
|
338
|
+
* a composing Escape so no close request follows, and dismisses the trap
|
|
339
|
+
* through `onEscape` above. The trap renders no element of its own, so it
|
|
340
|
+
* has no `cancel` to answer either.
|
|
358
341
|
*/
|
|
359
342
|
useEffect(() => {
|
|
360
343
|
if (!isActive) {
|
|
361
344
|
return;
|
|
362
345
|
}
|
|
363
346
|
|
|
364
|
-
// Register this trap on the shared Escape stack so only the top-most
|
|
365
|
-
// active trap responds to Escape. A stable identity per active period is
|
|
366
|
-
// enough — we push on activate and remove on cleanup. The container is
|
|
367
|
-
// read lazily: the ref may not be attached yet at effect time, and the
|
|
368
|
-
// stack resolves top-most by DOM containment at keydown time.
|
|
369
|
-
const escapeHandler = () => {
|
|
370
|
-
onEscape?.();
|
|
371
|
-
};
|
|
372
|
-
if (onEscape) {
|
|
373
|
-
pushEscapeHandler({
|
|
374
|
-
handler: escapeHandler,
|
|
375
|
-
getContainer: () => containerRef.current,
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
347
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
380
348
|
const container = containerRef.current;
|
|
381
349
|
if (!container) {
|
|
382
350
|
return;
|
|
383
351
|
}
|
|
384
352
|
|
|
385
|
-
if (event.key === 'Escape' && onEscape) {
|
|
386
|
-
// Ignore Escape that is cancelling an IME composition, already handled
|
|
387
|
-
// by a nested handler, or not targeting the top-most trap.
|
|
388
|
-
if (
|
|
389
|
-
event.defaultPrevented ||
|
|
390
|
-
isImeKeyEvent(event) ||
|
|
391
|
-
!isTopEscapeHandler(escapeHandler)
|
|
392
|
-
) {
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
// Mark handled and stop propagation so an outer layer (e.g. a Dialog
|
|
396
|
-
// hosting this popover) does not also dismiss on the same press.
|
|
397
|
-
event.preventDefault();
|
|
398
|
-
event.stopPropagation();
|
|
399
|
-
onEscape();
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
353
|
if (event.key === 'Tab') {
|
|
404
354
|
// Mark that keyboard navigation is happening
|
|
405
355
|
isKeyboardNavigationRef.current = true;
|
|
406
356
|
|
|
407
357
|
const focusable = getFocusableElements(container);
|
|
408
358
|
if (focusable.length === 0) {
|
|
359
|
+
const active = document.activeElement;
|
|
360
|
+
if (!(active instanceof HTMLElement) || !container.contains(active)) {
|
|
361
|
+
// A layer can be open while focus legitimately sits outside it —
|
|
362
|
+
// a listbox popup anchored to its own input. Cancelling there
|
|
363
|
+
// would take Tab away from the whole page.
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
409
366
|
// There is nowhere to advance to. Keep focus on the current
|
|
410
367
|
// programmatic target (for example a dialog panel with tabIndex=-1)
|
|
411
368
|
// rather than allowing the browser to move into background content.
|
|
412
369
|
event.preventDefault();
|
|
413
|
-
|
|
414
|
-
if (active instanceof HTMLElement && container.contains(active)) {
|
|
415
|
-
lastFocusRef.current = active;
|
|
416
|
-
}
|
|
370
|
+
lastFocusRef.current = active;
|
|
417
371
|
isKeyboardNavigationRef.current = false;
|
|
418
372
|
return;
|
|
419
373
|
}
|
|
@@ -441,9 +395,6 @@ export function useFocusTrap<T extends HTMLElement = HTMLElement>(
|
|
|
441
395
|
|
|
442
396
|
return () => {
|
|
443
397
|
document.removeEventListener('keydown', handleKeyDown);
|
|
444
|
-
if (onEscape) {
|
|
445
|
-
removeEscapeHandler(escapeHandler);
|
|
446
|
-
}
|
|
447
398
|
};
|
|
448
399
|
}, [isActive, onEscape]);
|
|
449
400
|
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file useFocusTrapEscapeShim.test.tsx
|
|
5
|
+
* @input Uses vitest, @testing-library/react, useFocusTrap, Dialog, Lightbox,
|
|
6
|
+
* MobileNav, Tooltip, Popover
|
|
7
|
+
* @output Tests that hasActiveFocusTrapEscape still means what it meant in
|
|
8
|
+
* 0.4.2 — an Escape-dismissible FOCUS TRAP is active, not "a layer is open"
|
|
9
|
+
* @position Testing; guards the deprecated public shim in useFocusTrap.ts
|
|
10
|
+
*
|
|
11
|
+
* The shim is public API. Its answer must not change now that Escape moved to
|
|
12
|
+
* the shared stack: `BottomSheetSwitcher` gates its own dismissal on it, so a
|
|
13
|
+
* shim that also counts tooltips, hover cards and dialogs tells the sheet a
|
|
14
|
+
* trap is above it when none is, and the sheet stops closing.
|
|
15
|
+
*
|
|
16
|
+
* SYNC: When useFocusTrap.ts changes, update tests to match new behavior
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
|
|
20
|
+
import {render} from '@testing-library/react';
|
|
21
|
+
|
|
22
|
+
import {hasActiveFocusTrapEscape, useFocusTrap} from './useFocusTrap';
|
|
23
|
+
import {Dialog} from '../Dialog/Dialog';
|
|
24
|
+
import {Lightbox} from '../Lightbox/Lightbox';
|
|
25
|
+
import {MobileNav} from '../MobileNav/MobileNav';
|
|
26
|
+
import {Popover} from '../Popover/Popover';
|
|
27
|
+
import {Tooltip} from '../Tooltip/Tooltip';
|
|
28
|
+
import {resetLayerStackForTests} from '../Layer/layerStack';
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
HTMLDialogElement.prototype.showModal = vi.fn(function (
|
|
32
|
+
this: HTMLDialogElement,
|
|
33
|
+
) {
|
|
34
|
+
this.setAttribute('open', '');
|
|
35
|
+
});
|
|
36
|
+
HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
|
|
37
|
+
this.removeAttribute('open');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
resetLayerStackForTests();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function Trap({
|
|
46
|
+
isActive = true,
|
|
47
|
+
hasEscape = true,
|
|
48
|
+
children,
|
|
49
|
+
}: {
|
|
50
|
+
isActive?: boolean;
|
|
51
|
+
hasEscape?: boolean;
|
|
52
|
+
children?: React.ReactNode;
|
|
53
|
+
}) {
|
|
54
|
+
const {containerRef} = useFocusTrap<HTMLDivElement>({
|
|
55
|
+
isActive,
|
|
56
|
+
onEscape: hasEscape ? () => {} : undefined,
|
|
57
|
+
});
|
|
58
|
+
return <div ref={containerRef}>{children}</div>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe('hasActiveFocusTrapEscape', () => {
|
|
62
|
+
it('is false with nothing mounted', () => {
|
|
63
|
+
expect(hasActiveFocusTrapEscape()).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('is true for an active trap with an Escape handler', () => {
|
|
67
|
+
render(<Trap />);
|
|
68
|
+
expect(hasActiveFocusTrapEscape()).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('is false for a trap with no Escape handler', () => {
|
|
72
|
+
render(<Trap hasEscape={false} />);
|
|
73
|
+
expect(hasActiveFocusTrapEscape()).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('is false for an inactive trap', () => {
|
|
77
|
+
render(<Trap isActive={false} />);
|
|
78
|
+
expect(hasActiveFocusTrapEscape()).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('is true for nested traps, and false again once both unmount', () => {
|
|
82
|
+
const {unmount} = render(
|
|
83
|
+
<Trap>
|
|
84
|
+
<Trap />
|
|
85
|
+
</Trap>,
|
|
86
|
+
);
|
|
87
|
+
expect(hasActiveFocusTrapEscape()).toBe(true);
|
|
88
|
+
unmount();
|
|
89
|
+
expect(hasActiveFocusTrapEscape()).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('is true for an open Popover, which traps focus', () => {
|
|
93
|
+
render(
|
|
94
|
+
<Popover
|
|
95
|
+
isOpen={true}
|
|
96
|
+
onOpenChange={() => {}}
|
|
97
|
+
content="Popover body"
|
|
98
|
+
label="Popover">
|
|
99
|
+
<button type="button">Open</button>
|
|
100
|
+
</Popover>,
|
|
101
|
+
);
|
|
102
|
+
expect(hasActiveFocusTrapEscape()).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// The families below are all on the shared dismissal stack and none of them
|
|
106
|
+
// traps focus. Answering `true` for these is what broke the bottom sheet.
|
|
107
|
+
it('is false for an open Dialog', () => {
|
|
108
|
+
render(
|
|
109
|
+
<Dialog isOpen={true} onOpenChange={() => {}} aria-label="Dialog">
|
|
110
|
+
Body
|
|
111
|
+
</Dialog>,
|
|
112
|
+
);
|
|
113
|
+
expect(hasActiveFocusTrapEscape()).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('is false for an open Lightbox', () => {
|
|
117
|
+
render(
|
|
118
|
+
<Lightbox
|
|
119
|
+
isOpen={true}
|
|
120
|
+
onOpenChange={() => {}}
|
|
121
|
+
media={{src: '/photo.jpg', alt: 'A photo'}}
|
|
122
|
+
/>,
|
|
123
|
+
);
|
|
124
|
+
expect(hasActiveFocusTrapEscape()).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('is false for an open MobileNav', () => {
|
|
128
|
+
render(
|
|
129
|
+
<MobileNav isOpen={true} onOpenChange={() => {}} label="Drawer">
|
|
130
|
+
<span>Nav</span>
|
|
131
|
+
</MobileNav>,
|
|
132
|
+
);
|
|
133
|
+
expect(hasActiveFocusTrapEscape()).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('is false for a showing Tooltip', () => {
|
|
137
|
+
render(
|
|
138
|
+
<Tooltip content="Tip" isDefaultOpen={true}>
|
|
139
|
+
<button type="button">Trigger</button>
|
|
140
|
+
</Tooltip>,
|
|
141
|
+
);
|
|
142
|
+
expect(hasActiveFocusTrapEscape()).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('is true for a Popover inside a Dialog, and false once the Popover closes', () => {
|
|
146
|
+
function Scene({isPopoverOpen}: {isPopoverOpen: boolean}) {
|
|
147
|
+
return (
|
|
148
|
+
<Dialog isOpen={true} onOpenChange={() => {}} aria-label="Dialog">
|
|
149
|
+
<Popover
|
|
150
|
+
isOpen={isPopoverOpen}
|
|
151
|
+
onOpenChange={() => {}}
|
|
152
|
+
content="Popover body"
|
|
153
|
+
label="Popover">
|
|
154
|
+
<button type="button">Open</button>
|
|
155
|
+
</Popover>
|
|
156
|
+
</Dialog>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const {rerender} = render(<Scene isPopoverOpen={true} />);
|
|
160
|
+
expect(hasActiveFocusTrapEscape()).toBe(true);
|
|
161
|
+
|
|
162
|
+
rerender(<Scene isPopoverOpen={false} />);
|
|
163
|
+
expect(hasActiveFocusTrapEscape()).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
});
|
package/src/hooks/useHotkeys.ts
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* unless a hotkey opts in via `allowInInputs`.
|
|
19
19
|
*
|
|
20
20
|
* Platform-aware: `mod` maps to metaKey (⌘) on Apple platforms and
|
|
21
|
-
* ctrlKey elsewhere
|
|
21
|
+
* ctrlKey elsewhere, through the same isApplePlatform util Kbd reads, so
|
|
22
|
+
* displayed and handled shortcuts cannot disagree.
|
|
22
23
|
*
|
|
23
24
|
* SYNC: When modified, update:
|
|
24
25
|
* - /packages/core/src/hooks/index.ts
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
*/
|
|
27
28
|
|
|
28
29
|
import {useEffect, useRef} from 'react';
|
|
30
|
+
import {isApplePlatform} from '../utils/isApplePlatform';
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* A single keyboard shortcut registration.
|
|
@@ -71,23 +73,6 @@ const KEY_ALIASES: Record<string, string> = {
|
|
|
71
73
|
plus: '+',
|
|
72
74
|
};
|
|
73
75
|
|
|
74
|
-
/**
|
|
75
|
-
* Detects whether the current platform is macOS/iOS.
|
|
76
|
-
* Prefers the User-Agent Client Hints API when available (modern Chrome/Edge),
|
|
77
|
-
* falls back to navigator.platform (deprecated but universally supported).
|
|
78
|
-
* Mirrors the detection used by Kbd so displayed and handled shortcuts agree.
|
|
79
|
-
*/
|
|
80
|
-
function isApplePlatform(): boolean {
|
|
81
|
-
if (typeof navigator === 'undefined') {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
const uaData = 'userAgentData' in navigator ? navigator.userAgentData : null;
|
|
85
|
-
if (uaData && typeof uaData === 'object' && 'platform' in uaData) {
|
|
86
|
-
return /mac/i.test((uaData as {platform: string}).platform ?? '');
|
|
87
|
-
}
|
|
88
|
-
return /Mac|iPhone|iPad|iPod/.test(navigator.platform ?? '');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
76
|
/**
|
|
92
77
|
* Whether the event target is a typing surface where global shortcuts
|
|
93
78
|
* should stay silent by default.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').HookDoc} */
|
|
4
|
+
export const docs = {
|
|
5
|
+
name: 'useMergedRefs',
|
|
6
|
+
displayName: 'useMergedRefs',
|
|
7
|
+
keywords: [
|
|
8
|
+
'ref',
|
|
9
|
+
'refs',
|
|
10
|
+
'merge',
|
|
11
|
+
'forwardRef',
|
|
12
|
+
'callback ref',
|
|
13
|
+
'stable ref',
|
|
14
|
+
],
|
|
15
|
+
params: [
|
|
16
|
+
{
|
|
17
|
+
name: 'refs',
|
|
18
|
+
type: 'Array<Ref<T> | undefined>',
|
|
19
|
+
description: 'Up to six refs that should all receive the same element.',
|
|
20
|
+
required: true,
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
returns: [
|
|
24
|
+
{
|
|
25
|
+
name: 'ref',
|
|
26
|
+
type: 'RefCallback<T>',
|
|
27
|
+
description:
|
|
28
|
+
'A merged callback ref that remains stable until an input ref changes.',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
usage: {
|
|
32
|
+
description:
|
|
33
|
+
'Combines multiple object or callback refs into one stable callback ref. Use it when a component must forward a consumer ref while also attaching internal refs. Unlike calling mergeRefs during render, the callback identity stays stable across unrelated rerenders, so React does not detach and reattach the element.',
|
|
34
|
+
bestPractices: [
|
|
35
|
+
{
|
|
36
|
+
guidance: true,
|
|
37
|
+
description:
|
|
38
|
+
'Use useMergedRefs when one element must receive both a forwarded ref and one or more internal refs.',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
guidance: false,
|
|
42
|
+
description:
|
|
43
|
+
'Call mergeRefs directly in a JSX ref prop; that creates a new callback on every render and forces unnecessary detach and attach work.',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
relatedComponents: [],
|
|
48
|
+
relatedHooks: [],
|
|
49
|
+
importPath: '@astryxdesign/core/hooks',
|
|
50
|
+
category: 'utility',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** @type {import('@astryxdesign/cli/authoring').HookTranslationDoc} */
|
|
54
|
+
export const docsDense = {
|
|
55
|
+
description:
|
|
56
|
+
'Combines object/callback refs into one stable callback ref. Identity changes only when an input ref changes, avoiding detach/attach churn from inline mergeRefs calls.',
|
|
57
|
+
paramDescriptions: {
|
|
58
|
+
refs: 'refs that should all receive the same element.',
|
|
59
|
+
},
|
|
60
|
+
returnDescriptions: {
|
|
61
|
+
ref: 'stable merged callback ref; changes only when an input ref changes.',
|
|
62
|
+
},
|
|
63
|
+
usage: {
|
|
64
|
+
description:
|
|
65
|
+
'Use when one element needs a forwarded consumer ref plus internal refs. Keeps the callback stable across unrelated rerenders.',
|
|
66
|
+
bestPractices: [
|
|
67
|
+
{
|
|
68
|
+
guidance: true,
|
|
69
|
+
description:
|
|
70
|
+
'Use for a forwarded ref plus internal measurement, focus, or anchor refs.',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
guidance: false,
|
|
74
|
+
description:
|
|
75
|
+
'Call mergeRefs inline in JSX; it forces ref detach/attach on every render.',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file useMergedRefs.test.tsx
|
|
5
|
+
* @input Uses vitest, @testing-library/react, useMergedRefs hook
|
|
6
|
+
* @output Unit tests for stable merged ref behavior
|
|
7
|
+
* @position Testing; validates useMergedRefs.ts
|
|
8
|
+
*
|
|
9
|
+
* SYNC: When useMergedRefs.ts changes, update tests to match new behavior
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {renderHook} from '@testing-library/react';
|
|
13
|
+
import {createRef} from 'react';
|
|
14
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
15
|
+
import {useMergedRefs} from './useMergedRefs';
|
|
16
|
+
|
|
17
|
+
describe('useMergedRefs', () => {
|
|
18
|
+
it('forwards values to callback and object refs', () => {
|
|
19
|
+
const callbackRef = vi.fn();
|
|
20
|
+
const objectRef = createRef<HTMLDivElement>();
|
|
21
|
+
const {result} = renderHook(() =>
|
|
22
|
+
useMergedRefs<HTMLDivElement>(callbackRef, objectRef),
|
|
23
|
+
);
|
|
24
|
+
const element = document.createElement('div');
|
|
25
|
+
|
|
26
|
+
const cleanup = result.current(element);
|
|
27
|
+
|
|
28
|
+
expect(callbackRef).toHaveBeenCalledWith(element);
|
|
29
|
+
expect(objectRef.current).toBe(element);
|
|
30
|
+
|
|
31
|
+
cleanup?.();
|
|
32
|
+
expect(callbackRef).toHaveBeenLastCalledWith(null);
|
|
33
|
+
expect(objectRef.current).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('keeps the merged callback stable when input refs are unchanged', () => {
|
|
37
|
+
const callbackRef = vi.fn();
|
|
38
|
+
const objectRef = createRef<HTMLDivElement>();
|
|
39
|
+
const {result, rerender} = renderHook(() =>
|
|
40
|
+
useMergedRefs<HTMLDivElement>(callbackRef, objectRef),
|
|
41
|
+
);
|
|
42
|
+
const first = result.current;
|
|
43
|
+
|
|
44
|
+
rerender();
|
|
45
|
+
|
|
46
|
+
expect(result.current).toBe(first);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('updates the merged callback when an input ref changes', () => {
|
|
50
|
+
const firstRef = vi.fn();
|
|
51
|
+
const secondRef = vi.fn();
|
|
52
|
+
const {result, rerender} = renderHook(
|
|
53
|
+
({ref}) => useMergedRefs<HTMLDivElement>(ref),
|
|
54
|
+
{initialProps: {ref: firstRef}},
|
|
55
|
+
);
|
|
56
|
+
const first = result.current;
|
|
57
|
+
|
|
58
|
+
rerender({ref: secondRef});
|
|
59
|
+
|
|
60
|
+
expect(result.current).not.toBe(first);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useMergedRefs.ts
|
|
7
|
+
* @input React refs to combine into one stable callback ref
|
|
8
|
+
* @output Exports useMergedRefs
|
|
9
|
+
* @position Core hook; use when one element must receive multiple refs
|
|
10
|
+
*
|
|
11
|
+
* SYNC: When modified, update:
|
|
12
|
+
* - /packages/core/src/hooks/index.ts
|
|
13
|
+
* - /packages/core/src/hooks/useMergedRefs.doc.mjs
|
|
14
|
+
* - /packages/core/src/hooks/useMergedRefs.test.tsx
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {useMemo, type Ref, type RefCallback} from 'react';
|
|
18
|
+
import {mergeRefs} from '../utils/mergeRefs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Combine up to six refs into a callback ref whose identity changes only when
|
|
22
|
+
* one of the input refs changes.
|
|
23
|
+
*/
|
|
24
|
+
export function useMergedRefs<T>(
|
|
25
|
+
refA?: Ref<T>,
|
|
26
|
+
refB?: Ref<T>,
|
|
27
|
+
refC?: Ref<T>,
|
|
28
|
+
refD?: Ref<T>,
|
|
29
|
+
refE?: Ref<T>,
|
|
30
|
+
refF?: Ref<T>,
|
|
31
|
+
): RefCallback<T> {
|
|
32
|
+
return useMemo(
|
|
33
|
+
() => mergeRefs(refA, refB, refC, refD, refE, refF),
|
|
34
|
+
[refA, refB, refC, refD, refE, refF],
|
|
35
|
+
);
|
|
36
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -68,7 +68,7 @@ export const docs = {
|
|
|
68
68
|
{
|
|
69
69
|
guidance: true,
|
|
70
70
|
description:
|
|
71
|
-
'Prefer mode="auto" when the surface color comes from a theme token. A token named "inverted" is not guaranteed to be inverted, and auto measures what was actually painted instead of trusting the name
|
|
71
|
+
'Prefer mode="auto" when the surface color comes from a theme token. A token named "inverted" is not guaranteed to be inverted, and auto measures what was actually painted instead of trusting the name. It can even decide that a surface needs no media context at all.',
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
guidance: true,
|
|
@@ -93,14 +93,14 @@ export const docs = {
|
|
|
93
93
|
type: "'dark' | 'light' | 'auto' | 'off'",
|
|
94
94
|
required: true,
|
|
95
95
|
description:
|
|
96
|
-
'Surface luminance context: dark for content over dark backgrounds (light text, white-tinted interactions), light for content over light backgrounds (dark text, black-tinted interactions), auto to decide from the painted surface
|
|
96
|
+
'Surface luminance context: dark for content over dark backgrounds (light text, white-tinted interactions), light for content over light backgrounds (dark text, black-tinted interactions), auto to decide from the painted surface (no media context when the ambient text already reads on the surface at 3:1, otherwise the side that reads better), and off to turn it off explicitly. The element renders either way, so a surface can switch contexts without remounting children.',
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
name: 'fallback',
|
|
100
100
|
type: "'dark' | 'light'",
|
|
101
101
|
default: "'dark'",
|
|
102
102
|
description:
|
|
103
|
-
'Which side auto uses when the surface cannot be measured: during SSR, on the first client frame, and whenever the backdrop is not knowable from CSS
|
|
103
|
+
'Which side auto uses when the surface cannot be measured: during SSR, on the first client frame, and whenever the backdrop is not knowable from CSS, most often a background-image, whose pixels need sampling (useImageMode) rather than a computed style. Ignored unless mode is auto.',
|
|
104
104
|
},
|
|
105
105
|
{
|
|
106
106
|
name: 'children',
|
|
@@ -126,7 +126,7 @@ export const docsDense = {
|
|
|
126
126
|
{
|
|
127
127
|
guidance: true,
|
|
128
128
|
description:
|
|
129
|
-
'Prefer mode="auto" when surface color comes from a theme token
|
|
129
|
+
'Prefer mode="auto" when surface color comes from a theme token; a token named "inverted" is not guaranteed to be; auto measures what was painted.',
|
|
130
130
|
},
|
|
131
131
|
{
|
|
132
132
|
guidance: true,
|
|
@@ -148,6 +148,6 @@ export const docsDense = {
|
|
|
148
148
|
propDescriptions: {
|
|
149
149
|
mode: 'surface luminance context: dark for content over dark backgrounds (light text, white-tinted interactions), light for content over light backgrounds (dark text, black-tinted interactions), auto to decide from painted surface (none if ambient text already reads at 3:1, else better-reading side), off to turn off explicitly (element still renders, so children never remount)',
|
|
150
150
|
fallback:
|
|
151
|
-
'side auto uses when surface is unmeasurable (SSR, first frame, background-image
|
|
151
|
+
'side auto uses when surface is unmeasurable (SSR, first frame, background-image, which needs useImageMode sampling); ignored unless mode is auto',
|
|
152
152
|
},
|
|
153
153
|
};
|