@astryxdesign/core 0.1.4 → 0.1.5-canary.76e6697
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 +141 -0
- package/dist/AlertDialog/AlertDialog.d.ts +1 -1
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +20 -18
- package/dist/AlertDialog/useImperativeAlertDialog.d.ts +0 -2
- package/dist/AlertDialog/useImperativeAlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/useImperativeAlertDialog.js +3 -5
- package/dist/AppShell/AppShell.d.ts +1 -1
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +55 -53
- package/dist/AspectRatio/AspectRatio.d.ts +48 -10
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +33 -12
- package/dist/AspectRatio/index.d.ts +1 -1
- package/dist/AspectRatio/index.d.ts.map +1 -1
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +28 -26
- package/dist/Avatar/AvatarStatusDot.js +6 -6
- package/dist/AvatarGroup/AvatarGroup.js +6 -6
- package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -7
- package/dist/Badge/Badge.js +3 -3
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +32 -25
- package/dist/Blockquote/Blockquote.js +7 -7
- package/dist/Breadcrumbs/BreadcrumbItem.js +17 -17
- package/dist/Breadcrumbs/Breadcrumbs.js +7 -7
- package/dist/Button/Button.js +19 -19
- package/dist/ButtonGroup/ButtonGroup.js +7 -7
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +68 -48
- package/dist/Calendar/dayCellUtils.d.ts.map +1 -1
- package/dist/Calendar/styles.d.ts +2 -2
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +2 -2
- package/dist/Card/Card.d.ts +4 -2
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +18 -10
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +24 -21
- package/dist/Center/Center.js +3 -3
- package/dist/Chat/ChatComposer.js +23 -23
- package/dist/Chat/ChatComposerDrawer.js +14 -14
- package/dist/Chat/ChatComposerInput.d.ts +1 -1
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +27 -15
- package/dist/Chat/ChatDictationButton.d.ts +1 -1
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +13 -11
- package/dist/Chat/ChatLayout.js +17 -17
- package/dist/Chat/ChatLayoutScrollButton.d.ts +1 -1
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +12 -10
- package/dist/Chat/ChatMessage.js +12 -12
- package/dist/Chat/ChatMessageBubble.js +7 -7
- package/dist/Chat/ChatMessageList.js +14 -14
- package/dist/Chat/ChatMessageMetadata.d.ts +1 -1
- package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
- package/dist/Chat/ChatMessageMetadata.js +14 -12
- package/dist/Chat/ChatPastedTextToken.js +13 -13
- package/dist/Chat/ChatSendButton.js +3 -3
- package/dist/Chat/ChatSystemMessage.d.ts +1 -1
- package/dist/Chat/ChatSystemMessage.d.ts.map +1 -1
- package/dist/Chat/ChatSystemMessage.js +17 -17
- package/dist/Chat/ChatTokenizedText.d.ts +1 -1
- package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
- package/dist/Chat/ChatTokenizedText.js +24 -15
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +81 -74
- package/dist/Chat/index.d.ts +1 -1
- package/dist/Chat/index.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +15 -15
- package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +25 -23
- package/dist/CheckboxList/CheckboxList.js +9 -9
- package/dist/CheckboxList/CheckboxListItem.d.ts +1 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +8 -6
- package/dist/Citation/Citation.js +10 -10
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +32 -9
- package/dist/Code/Code.js +3 -3
- package/dist/CodeBlock/CodeBlock.d.ts +10 -1
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +88 -52
- package/dist/Collapsible/Collapsible.d.ts +9 -3
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +109 -22
- package/dist/Collapsible/CollapsibleGroup.d.ts +37 -7
- package/dist/Collapsible/CollapsibleGroup.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroup.js +64 -12
- package/dist/Collapsible/CollapsibleGroupContext.d.ts +24 -0
- package/dist/Collapsible/CollapsibleGroupContext.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroupContext.js +26 -3
- package/dist/Collapsible/index.d.ts +1 -0
- package/dist/Collapsible/index.d.ts.map +1 -1
- package/dist/Collapsible/useCollapsible.d.ts +4 -1
- package/dist/Collapsible/useCollapsible.d.ts.map +1 -1
- package/dist/Collapsible/useCollapsible.js +20 -6
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +36 -34
- package/dist/CommandPalette/CommandPaletteEmpty.js +3 -3
- package/dist/CommandPalette/CommandPaletteFooter.js +16 -16
- package/dist/CommandPalette/CommandPaletteGroup.js +5 -5
- package/dist/CommandPalette/CommandPaletteInput.js +13 -13
- package/dist/CommandPalette/CommandPaletteItem.js +3 -3
- package/dist/CommandPalette/CommandPaletteList.js +3 -3
- package/dist/ContextMenu/ContextMenu.d.ts +7 -2
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +76 -43
- package/dist/DateInput/DateInput.js +18 -18
- package/dist/DateRangeInput/DateRangeInput.js +21 -21
- package/dist/DateTimeInput/DateTimeInput.d.ts +5 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +30 -26
- package/dist/DateTimeInput/index.d.ts +1 -1
- package/dist/DateTimeInput/index.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +9 -9
- package/dist/Dialog/DialogHeader.js +16 -16
- package/dist/Dialog/useImperativeDialog.js +3 -3
- package/dist/Divider/Divider.js +7 -7
- package/dist/DropdownMenu/DropdownMenu.js +10 -10
- package/dist/DropdownMenu/DropdownMenuItem.d.ts +2 -17
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +3 -3
- package/dist/DropdownMenu/renderDropdownItems.js +10 -10
- package/dist/EmptyState/EmptyState.js +9 -9
- package/dist/Field/Field.js +17 -17
- package/dist/Field/FieldLabel.js +15 -15
- package/dist/Field/InputClearButton.js +5 -5
- package/dist/FieldStatus/FieldStatus.js +3 -3
- package/dist/FileInput/FileInput.js +29 -29
- package/dist/FormLayout/FormLayout.js +5 -5
- package/dist/Grid/Grid.d.ts +4 -3
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +29 -12
- package/dist/Grid/GridSpan.js +3 -3
- package/dist/HStack/HStack.js +3 -3
- package/dist/Heading/Heading.js +10 -10
- package/dist/HoverCard/HoverCard.js +9 -9
- package/dist/HoverCard/useHoverCard.d.ts +5 -1
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +3 -3
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +16 -12
- package/dist/Icon/defaultIcons.js +116 -116
- package/dist/IconButton/IconButton.js +3 -3
- package/dist/InputGroup/InputGroup.js +11 -11
- package/dist/InputGroup/InputGroupText.js +3 -3
- package/dist/Item/Item.js +18 -18
- package/dist/Kbd/Kbd.js +5 -5
- package/dist/Layer/LayerProvider.js +7 -7
- package/dist/Layer/useLayer.d.ts +29 -2
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +44 -31
- package/dist/Layout/Layout.js +21 -21
- package/dist/Layout/LayoutContent.js +3 -3
- package/dist/Layout/LayoutFooter.js +5 -5
- package/dist/Layout/LayoutHeader.js +5 -5
- package/dist/Layout/LayoutPanel.js +3 -3
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +55 -31
- package/dist/Lightbox/useLightbox.d.ts +0 -1
- package/dist/Lightbox/useLightbox.d.ts.map +1 -1
- package/dist/Lightbox/useLightbox.js +3 -4
- package/dist/Link/Link.d.ts +0 -2
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +15 -17
- package/dist/Link/LinkProvider.js +3 -3
- package/dist/Link/useLinkify.js +3 -3
- package/dist/List/List.js +11 -11
- package/dist/List/ListItem.js +11 -11
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +102 -102
- package/dist/MetadataList/MetadataList.js +10 -10
- package/dist/MetadataList/MetadataListItem.js +15 -15
- package/dist/MobileNav/MobileNav.d.ts +1 -1
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +26 -23
- package/dist/MobileNav/MobileNavToggle.js +5 -5
- package/dist/MoreMenu/MoreMenu.js +3 -3
- package/dist/MultiSelector/MultiSelector.js +56 -56
- package/dist/NavIcon/NavIcon.js +3 -3
- package/dist/NavMenu/NavHeadingMenu.js +5 -5
- package/dist/NavMenu/NavHeadingMenuItem.js +8 -8
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +48 -32
- package/dist/Outline/Outline.js +15 -15
- package/dist/OverflowList/OverflowList.js +8 -8
- package/dist/Overlay/Overlay.js +3 -3
- package/dist/Overlay/OverlayScrim.js +5 -5
- package/dist/Overlay/useOverlay.d.ts +0 -3
- package/dist/Overlay/useOverlay.d.ts.map +1 -1
- package/dist/Overlay/useOverlay.js +3 -6
- package/dist/Pagination/Pagination.d.ts +1 -1
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +125 -72
- package/dist/Popover/Popover.js +16 -16
- package/dist/Popover/usePopover.js +7 -7
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +110 -92
- package/dist/PowerSearch/PowerSearchEditPopover.js +84 -84
- package/dist/PowerSearch/PowerSearchFilterEditor.js +3 -3
- package/dist/PowerSearch/PowerSearchToken.js +5 -5
- package/dist/PowerSearch/PowerSearchValueEditor.js +54 -54
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +25 -18
- package/dist/RadioList/RadioList.js +7 -7
- package/dist/RadioList/RadioListItem.d.ts +1 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +24 -21
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +28 -10
- package/dist/Section/Section.js +5 -5
- package/dist/SegmentedControl/SegmentedControl.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +32 -10
- package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +18 -12
- package/dist/SelectableCard/SelectableCard.js +5 -5
- package/dist/Selector/Selector.js +46 -46
- package/dist/Selector/SelectorOption.js +3 -3
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +38 -34
- package/dist/SideNav/SideNavCollapseButton.d.ts +1 -1
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +8 -6
- package/dist/SideNav/SideNavHeading.js +87 -87
- package/dist/SideNav/SideNavItem.js +42 -42
- package/dist/SideNav/SideNavSection.d.ts +1 -1
- package/dist/SideNav/SideNavSection.d.ts.map +1 -1
- package/dist/SideNav/SideNavSection.js +18 -16
- package/dist/Skeleton/Skeleton.js +3 -3
- package/dist/Slider/Slider.js +23 -23
- package/dist/Spinner/Spinner.js +9 -9
- package/dist/StatusDot/StatusDot.js +5 -5
- package/dist/Switch/Switch.d.ts +8 -4
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +34 -29
- package/dist/TabList/Tab.js +18 -18
- package/dist/TabList/TabList.js +5 -5
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +57 -30
- package/dist/TabList/tab.markers.stylex.d.ts +1 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +50 -39
- package/dist/Table/Table.js +7 -7
- package/dist/Table/TableBody.d.ts +1 -1
- package/dist/Table/TableBody.d.ts.map +1 -1
- package/dist/Table/TableBody.js +9 -5
- package/dist/Table/TableCell.js +3 -3
- package/dist/Table/TableFooter.d.ts +1 -1
- package/dist/Table/TableFooter.d.ts.map +1 -1
- package/dist/Table/TableFooter.js +9 -5
- package/dist/Table/TableHeader.d.ts +1 -1
- package/dist/Table/TableHeader.d.ts.map +1 -1
- package/dist/Table/TableHeader.js +9 -5
- package/dist/Table/TableHeaderCell.js +3 -3
- package/dist/Table/TableRow.js +5 -5
- package/dist/Table/index.d.ts +2 -0
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +1 -0
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +14 -11
- package/dist/Table/plugins/filtering/useTableFiltering.js +71 -71
- package/dist/Table/plugins/groupedRows/index.d.ts +3 -0
- package/dist/Table/plugins/groupedRows/index.d.ts.map +1 -0
- package/dist/Table/plugins/groupedRows/index.js +3 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts +78 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +250 -0
- package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/paginateData.js +5 -1
- package/dist/Table/plugins/pagination/useTablePagination.js +7 -7
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +23 -23
- package/dist/Table/plugins/selection/useTableSelection.js +18 -18
- package/dist/Table/plugins/selection/useTableSelectionState.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelectionState.js +11 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +17 -17
- package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortableState.js +8 -4
- package/dist/Table/tableContextMenu.js +9 -9
- package/dist/Text/Text.js +10 -10
- package/dist/TextArea/TextArea.js +14 -14
- package/dist/TextInput/TextInput.js +15 -15
- package/dist/Thumbnail/Thumbnail.d.ts +3 -2
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +30 -29
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +26 -21
- package/dist/Timestamp/Timestamp.d.ts +1 -1
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +37 -26
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +25 -17
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +23 -11
- package/dist/Toast/useToast.d.ts.map +1 -1
- package/dist/Toast/useToast.js +44 -4
- package/dist/ToggleButton/ToggleButton.js +12 -12
- package/dist/ToggleButton/ToggleButtonGroup.js +5 -5
- package/dist/Token/Token.js +21 -21
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +36 -34
- package/dist/Toolbar/Toolbar.js +18 -18
- package/dist/Tooltip/Tooltip.js +11 -11
- package/dist/Tooltip/useTooltip.d.ts +5 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +3 -3
- package/dist/TopNav/TopNav.js +32 -32
- package/dist/TopNav/TopNavHeading.js +67 -67
- package/dist/TopNav/TopNavItem.js +5 -5
- package/dist/TopNav/TopNavMegaMenu.js +30 -30
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts +1 -1
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +25 -14
- package/dist/TopNav/TopNavMegaMenuItem.js +16 -16
- package/dist/TopNav/TopNavMenu.js +32 -32
- package/dist/TreeList/TreeList.js +8 -8
- package/dist/TreeList/TreeListBranches.js +11 -11
- package/dist/TreeList/TreeListItem.js +30 -30
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +23 -20
- package/dist/Typeahead/Typeahead.js +12 -12
- package/dist/Typeahead/TypeaheadItem.js +10 -10
- package/dist/VStack/VStack.js +3 -3
- package/dist/VisuallyHidden/VisuallyHidden.d.ts +0 -3
- package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -1
- package/dist/VisuallyHidden/VisuallyHidden.js +0 -3
- package/dist/astryx.css +7 -0
- package/dist/astryx.umd.js +63 -63
- package/dist/astryx.umd.js.map +4 -4
- package/dist/docs-types.d.ts +8 -2
- package/dist/docs-types.d.ts.map +1 -1
- package/dist/hooks/focusableSelector.d.ts +1 -1
- package/dist/hooks/focusableSelector.d.ts.map +1 -1
- package/dist/hooks/focusableSelector.js +1 -1
- 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/useFocusTrap.d.ts +3 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +38 -1
- package/dist/hooks/useHotkeys.d.ts +51 -0
- package/dist/hooks/useHotkeys.d.ts.map +1 -0
- package/dist/hooks/useHotkeys.js +160 -0
- package/dist/hooks/useInteractiveRole.d.ts +6 -2
- package/dist/hooks/useInteractiveRole.d.ts.map +1 -1
- package/dist/hooks/useInteractiveRole.js +6 -2
- package/dist/hooks/useKeyboardHint.js +9 -9
- package/dist/hooks/useStreamingText.d.ts +3 -1
- package/dist/hooks/useStreamingText.d.ts.map +1 -1
- package/dist/hooks/useStreamingText.js +14 -5
- package/dist/theme/MediaTheme.js +3 -3
- package/dist/theme/Theme.js +7 -7
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +10 -3
- package/dist/theme/hct.d.ts +0 -15
- package/dist/theme/hct.d.ts.map +1 -1
- package/dist/theme/hct.js +9 -9
- package/dist/theme/syntax/SyntaxTheme.js +5 -5
- package/dist/theme/tokens.d.ts +10 -2
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +220 -4
- package/dist/theme/useTheme.d.ts +3 -2
- package/dist/theme/useTheme.d.ts.map +1 -1
- package/dist/theme/useTheme.js +73 -6
- package/dist/utils/color.d.ts +42 -0
- package/dist/utils/color.d.ts.map +1 -0
- package/dist/utils/color.js +150 -0
- package/dist/utils/composeEventHandlers.d.ts +30 -0
- package/dist/utils/composeEventHandlers.d.ts.map +1 -0
- package/dist/utils/composeEventHandlers.js +40 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +8 -5
- package/package.json +4 -4
- package/src/AlertDialog/AlertDialog.doc.mjs +4 -0
- package/src/AlertDialog/AlertDialog.tsx +2 -0
- package/src/AlertDialog/useImperativeAlertDialog.tsx +0 -2
- package/src/AppShell/AppShell.tsx +2 -0
- package/src/AspectRatio/AspectRatio.doc.mjs +28 -1
- package/src/AspectRatio/AspectRatio.test.tsx +142 -1
- package/src/AspectRatio/AspectRatio.tsx +70 -10
- package/src/AspectRatio/index.ts +5 -1
- package/src/Avatar/Avatar.doc.mjs +11 -0
- package/src/Avatar/Avatar.test.tsx +32 -1
- package/src/Avatar/Avatar.tsx +14 -22
- package/src/Avatar/AvatarStatusDot.tsx +2 -2
- package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -0
- package/src/AvatarGroup/AvatarGroup.tsx +2 -2
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -2
- package/src/Banner/Banner.doc.mjs +8 -0
- package/src/Banner/Banner.test.tsx +38 -0
- package/src/Banner/Banner.tsx +8 -1
- package/src/Calendar/Calendar.doc.mjs +1 -1
- package/src/Calendar/Calendar.test.tsx +122 -17
- package/src/Calendar/Calendar.tsx +26 -5
- package/src/Calendar/dayCellUtils.ts +3 -1
- package/src/Calendar/styles.ts +12 -2
- package/src/Card/Card.doc.mjs +1 -1
- package/src/Card/Card.tsx +16 -6
- package/src/Carousel/Carousel.doc.mjs +12 -12
- package/src/Carousel/Carousel.test.tsx +13 -0
- package/src/Carousel/Carousel.tsx +4 -2
- package/src/Chat/Chat.doc.mjs +1 -1
- package/src/Chat/ChatComposerInput.test.tsx +27 -1
- package/src/Chat/ChatComposerInput.tsx +20 -15
- package/src/Chat/ChatDictationButton.test.tsx +43 -0
- package/src/Chat/ChatDictationButton.tsx +4 -4
- package/src/Chat/ChatLayoutScrollButton.test.tsx +33 -0
- package/src/Chat/ChatLayoutScrollButton.tsx +3 -1
- package/src/Chat/ChatMessageMetadata.test.tsx +32 -0
- package/src/Chat/ChatMessageMetadata.tsx +4 -6
- package/src/Chat/ChatSystemMessage.test.tsx +26 -11
- package/src/Chat/ChatSystemMessage.tsx +7 -7
- package/src/Chat/ChatTokenizedText.test.tsx +29 -16
- package/src/Chat/ChatTokenizedText.tsx +15 -8
- package/src/Chat/ChatToolCalls.test.tsx +55 -0
- package/src/Chat/ChatToolCalls.tsx +10 -12
- package/src/Chat/index.ts +1 -4
- package/src/Chat/useTriggerMenu.tsx +1 -4
- package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -3
- package/src/CheckboxInput/CheckboxInput.test.tsx +71 -4
- package/src/CheckboxInput/CheckboxInput.tsx +2 -0
- package/src/CheckboxList/CheckboxList.test.tsx +19 -0
- package/src/CheckboxList/CheckboxListItem.tsx +11 -2
- package/src/Citation/Citation.doc.mjs +5 -0
- package/src/ClickableCard/ClickableCard.tsx +45 -2
- package/src/CodeBlock/CodeBlock.doc.mjs +7 -2
- package/src/CodeBlock/CodeBlock.test.tsx +122 -3
- package/src/CodeBlock/CodeBlock.tsx +62 -4
- package/src/Collapsible/Collapsible.doc.mjs +8 -4
- package/src/Collapsible/Collapsible.test.tsx +403 -0
- package/src/Collapsible/Collapsible.tsx +85 -10
- package/src/Collapsible/CollapsibleGroup.doc.mjs +27 -3
- package/src/Collapsible/CollapsibleGroup.test.tsx +314 -0
- package/src/Collapsible/CollapsibleGroup.tsx +97 -9
- package/src/Collapsible/CollapsibleGroupContext.tsx +34 -2
- package/src/Collapsible/index.ts +1 -0
- package/src/Collapsible/useCollapsible.ts +20 -6
- package/src/CommandPalette/CommandPalette.doc.mjs +5 -0
- package/src/CommandPalette/CommandPalette.tsx +5 -3
- package/src/CommandPalette/CommandPaletteEmpty.doc.mjs +5 -0
- package/src/CommandPalette/CommandPaletteGroup.doc.mjs +10 -0
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteList.doc.mjs +9 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +15 -0
- package/src/ContextMenu/ContextMenu.test.tsx +87 -0
- package/src/ContextMenu/ContextMenu.tsx +87 -28
- package/src/ContextMenu/ContextMenuItem.doc.mjs +6 -0
- package/src/DateInput/DateInput.test.tsx +11 -20
- package/src/DateRangeInput/DateRangeInput.doc.mjs +9 -6
- package/src/DateRangeInput/DateRangeInput.test.tsx +31 -44
- package/src/DateTimeInput/DateTimeInput.doc.mjs +3 -2
- package/src/DateTimeInput/DateTimeInput.test.tsx +118 -16
- package/src/DateTimeInput/DateTimeInput.tsx +8 -2
- package/src/DateTimeInput/index.ts +1 -0
- package/src/Dialog/Dialog.doc.mjs +4 -0
- package/src/Dialog/DialogHeader.doc.mjs +45 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -1
- package/src/DropdownMenu/DropdownMenu.test.tsx +22 -2
- package/src/DropdownMenu/DropdownMenuItem.tsx +5 -17
- package/src/EmptyState/EmptyState.doc.mjs +1 -1
- package/src/Field/Field.test.tsx +1 -4
- package/src/Field/FieldLabel.test.tsx +2 -2
- package/src/FieldStatus/FieldStatus.test.tsx +282 -0
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +0 -3
- package/src/Grid/Grid.test.tsx +43 -18
- package/src/Grid/Grid.tsx +26 -9
- package/src/HoverCard/HoverCard.doc.mjs +6 -6
- package/src/HoverCard/useHoverCard.doc.mjs +5 -5
- package/src/HoverCard/useHoverCard.tsx +9 -2
- package/src/Icon/Icon.test.tsx +41 -29
- package/src/Icon/Icon.tsx +6 -2
- package/src/InputGroup/InputGroup.doc.mjs +1 -0
- package/src/InputGroup/InputGroup.tsx +2 -2
- package/src/InteractiveRoleContext/InteractiveRoleContext.test.tsx +185 -0
- package/src/Item/Item.tsx +1 -1
- package/src/Layer/useLayer.doc.mjs +2 -2
- package/src/Layer/useLayer.test.tsx +221 -2
- package/src/Layer/useLayer.tsx +69 -30
- package/src/Layout/Layout.test.tsx +285 -0
- package/src/Layout/LayoutContent.doc.mjs +12 -0
- package/src/Layout/LayoutPanel.doc.mjs +40 -0
- package/src/Layout/LayoutSlots.test.tsx +364 -0
- package/src/Lightbox/Lightbox.doc.mjs +5 -0
- package/src/Lightbox/Lightbox.test.tsx +123 -2
- package/src/Lightbox/Lightbox.tsx +29 -1
- package/src/Lightbox/useLightbox.tsx +0 -1
- package/src/Link/Link.tsx +0 -2
- package/src/Markdown/Markdown.tsx +9 -7
- package/src/Markdown/parser.perf.test.ts +34 -12
- package/src/MobileNav/MobileNav.test.tsx +56 -0
- package/src/MobileNav/MobileNav.tsx +13 -8
- package/src/NavMenu/NavMenu.doc.mjs +15 -0
- package/src/NumberInput/NumberInput.test.tsx +48 -3
- package/src/NumberInput/NumberInput.tsx +32 -15
- package/src/Outline/Outline.doc.mjs +2 -0
- package/src/Outline/parseOutlineFromMarkdown.test.ts +80 -0
- package/src/OverflowList/OverflowList.test.tsx +416 -0
- package/src/Overlay/Overlay.doc.mjs +6 -0
- package/src/Overlay/Overlay.test.tsx +356 -0
- package/src/Overlay/useOverlay.tsx +0 -3
- package/src/Pagination/Pagination.test.tsx +162 -0
- package/src/Pagination/Pagination.tsx +80 -26
- package/src/Popover/Popover.doc.mjs +6 -6
- package/src/Popover/Popover.test.tsx +65 -0
- package/src/Popover/usePopover.doc.mjs +1 -1
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearch.test.tsx +109 -1
- package/src/PowerSearch/PowerSearch.tsx +37 -16
- package/src/ProgressBar/ProgressBar.doc.mjs +2 -0
- package/src/ProgressBar/ProgressBar.test.tsx +30 -0
- package/src/ProgressBar/ProgressBar.tsx +12 -5
- package/src/RadioList/RadioList.test.tsx +19 -0
- package/src/RadioList/RadioListItem.tsx +9 -4
- package/src/Resizable/ResizeHandle.test.tsx +218 -0
- package/src/Resizable/ResizeHandle.tsx +22 -4
- package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
- package/src/SegmentedControl/SegmentedControl.test.tsx +192 -0
- package/src/SegmentedControl/SegmentedControl.tsx +27 -5
- package/src/SegmentedControl/SegmentedControlItem.tsx +9 -4
- package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
- package/src/Selector/Selector.test.tsx +27 -1
- package/src/SideNav/SideNav.doc.mjs +7 -1
- package/src/SideNav/SideNav.test.tsx +66 -0
- package/src/SideNav/SideNav.tsx +6 -2
- package/src/SideNav/SideNavCollapseButton.tsx +4 -3
- package/src/SideNav/SideNavSection.tsx +6 -4
- package/src/SizeContext/SizeContext.test.tsx +226 -0
- package/src/Switch/Switch.doc.mjs +7 -7
- package/src/Switch/Switch.test.tsx +102 -3
- package/src/Switch/Switch.tsx +20 -7
- package/src/TabList/TabList.test.tsx +185 -1
- package/src/TabList/TabMenu.tsx +45 -9
- package/src/Table/BaseTable.tsx +13 -2
- package/src/Table/Table.doc.mjs +6 -1
- package/src/Table/Table.test.tsx +87 -0
- package/src/Table/TableBody.tsx +15 -2
- package/src/Table/TableFooter.tsx +15 -2
- package/src/Table/TableHeader.tsx +15 -2
- package/src/Table/index.ts +5 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +13 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +3 -1
- package/src/Table/plugins/groupedRows/index.ts +7 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +231 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +349 -0
- package/src/Table/plugins/pagination/paginateData.ts +5 -1
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +13 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +1 -1
- package/src/Table/plugins/selection/useTableSelectionState.test.tsx +23 -0
- package/src/Table/plugins/selection/useTableSelectionState.tsx +11 -6
- package/src/Table/plugins/sortable/useTableSortableState.test.tsx +35 -1
- package/src/Table/plugins/sortable/useTableSortableState.tsx +8 -4
- package/src/Table/useTableGroupedRows.doc.mjs +71 -0
- package/src/TextArea/TextArea.test.tsx +3 -3
- package/src/TextInput/TextInput.test.tsx +3 -3
- package/src/Thumbnail/Thumbnail.test.tsx +36 -2
- package/src/Thumbnail/Thumbnail.tsx +4 -2
- package/src/TimeInput/TimeInput.test.tsx +37 -0
- package/src/TimeInput/TimeInput.tsx +11 -1
- package/src/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/Timestamp/Timestamp.test.tsx +52 -0
- package/src/Timestamp/Timestamp.tsx +30 -16
- package/src/Toast/Toast.tsx +11 -3
- package/src/Toast/ToastViewport.test.tsx +64 -0
- package/src/Toast/ToastViewport.tsx +12 -0
- package/src/Toast/useToast.test.tsx +258 -0
- package/src/Toast/useToast.tsx +41 -0
- package/src/ToggleButton/ToggleButton.doc.mjs +1 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
- package/src/Tokenizer/Tokenizer.test.tsx +54 -0
- package/src/Tokenizer/Tokenizer.tsx +5 -4
- package/src/Tooltip/Tooltip.doc.mjs +6 -6
- package/src/Tooltip/useTooltip.doc.mjs +5 -5
- package/src/Tooltip/useTooltip.tsx +12 -2
- package/src/TopNav/TopNav.doc.mjs +1 -1
- package/src/TopNav/TopNav.test.tsx +23 -7
- package/src/TopNav/TopNavMegaMenuFeaturedCard.tsx +9 -2
- package/src/TreeList/TreeList.doc.mjs +2 -2
- package/src/Typeahead/BaseTypeahead.doc.mjs +19 -0
- package/src/Typeahead/BaseTypeahead.tsx +5 -2
- package/src/Typeahead/Typeahead.doc.mjs +1 -1
- package/src/Typeahead/Typeahead.test.tsx +45 -1
- package/src/VisuallyHidden/VisuallyHidden.tsx +0 -3
- package/src/__tests__/TestIcon.tsx +26 -0
- package/src/__tests__/fastRoleQueries.ts +66 -0
- package/src/docPropReferences.test.ts +178 -0
- package/src/docs-types.ts +9 -1
- package/src/hooks/focusableSelector.ts +1 -1
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useFocusTrap.doc.mjs +4 -2
- package/src/hooks/useFocusTrap.test.tsx +149 -1
- package/src/hooks/useFocusTrap.ts +50 -1
- package/src/hooks/useHotkeys.doc.mjs +52 -0
- package/src/hooks/useHotkeys.test.ts +245 -0
- package/src/hooks/useHotkeys.ts +203 -0
- package/src/hooks/useInteractiveRole.ts +12 -4
- package/src/hooks/useStreamingText.test.ts +54 -19
- package/src/hooks/useStreamingText.ts +16 -5
- package/src/hooks/useTreeFocus.test.tsx +1 -3
- package/src/reset.css +29 -0
- package/src/theme/SyntaxTheme.doc.mjs +4 -4
- package/src/theme/defineTheme.ts +4 -13
- package/src/theme/derivedVarRegistry.test.ts +2 -2
- package/src/theme/expandColorScale.test.ts +29 -0
- package/src/theme/expandColorScale.ts +11 -6
- package/src/theme/hct.ts +9 -17
- package/src/theme/themingTargets.test.ts +353 -0
- package/src/theme/tokens.test.ts +182 -9
- package/src/theme/tokens.ts +239 -4
- package/src/theme/useTheme.test.tsx +126 -2
- package/src/theme/useTheme.ts +87 -6
- package/src/utils/color.test.ts +133 -0
- package/src/utils/color.ts +148 -0
- package/src/utils/composeEventHandlers.test.ts +71 -0
- package/src/utils/composeEventHandlers.ts +44 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/mergeProps.test.ts +166 -0
- package/src/utils/sharedResizeObserver.test.ts +2 -2
- package/src/utils/timeParser.test.ts +13 -0
- package/src/utils/timeParser.ts +8 -5
- package/dist/Carousel/carousel.markers.stylex.d.ts +0 -7
- package/dist/Carousel/carousel.markers.stylex.d.ts.map +0 -1
- package/dist/Carousel/carousel.markers.stylex.js +0 -12
- package/src/Carousel/carousel.markers.stylex.ts +0 -10
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useFocusTrap.ts
|
|
7
7
|
* @input Uses React useCallback, useEffect, useRef
|
|
8
|
-
* @output Exports useFocusTrap hook for trapping focus within a container
|
|
8
|
+
* @output Exports useFocusTrap hook for trapping focus within a container and
|
|
9
|
+
* restoring focus to the previously-focused element on deactivation
|
|
9
10
|
* @position Core hook; used by dialogs, modals, date pickers
|
|
10
11
|
*
|
|
11
12
|
* Based on WAI-ARIA dialog pattern:
|
|
@@ -178,6 +179,9 @@ export interface UseFocusTrapReturn<T extends HTMLElement = HTMLElement> {
|
|
|
178
179
|
* - Listens to focus events on the document
|
|
179
180
|
* - Redirects focus back into the container if it escapes
|
|
180
181
|
* - Handles both Tab and Shift+Tab navigation
|
|
182
|
+
* - Restores focus to the element that was focused before activation when the
|
|
183
|
+
* trap deactivates or unmounts, unless focus was already moved elsewhere
|
|
184
|
+
* (so consumers that restore focus themselves are unaffected)
|
|
181
185
|
*
|
|
182
186
|
* @example
|
|
183
187
|
* ```
|
|
@@ -217,6 +221,51 @@ export function useFocusTrap<T extends HTMLElement = HTMLElement>(
|
|
|
217
221
|
}
|
|
218
222
|
}, []);
|
|
219
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Capture the element focused before the trap activated, and restore focus to
|
|
226
|
+
* it when the trap deactivates (or the component unmounts). Overlays are
|
|
227
|
+
* opened imperatively (e.g. `showPopover()`), so the browser's declarative
|
|
228
|
+
* popover focus restoration does not apply — without this, closing a Popover
|
|
229
|
+
* via Escape or light dismiss drops keyboard focus to `<body>`.
|
|
230
|
+
*
|
|
231
|
+
* The restore is guarded so it never steals focus a consumer moved on
|
|
232
|
+
* purpose: it only runs when focus would otherwise be lost — i.e. the active
|
|
233
|
+
* element is nothing, the document body/root, or still inside the (possibly
|
|
234
|
+
* now-unmounted) trap container. If focus already moved to some other element
|
|
235
|
+
* outside the trap (the user clicked elsewhere, or a consumer such as
|
|
236
|
+
* DropdownMenu already refocused its trigger), the restore is a no-op.
|
|
237
|
+
*/
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
if (!isActive) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const previouslyFocused = document.activeElement as HTMLElement | null;
|
|
244
|
+
// Snapshot the container now; by cleanup it may be detached or unmounted.
|
|
245
|
+
const container = containerRef.current;
|
|
246
|
+
|
|
247
|
+
return () => {
|
|
248
|
+
const active = document.activeElement;
|
|
249
|
+
const focusWasLost =
|
|
250
|
+
active == null ||
|
|
251
|
+
active === document.body ||
|
|
252
|
+
active === document.documentElement ||
|
|
253
|
+
(container != null && container.contains(active));
|
|
254
|
+
|
|
255
|
+
if (!focusWasLost) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (
|
|
260
|
+
previouslyFocused != null &&
|
|
261
|
+
previouslyFocused.isConnected &&
|
|
262
|
+
typeof previouslyFocused.focus === 'function'
|
|
263
|
+
) {
|
|
264
|
+
previouslyFocused.focus();
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}, [isActive]);
|
|
268
|
+
|
|
220
269
|
/**
|
|
221
270
|
* Handle focus events - redirect focus back into container if it escapes.
|
|
222
271
|
* Only redirects for keyboard navigation, not mouse clicks.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../docs-types').HookDoc} */
|
|
4
|
+
export const docs = {
|
|
5
|
+
name: 'useHotkeys',
|
|
6
|
+
displayName: 'useHotkeys',
|
|
7
|
+
keywords: ['hotkey', 'shortcut', 'keyboard', 'keydown', 'command', 'palette', 'mod', 'cmd', 'ctrl', 'kbd'],
|
|
8
|
+
params: [
|
|
9
|
+
{
|
|
10
|
+
name: 'hotkeys',
|
|
11
|
+
type: 'Hotkey[]',
|
|
12
|
+
description:
|
|
13
|
+
'Shortcut registrations. Each entry: `{ keys, onPress, allowInInputs?, isDisabled? }`. `keys` is a "+"-separated combo like "mod+k", "shift+/", "escape" — "mod" is ⌘ on macOS and Ctrl elsewhere. `onPress` receives the KeyboardEvent after preventDefault(). `allowInInputs` (default false) lets the hotkey fire while typing in inputs/textareas/selects/contenteditable. `isDisabled` temporarily disables the entry.',
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
returns: [],
|
|
18
|
+
usage: {
|
|
19
|
+
description:
|
|
20
|
+
'Registers global keyboard shortcuts with a single window keydown listener per hook instance. Handlers live in a ref, so re-renders never re-subscribe. Skips events from typing targets (input, textarea, select, contenteditable) unless allowInInputs, skips defaultPrevented events, and calls preventDefault() on match. SSR-safe.',
|
|
21
|
+
bestPractices: [
|
|
22
|
+
{ guidance: true, description: 'Use for app-level shortcuts like command palettes (mod+k), help overlays (shift+/), and navigation keys.' },
|
|
23
|
+
{ guidance: true, description: 'Pair with the Kbd component to display the same combo you register — both resolve "mod" per platform identically.' },
|
|
24
|
+
{ guidance: true, description: 'Use isDisabled to suspend shortcuts while a modal or wizard owns the keyboard, instead of unmounting the hook.' },
|
|
25
|
+
{ guidance: false, description: 'Use for focus-scoped keyboard navigation inside a widget; use useListFocus or useGridFocus instead.' },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
relatedComponents: ['Kbd'],
|
|
29
|
+
relatedHooks: ['useListFocus', 'useGridFocus'],
|
|
30
|
+
importPath: '@astryxdesign/core/hooks',
|
|
31
|
+
category: 'interaction',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** @type {import('../docs-types').HookTranslationDoc} */
|
|
35
|
+
export const docsDense = {
|
|
36
|
+
description:
|
|
37
|
+
'Global keyboard shortcuts w/ one window keydown listener per hook instance. Handlers kept in ref — re-renders never re-subscribe. Skips typing targets (input/textarea/select/contenteditable) unless allowInInputs, skips defaultPrevented, preventDefault() on match. "mod" = ⌘ on macOS, Ctrl elsewhere. SSR-safe.',
|
|
38
|
+
paramDescriptions: {
|
|
39
|
+
hotkeys:
|
|
40
|
+
'array of { keys, onPress, allowInInputs?, isDisabled? }. keys = "+"-separated combo ("mod+k", "shift+/", "escape").',
|
|
41
|
+
},
|
|
42
|
+
usage: {
|
|
43
|
+
description:
|
|
44
|
+
'Global keyboard shortcuts w/ one window keydown listener per hook instance. Handlers kept in ref — re-renders never re-subscribe. Skips typing targets unless allowInInputs, skips defaultPrevented, preventDefault() on match. SSR-safe.',
|
|
45
|
+
bestPractices: [
|
|
46
|
+
{ guidance: true, description: 'Use for app-level shortcuts: command palettes (mod+k), help overlays (shift+/), navigation keys.' },
|
|
47
|
+
{ guidance: true, description: 'Pair w/ Kbd component to display same combo registered — both resolve "mod" per platform identically.' },
|
|
48
|
+
{ guidance: true, description: 'Use isDisabled to suspend shortcuts while modal/wizard owns keyboard, instead of unmounting hook.' },
|
|
49
|
+
{ guidance: false, description: 'Use for focus-scoped keyboard nav inside widget; use useListFocus / useGridFocus instead.' },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file useHotkeys.test.ts
|
|
5
|
+
* @input Uses vitest, @testing-library/react, useHotkeys hook
|
|
6
|
+
* @output Unit tests for useHotkeys hook
|
|
7
|
+
* @position Testing; validates useHotkeys.ts implementation
|
|
8
|
+
*
|
|
9
|
+
* SYNC: When useHotkeys.ts changes, update tests to match new behavior
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {describe, it, expect, vi, afterEach} from 'vitest';
|
|
13
|
+
import {renderHook} from '@testing-library/react';
|
|
14
|
+
import {useHotkeys, type Hotkey} from './useHotkeys';
|
|
15
|
+
|
|
16
|
+
function stubApplePlatform() {
|
|
17
|
+
// jsdom's navigator has no userAgentData; platform drives detection.
|
|
18
|
+
vi.stubGlobal('navigator', {platform: 'MacIntel'});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function stubOtherPlatform() {
|
|
22
|
+
vi.stubGlobal('navigator', {platform: 'Win32'});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function press(
|
|
26
|
+
key: string,
|
|
27
|
+
init: KeyboardEventInit & {target?: HTMLElement} = {},
|
|
28
|
+
): KeyboardEvent {
|
|
29
|
+
const {target, ...eventInit} = init;
|
|
30
|
+
const event = new KeyboardEvent('keydown', {
|
|
31
|
+
key,
|
|
32
|
+
bubbles: true,
|
|
33
|
+
cancelable: true,
|
|
34
|
+
...eventInit,
|
|
35
|
+
});
|
|
36
|
+
(target ?? window).dispatchEvent(event);
|
|
37
|
+
return event;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
vi.unstubAllGlobals();
|
|
42
|
+
vi.restoreAllMocks();
|
|
43
|
+
document.body.innerHTML = '';
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('useHotkeys', () => {
|
|
47
|
+
it('fires on mod+k via metaKey on Apple platforms', () => {
|
|
48
|
+
stubApplePlatform();
|
|
49
|
+
const onPress = vi.fn();
|
|
50
|
+
renderHook(() => useHotkeys([{keys: 'mod+k', onPress}]));
|
|
51
|
+
|
|
52
|
+
const event = press('k', {metaKey: true});
|
|
53
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
54
|
+
expect(onPress).toHaveBeenCalledWith(event);
|
|
55
|
+
expect(event.defaultPrevented).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('maps mod to ctrlKey on non-Apple platforms', () => {
|
|
59
|
+
stubOtherPlatform();
|
|
60
|
+
const onPress = vi.fn();
|
|
61
|
+
renderHook(() => useHotkeys([{keys: 'mod+k', onPress}]));
|
|
62
|
+
|
|
63
|
+
press('k', {metaKey: true});
|
|
64
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
65
|
+
|
|
66
|
+
press('k', {ctrlKey: true});
|
|
67
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('does not fire a bare key when modifiers are held', () => {
|
|
71
|
+
stubApplePlatform();
|
|
72
|
+
const onPress = vi.fn();
|
|
73
|
+
renderHook(() => useHotkeys([{keys: 'k', onPress}]));
|
|
74
|
+
|
|
75
|
+
press('k', {metaKey: true});
|
|
76
|
+
press('k', {ctrlKey: true});
|
|
77
|
+
press('k', {altKey: true});
|
|
78
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
79
|
+
|
|
80
|
+
press('k');
|
|
81
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('matches named keys: escape, enter, space, arrows', () => {
|
|
85
|
+
stubApplePlatform();
|
|
86
|
+
const onEscape = vi.fn();
|
|
87
|
+
const onEnter = vi.fn();
|
|
88
|
+
const onSpace = vi.fn();
|
|
89
|
+
const onDown = vi.fn();
|
|
90
|
+
renderHook(() =>
|
|
91
|
+
useHotkeys([
|
|
92
|
+
{keys: 'escape', onPress: onEscape},
|
|
93
|
+
{keys: 'enter', onPress: onEnter},
|
|
94
|
+
{keys: 'space', onPress: onSpace},
|
|
95
|
+
{keys: 'down', onPress: onDown},
|
|
96
|
+
]),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
press('Escape');
|
|
100
|
+
press('Enter');
|
|
101
|
+
press(' ');
|
|
102
|
+
press('ArrowDown');
|
|
103
|
+
expect(onEscape).toHaveBeenCalledTimes(1);
|
|
104
|
+
expect(onEnter).toHaveBeenCalledTimes(1);
|
|
105
|
+
expect(onSpace).toHaveBeenCalledTimes(1);
|
|
106
|
+
expect(onDown).toHaveBeenCalledTimes(1);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('requires shift when specified and ignores shift otherwise', () => {
|
|
110
|
+
stubApplePlatform();
|
|
111
|
+
const onSlash = vi.fn();
|
|
112
|
+
const onLetter = vi.fn();
|
|
113
|
+
renderHook(() =>
|
|
114
|
+
useHotkeys([
|
|
115
|
+
{keys: 'shift+/', onPress: onSlash},
|
|
116
|
+
{keys: 'c', onPress: onLetter},
|
|
117
|
+
]),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
press('/');
|
|
121
|
+
expect(onSlash).not.toHaveBeenCalled();
|
|
122
|
+
press('/', {shiftKey: true});
|
|
123
|
+
expect(onSlash).toHaveBeenCalledTimes(1);
|
|
124
|
+
|
|
125
|
+
// Shift not specified → matches regardless of shift state.
|
|
126
|
+
press('C', {shiftKey: true});
|
|
127
|
+
press('c');
|
|
128
|
+
expect(onLetter).toHaveBeenCalledTimes(2);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('skips typing targets by default', () => {
|
|
132
|
+
stubApplePlatform();
|
|
133
|
+
const onPress = vi.fn();
|
|
134
|
+
renderHook(() => useHotkeys([{keys: 'mod+k', onPress}]));
|
|
135
|
+
|
|
136
|
+
const input = document.createElement('input');
|
|
137
|
+
document.body.appendChild(input);
|
|
138
|
+
press('k', {metaKey: true, target: input});
|
|
139
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
140
|
+
|
|
141
|
+
const textarea = document.createElement('textarea');
|
|
142
|
+
document.body.appendChild(textarea);
|
|
143
|
+
press('k', {metaKey: true, target: textarea});
|
|
144
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('fires in typing targets when allowInInputs is true', () => {
|
|
148
|
+
stubApplePlatform();
|
|
149
|
+
const onPress = vi.fn();
|
|
150
|
+
renderHook(() =>
|
|
151
|
+
useHotkeys([{keys: 'escape', onPress, allowInInputs: true}]),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const input = document.createElement('input');
|
|
155
|
+
document.body.appendChild(input);
|
|
156
|
+
press('Escape', {target: input});
|
|
157
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('respects isDisabled', () => {
|
|
161
|
+
stubApplePlatform();
|
|
162
|
+
const onPress = vi.fn();
|
|
163
|
+
const {rerender} = renderHook(
|
|
164
|
+
({isDisabled}: {isDisabled: boolean}) =>
|
|
165
|
+
useHotkeys([{keys: 'mod+k', onPress, isDisabled}]),
|
|
166
|
+
{initialProps: {isDisabled: true}},
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
press('k', {metaKey: true});
|
|
170
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
171
|
+
|
|
172
|
+
rerender({isDisabled: false});
|
|
173
|
+
press('k', {metaKey: true});
|
|
174
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('skips events that are already defaultPrevented', () => {
|
|
178
|
+
stubApplePlatform();
|
|
179
|
+
const onPress = vi.fn();
|
|
180
|
+
renderHook(() => useHotkeys([{keys: 'mod+k', onPress}]));
|
|
181
|
+
|
|
182
|
+
const event = new KeyboardEvent('keydown', {
|
|
183
|
+
key: 'k',
|
|
184
|
+
metaKey: true,
|
|
185
|
+
bubbles: true,
|
|
186
|
+
cancelable: true,
|
|
187
|
+
});
|
|
188
|
+
event.preventDefault();
|
|
189
|
+
window.dispatchEvent(event);
|
|
190
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('unsubscribes on unmount', () => {
|
|
194
|
+
stubApplePlatform();
|
|
195
|
+
const onPress = vi.fn();
|
|
196
|
+
const removeSpy = vi.spyOn(window, 'removeEventListener');
|
|
197
|
+
const {unmount} = renderHook(() => useHotkeys([{keys: 'mod+k', onPress}]));
|
|
198
|
+
|
|
199
|
+
unmount();
|
|
200
|
+
expect(removeSpy).toHaveBeenCalledWith('keydown', expect.any(Function));
|
|
201
|
+
|
|
202
|
+
press('k', {metaKey: true});
|
|
203
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('does not re-subscribe on re-render, but uses latest handlers', () => {
|
|
207
|
+
stubApplePlatform();
|
|
208
|
+
const addSpy = vi.spyOn(window, 'addEventListener');
|
|
209
|
+
const first = vi.fn();
|
|
210
|
+
const second = vi.fn();
|
|
211
|
+
|
|
212
|
+
const {rerender} = renderHook(
|
|
213
|
+
({hotkeys}: {hotkeys: Hotkey[]}) => useHotkeys(hotkeys),
|
|
214
|
+
{initialProps: {hotkeys: [{keys: 'mod+k', onPress: first}]}},
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const keydownSubscriptions = () =>
|
|
218
|
+
addSpy.mock.calls.filter(([type]) => type === 'keydown').length;
|
|
219
|
+
const initialCount = keydownSubscriptions();
|
|
220
|
+
|
|
221
|
+
rerender({hotkeys: [{keys: 'mod+k', onPress: second}]});
|
|
222
|
+
rerender({hotkeys: [{keys: 'mod+k', onPress: second}]});
|
|
223
|
+
expect(keydownSubscriptions()).toBe(initialCount);
|
|
224
|
+
|
|
225
|
+
press('k', {metaKey: true});
|
|
226
|
+
expect(first).not.toHaveBeenCalled();
|
|
227
|
+
expect(second).toHaveBeenCalledTimes(1);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('only fires the first matching hotkey per event', () => {
|
|
231
|
+
stubApplePlatform();
|
|
232
|
+
const first = vi.fn();
|
|
233
|
+
const second = vi.fn();
|
|
234
|
+
renderHook(() =>
|
|
235
|
+
useHotkeys([
|
|
236
|
+
{keys: 'mod+k', onPress: first},
|
|
237
|
+
{keys: 'mod+k', onPress: second},
|
|
238
|
+
]),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
press('k', {metaKey: true});
|
|
242
|
+
expect(first).toHaveBeenCalledTimes(1);
|
|
243
|
+
expect(second).not.toHaveBeenCalled();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useHotkeys.ts
|
|
7
|
+
* @input Uses React useEffect, useRef
|
|
8
|
+
* @output Exports useHotkeys hook and Hotkey interface for global keyboard shortcuts
|
|
9
|
+
* @position Core hook; used by consumers for app-level keyboard shortcuts
|
|
10
|
+
*
|
|
11
|
+
* Registers a single window keydown listener per hook instance. Handlers
|
|
12
|
+
* are kept in a ref so re-renders never re-subscribe (subscribe once on
|
|
13
|
+
* mount, unsubscribe on unmount). SSR-safe — window is only touched
|
|
14
|
+
* inside the effect.
|
|
15
|
+
*
|
|
16
|
+
* Skips events that are already handled (defaultPrevented) and events
|
|
17
|
+
* targeting typing surfaces (input, textarea, select, contenteditable)
|
|
18
|
+
* unless a hotkey opts in via `allowInInputs`.
|
|
19
|
+
*
|
|
20
|
+
* Platform-aware: `mod` maps to metaKey (⌘) on Apple platforms and
|
|
21
|
+
* ctrlKey elsewhere — mirrors the detection used by Kbd.
|
|
22
|
+
*
|
|
23
|
+
* SYNC: When modified, update:
|
|
24
|
+
* - /packages/core/src/hooks/index.ts
|
|
25
|
+
* - /packages/core/src/hooks/useHotkeys.doc.mjs
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import {useEffect, useRef} from 'react';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A single keyboard shortcut registration.
|
|
32
|
+
*/
|
|
33
|
+
export interface Hotkey {
|
|
34
|
+
/**
|
|
35
|
+
* Key combo, e.g. 'mod+k', 'shift+/', 'c', 'escape'.
|
|
36
|
+
* 'mod' = ⌘ on macOS, Ctrl elsewhere.
|
|
37
|
+
*
|
|
38
|
+
* Format: '+'-separated modifiers (mod, ctrl, meta, shift, alt) followed
|
|
39
|
+
* by a final key compared case-insensitively against event.key.
|
|
40
|
+
* Named keys: 'escape', 'enter', 'space', 'up', 'down', 'left', 'right'
|
|
41
|
+
* (arrow names map to ArrowUp/ArrowDown/ArrowLeft/ArrowRight).
|
|
42
|
+
* Use 'plus' for a literal '+' key.
|
|
43
|
+
*
|
|
44
|
+
* Shift semantics: when 'shift' is not specified, shift state is ignored,
|
|
45
|
+
* so single letters match regardless of shift. When 'shift' is specified,
|
|
46
|
+
* shiftKey must be pressed and the final key is still compared against
|
|
47
|
+
* event.key — for punctuation that shift transforms (e.g. shift+/ produces
|
|
48
|
+
* '?' on US layouts), prefer registering the produced character instead.
|
|
49
|
+
*/
|
|
50
|
+
keys: string;
|
|
51
|
+
/** Called when the combo matches. The event has preventDefault() applied. */
|
|
52
|
+
onPress: (event: KeyboardEvent) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Fire even when focus is in an input/textarea/select/contenteditable.
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
allowInInputs?: boolean;
|
|
58
|
+
/** Temporarily disable this hotkey without unregistering it. */
|
|
59
|
+
isDisabled?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Aliases for the final key of a combo, normalized to event.key values. */
|
|
63
|
+
const KEY_ALIASES: Record<string, string> = {
|
|
64
|
+
esc: 'escape',
|
|
65
|
+
space: ' ',
|
|
66
|
+
up: 'arrowup',
|
|
67
|
+
down: 'arrowdown',
|
|
68
|
+
left: 'arrowleft',
|
|
69
|
+
right: 'arrowright',
|
|
70
|
+
return: 'enter',
|
|
71
|
+
plus: '+',
|
|
72
|
+
};
|
|
73
|
+
|
|
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
|
+
/**
|
|
92
|
+
* Whether the event target is a typing surface where global shortcuts
|
|
93
|
+
* should stay silent by default.
|
|
94
|
+
*/
|
|
95
|
+
function isTypingTarget(target: EventTarget | null): boolean {
|
|
96
|
+
if (!(target instanceof HTMLElement)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (target.isContentEditable) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
const tag = target.tagName;
|
|
103
|
+
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Whether a keydown event matches a '+'-separated combo string.
|
|
108
|
+
*/
|
|
109
|
+
function matchesCombo(
|
|
110
|
+
keys: string,
|
|
111
|
+
event: KeyboardEvent,
|
|
112
|
+
isApple: boolean,
|
|
113
|
+
): boolean {
|
|
114
|
+
const parts = keys
|
|
115
|
+
.toLowerCase()
|
|
116
|
+
.split('+')
|
|
117
|
+
.map(part => part.trim())
|
|
118
|
+
.filter(Boolean);
|
|
119
|
+
if (parts.length === 0) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const key = parts[parts.length - 1];
|
|
124
|
+
const mods = new Set(parts.slice(0, -1));
|
|
125
|
+
|
|
126
|
+
const wantsMod = mods.has('mod');
|
|
127
|
+
const wantsCtrl = mods.has('ctrl') || (wantsMod && !isApple);
|
|
128
|
+
const wantsMeta = mods.has('meta') || (wantsMod && isApple);
|
|
129
|
+
const wantsAlt = mods.has('alt');
|
|
130
|
+
const wantsShift = mods.has('shift');
|
|
131
|
+
|
|
132
|
+
// Non-shift modifiers must match exactly so 'k' doesn't fire on 'mod+k'.
|
|
133
|
+
if (event.ctrlKey !== wantsCtrl) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
if (event.metaKey !== wantsMeta) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (event.altKey !== wantsAlt) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
// Shift is only enforced when specified — see Hotkey.keys docs.
|
|
143
|
+
if (wantsShift && !event.shiftKey) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const expected = KEY_ALIASES[key] ?? key;
|
|
148
|
+
return event.key.toLowerCase() === expected;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Registers global keyboard shortcuts on window.
|
|
153
|
+
*
|
|
154
|
+
* A single keydown listener is attached per hook instance; the hotkey
|
|
155
|
+
* definitions live in a ref, so re-renders update behavior without
|
|
156
|
+
* re-subscribing. First matching hotkey wins per event.
|
|
157
|
+
*
|
|
158
|
+
* @param hotkeys - Shortcut registrations to listen for.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```
|
|
162
|
+
* useHotkeys([
|
|
163
|
+
* { keys: 'mod+k', onPress: () => openCommandPalette() },
|
|
164
|
+
* { keys: 'escape', onPress: () => closePanel(), allowInInputs: true },
|
|
165
|
+
* { keys: 'c', onPress: () => compose(), isDisabled: isModalOpen },
|
|
166
|
+
* ]);
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
export function useHotkeys(hotkeys: Hotkey[]): void {
|
|
170
|
+
const hotkeysRef = useRef(hotkeys);
|
|
171
|
+
|
|
172
|
+
// Keep the latest definitions available to the stable listener.
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
hotkeysRef.current = hotkeys;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
const isApple = isApplePlatform();
|
|
179
|
+
|
|
180
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
181
|
+
if (event.defaultPrevented) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const isTyping = isTypingTarget(event.target);
|
|
185
|
+
for (const hotkey of hotkeysRef.current) {
|
|
186
|
+
if (hotkey.isDisabled) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (isTyping && !hotkey.allowInInputs) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (matchesCombo(hotkey.keys, event, isApple)) {
|
|
193
|
+
event.preventDefault();
|
|
194
|
+
hotkey.onPress(event);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
201
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
202
|
+
}, []);
|
|
203
|
+
}
|
|
@@ -11,11 +11,14 @@
|
|
|
11
11
|
* Centralizes the "what element should I render as?" decision for
|
|
12
12
|
* polymorphic components. The priority order:
|
|
13
13
|
*
|
|
14
|
-
* 1. href → 'link' (navigation always wins)
|
|
14
|
+
* 1. href → 'link' (navigation always wins, unless disabled)
|
|
15
15
|
* 2. onClick → 'button' (explicit interactivity)
|
|
16
16
|
* 3. interactive trigger context → 'button' (implicit via parent)
|
|
17
17
|
* 4. else → 'inert' (non-interactive)
|
|
18
18
|
*
|
|
19
|
+
* A disabled `href` is excluded at step 1 and therefore resolves via the
|
|
20
|
+
* remaining steps — 'inert' when no onClick or context applies.
|
|
21
|
+
*
|
|
19
22
|
* This hook is the single place to add new context-based triggers
|
|
20
23
|
* (e.g., Popover, DropdownMenu, Disclosure). Components that consume
|
|
21
24
|
* this hook never need updating when new trigger contexts are added.
|
|
@@ -49,8 +52,12 @@ export interface UseInteractiveRoleOptions {
|
|
|
49
52
|
onClick?: ((...args: never[]) => unknown) | null;
|
|
50
53
|
|
|
51
54
|
/**
|
|
52
|
-
* Whether the component is disabled. When true
|
|
53
|
-
*
|
|
55
|
+
* Whether the component is disabled. When true, an `href` is ignored for
|
|
56
|
+
* role resolution (a disabled link is an a11y anti-pattern), so the role is
|
|
57
|
+
* decided by the remaining inputs: `onClick` → `'button'`, an interactive
|
|
58
|
+
* context → its role, otherwise `'inert'`. In particular a disabled `href`
|
|
59
|
+
* with no `onClick` and no context override resolves to `'inert'`, not a
|
|
60
|
+
* link or a button.
|
|
54
61
|
* @default false
|
|
55
62
|
*/
|
|
56
63
|
isDisabled?: boolean;
|
|
@@ -83,7 +90,8 @@ export function useInteractiveRole({
|
|
|
83
90
|
}: UseInteractiveRoleOptions): InteractiveRole {
|
|
84
91
|
const contextRole = useInteractiveRoleContext();
|
|
85
92
|
|
|
86
|
-
// 1. href → link (unless disabled — disabled
|
|
93
|
+
// 1. href → link (unless disabled — a disabled href is skipped here and
|
|
94
|
+
// resolved by the checks below, landing on 'inert' if nothing else applies)
|
|
87
95
|
if (href != null && !isDisabled) {
|
|
88
96
|
return 'link';
|
|
89
97
|
}
|