@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
|
@@ -8,6 +8,16 @@ export const docs = {
|
|
|
8
8
|
displayName: 'Base Typeahead',
|
|
9
9
|
isHiddenFromOverview: true,
|
|
10
10
|
description: 'Unstyled combobox engine providing input, search, keyboard navigation, and dropdown. No wrapper div, no border styling, no token rendering. Used by Typeahead and Tokenizer for custom compositions.',
|
|
11
|
+
usage: {
|
|
12
|
+
description: 'Unstyled combobox engine providing input, search, keyboard navigation, and dropdown. No wrapper div, no border styling, no token rendering. Used by Typeahead and Tokenizer for custom compositions.',
|
|
13
|
+
bestPractices: [
|
|
14
|
+
{ guidance: true, description: 'Use Typeahead or Tokenizer for standard fields; they wrap BaseTypeahead with the wrapper div, border styling, and token rendering it intentionally omits.' },
|
|
15
|
+
{ guidance: true, description: 'Provide your own wrapper div with border and layout when composing directly, since BaseTypeahead renders no visual chrome of its own.' },
|
|
16
|
+
{ guidance: true, description: 'Pass anchorRef pointing to your wrapper so the dropdown positions against your custom input chrome, not just the bare input element.' },
|
|
17
|
+
{ guidance: false, description: 'Expect a wrapper div, border, or token rendering. BaseTypeahead is an engine only; all visual chrome is the caller\'s responsibility.' },
|
|
18
|
+
{ guidance: false, description: 'Use BaseTypeahead when Typeahead or Tokenizer would suffice; the extra wrapper and styling work is only justified for truly custom compositions.' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
11
21
|
props: [
|
|
12
22
|
{
|
|
13
23
|
name: 'searchSource',
|
|
@@ -226,6 +236,15 @@ export const docsDense = {
|
|
|
226
236
|
isHiddenFromOverview: true,
|
|
227
237
|
displayName: 'Base Typeahead',
|
|
228
238
|
description: 'Unstyled combobox engine; input+search+keyboard nav+dropdown. No wrapper/border/token. Used by Typeahead+Tokenizer.',
|
|
239
|
+
usage: {
|
|
240
|
+
bestPractices: [
|
|
241
|
+
{ guidance: true, description: 'Use Typeahead or Tokenizer for standard fields; they add the wrapper, border, and token rendering BaseTypeahead omits.' },
|
|
242
|
+
{ guidance: true, description: 'Provide your own wrapper div with border and layout when composing directly; BaseTypeahead renders no chrome.' },
|
|
243
|
+
{ guidance: true, description: 'Pass anchorRef to your wrapper so the dropdown positions against your input chrome, not the bare input.' },
|
|
244
|
+
{ guidance: false, description: 'Expect wrapper, border, or token rendering. Engine only; chrome is the caller\'s job.' },
|
|
245
|
+
{ guidance: false, description: 'Use BaseTypeahead when Typeahead or Tokenizer suffice; extra work only pays off for custom compositions.' },
|
|
246
|
+
],
|
|
247
|
+
},
|
|
229
248
|
propDescriptions: {
|
|
230
249
|
searchSource: 'Data source w/ search+bootstrap methods.',
|
|
231
250
|
value: 'Currently selected item.',
|
|
@@ -410,7 +410,10 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
410
410
|
const performSearch = useCallback(
|
|
411
411
|
async (searchQuery: string) => {
|
|
412
412
|
searchSource.cancel?.();
|
|
413
|
-
|
|
413
|
+
// Claim a new generation so overlapping searches can't race: an
|
|
414
|
+
// in-flight response for an older query fails the gen check below
|
|
415
|
+
// instead of overwriting the newer results.
|
|
416
|
+
const gen = ++searchGenRef.current;
|
|
414
417
|
setIsLoading(true);
|
|
415
418
|
setHasSearched(true);
|
|
416
419
|
try {
|
|
@@ -451,7 +454,7 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
451
454
|
|
|
452
455
|
// Perform bootstrap
|
|
453
456
|
const performBootstrap = useCallback(async () => {
|
|
454
|
-
const gen = searchGenRef.current;
|
|
457
|
+
const gen = ++searchGenRef.current;
|
|
455
458
|
setIsLoading(true);
|
|
456
459
|
try {
|
|
457
460
|
const bootstrapResults = await searchSource.bootstrap();
|
|
@@ -168,7 +168,7 @@ export const docs = {
|
|
|
168
168
|
components: [{name: 'BaseTypeahead'}, {name: 'TypeaheadItem'}],
|
|
169
169
|
theming: {
|
|
170
170
|
targets: [
|
|
171
|
-
{className: 'astryx-typeahead', visualProps: ['status']},
|
|
171
|
+
{className: 'astryx-typeahead', visualProps: ['status', 'size']},
|
|
172
172
|
{className: 'astryx-typeahead-dropdown'},
|
|
173
173
|
{className: 'astryx-typeahead-item'},
|
|
174
174
|
],
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
afterAll,
|
|
19
19
|
beforeEach,
|
|
20
20
|
} from 'vitest';
|
|
21
|
-
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
21
|
+
import {render, screen, fireEvent, waitFor, act} from '@testing-library/react';
|
|
22
22
|
import userEvent from '@testing-library/user-event';
|
|
23
23
|
import {Typeahead} from './Typeahead';
|
|
24
24
|
import {BaseTypeahead} from './BaseTypeahead';
|
|
@@ -314,6 +314,50 @@ describe('BaseTypeahead focus-out', () => {
|
|
|
314
314
|
});
|
|
315
315
|
|
|
316
316
|
describe('Typeahead', () => {
|
|
317
|
+
describe('out-of-order async results', () => {
|
|
318
|
+
it('discards a stale response that resolves after a newer query', async () => {
|
|
319
|
+
const resolvers = new Map<string, (items: SearchableItem[]) => void>();
|
|
320
|
+
const rawSource: SearchSource = {
|
|
321
|
+
search: async (query: string) =>
|
|
322
|
+
new Promise<SearchableItem[]>(resolve => {
|
|
323
|
+
resolvers.set(query, resolve);
|
|
324
|
+
}),
|
|
325
|
+
bootstrap: () => [],
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
render(
|
|
329
|
+
<Typeahead
|
|
330
|
+
label="Fruit"
|
|
331
|
+
searchSource={rawSource}
|
|
332
|
+
value={null}
|
|
333
|
+
onChange={() => {}}
|
|
334
|
+
debounceMs={0}
|
|
335
|
+
/>,
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const input = screen.getByRole('combobox');
|
|
339
|
+
fireEvent.change(input, {target: {value: 'a'}});
|
|
340
|
+
fireEvent.change(input, {target: {value: 'ap'}});
|
|
341
|
+
|
|
342
|
+
// The newer query resolves first…
|
|
343
|
+
await act(async () => {
|
|
344
|
+
resolvers.get('ap')!([{id: 'apple', label: 'Apple'}]);
|
|
345
|
+
});
|
|
346
|
+
expect(screen.getByText('Apple')).toBeInTheDocument();
|
|
347
|
+
|
|
348
|
+
// …then the abandoned query's slow response arrives and must be
|
|
349
|
+
// discarded rather than overwriting the current results.
|
|
350
|
+
await act(async () => {
|
|
351
|
+
resolvers.get('a')!([
|
|
352
|
+
{id: 'avocado', label: 'Avocado'},
|
|
353
|
+
{id: 'apricot', label: 'Apricot'},
|
|
354
|
+
]);
|
|
355
|
+
});
|
|
356
|
+
expect(screen.getByText('Apple')).toBeInTheDocument();
|
|
357
|
+
expect(screen.queryByText('Avocado')).not.toBeInTheDocument();
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
317
361
|
it('renders with label', () => {
|
|
318
362
|
render(
|
|
319
363
|
<Typeahead
|
|
@@ -79,12 +79,9 @@ const styles = stylex.create({
|
|
|
79
79
|
*
|
|
80
80
|
* @example
|
|
81
81
|
* ```
|
|
82
|
-
* // Accessible name for an icon-only button
|
|
83
82
|
* <IconButton icon="trash" label="">
|
|
84
83
|
* <VisuallyHidden>Delete incident</VisuallyHidden>
|
|
85
84
|
* </IconButton>
|
|
86
|
-
*
|
|
87
|
-
* // Live region for announcements
|
|
88
85
|
* <VisuallyHidden as="div" aria-live="polite">
|
|
89
86
|
* {`Moved ${task} to ${column}`}
|
|
90
87
|
* </VisuallyHidden>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file TestIcon.tsx
|
|
5
|
+
* @input Standard SVG props
|
|
6
|
+
* @output A minimal SVG React component for tests
|
|
7
|
+
* @position Test fixture; a stand-in external SVG icon for Icon component-mode tests
|
|
8
|
+
*
|
|
9
|
+
* A trivial SVG component used by tests that need to pass a custom SVG icon to
|
|
10
|
+
* `Icon` (component mode) or component `*Icon` props. It forwards all standard
|
|
11
|
+
* SVG props and its ref so tests exercising prop/ref forwarding behave the same
|
|
12
|
+
* as a real third-party icon component.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import React, {type SVGProps} from 'react';
|
|
16
|
+
|
|
17
|
+
export function TestIcon({
|
|
18
|
+
ref,
|
|
19
|
+
...props
|
|
20
|
+
}: SVGProps<SVGSVGElement> & {ref?: React.Ref<SVGSVGElement>}) {
|
|
21
|
+
return (
|
|
22
|
+
<svg ref={ref} viewBox="0 0 24 24" {...props}>
|
|
23
|
+
<path d="M4 4h16v16H4z" />
|
|
24
|
+
</svg>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file fastRoleQueries.ts
|
|
5
|
+
* @input Uses @testing-library/react screen, dom-accessibility-api
|
|
6
|
+
* @output getButton/queryButton — role+name lookups that stay O(match) in jsdom
|
|
7
|
+
* @position Shared test helper; imported by component tests that render large
|
|
8
|
+
* always-mounted button collections (Calendar month grids)
|
|
9
|
+
*
|
|
10
|
+
* RTL's getByRole('button', {name}) computes the accessible name of EVERY
|
|
11
|
+
* role candidate before filtering, and each computation consults jsdom's slow
|
|
12
|
+
* getComputedStyle (~5ms per node). The date components keep a month grid of
|
|
13
|
+
* ~35–85 role=button nodes mounted — visible in Calendar's case, inside the
|
|
14
|
+
* closed popover for the *Input components — so a single trigger lookup cost
|
|
15
|
+
* ~450ms and dominated those suites' runtime (DateRangeInput: 34s for 34
|
|
16
|
+
* tests before this helper existed).
|
|
17
|
+
*
|
|
18
|
+
* These helpers keep RTL's exact name algorithm (dom-accessibility-api is the
|
|
19
|
+
* library RTL uses internally) but:
|
|
20
|
+
* - collect candidates with {hidden: true}, skipping the per-node
|
|
21
|
+
* getComputedStyle visibility checks, and
|
|
22
|
+
* - inject a constant "visible" style stub into the name computation —
|
|
23
|
+
* names only consult display/visibility, and under {hidden: true}
|
|
24
|
+
* semantics visibility must not exclude candidates anyway.
|
|
25
|
+
*
|
|
26
|
+
* Trade-off vs getByRole: first match wins — no tree-wide uniqueness check.
|
|
27
|
+
*
|
|
28
|
+
* SYNC: When modified, update this header.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import {screen} from '@testing-library/react';
|
|
32
|
+
import {computeAccessibleName} from 'dom-accessibility-api';
|
|
33
|
+
|
|
34
|
+
// The name algorithm only reads getPropertyValue (for display/visibility/
|
|
35
|
+
// content), so a Pick is all we need to type honestly — no object-literal
|
|
36
|
+
// cast. The widening to CSSStyleDeclaration happens at the call site, where
|
|
37
|
+
// getComputedStyle's signature demands the full type.
|
|
38
|
+
const visibleStyleStub: Pick<CSSStyleDeclaration, 'getPropertyValue'> = {
|
|
39
|
+
getPropertyValue: prop =>
|
|
40
|
+
prop === 'display' ? 'block' : prop === 'visibility' ? 'visible' : '',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function matchesName(el: Element, name: string | RegExp): boolean {
|
|
44
|
+
const accessibleName = computeAccessibleName(el, {
|
|
45
|
+
getComputedStyle: () => visibleStyleStub as CSSStyleDeclaration,
|
|
46
|
+
});
|
|
47
|
+
return typeof name === 'string'
|
|
48
|
+
? accessibleName === name
|
|
49
|
+
: name.test(accessibleName);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function queryButton(name: string | RegExp): HTMLElement | null {
|
|
53
|
+
return (
|
|
54
|
+
screen
|
|
55
|
+
.queryAllByRole('button', {hidden: true})
|
|
56
|
+
.find(el => matchesName(el, name)) ?? null
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getButton(name: string | RegExp): HTMLElement {
|
|
61
|
+
const el = queryButton(name);
|
|
62
|
+
if (!el) {
|
|
63
|
+
throw new Error(`Unable to find a role=button named ${String(name)}`);
|
|
64
|
+
}
|
|
65
|
+
return el;
|
|
66
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
4
|
+
/**
|
|
5
|
+
* @file Guards doc prose against docs/source prop mismatches (#3360).
|
|
6
|
+
*
|
|
7
|
+
* Scans every {Name}.doc.mjs for prose phrases like "the foo prop on Bar"
|
|
8
|
+
* and verifies the referenced prop is documented on the referenced
|
|
9
|
+
* component. Doc prose is LLM training signal: a reference to a prop that
|
|
10
|
+
* doesn't exist steers codegen toward hallucinated APIs (#3360 shipped a
|
|
11
|
+
* bullet pointing at a nonexistent CodeBlock syntaxTheme prop).
|
|
12
|
+
*
|
|
13
|
+
* A reference to a missing prop is allowed only when the same string
|
|
14
|
+
* explicitly negates it ("no such prop exists" / "does not exist"), the
|
|
15
|
+
* corrective-bullet pattern used to counter known hallucinations.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {describe, it, expect} from 'vitest';
|
|
19
|
+
import {readdirSync} from 'node:fs';
|
|
20
|
+
import {join, relative} from 'node:path';
|
|
21
|
+
|
|
22
|
+
const SRC_DIR = __dirname;
|
|
23
|
+
|
|
24
|
+
type DocModule = {
|
|
25
|
+
docs?: {
|
|
26
|
+
name?: string;
|
|
27
|
+
props?: {name: string}[];
|
|
28
|
+
components?: {name?: string; props?: {name: string}[]}[];
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Props intentionally omitted from doc props[]: PropDoc in docs-types.ts
|
|
34
|
+
* says to skip internal/styling props, so prose may reference them even
|
|
35
|
+
* though no doc lists them.
|
|
36
|
+
*/
|
|
37
|
+
const UNIVERSAL_PROPS = new Set([
|
|
38
|
+
'xstyle',
|
|
39
|
+
'className',
|
|
40
|
+
'style',
|
|
41
|
+
'ref',
|
|
42
|
+
'data-testid',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Matches prose like "the foo prop on Bar", "foo prop of Bar", and
|
|
47
|
+
* conjunction targets: "foo prop on Bar or Baz". Group 1 is the prop
|
|
48
|
+
* (camelCase, lowercase first letter), group 2 the component name list.
|
|
49
|
+
*/
|
|
50
|
+
const PROP_REF_PATTERN =
|
|
51
|
+
/\b([a-z][A-Za-z0-9]*) prop (?:on|of) (?:the )?([A-Z][A-Za-z0-9]*(?:(?:,(?: and| or)?| and| or) [A-Z][A-Za-z0-9]*)*)/g;
|
|
52
|
+
|
|
53
|
+
/** A same-string negation marks the reference as a deliberate counter-signal. */
|
|
54
|
+
const NEGATION_PATTERN = /no such prop|does not exist|doesn't exist/i;
|
|
55
|
+
|
|
56
|
+
function findDocFiles(dir: string): string[] {
|
|
57
|
+
const files: string[] = [];
|
|
58
|
+
for (const entry of readdirSync(dir, {withFileTypes: true})) {
|
|
59
|
+
const full = join(dir, entry.name);
|
|
60
|
+
if (entry.isDirectory()) {
|
|
61
|
+
files.push(...findDocFiles(full));
|
|
62
|
+
} else if (entry.name.endsWith('.doc.mjs')) {
|
|
63
|
+
files.push(full);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return files;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Collect every string value reachable from a doc module export. */
|
|
70
|
+
function collectStrings(value: unknown, out: string[]): void {
|
|
71
|
+
if (typeof value === 'string') {
|
|
72
|
+
out.push(value);
|
|
73
|
+
} else if (Array.isArray(value)) {
|
|
74
|
+
for (const item of value) {
|
|
75
|
+
collectStrings(item, out);
|
|
76
|
+
}
|
|
77
|
+
} else if (value && typeof value === 'object') {
|
|
78
|
+
for (const item of Object.values(value)) {
|
|
79
|
+
collectStrings(item, out);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const docFiles = findDocFiles(SRC_DIR);
|
|
85
|
+
|
|
86
|
+
// Component name → documented prop names, from every doc file's props[]
|
|
87
|
+
// (single and sub-component docs) and inline components[] entries.
|
|
88
|
+
const documentedProps = new Map<string, Set<string>>();
|
|
89
|
+
for (const file of docFiles) {
|
|
90
|
+
const mod = require(file) as DocModule;
|
|
91
|
+
const docs = mod.docs;
|
|
92
|
+
if (!docs) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const entries = [
|
|
96
|
+
{name: docs.name, props: docs.props},
|
|
97
|
+
...(docs.components || []),
|
|
98
|
+
];
|
|
99
|
+
for (const {name, props} of entries) {
|
|
100
|
+
if (!name || !Array.isArray(props)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const set = documentedProps.get(name) || new Set<string>();
|
|
104
|
+
for (const prop of props) {
|
|
105
|
+
set.add(prop.name);
|
|
106
|
+
}
|
|
107
|
+
documentedProps.set(name, set);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface Violation {
|
|
112
|
+
file: string;
|
|
113
|
+
component: string;
|
|
114
|
+
prop: string;
|
|
115
|
+
excerpt: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function findViolations(): Violation[] {
|
|
119
|
+
const violations: Violation[] = [];
|
|
120
|
+
for (const file of docFiles) {
|
|
121
|
+
const strings: string[] = [];
|
|
122
|
+
collectStrings(require(file), strings);
|
|
123
|
+
for (const text of strings) {
|
|
124
|
+
let match;
|
|
125
|
+
while ((match = PROP_REF_PATTERN.exec(text)) !== null) {
|
|
126
|
+
const [, prop, componentList] = match;
|
|
127
|
+
if (UNIVERSAL_PROPS.has(prop) || NEGATION_PATTERN.test(text)) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
for (const component of componentList.split(
|
|
131
|
+
/,(?: and| or)?| and | or /,
|
|
132
|
+
)) {
|
|
133
|
+
const name = component.trim();
|
|
134
|
+
if (!name) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const known = documentedProps.get(name);
|
|
138
|
+
if (known && !known.has(prop)) {
|
|
139
|
+
violations.push({
|
|
140
|
+
file: relative(SRC_DIR, file),
|
|
141
|
+
component: name,
|
|
142
|
+
prop,
|
|
143
|
+
excerpt: text.slice(
|
|
144
|
+
Math.max(0, match.index - 40),
|
|
145
|
+
match.index + match[0].length + 20,
|
|
146
|
+
),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return violations;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
describe('doc prose prop references', () => {
|
|
157
|
+
it('sanity: discovers doc files and documented components', () => {
|
|
158
|
+
expect(docFiles.length).toBeGreaterThan(0);
|
|
159
|
+
expect(documentedProps.size).toBeGreaterThan(0);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('every "X prop on Y" reference names a documented prop of Y', () => {
|
|
163
|
+
const violations = findViolations();
|
|
164
|
+
expect(
|
|
165
|
+
violations,
|
|
166
|
+
violations
|
|
167
|
+
.map(
|
|
168
|
+
v =>
|
|
169
|
+
`${v.file} references prop "${v.prop}" on ${v.component}, ` +
|
|
170
|
+
`which does not document it (…${v.excerpt}…). Either the prop ` +
|
|
171
|
+
`doesn't exist (fix the prose; negate with "no such prop ` +
|
|
172
|
+
`exists" if countering a known hallucination) or it exists in ` +
|
|
173
|
+
`source but is missing from the component's doc props[] (add it).`,
|
|
174
|
+
)
|
|
175
|
+
.join('\n'),
|
|
176
|
+
).toEqual([]);
|
|
177
|
+
});
|
|
178
|
+
});
|
package/src/docs-types.ts
CHANGED
|
@@ -400,7 +400,13 @@ export interface PlaygroundConfig {
|
|
|
400
400
|
* interactive preview shows an open-trigger placeholder instead of an
|
|
401
401
|
* empty stage while `isOpen` is false, and lets the real overlay render
|
|
402
402
|
* when opened. Include `isOpen: false` in `defaults` so the preview can
|
|
403
|
-
* bridge `onOpenChange` back into playground state.
|
|
403
|
+
* bridge `onOpenChange` back into playground state.
|
|
404
|
+
*
|
|
405
|
+
* Only for components with no inline containment (MobileNav, Lightbox).
|
|
406
|
+
* Components with an `isInline` docs-preview prop (Dialog, AlertDialog,
|
|
407
|
+
* CommandPalette) intentionally keep contained inline previews instead:
|
|
408
|
+
* the component is visible on load and knobs stay usable, whereas a real
|
|
409
|
+
* top-layer modal makes the rest of the page inert (#3657). */
|
|
404
410
|
overlay?: boolean;
|
|
405
411
|
/** Required parent wrapper for sub-components that depend on a parent
|
|
406
412
|
* context provider (e.g. `Tab` calls `useTabListContext()` and throws
|
|
@@ -859,6 +865,7 @@ export type TemplateCategory =
|
|
|
859
865
|
| 'Tools - File Explorer'
|
|
860
866
|
| 'Tools - Page Editor'
|
|
861
867
|
| 'Tools - IDE'
|
|
868
|
+
| 'Tools - Incident Console'
|
|
862
869
|
| 'Tools - Kanban Board'
|
|
863
870
|
| 'Tools - Notebook/Report Page'
|
|
864
871
|
| 'Tools - Diff Compare Viewer'
|
|
@@ -889,6 +896,7 @@ export type TemplateCategory =
|
|
|
889
896
|
| 'Shell - Top Nav'
|
|
890
897
|
| 'Shell - Top Nav + Left Sidebar'
|
|
891
898
|
| 'Shell - Breadcrumb Driven Layout'
|
|
899
|
+
| 'Shell - Messaging'
|
|
892
900
|
| 'Shell - Blank';
|
|
893
901
|
|
|
894
902
|
interface BaseTemplateDoc {
|
|
@@ -22,4 +22,4 @@
|
|
|
22
22
|
* re-declaring the string so behavior stays consistent across hooks.
|
|
23
23
|
*/
|
|
24
24
|
export const FOCUSABLE_SELECTOR =
|
|
25
|
-
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), [contenteditable]:not([contenteditable="false"]), audio[controls], video[controls], iframe, details > summary:first-child';
|
|
25
|
+
'button:not([disabled]), a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), [contenteditable]:not([contenteditable="false"]), audio[controls], video[controls], iframe, details > summary:first-child';
|
package/src/hooks/index.ts
CHANGED
|
@@ -30,6 +30,9 @@ export type {
|
|
|
30
30
|
export {useTreeFocus} from './useTreeFocus';
|
|
31
31
|
export type {UseTreeFocusOptions, UseTreeFocusReturn} from './useTreeFocus';
|
|
32
32
|
|
|
33
|
+
export {useHotkeys} from './useHotkeys';
|
|
34
|
+
export type {Hotkey} from './useHotkeys';
|
|
35
|
+
|
|
33
36
|
export {useTypeahead} from './useTypeahead';
|
|
34
37
|
|
|
35
38
|
export {useKeyboardHint} from './useKeyboardHint';
|
|
@@ -39,10 +39,11 @@ export const docs = {
|
|
|
39
39
|
],
|
|
40
40
|
usage: {
|
|
41
41
|
description:
|
|
42
|
-
'Traps focus within a container element following the WAI-ARIA dialog focus trap pattern. Listens to focus events on the document and redirects focus back into the container if it escapes via keyboard navigation. Handles both Tab and Shift+Tab wrapping. Mouse clicks outside the container are not intercepted; use a light-dismiss handler for that.',
|
|
42
|
+
'Traps focus within a container element following the WAI-ARIA dialog focus trap pattern. Listens to focus events on the document and redirects focus back into the container if it escapes via keyboard navigation. Handles both Tab and Shift+Tab wrapping. When the trap deactivates or unmounts, focus is restored to the element that was focused before activation, unless focus was already moved elsewhere. Mouse clicks outside the container are not intercepted; use a light-dismiss handler for that.',
|
|
43
43
|
bestPractices: [
|
|
44
44
|
{ guidance: true, description: 'Call focusFirst() when opening a dialog/modal to move focus into the trapped region.' },
|
|
45
45
|
{ guidance: true, description: 'Provide an onEscape callback to close the dialog when Escape is pressed.' },
|
|
46
|
+
{ guidance: true, description: 'Rely on the built-in focus restoration on close; only add your own onHide focus handling when you need to send focus somewhere other than the previously-focused element.' },
|
|
46
47
|
{ guidance: false, description: 'Use on non-modal content like tooltips or dropdowns; those need light-dismiss, not focus trapping.' },
|
|
47
48
|
],
|
|
48
49
|
},
|
|
@@ -67,10 +68,11 @@ export const docsDense = {
|
|
|
67
68
|
},
|
|
68
69
|
usage: {
|
|
69
70
|
description:
|
|
70
|
-
'Traps focus within container element following WAI-ARIA dialog focus trap pattern. Listens to document focus events + redirects focus back into container if it escapes via keyboard navigation. Handles both Tab + Shift+Tab wrapping. Mouse clicks outside container not intercepted; use light-dismiss handler for that.',
|
|
71
|
+
'Traps focus within container element following WAI-ARIA dialog focus trap pattern. Listens to document focus events + redirects focus back into container if it escapes via keyboard navigation. Handles both Tab + Shift+Tab wrapping. On deactivate/unmount, restores focus to the element focused before activation unless focus already moved elsewhere. Mouse clicks outside container not intercepted; use light-dismiss handler for that.',
|
|
71
72
|
bestPractices: [
|
|
72
73
|
{ guidance: true, description: 'Call focusFirst() when opening dialog/modal to move focus into trapped region.' },
|
|
73
74
|
{ guidance: true, description: 'Provide onEscape callback to close dialog when Escape pressed.' },
|
|
75
|
+
{ guidance: true, description: 'Rely on built-in focus restoration on close; add own onHide focus handling only to send focus somewhere other than previously-focused element.' },
|
|
74
76
|
{ guidance: false, description: 'Use on non-modal content like tooltips / dropdowns; those need light-dismiss, not focus trapping.' },
|
|
75
77
|
],
|
|
76
78
|
},
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import {describe, it, expect, vi} from 'vitest';
|
|
13
13
|
import {render, screen, fireEvent} from '@testing-library/react';
|
|
14
|
+
import {FOCUSABLE_SELECTOR} from './focusableSelector';
|
|
14
15
|
import {useFocusTrap} from './useFocusTrap';
|
|
15
16
|
|
|
16
17
|
function Trap({children}: {children: React.ReactNode}) {
|
|
@@ -49,6 +50,41 @@ function EscapeTrap({
|
|
|
49
50
|
);
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
function RestoreTrap({isActive}: {isActive: boolean}) {
|
|
54
|
+
const {containerRef} = useFocusTrap<HTMLDivElement>({isActive});
|
|
55
|
+
return (
|
|
56
|
+
<div ref={containerRef} data-testid="restore-trap">
|
|
57
|
+
<button type="button" data-testid="inside">
|
|
58
|
+
Inside trap
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function RestoreFixture({
|
|
65
|
+
isActive,
|
|
66
|
+
showPrev = true,
|
|
67
|
+
showTrap = true,
|
|
68
|
+
}: {
|
|
69
|
+
isActive: boolean;
|
|
70
|
+
showPrev?: boolean;
|
|
71
|
+
showTrap?: boolean;
|
|
72
|
+
}) {
|
|
73
|
+
return (
|
|
74
|
+
<div>
|
|
75
|
+
{showPrev && (
|
|
76
|
+
<button type="button" data-testid="prev">
|
|
77
|
+
Previously focused
|
|
78
|
+
</button>
|
|
79
|
+
)}
|
|
80
|
+
<button type="button" data-testid="other">
|
|
81
|
+
Other outside
|
|
82
|
+
</button>
|
|
83
|
+
{showTrap && <RestoreTrap isActive={isActive} />}
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
52
88
|
describe('useFocusTrap tabbable model (infra-8)', () => {
|
|
53
89
|
it('treats a contenteditable as focusable (focusFirst lands on it)', () => {
|
|
54
90
|
render(
|
|
@@ -84,6 +120,48 @@ describe('useFocusTrap tabbable model (infra-8)', () => {
|
|
|
84
120
|
});
|
|
85
121
|
});
|
|
86
122
|
|
|
123
|
+
describe('FOCUSABLE_SELECTOR href matching', () => {
|
|
124
|
+
// Only real links (<a href>/<area href>) are focusable via href. A bare
|
|
125
|
+
// [href] term also matched non-focusable elements carrying href (e.g. a
|
|
126
|
+
// <link> in the head, or a custom element), which useFocusTrap would then
|
|
127
|
+
// treat as tab stops when computing trap boundaries.
|
|
128
|
+
it('matches real links but not other elements carrying href', () => {
|
|
129
|
+
const container = document.createElement('div');
|
|
130
|
+
container.innerHTML =
|
|
131
|
+
'<a href="#a" data-testid="anchor">Anchor</a>' +
|
|
132
|
+
'<map name="m">' +
|
|
133
|
+
'<area href="#area" shape="rect" coords="0,0,1,1" data-testid="area" />' +
|
|
134
|
+
'</map>' +
|
|
135
|
+
'<span href="#span" data-testid="span">Span</span>' +
|
|
136
|
+
'<link href="#link" data-testid="link" />';
|
|
137
|
+
|
|
138
|
+
const matches = Array.from(
|
|
139
|
+
container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),
|
|
140
|
+
);
|
|
141
|
+
const byTestId = (id: string) =>
|
|
142
|
+
container.querySelector(`[data-testid="${id}"]`);
|
|
143
|
+
|
|
144
|
+
// Real links are focusable via href.
|
|
145
|
+
expect(matches).toContain(byTestId('anchor'));
|
|
146
|
+
expect(matches).toContain(byTestId('area'));
|
|
147
|
+
// Non-link elements carrying href are not focusable and must be excluded.
|
|
148
|
+
expect(matches).not.toContain(byTestId('span'));
|
|
149
|
+
expect(matches).not.toContain(byTestId('link'));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('treats an <a href> inside a trap as focusable (focusFirst lands on it)', () => {
|
|
153
|
+
render(
|
|
154
|
+
<Trap>
|
|
155
|
+
<a href="#link" data-testid="anchor">
|
|
156
|
+
Link
|
|
157
|
+
</a>
|
|
158
|
+
</Trap>,
|
|
159
|
+
);
|
|
160
|
+
fireEvent.click(screen.getByTestId('focus-first'));
|
|
161
|
+
expect(screen.getByTestId('anchor')).toHaveFocus();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
87
165
|
describe('useFocusTrap Escape coordination', () => {
|
|
88
166
|
it('calls onEscape for a single active trap', () => {
|
|
89
167
|
const onEscape = vi.fn();
|
|
@@ -125,8 +203,78 @@ describe('useFocusTrap Escape coordination', () => {
|
|
|
125
203
|
const {rerender} = render(
|
|
126
204
|
<EscapeTrap isActive onEscape={onEscape} label="toggle" />,
|
|
127
205
|
);
|
|
128
|
-
rerender(
|
|
206
|
+
rerender(
|
|
207
|
+
<EscapeTrap isActive={false} onEscape={onEscape} label="toggle" />,
|
|
208
|
+
);
|
|
129
209
|
fireEvent.keyDown(document, {key: 'Escape'});
|
|
130
210
|
expect(onEscape).not.toHaveBeenCalled();
|
|
131
211
|
});
|
|
132
212
|
});
|
|
213
|
+
|
|
214
|
+
describe('useFocusTrap focus restoration', () => {
|
|
215
|
+
it('restores focus to the previously-focused element when deactivated', () => {
|
|
216
|
+
const {rerender} = render(<RestoreFixture isActive={false} />);
|
|
217
|
+
const prev = screen.getByTestId('prev');
|
|
218
|
+
prev.focus();
|
|
219
|
+
expect(prev).toHaveFocus();
|
|
220
|
+
|
|
221
|
+
// Activate the trap (captures `prev` as the restore target) and move focus
|
|
222
|
+
// inside it, as auto-focus or a keyboard user would.
|
|
223
|
+
rerender(<RestoreFixture isActive={true} />);
|
|
224
|
+
screen.getByTestId('inside').focus();
|
|
225
|
+
expect(screen.getByTestId('inside')).toHaveFocus();
|
|
226
|
+
|
|
227
|
+
// Deactivating returns focus to where it was before the trap opened.
|
|
228
|
+
rerender(<RestoreFixture isActive={false} />);
|
|
229
|
+
expect(prev).toHaveFocus();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('does not steal focus when it was moved elsewhere outside the trap', () => {
|
|
233
|
+
const {rerender} = render(<RestoreFixture isActive={false} />);
|
|
234
|
+
const prev = screen.getByTestId('prev');
|
|
235
|
+
prev.focus();
|
|
236
|
+
|
|
237
|
+
rerender(<RestoreFixture isActive={true} />);
|
|
238
|
+
// The user (or a consumer that self-restores) moves focus to a different
|
|
239
|
+
// outside control while the trap is open.
|
|
240
|
+
const other = screen.getByTestId('other');
|
|
241
|
+
other.focus();
|
|
242
|
+
|
|
243
|
+
rerender(<RestoreFixture isActive={false} />);
|
|
244
|
+
// Focus is left where the user put it — not yanked back to `prev`.
|
|
245
|
+
expect(other).toHaveFocus();
|
|
246
|
+
expect(prev).not.toHaveFocus();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('does not crash or restore when the captured element was removed', () => {
|
|
250
|
+
const {rerender} = render(<RestoreFixture isActive={false} />);
|
|
251
|
+
const prev = screen.getByTestId('prev');
|
|
252
|
+
prev.focus();
|
|
253
|
+
|
|
254
|
+
rerender(<RestoreFixture isActive={true} />);
|
|
255
|
+
screen.getByTestId('inside').focus();
|
|
256
|
+
|
|
257
|
+
// Remove the captured element from the DOM before the trap deactivates.
|
|
258
|
+
rerender(<RestoreFixture isActive={true} showPrev={false} />);
|
|
259
|
+
expect(() =>
|
|
260
|
+
rerender(<RestoreFixture isActive={false} showPrev={false} />),
|
|
261
|
+
).not.toThrow();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('restores focus when the trap unmounts while active', () => {
|
|
265
|
+
const {rerender} = render(
|
|
266
|
+
<RestoreFixture isActive={true} showTrap={false} />,
|
|
267
|
+
);
|
|
268
|
+
const prev = screen.getByTestId('prev');
|
|
269
|
+
prev.focus();
|
|
270
|
+
|
|
271
|
+
// Mounting the trap active captures `prev`; then focus moves inside it.
|
|
272
|
+
rerender(<RestoreFixture isActive={true} showTrap={true} />);
|
|
273
|
+
screen.getByTestId('inside').focus();
|
|
274
|
+
expect(screen.getByTestId('inside')).toHaveFocus();
|
|
275
|
+
|
|
276
|
+
// Unmounting the trap (cleanup path, not an isActive flip) still restores.
|
|
277
|
+
rerender(<RestoreFixture isActive={true} showTrap={false} />);
|
|
278
|
+
expect(prev).toHaveFocus();
|
|
279
|
+
});
|
|
280
|
+
});
|