@astryxdesign/core 0.7.5 → 0.7.6-canary.0ef60dd
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/dist/AlertDialog/AlertDialog.d.ts +1 -1
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +5 -5
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +12 -3
- package/dist/Badge/Badge.d.ts.map +1 -1
- package/dist/Badge/Badge.js +7 -1
- package/dist/BottomSheet/BottomSheet.js +1 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +2 -1
- package/dist/BottomSheet/useSheetGestures.d.ts +12 -0
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +30 -3
- package/dist/Button/Button.js +4 -4
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +6 -5
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +37 -4
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +12 -4
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +101 -15
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +8 -3
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +1 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts +5 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +23 -12
- package/dist/DateTimeInput/NativeTimeSegment.d.ts.map +1 -1
- package/dist/DateTimeInput/NativeTimeSegment.js +5 -4
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +4 -3
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +34 -9
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +5 -4
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +2 -1
- package/dist/Drawer/Drawer.d.ts +1 -0
- package/dist/Drawer/Drawer.d.ts.map +1 -1
- package/dist/Drawer/Drawer.js +7 -2
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +7 -3
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -1
- package/dist/DropdownMenu/MenuBottomSheetActionList.js +6 -5
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +20 -2
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -1
- package/dist/DropdownMenu/lazyMenuBottomSheet.js +31 -3
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
- package/dist/DropdownMenu/renderDropdownItems.js +21 -16
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +2 -1
- package/dist/Field/FieldLabel.d.ts +1 -1
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +4 -4
- package/dist/Field/InputClearButton.js +3 -3
- package/dist/Field/PanelSearchInput.js +2 -2
- package/dist/FieldStatus/FieldStatus.js +2 -2
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +7 -1
- package/dist/Heading/Heading.js +1 -1
- package/dist/Icon/Icon.js +1 -1
- package/dist/Indicator/CheckIndicator.d.ts.map +1 -1
- package/dist/Indicator/CheckIndicator.js +2 -1
- package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
- package/dist/Indicator/CheckboxIndicator.js +3 -1
- package/dist/Indicator/RadioIndicator.d.ts.map +1 -1
- package/dist/Indicator/RadioIndicator.js +3 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +2 -1
- package/dist/Item/ItemSwipeLayer.js +1 -1
- package/dist/Layer/popoverOperations.d.ts +27 -0
- package/dist/Layer/popoverOperations.d.ts.map +1 -0
- package/dist/Layer/popoverOperations.js +159 -0
- package/dist/Layer/useLayer.d.ts +1 -1
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +93 -59
- package/dist/Layout/Layout.js +1 -1
- package/dist/Layout/LayoutContent.js +1 -1
- package/dist/Layout/LayoutFooter.js +1 -1
- package/dist/Layout/LayoutHeader.js +1 -1
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +5 -0
- package/dist/Lightbox/LightboxViewport.a11y.browser.spec.js +5 -4
- package/dist/List/List.js +1 -1
- package/dist/List/ListItem.js +3 -3
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +7 -2
- package/dist/MultiSelector/CreateRow.a11y.chromium.spec.js +7 -6
- package/dist/MultiSelector/MultiSelector.d.ts +1 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +26 -10
- package/dist/NumberInput/NumberInput.d.ts +3 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +3 -3
- package/dist/NumberInput/numberParser.d.ts +3 -0
- package/dist/NumberInput/numberParser.d.ts.map +1 -1
- package/dist/NumberInput/numberParser.js +7 -44
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +16 -12
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +28 -2
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +4 -1
- package/dist/Selector/useSelectorPresentation.d.ts +8 -1
- package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
- package/dist/Selector/useSelectorPresentation.js +20 -4
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +10 -2
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +3 -3
- package/dist/Stack/Stack.js +1 -1
- package/dist/Stack/StackItem.js +1 -1
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +16 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +9 -1
- package/dist/Text/Text.js +1 -1
- package/dist/Text/useTruncation.d.ts.map +1 -1
- package/dist/Text/useTruncation.js +23 -5
- package/dist/TextArea/TextArea.d.ts +1 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +13 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +3 -3
- package/dist/TimeInput/TimeInput.d.ts +6 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +21 -14
- package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
- package/dist/TimeInput/TouchTimeField.js +5 -4
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +2 -2
- package/dist/Timestamp/TimestampHoverCard.js +4 -4
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +60 -20
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +82 -51
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +13 -2
- package/dist/Tokenizer/TokenizerBottomSheet.js +3 -3
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +2 -1
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +3 -0
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +1 -0
- package/dist/Typeahead/Typeahead.d.ts +1 -1
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +9 -1
- package/dist/astryx.css +20 -4
- package/dist/gestures/GestureArena.d.ts +6 -0
- package/dist/gestures/GestureArena.d.ts.map +1 -1
- package/dist/gestures/GestureArena.js +39 -0
- package/dist/gestures/GestureHost.d.ts +352 -0
- package/dist/gestures/GestureHost.d.ts.map +1 -0
- package/dist/gestures/GestureHost.js +1773 -0
- package/dist/gestures/GestureRecognizer.d.ts +10 -0
- package/dist/gestures/GestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/GestureRecognizer.js +16 -0
- package/dist/gestures/LongPressGestureRecognizer.d.ts +1 -0
- package/dist/gestures/LongPressGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/LongPressGestureRecognizer.js +3 -0
- package/dist/gestures/PanGestureRecognizer.d.ts +1 -0
- package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/PanGestureRecognizer.js +3 -0
- package/dist/gestures/PinchGestureRecognizer.d.ts +31 -0
- package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -0
- package/dist/gestures/PinchGestureRecognizer.js +128 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +71 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.js +223 -0
- package/dist/gestures/SwipeGestureRecognizer.d.ts +37 -0
- package/dist/gestures/SwipeGestureRecognizer.d.ts.map +1 -0
- package/dist/gestures/SwipeGestureRecognizer.js +148 -0
- package/dist/gestures/TapGestureRecognizer.d.ts +1 -0
- package/dist/gestures/TapGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/TapGestureRecognizer.js +3 -0
- package/dist/gestures/VelocityTracker.d.ts +6 -2
- package/dist/gestures/VelocityTracker.d.ts.map +1 -1
- package/dist/gestures/VelocityTracker.js +7 -3
- package/dist/gestures/haptics.d.ts +65 -0
- package/dist/gestures/haptics.d.ts.map +1 -0
- package/dist/gestures/haptics.js +182 -0
- package/dist/gestures/nativeScroll.d.ts +42 -0
- package/dist/gestures/nativeScroll.d.ts.map +1 -0
- package/dist/gestures/nativeScroll.js +228 -0
- package/dist/gestures/touchAction.d.ts +60 -0
- package/dist/gestures/touchAction.d.ts.map +1 -0
- package/dist/gestures/touchAction.js +157 -0
- package/dist/gestures/touchActionRule.d.ts +34 -0
- package/dist/gestures/touchActionRule.d.ts.map +1 -0
- package/dist/gestures/touchActionRule.js +140 -0
- package/dist/gestures/types.d.ts +18 -7
- package/dist/gestures/types.d.ts.map +1 -1
- package/dist/gestures/types.js +22 -2
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/modalAnnounce.d.ts.map +1 -1
- package/dist/hooks/modalAnnounce.js +107 -61
- package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -1
- package/dist/hooks/useAdaptivePresentation.js +4 -1
- package/dist/hooks/useAnnounce.d.ts +101 -42
- package/dist/hooks/useAnnounce.d.ts.map +1 -1
- package/dist/hooks/useAnnounce.js +161 -75
- package/dist/hooks/useAnnounceRenderedText.d.ts +11 -3
- package/dist/hooks/useAnnounceRenderedText.d.ts.map +1 -1
- package/dist/hooks/useAnnounceRenderedText.js +5 -3
- package/dist/hooks/useBusyAnnouncement.d.ts.map +1 -1
- package/dist/hooks/useBusyAnnouncement.js +6 -12
- package/dist/hooks/useMenuHover.d.ts.map +1 -1
- package/dist/hooks/useMenuHover.js +12 -1
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +5 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +5 -0
- package/dist/motion/animator.d.ts +7 -1
- package/dist/motion/animator.d.ts.map +1 -1
- package/dist/motion/animator.js +119 -27
- package/dist/theme/defineTheme.d.ts +19 -5
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +64 -4
- package/dist/theme/domainTokens/domainTokenDefaults.d.ts +3 -0
- package/dist/theme/domainTokens/domainTokenDefaults.d.ts.map +1 -0
- package/dist/theme/domainTokens/domainTokenDefaults.js +19 -0
- package/dist/theme/domainTokens/index.d.ts +5 -4
- package/dist/theme/domainTokens/index.d.ts.map +1 -1
- package/dist/theme/domainTokens/index.js +5 -9
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/motionKnobs.d.ts +56 -0
- package/dist/theme/motionKnobs.d.ts.map +1 -0
- package/dist/theme/motionKnobs.js +48 -0
- package/dist/theme/resolveThemeValues.d.ts +11 -4
- package/dist/theme/resolveThemeValues.d.ts.map +1 -1
- package/dist/theme/resolveThemeValues.js +40 -4
- package/dist/theme/themeAdaptations.d.ts +7 -3
- package/dist/theme/themeAdaptations.d.ts.map +1 -1
- package/dist/theme/themeAdaptations.js +36 -7
- package/dist/theme/themeMotion.d.ts +96 -0
- package/dist/theme/themeMotion.d.ts.map +1 -0
- package/dist/theme/themeMotion.js +177 -0
- package/dist/theme/tokenReferences.d.ts +31 -0
- package/dist/theme/tokenReferences.d.ts.map +1 -0
- package/dist/theme/tokenReferences.js +132 -0
- package/dist/theme/tokens.d.ts +1 -0
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +9 -129
- package/dist/theme/tokens.stylex.d.ts +12 -0
- package/dist/theme/tokens.stylex.d.ts.map +1 -1
- package/dist/theme/tokens.stylex.js +12 -0
- package/dist/utils/focusReturn.d.ts +17 -1
- package/dist/utils/focusReturn.d.ts.map +1 -1
- package/dist/utils/focusReturn.js +11 -4
- package/dist/utils/getNodeText.d.ts +8 -0
- package/dist/utils/getNodeText.d.ts.map +1 -0
- package/dist/utils/getNodeText.js +37 -0
- package/dist/utils/interactionModality.d.ts +11 -2
- package/dist/utils/interactionModality.d.ts.map +1 -1
- package/dist/utils/interactionModality.js +58 -5
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +17 -8
- package/dist/utils/plainDate.d.ts +2 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +46 -3
- package/dist/utils/presentation.d.ts +50 -0
- package/dist/utils/presentation.d.ts.map +1 -0
- package/dist/utils/presentation.js +125 -0
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +20 -1
- package/dist/utils/primaryClick.d.ts +11 -0
- package/dist/utils/primaryClick.d.ts.map +1 -0
- package/dist/utils/primaryClick.js +25 -0
- package/dist/utils/timeFormat.d.ts +47 -0
- package/dist/utils/timeFormat.d.ts.map +1 -0
- package/dist/utils/timeFormat.js +240 -0
- package/dist/utils/touchPresentation.d.ts.map +1 -1
- package/dist/utils/touchPresentation.js +5 -3
- package/dist/utils/touchPresentation.stylex.d.ts +15 -0
- package/dist/utils/touchPresentation.stylex.d.ts.map +1 -0
- package/dist/utils/touchPresentation.stylex.js +33 -0
- package/dist/utils/unicodeDigits.d.ts +20 -0
- package/dist/utils/unicodeDigits.d.ts.map +1 -0
- package/dist/utils/unicodeDigits.js +70 -0
- package/locales/en.json +23 -3
- package/locales/pseudo.json +15 -0
- package/package.json +5 -4
- package/src/AlertDialog/AlertDialog.tsx +6 -5
- package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +18 -6
- package/src/Avatar/Avatar.spec.md +22 -22
- package/src/Avatar/Avatar.tsx +18 -1
- package/src/Avatar/__tests__/AvatarForcedColors.a11y.chromium.spec.ts +126 -0
- package/src/Badge/Badge.doc.mjs +1 -1
- package/src/Badge/Badge.tsx +19 -2
- package/src/Badge/__tests__/BadgeLargerText.a11y.chromium.spec.ts +260 -0
- package/src/BottomSheet/BottomSheet.test.tsx +46 -0
- package/src/BottomSheet/BottomSheet.tsx +1 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +2 -1
- package/src/BottomSheet/useSheetGestures.test.ts +52 -0
- package/src/BottomSheet/useSheetGestures.ts +37 -6
- package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.chromium.spec.ts +18 -6
- package/src/Button/Button.tsx +4 -4
- package/src/Calendar/Calendar.spec.md +87 -48
- package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +21 -7
- package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +21 -7
- package/src/CheckboxInput/CheckboxInput.tsx +6 -5
- package/src/ClickableCard/ClickableCard.test.tsx +96 -0
- package/src/ClickableCard/ClickableCard.tsx +48 -4
- package/src/Collapsible/Collapsible.spec.md +2 -0
- package/src/Collapsible/CollapsibleGroup.spec.md +57 -29
- package/src/Collapsible/__tests__/Collapsible.a11y.chromium.spec.ts +18 -7
- package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +18 -6
- package/src/ComplexSelector/ComplexSelector.test.tsx +37 -0
- package/src/ContextMenu/ContextMenu.test.tsx +35 -0
- package/src/DateInput/DateInput.test.tsx +115 -3
- package/src/DateInput/DateInput.tsx +29 -14
- package/src/DateInput/DateInputTouch.test.tsx +76 -0
- package/src/DateInput/MonthScroller.tsx +106 -20
- package/src/DateInput/MonthScrollerSettle.test.tsx +165 -0
- package/src/DateInput/TouchDateField.tsx +14 -3
- package/src/DateInput/__tests__/MonthScrollerSteps.interaction.browser.spec.ts +312 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +46 -3
- package/src/DateRangeInput/DateRangeInput.tsx +16 -12
- package/src/DateTimeInput/DateTimeInput.doc.mjs +10 -8
- package/src/DateTimeInput/DateTimeInput.spec.md +75 -51
- package/src/DateTimeInput/DateTimeInput.test.tsx +81 -0
- package/src/DateTimeInput/DateTimeInput.tsx +46 -24
- package/src/DateTimeInput/NativeTimeSegment.tsx +4 -6
- package/src/DateTimeInput/TouchDateTimeField.tsx +3 -7
- package/src/Dialog/Dialog.test.tsx +216 -2
- package/src/Dialog/Dialog.tsx +42 -9
- package/src/Dialog/DialogHeader.tsx +5 -4
- package/src/Dialog/__tests__/DialogSecondDocument.a11y.browser.spec.ts +202 -0
- package/src/Divider/Divider.tsx +2 -1
- package/src/Drawer/Drawer.spec.md +111 -80
- package/src/Drawer/Drawer.test.tsx +35 -0
- package/src/Drawer/Drawer.tsx +13 -2
- package/src/Drawer/DrawerHeader.spec.md +181 -0
- package/src/Drawer/__tests__/DrawerNonModalPageScroll.a11y.chromium.spec.ts +267 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +41 -0
- package/src/DropdownMenu/DropdownMenu.tsx +10 -3
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +34 -0
- package/src/DropdownMenu/MenuBottomSheetActionList.tsx +6 -5
- package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +19 -7
- package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +21 -9
- package/src/DropdownMenu/lazyMenuBottomSheet.test.ts +60 -0
- package/src/DropdownMenu/lazyMenuBottomSheet.ts +36 -7
- package/src/DropdownMenu/renderDropdownItems.tsx +24 -19
- package/src/EmptyState/EmptyState.tsx +2 -1
- package/src/Field/FieldLabel.tsx +5 -4
- package/src/Field/InputClearButton.tsx +3 -3
- package/src/Field/PanelSearchInput.tsx +2 -2
- package/src/FieldStatus/FieldStatus.tsx +2 -2
- package/src/FileInput/FileInput.test.tsx +21 -0
- package/src/FileInput/FileInput.tsx +4 -1
- package/src/FormLayout/FormLayout.spec.md +7 -8
- package/src/Heading/Heading.tsx +1 -1
- package/src/Icon/Icon.tsx +1 -1
- package/src/Indicator/CheckIndicator.spec.md +47 -42
- package/src/Indicator/CheckIndicator.tsx +2 -1
- package/src/Indicator/CheckboxIndicator.spec.md +40 -37
- package/src/Indicator/CheckboxIndicator.tsx +3 -1
- package/src/Indicator/Indicator.spec.md +188 -0
- package/src/Indicator/RadioIndicator.tsx +3 -1
- package/src/Item/Item.tsx +2 -1
- package/src/Item/ItemSwipeLayer.tsx +1 -1
- package/src/Kbd/Kbd.spec.md +81 -38
- package/src/Layer/__tests__/LayerBrowserHide.a11y.chromium.spec.ts +126 -0
- package/src/Layer/popoverOperations.test.ts +205 -0
- package/src/Layer/popoverOperations.ts +172 -0
- package/src/Layer/useLayer.test.tsx +348 -1
- package/src/Layer/useLayer.tsx +118 -71
- package/src/Layout/Layout.spec.md +127 -131
- package/src/Layout/Layout.tsx +1 -1
- package/src/Layout/LayoutContent.spec.md +205 -0
- package/src/Layout/LayoutContent.tsx +1 -1
- package/src/Layout/LayoutFooter.spec.md +190 -0
- package/src/Layout/LayoutFooter.tsx +1 -1
- package/src/Layout/LayoutHeader.spec.md +193 -0
- package/src/Layout/LayoutHeader.tsx +1 -1
- package/src/Layout/LayoutPanel.spec.md +194 -0
- package/src/Layout/__tests__/Layout.a11y.chromium.spec.ts +18 -6
- package/src/Lightbox/Lightbox.test.tsx +18 -0
- package/src/Lightbox/Lightbox.tsx +5 -0
- package/src/Lightbox/LightboxViewport.a11y.browser.spec.ts +8 -4
- package/src/Link/__tests__/Link.a11y.chromium.spec.ts +18 -8
- package/src/List/List.tsx +1 -1
- package/src/List/ListItem.spec.md +205 -0
- package/src/List/ListItem.tsx +3 -3
- package/src/MetadataList/MetadataList.spec.md +187 -0
- package/src/MetadataList/MetadataListItem.spec.md +165 -0
- package/src/MobileNav/MobileNav.test.tsx +15 -0
- package/src/MobileNav/MobileNav.tsx +9 -1
- package/src/MobileNav/__tests__/MobileNavHeader.a11y.chromium.spec.ts +89 -0
- package/src/MoreMenu/MoreMenu.test.tsx +11 -0
- package/src/MultiSelector/CreateRow.a11y.chromium.spec.ts +7 -8
- package/src/MultiSelector/MultiSelector.spec.md +7 -4
- package/src/MultiSelector/MultiSelector.test.tsx +270 -1
- package/src/MultiSelector/MultiSelector.tsx +25 -10
- package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +8 -1
- package/src/NavMenu/NavHeadingMenu.spec.md +1 -5
- package/src/NumberInput/NumberInput.doc.mjs +13 -7
- package/src/NumberInput/NumberInput.spec.md +59 -37
- package/src/NumberInput/NumberInput.tsx +13 -3
- package/src/NumberInput/__tests__/NumberInput.coarse-pointer.a11y.chromium.spec.ts +102 -0
- package/src/NumberInput/numberParser.ts +7 -45
- package/src/ProgressBar/ProgressBar.test.tsx +108 -32
- package/src/ProgressBar/ProgressBar.tsx +58 -10
- package/src/ProgressBar/__tests__/ProgressBar.a11y.chromium.spec.ts +21 -9
- package/src/ProgressBar/__tests__/ProgressBarMotion.a11y.chromium.spec.ts +831 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +21 -14
- package/src/Resizable/useResizable.test.ts +43 -0
- package/src/Resizable/useResizable.ts +32 -9
- package/src/SegmentedControl/SegmentedControl.test.tsx +84 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +11 -5
- package/src/Selector/Selector.test.tsx +90 -1
- package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +8 -1
- package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +8 -1
- package/src/Selector/useSelectorPresentation.ts +31 -3
- package/src/SideNav/SideNav.test.tsx +27 -0
- package/src/SideNav/__tests__/SideNavRailMotion.interaction.browser.spec.ts +176 -51
- package/src/Skeleton/Skeleton.tsx +3 -1
- package/src/Slider/Slider.test.tsx +31 -0
- package/src/Slider/Slider.tsx +8 -3
- package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +19 -7
- package/src/Spinner/Spinner.tsx +5 -3
- package/src/Stack/Stack.tsx +1 -1
- package/src/Stack/StackItem.tsx +1 -1
- package/src/TabList/TabList.test.tsx +58 -4
- package/src/TabList/TabList.tsx +16 -1
- package/src/TabList/__tests__/TabPressPointer.interaction.browser.spec.ts +163 -0
- package/src/Table/__tests__/Table.a11y.chromium.spec.ts +18 -6
- package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +22 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -4
- package/src/Text/Text.tsx +1 -1
- package/src/Text/useTruncation.test.tsx +128 -1
- package/src/Text/useTruncation.ts +23 -5
- package/src/TextArea/TextArea.spec.md +10 -13
- package/src/TextArea/TextArea.test.tsx +60 -0
- package/src/TextArea/TextArea.tsx +15 -1
- package/src/TextInput/TextInput.test.tsx +21 -0
- package/src/TextInput/TextInput.tsx +3 -1
- package/src/TimeInput/NativeTimeInput.test.tsx +17 -0
- package/src/TimeInput/Presentation.test.tsx +21 -0
- package/src/TimeInput/TimeInput.doc.mjs +8 -8
- package/src/TimeInput/TimeInput.spec.md +45 -48
- package/src/TimeInput/TimeInput.test.tsx +76 -0
- package/src/TimeInput/TimeInput.tsx +36 -18
- package/src/TimeInput/TouchTimeField.tsx +4 -6
- package/src/Timestamp/Timestamp.spec.md +6 -12
- package/src/Timestamp/Timestamp.test.tsx +30 -5
- package/src/Timestamp/Timestamp.tsx +6 -2
- package/src/Timestamp/TimestampHoverCard.tsx +4 -4
- package/src/Toast/Toast.spec.md +14 -20
- package/src/Toast/Toast.tsx +104 -36
- package/src/Toast/ToastViewport.test.tsx +719 -1
- package/src/Toast/ToastViewport.tsx +100 -50
- package/src/Tokenizer/Tokenizer.spec.md +5 -3
- package/src/Tokenizer/Tokenizer.test.tsx +68 -0
- package/src/Tokenizer/Tokenizer.tsx +14 -2
- package/src/Tokenizer/TokenizerBottomSheet.test.tsx +15 -4
- package/src/Tokenizer/TokenizerBottomSheet.tsx +3 -3
- package/src/Toolbar/Toolbar.spec.md +5 -6
- package/src/Toolbar/Toolbar.test.tsx +30 -0
- package/src/Toolbar/Toolbar.tsx +2 -1
- package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +18 -6
- package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +18 -7
- package/src/TopNav/TopNav.test.tsx +27 -0
- package/src/TopNav/TopNavMegaMenu.test.tsx +21 -0
- package/src/TopNav/TopNavMegaMenu.tsx +4 -1
- package/src/TopNav/TopNavMenu.test.tsx +17 -0
- package/src/TopNav/TopNavMenu.tsx +1 -0
- package/src/Typeahead/Typeahead.test.tsx +40 -0
- package/src/Typeahead/Typeahead.tsx +12 -1
- package/src/__tests__/pressState.ts +8 -4
- package/src/gestures/GestureArena.ts +35 -0
- package/src/gestures/GestureHost.ts +2065 -0
- package/src/gestures/GestureRecognizer.ts +18 -0
- package/src/gestures/LongPressGestureRecognizer.ts +4 -0
- package/src/gestures/PanGestureRecognizer.ts +4 -0
- package/src/gestures/PinchGestureRecognizer.ts +148 -0
- package/src/gestures/ScreenEdgePanGestureRecognizer.ts +256 -0
- package/src/gestures/SwipeGestureRecognizer.ts +197 -0
- package/src/gestures/TapGestureRecognizer.ts +4 -0
- package/src/gestures/VelocityTracker.ts +10 -3
- package/src/gestures/__tests__/GestureHost.test.ts +2087 -0
- package/src/gestures/__tests__/PanGestureRecognizer.test.ts +4 -4
- package/src/gestures/__tests__/PinchGestureRecognizer.test.ts +74 -0
- package/src/gestures/__tests__/ScreenEdgePanGestureRecognizer.test.ts +498 -0
- package/src/gestures/__tests__/SwipeGestureRecognizer.test.ts +108 -0
- package/src/gestures/__tests__/arena.test.ts +5 -0
- package/src/gestures/__tests__/arenaPinchSwipeEdge.test.ts +193 -0
- package/src/gestures/__tests__/browser/build-fixture.mjs +46 -0
- package/src/gestures/__tests__/browser/fixture.ts +307 -0
- package/src/gestures/__tests__/browser/gestures.interaction.browser.spec.ts +1137 -0
- package/src/gestures/__tests__/browserNegotiation.test.ts +293 -0
- package/src/gestures/__tests__/haptics.test.ts +187 -0
- package/src/gestures/__tests__/harness.ts +21 -2
- package/src/gestures/__tests__/recognizerKind.test.ts +78 -0
- package/src/gestures/__tests__/touchActionRule.test.ts +54 -0
- package/src/gestures/haptics.ts +203 -0
- package/src/gestures/nativeScroll.ts +277 -0
- package/src/gestures/touchAction.ts +191 -0
- package/src/gestures/touchActionRule.ts +157 -0
- package/src/gestures/types.ts +30 -9
- package/src/hooks/index.ts +5 -1
- package/src/hooks/modalAnnounce.ts +129 -72
- package/src/hooks/useAdaptivePresentation.ts +4 -1
- package/src/hooks/useAnnounce.doc.mjs +35 -3
- package/src/hooks/useAnnounce.replace.test.tsx +516 -0
- package/src/hooks/useAnnounce.ts +209 -102
- package/src/hooks/useAnnounceRenderedText.adopters.test.tsx +10 -1
- package/src/hooks/useAnnounceRenderedText.ts +17 -3
- package/src/hooks/useBusyAnnouncement.ts +7 -10
- package/src/hooks/useMenuHover.ts +15 -1
- package/src/i18n/generated-locales/en.generated.ts +6 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +6 -0
- package/src/motion/README.md +11 -1
- package/src/motion/animator.test.ts +151 -3
- package/src/motion/animator.ts +149 -33
- package/src/theme/Theme.doc.mjs +1 -1
- package/src/theme/defineTheme.ts +117 -8
- package/src/theme/derivedVarRegistry.test.ts +4 -0
- package/src/theme/domainTokens/domainTokenDefaults.ts +19 -0
- package/src/theme/domainTokens/index.ts +6 -10
- package/src/theme/expandMotionScale.test.ts +1 -1
- package/src/theme/index.ts +5 -0
- package/src/theme/motionKnobs.ts +82 -0
- package/src/theme/resolveThemeValues.ts +68 -6
- package/src/theme/themeAdaptations.ts +52 -12
- package/src/theme/themeMotion.test.ts +620 -0
- package/src/theme/themeMotion.ts +221 -0
- package/src/theme/tokenReferences.ts +149 -0
- package/src/theme/tokens.stylex.ts +10 -0
- package/src/theme/tokens.ts +15 -158
- package/src/utils/focusReturn.ts +17 -6
- package/src/utils/getNodeText.test.ts +24 -0
- package/src/utils/getNodeText.ts +37 -0
- package/src/utils/interactionModality.test.ts +45 -1
- package/src/utils/interactionModality.ts +64 -5
- package/src/utils/interactionOverlay.stylex.ts +12 -3
- package/src/utils/plainDate.test.ts +90 -0
- package/src/utils/plainDate.ts +58 -3
- package/src/utils/presentation.test.ts +380 -0
- package/src/utils/presentation.ts +180 -0
- package/src/utils/pressFeedback.test.ts +138 -0
- package/src/utils/pressFeedback.ts +34 -11
- package/src/utils/pressPaintPriority.test.ts +80 -0
- package/src/utils/primaryClick.test.ts +38 -0
- package/src/utils/primaryClick.ts +28 -0
- package/src/utils/timeFormat.test.ts +204 -0
- package/src/utils/timeFormat.ts +314 -0
- package/src/utils/touchPresentation.stylex.ts +43 -0
- package/src/utils/touchPresentation.test.ts +140 -1
- package/src/utils/touchPresentation.ts +5 -3
- package/src/utils/unicodeDigits.test.ts +44 -0
- package/src/utils/unicodeDigits.ts +71 -0
|
@@ -225,7 +225,10 @@ const styles = stylex.create({
|
|
|
225
225
|
display: 'flex',
|
|
226
226
|
alignItems: 'center',
|
|
227
227
|
justifyContent: 'space-between',
|
|
228
|
-
|
|
228
|
+
// At least one bar high, and taller for header content that needs it (a
|
|
229
|
+
// stacked title and search, or a title wrapped at a larger text size), so
|
|
230
|
+
// it never runs into the first item below.
|
|
231
|
+
minHeight: spacingVars['--spacing-12'],
|
|
229
232
|
paddingInline: spacingVars['--spacing-2'],
|
|
230
233
|
flexShrink: 0,
|
|
231
234
|
borderBlockEndWidth: borderVars['--border-width'],
|
|
@@ -648,6 +651,11 @@ export function MobileNav({
|
|
|
648
651
|
// only, and never while an IME composition is in progress.
|
|
649
652
|
const handleCancel = useCallback(
|
|
650
653
|
(event: React.SyntheticEvent<HTMLDialogElement>) => {
|
|
654
|
+
// A cancel that bubbles up from inside (a dismissed file picker) is not
|
|
655
|
+
// a close request; the platform's is fired at the dialog itself.
|
|
656
|
+
if (event.target !== event.currentTarget) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
651
659
|
event.preventDefault();
|
|
652
660
|
if (!shouldDismissOnCloseRequest()) {
|
|
653
661
|
return;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file MobileNavHeader.a11y.chromium.spec.ts
|
|
5
|
+
* @input Built MobileNav stories in a phone context in real Chromium
|
|
6
|
+
* @output Browser evidence that the header row is at least one bar high and
|
|
7
|
+
* grows with taller header content: a stacked title and search field stay
|
|
8
|
+
* inside the row, and the first navigation item starts below it
|
|
9
|
+
* @position Chromium proof for MobileNav's header row (`styles.header` in
|
|
10
|
+
* MobileNav.tsx, `minHeight`); jsdom has no layout to measure it
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {expect, test, type Page} from '@playwright/test';
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_STORYBOOK_DIR,
|
|
16
|
+
serveStorybook,
|
|
17
|
+
type StaticServer,
|
|
18
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
19
|
+
|
|
20
|
+
let storybook: StaticServer;
|
|
21
|
+
test.beforeAll(async () => {
|
|
22
|
+
storybook = await serveStorybook(
|
|
23
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
test.afterAll(async () => {
|
|
27
|
+
await storybook?.close();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test.use({
|
|
31
|
+
hasTouch: true,
|
|
32
|
+
isMobile: true,
|
|
33
|
+
viewport: {width: 390, height: 844},
|
|
34
|
+
deviceScaleFactor: 1,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/** Opens a story's drawer and returns the boxes of its header row and first item. */
|
|
38
|
+
async function openAndMeasure(page: Page, story: string, firstItem: string) {
|
|
39
|
+
await page.goto(
|
|
40
|
+
`${storybook.origin}/iframe.html?id=core-mobilenav--${story}&viewMode=story`,
|
|
41
|
+
);
|
|
42
|
+
await page.getByRole('button', {name: 'Open Navigation'}).click();
|
|
43
|
+
const nav = page.getByRole('dialog');
|
|
44
|
+
await expect(nav).toBeVisible();
|
|
45
|
+
const close = nav.getByRole('button', {name: 'Close navigation'});
|
|
46
|
+
// The Close button sits in the header row.
|
|
47
|
+
const header = close.locator('xpath=..');
|
|
48
|
+
const headerBox = await header.boundingBox();
|
|
49
|
+
const itemBox = await nav
|
|
50
|
+
.getByRole('link', {name: firstItem})
|
|
51
|
+
.first()
|
|
52
|
+
.boundingBox();
|
|
53
|
+
if (headerBox == null || itemBox == null) {
|
|
54
|
+
throw new Error('the drawer did not render');
|
|
55
|
+
}
|
|
56
|
+
return {nav, header, headerBox, itemBox};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
test('a one-line header keeps the one-bar row', async ({page}) => {
|
|
60
|
+
const {headerBox, itemBox} = await openAndMeasure(
|
|
61
|
+
page,
|
|
62
|
+
'default',
|
|
63
|
+
'Dashboard',
|
|
64
|
+
);
|
|
65
|
+
// --spacing-12 at the default root size, as before.
|
|
66
|
+
expect(headerBox.height).toBeCloseTo(48, 0);
|
|
67
|
+
expect(itemBox.y).toBeGreaterThanOrEqual(headerBox.y + headerBox.height);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('a taller header grows its row, and the first item starts below it', async ({
|
|
71
|
+
page,
|
|
72
|
+
}) => {
|
|
73
|
+
const {nav, headerBox, itemBox} = await openAndMeasure(
|
|
74
|
+
page,
|
|
75
|
+
'tall-header',
|
|
76
|
+
'Dashboard',
|
|
77
|
+
);
|
|
78
|
+
expect(headerBox.height).toBeGreaterThan(48);
|
|
79
|
+
const search = await nav.getByRole('textbox', {name: 'Search'}).boundingBox();
|
|
80
|
+
if (search == null) {
|
|
81
|
+
throw new Error('the search field did not render');
|
|
82
|
+
}
|
|
83
|
+
// The search field is inside the row, and nothing overlaps the first item.
|
|
84
|
+
expect(search.y).toBeGreaterThanOrEqual(headerBox.y);
|
|
85
|
+
expect(search.y + search.height).toBeLessThanOrEqual(
|
|
86
|
+
headerBox.y + headerBox.height,
|
|
87
|
+
);
|
|
88
|
+
expect(itemBox.y).toBeGreaterThanOrEqual(headerBox.y + headerBox.height);
|
|
89
|
+
});
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* SYNC: When MoreMenu.tsx changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import {createRef} from 'react';
|
|
12
13
|
import {describe, it, expect, vi, beforeAll, beforeEach} from 'vitest';
|
|
13
14
|
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
14
15
|
import userEvent from '@testing-library/user-event';
|
|
@@ -256,6 +257,16 @@ describe('MoreMenu', () => {
|
|
|
256
257
|
expect(handleEdit).not.toHaveBeenCalled();
|
|
257
258
|
});
|
|
258
259
|
|
|
260
|
+
it('forwards its ref to the bottom-sheet trigger', () => {
|
|
261
|
+
const ref = createRef<HTMLButtonElement>();
|
|
262
|
+
render(
|
|
263
|
+
<MoreMenu items={defaultItems} ref={ref} presentation="bottom-sheet" />,
|
|
264
|
+
);
|
|
265
|
+
expect(ref.current).toBe(
|
|
266
|
+
screen.getByRole('button', {name: 'More options'}),
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
259
270
|
it('supports forwardRef', () => {
|
|
260
271
|
const ref = vi.fn();
|
|
261
272
|
render(<MoreMenu items={defaultItems} ref={ref} />);
|
|
@@ -78,10 +78,11 @@ async function politeSince(page: Page, since: number): Promise<string> {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* Matches a polite region whose newest message is `message`. Messages
|
|
82
|
-
* before the region exposes the previous one are
|
|
83
|
-
* (spec:AST-086 FR9), so
|
|
84
|
-
*
|
|
81
|
+
* Matches a polite region whose newest message is `message`. Messages from
|
|
82
|
+
* different announcers written before the region exposes the previous one are
|
|
83
|
+
* joined in order (spec:AST-086 FR9), so the creation can follow a search
|
|
84
|
+
* status still waiting. The search's own statuses replace each other instead
|
|
85
|
+
* (its `hasReplace` announcer), so they are read whole.
|
|
85
86
|
*/
|
|
86
87
|
function newestMessage(message: string): RegExp {
|
|
87
88
|
const escaped = message.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -108,9 +109,7 @@ test('the create row is announced as it appears and the highlight survives it va
|
|
|
108
109
|
// "Bu": a partial match plus the create row above it.
|
|
109
110
|
await search.pressSequentially('Bu');
|
|
110
111
|
await expect(page.getByRole('option')).toHaveText(['Create "Bu"', 'Bug']);
|
|
111
|
-
await expect
|
|
112
|
-
.poll(async () => politeSince(page, 0))
|
|
113
|
-
.toMatch(newestMessage('1 result'));
|
|
112
|
+
await expect.poll(async () => politeSince(page, 0)).toBe('1 result');
|
|
114
113
|
|
|
115
114
|
// Highlight the create row, then type the letter that makes it vanish: the
|
|
116
115
|
// highlight must not be left pointing at a row that no longer exists.
|
|
@@ -132,7 +131,7 @@ test('the create row is announced as it appears and the highlight survives it va
|
|
|
132
131
|
await expect(page.getByRole('option')).toHaveText(['Create "Urgent"']);
|
|
133
132
|
await expect
|
|
134
133
|
.poll(async () => politeSince(page, beforeUrgent))
|
|
135
|
-
.
|
|
134
|
+
.toBe('Create "Urgent"');
|
|
136
135
|
|
|
137
136
|
// Enter commits: the caller adds the option, the search clears, and the new
|
|
138
137
|
// label is a real row.
|
|
@@ -182,10 +182,13 @@ and `aria-controls` for the listbox, the listbox carries `aria-label` from
|
|
|
182
182
|
focused element that owns arrow, Enter, Escape, and typeahead keys while open,
|
|
183
183
|
so an anchor that cannot take focus still leaves a keyboard path.
|
|
184
184
|
|
|
185
|
-
The create row is a `role="option"` with `aria-selected="false"` and no
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
The create row is a `role="option"` with `aria-selected="false"` and no checkbox
|
|
186
|
+
indicator. When it is the only result, the polite live region announces its
|
|
187
|
+
label rather than the empty-search message, so what is announced matches what is
|
|
188
|
+
on screen; picking it announces the creation. The result count and the create
|
|
189
|
+
row's label come from the search's own `useAnnounce({hasReplace: true})`
|
|
190
|
+
instance, so a newer one replaces one not yet spoken (`spec:AST-086` FR9), and
|
|
191
|
+
selection messages keep their own instance.
|
|
189
192
|
|
|
190
193
|
## Design relationships
|
|
191
194
|
|
|
@@ -9,7 +9,15 @@
|
|
|
9
9
|
* SYNC: When MultiSelector.tsx API changes, update these tests.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
describe,
|
|
14
|
+
it,
|
|
15
|
+
expect,
|
|
16
|
+
vi,
|
|
17
|
+
beforeAll,
|
|
18
|
+
beforeEach,
|
|
19
|
+
afterEach,
|
|
20
|
+
} from 'vitest';
|
|
13
21
|
import {
|
|
14
22
|
act,
|
|
15
23
|
render,
|
|
@@ -26,6 +34,7 @@ import {
|
|
|
26
34
|
type MultiSelectorChange,
|
|
27
35
|
} from './MultiSelector';
|
|
28
36
|
import {useRef, useState} from 'react';
|
|
37
|
+
import {Dialog} from '../Dialog/Dialog';
|
|
29
38
|
import {Icon} from '../Icon';
|
|
30
39
|
import {InternationalizationProvider} from '../i18n';
|
|
31
40
|
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
@@ -65,6 +74,15 @@ function politeRegion(): HTMLElement | null {
|
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
// Mock showPopover and hidePopover methods since they're not implemented in jsdom
|
|
77
|
+
// The bottom-sheet presentation renders BottomSheet through React.lazy. Its
|
|
78
|
+
// first import transforms the BottomSheet module tree, which on a loaded
|
|
79
|
+
// runner takes longer than a query's 1 s wait, so the first sheet test timed
|
|
80
|
+
// out before the dialog existed. Load the module once up front: lazy() then
|
|
81
|
+
// resolves from the module cache and every sheet test waits only on the open.
|
|
82
|
+
beforeAll(async () => {
|
|
83
|
+
await import('../BottomSheet/BottomSheet');
|
|
84
|
+
});
|
|
85
|
+
|
|
68
86
|
beforeEach(() => {
|
|
69
87
|
__resetInteractionModalityForTest();
|
|
70
88
|
HTMLDialogElement.prototype.showModal = vi.fn(function (
|
|
@@ -128,6 +146,13 @@ function expectSilentBusySpinner() {
|
|
|
128
146
|
expect(screen.queryByRole('status', {name: 'Loading'})).toBeNull();
|
|
129
147
|
}
|
|
130
148
|
|
|
149
|
+
// The bottom sheet is loaded on demand (SelectorBottomSheet's lazy import).
|
|
150
|
+
// Loading its module is not part of an open, so the file waits for it once
|
|
151
|
+
// here, and each test's open waits only for the open.
|
|
152
|
+
beforeAll(async () => {
|
|
153
|
+
await import('../BottomSheet/BottomSheet');
|
|
154
|
+
});
|
|
155
|
+
|
|
131
156
|
describe('MultiSelector', () => {
|
|
132
157
|
const defaultOptions = ['Apple', 'Banana', 'Orange'];
|
|
133
158
|
|
|
@@ -1388,6 +1413,58 @@ describe('MultiSelector', () => {
|
|
|
1388
1413
|
expect(within(listbox).getByText('Add a fruit first')).toBeInTheDocument();
|
|
1389
1414
|
});
|
|
1390
1415
|
|
|
1416
|
+
describe('count trigger text', () => {
|
|
1417
|
+
it('shows the default count in the catalog message', () => {
|
|
1418
|
+
render(
|
|
1419
|
+
<MultiSelector
|
|
1420
|
+
label="Fruit"
|
|
1421
|
+
options={defaultOptions}
|
|
1422
|
+
value={['Apple', 'Banana']}
|
|
1423
|
+
onChange={() => {}}
|
|
1424
|
+
triggerDisplay="count"
|
|
1425
|
+
/>,
|
|
1426
|
+
);
|
|
1427
|
+
expect(screen.getByText('2 selected')).toBeInTheDocument();
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
it('translates the default count, with plural forms, under another locale', () => {
|
|
1431
|
+
render(
|
|
1432
|
+
<InternationalizationProvider
|
|
1433
|
+
locale="fr"
|
|
1434
|
+
overrides={{
|
|
1435
|
+
fr: {
|
|
1436
|
+
'@astryx.multiSelector.selectedCount':
|
|
1437
|
+
'{count, number} {count, plural, one {sélectionné} other {sélectionnés}}',
|
|
1438
|
+
},
|
|
1439
|
+
}}>
|
|
1440
|
+
<MultiSelector
|
|
1441
|
+
label="Fruit"
|
|
1442
|
+
options={defaultOptions}
|
|
1443
|
+
value={['Apple', 'Banana']}
|
|
1444
|
+
onChange={() => {}}
|
|
1445
|
+
triggerDisplay="count"
|
|
1446
|
+
/>
|
|
1447
|
+
</InternationalizationProvider>,
|
|
1448
|
+
);
|
|
1449
|
+
expect(screen.getByText('2 sélectionnés')).toBeInTheDocument();
|
|
1450
|
+
expect(screen.queryByText('2 selected')).not.toBeInTheDocument();
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
it('still lets formatValue replace the count text', () => {
|
|
1454
|
+
render(
|
|
1455
|
+
<MultiSelector
|
|
1456
|
+
label="Fruit"
|
|
1457
|
+
options={defaultOptions}
|
|
1458
|
+
value={['Apple', 'Banana']}
|
|
1459
|
+
onChange={() => {}}
|
|
1460
|
+
triggerDisplay="count"
|
|
1461
|
+
formatValue={items => `${items.length} fruits`}
|
|
1462
|
+
/>,
|
|
1463
|
+
);
|
|
1464
|
+
expect(screen.getByText('2 fruits')).toBeInTheDocument();
|
|
1465
|
+
});
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1391
1468
|
describe('result announcements', () => {
|
|
1392
1469
|
it('announces the match count politely while searching', async () => {
|
|
1393
1470
|
const user = userEvent.setup();
|
|
@@ -3436,6 +3513,47 @@ describe('MultiSelector trigger render prop', () => {
|
|
|
3436
3513
|
expect(screen.queryByText('Labels')).toBeNull();
|
|
3437
3514
|
});
|
|
3438
3515
|
|
|
3516
|
+
// spec:AST-055 AR3: a light dismiss returns focus to the caller's control,
|
|
3517
|
+
// even when the press that closed the list focused another field.
|
|
3518
|
+
it('returns focus to the caller control after a light dismiss onto another field', async () => {
|
|
3519
|
+
const user = userEvent.setup();
|
|
3520
|
+
render(
|
|
3521
|
+
<>
|
|
3522
|
+
<MultiSelector
|
|
3523
|
+
label="Labels"
|
|
3524
|
+
options={OPTIONS}
|
|
3525
|
+
value={[]}
|
|
3526
|
+
onChange={() => {}}
|
|
3527
|
+
renderTrigger={props => (
|
|
3528
|
+
<button type="button" {...props}>
|
|
3529
|
+
Pick labels
|
|
3530
|
+
</button>
|
|
3531
|
+
)}
|
|
3532
|
+
/>
|
|
3533
|
+
<input aria-label="Notes" />
|
|
3534
|
+
</>,
|
|
3535
|
+
);
|
|
3536
|
+
const opener = screen.getByRole('button', {name: 'Pick labels'});
|
|
3537
|
+
await user.click(opener);
|
|
3538
|
+
expect(opener).toHaveAttribute('aria-expanded', 'true');
|
|
3539
|
+
|
|
3540
|
+
act(() => {
|
|
3541
|
+
screen.getByRole('textbox', {name: 'Notes'}).focus();
|
|
3542
|
+
});
|
|
3543
|
+
const popover = document.querySelector('[popover]') as HTMLElement;
|
|
3544
|
+
act(() => {
|
|
3545
|
+
popover.dispatchEvent(
|
|
3546
|
+
Object.assign(new Event('toggle'), {
|
|
3547
|
+
oldState: 'open',
|
|
3548
|
+
newState: 'closed',
|
|
3549
|
+
}),
|
|
3550
|
+
);
|
|
3551
|
+
});
|
|
3552
|
+
|
|
3553
|
+
expect(opener).toHaveAttribute('aria-expanded', 'false');
|
|
3554
|
+
expect(opener).toHaveFocus();
|
|
3555
|
+
});
|
|
3556
|
+
|
|
3439
3557
|
it('opens against the caller control and names the listbox by label', async () => {
|
|
3440
3558
|
const user = userEvent.setup();
|
|
3441
3559
|
render(
|
|
@@ -3720,6 +3838,157 @@ describe('MultiSelector hasCreate', () => {
|
|
|
3720
3838
|
expect(search).toHaveValue('');
|
|
3721
3839
|
});
|
|
3722
3840
|
|
|
3841
|
+
it('says only the latest status when several keystrokes land in one frame (spec:AST-086 FR9)', async () => {
|
|
3842
|
+
// A barcode scanner, dictation or automation types faster than a frame:
|
|
3843
|
+
// each keystroke states the panel's status through the search's
|
|
3844
|
+
// `hasReplace` announcer, so a status the next keystroke replaces before
|
|
3845
|
+
// it is exposed is withdrawn, not joined.
|
|
3846
|
+
const user = userEvent.setup();
|
|
3847
|
+
render(
|
|
3848
|
+
<MultiSelector
|
|
3849
|
+
label="Labels"
|
|
3850
|
+
options={OPTIONS}
|
|
3851
|
+
value={['bug']}
|
|
3852
|
+
onChange={() => {}}
|
|
3853
|
+
hasSearch
|
|
3854
|
+
hasCreate
|
|
3855
|
+
/>,
|
|
3856
|
+
);
|
|
3857
|
+
await user.click(screen.getByRole('button', {name: 'Labels'}));
|
|
3858
|
+
const search = screen.getByRole('combobox', h);
|
|
3859
|
+
for (const value of ['Ur', 'Urg', 'Urge', 'Urgent']) {
|
|
3860
|
+
fireEvent.change(search, {target: {value}});
|
|
3861
|
+
}
|
|
3862
|
+
await act(
|
|
3863
|
+
async () =>
|
|
3864
|
+
new Promise<void>(resolve => requestAnimationFrame(() => resolve())),
|
|
3865
|
+
);
|
|
3866
|
+
expect(politeRegion()?.textContent).toBe('Create "Urgent"');
|
|
3867
|
+
});
|
|
3868
|
+
|
|
3869
|
+
it('says only the empty panel’s message when the next keystroke in the frame empties the panel (spec:AST-086 FR13)', async () => {
|
|
3870
|
+
const user = userEvent.setup();
|
|
3871
|
+
render(
|
|
3872
|
+
<MultiSelector
|
|
3873
|
+
label="Labels"
|
|
3874
|
+
options={OPTIONS}
|
|
3875
|
+
value={[]}
|
|
3876
|
+
onChange={() => {}}
|
|
3877
|
+
hasSearch
|
|
3878
|
+
/>,
|
|
3879
|
+
);
|
|
3880
|
+
await user.click(screen.getByRole('button', {name: 'Labels'}));
|
|
3881
|
+
const search = screen.getByRole('combobox', h);
|
|
3882
|
+
// "Fe" matches Feature; "Fex" matches nothing, in the same frame.
|
|
3883
|
+
fireEvent.change(search, {target: {value: 'Fe'}});
|
|
3884
|
+
fireEvent.change(search, {target: {value: 'Fex'}});
|
|
3885
|
+
await act(
|
|
3886
|
+
async () =>
|
|
3887
|
+
new Promise<void>(resolve => requestAnimationFrame(() => resolve())),
|
|
3888
|
+
);
|
|
3889
|
+
expect(politeRegion()?.textContent).toBe('No results found');
|
|
3890
|
+
});
|
|
3891
|
+
|
|
3892
|
+
it('says only the count when the next keystroke in the frame fills an empty panel (spec:AST-086 FR13)', async () => {
|
|
3893
|
+
// The empty panel's message comes from the search's `hasReplace`
|
|
3894
|
+
// announcer too, so the count that follows it in the frame replaces it.
|
|
3895
|
+
const user = userEvent.setup();
|
|
3896
|
+
render(
|
|
3897
|
+
<MultiSelector
|
|
3898
|
+
label="Labels"
|
|
3899
|
+
options={OPTIONS}
|
|
3900
|
+
value={[]}
|
|
3901
|
+
onChange={() => {}}
|
|
3902
|
+
hasSearch
|
|
3903
|
+
/>,
|
|
3904
|
+
);
|
|
3905
|
+
await user.click(screen.getByRole('button', {name: 'Labels'}));
|
|
3906
|
+
const search = screen.getByRole('combobox', h);
|
|
3907
|
+
fireEvent.change(search, {target: {value: 'Fex'}});
|
|
3908
|
+
fireEvent.change(search, {target: {value: 'Fe'}});
|
|
3909
|
+
await act(
|
|
3910
|
+
async () =>
|
|
3911
|
+
new Promise<void>(resolve => requestAnimationFrame(() => resolve())),
|
|
3912
|
+
);
|
|
3913
|
+
expect(politeRegion()?.textContent).toBe('1 result');
|
|
3914
|
+
});
|
|
3915
|
+
|
|
3916
|
+
it('says only the empty panel’s message from inside a modal when the next keystroke in the frame empties the panel (spec:AST-086 FR14)', async () => {
|
|
3917
|
+
// Inside an open modal the search's announcer writes to the modal's own
|
|
3918
|
+
// container, which replaces the same way.
|
|
3919
|
+
HTMLDialogElement.prototype.showModal = vi.fn(function (
|
|
3920
|
+
this: HTMLDialogElement,
|
|
3921
|
+
) {
|
|
3922
|
+
this.setAttribute('open', '');
|
|
3923
|
+
this.dataset.modal = 'true';
|
|
3924
|
+
});
|
|
3925
|
+
// The file's own `matches` stand-in (for :popover-open) sits on
|
|
3926
|
+
// HTMLElement.prototype; :modal is added on top of it.
|
|
3927
|
+
const matches = HTMLElement.prototype.matches;
|
|
3928
|
+
vi.spyOn(HTMLElement.prototype, 'matches').mockImplementation(function (
|
|
3929
|
+
this: HTMLElement,
|
|
3930
|
+
selector: string,
|
|
3931
|
+
) {
|
|
3932
|
+
return selector === ':modal'
|
|
3933
|
+
? this instanceof HTMLDialogElement &&
|
|
3934
|
+
this.open &&
|
|
3935
|
+
this.dataset.modal === 'true'
|
|
3936
|
+
: matches.call(this, selector);
|
|
3937
|
+
});
|
|
3938
|
+
const user = userEvent.setup();
|
|
3939
|
+
render(
|
|
3940
|
+
<Dialog isOpen onOpenChange={() => {}} aria-label="Edit labels">
|
|
3941
|
+
<MultiSelector
|
|
3942
|
+
label="Labels"
|
|
3943
|
+
options={OPTIONS}
|
|
3944
|
+
value={[]}
|
|
3945
|
+
onChange={() => {}}
|
|
3946
|
+
hasSearch
|
|
3947
|
+
/>
|
|
3948
|
+
</Dialog>,
|
|
3949
|
+
);
|
|
3950
|
+
const dialog = screen.getByRole('dialog', {name: 'Edit labels'});
|
|
3951
|
+
await user.click(screen.getByRole('button', {name: 'Labels'}));
|
|
3952
|
+
const search = screen.getByRole('combobox', h);
|
|
3953
|
+
fireEvent.change(search, {target: {value: 'Fe'}});
|
|
3954
|
+
fireEvent.change(search, {target: {value: 'Fex'}});
|
|
3955
|
+
await act(
|
|
3956
|
+
async () =>
|
|
3957
|
+
new Promise<void>(resolve => requestAnimationFrame(() => resolve())),
|
|
3958
|
+
);
|
|
3959
|
+
expect(
|
|
3960
|
+
dialog.querySelector(':scope > [data-astryx-modal-live-region="polite"]')
|
|
3961
|
+
?.textContent,
|
|
3962
|
+
).toBe('No results found');
|
|
3963
|
+
});
|
|
3964
|
+
|
|
3965
|
+
it('still says a selection change and a result count from the same frame: each has its own announcer (spec:AST-086 FR9)', async () => {
|
|
3966
|
+
const user = userEvent.setup();
|
|
3967
|
+
render(
|
|
3968
|
+
<MultiSelector
|
|
3969
|
+
label="Labels"
|
|
3970
|
+
options={OPTIONS}
|
|
3971
|
+
value={[]}
|
|
3972
|
+
onChange={() => {}}
|
|
3973
|
+
hasSearch
|
|
3974
|
+
/>,
|
|
3975
|
+
);
|
|
3976
|
+
await user.click(screen.getByRole('button', {name: 'Labels'}));
|
|
3977
|
+
await act(
|
|
3978
|
+
async () =>
|
|
3979
|
+
new Promise<void>(resolve => requestAnimationFrame(() => resolve())),
|
|
3980
|
+
);
|
|
3981
|
+
fireEvent.click(screen.getByRole('option', {name: 'Bug', hidden: true}));
|
|
3982
|
+
fireEvent.change(screen.getByRole('combobox', h), {
|
|
3983
|
+
target: {value: 'Fe'},
|
|
3984
|
+
});
|
|
3985
|
+
await act(
|
|
3986
|
+
async () =>
|
|
3987
|
+
new Promise<void>(resolve => requestAnimationFrame(() => resolve())),
|
|
3988
|
+
);
|
|
3989
|
+
expect(politeRegion()?.textContent).toBe('1 of 3 selected 1 result');
|
|
3990
|
+
});
|
|
3991
|
+
|
|
3723
3992
|
it('a plain toggle passes no descriptor, so a one-argument handler is unchanged', async () => {
|
|
3724
3993
|
const user = userEvent.setup();
|
|
3725
3994
|
const onChange = vi.fn();
|
|
@@ -905,7 +905,7 @@ export interface MultiSelectorProps<
|
|
|
905
905
|
* Not called when nothing is selected — the placeholder shows instead — and
|
|
906
906
|
* not called for triggerDisplay 'badges', which renders Badge elements
|
|
907
907
|
* rather than text.
|
|
908
|
-
* @default
|
|
908
|
+
* @default the translated `@astryx.multiSelector.selectedCount` message ("2 selected") for 'count', "A, B, C, +N" for 'labels'
|
|
909
909
|
*/
|
|
910
910
|
formatValue?: (items: MultiSelectorSelectedItem[]) => string;
|
|
911
911
|
|
|
@@ -1450,6 +1450,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1450
1450
|
onHide: handleLayerHide,
|
|
1451
1451
|
onShow: handleLayerShow,
|
|
1452
1452
|
triggerRef,
|
|
1453
|
+
isFocusReturnAlways: hasExternalTrigger,
|
|
1453
1454
|
popoverOptions: {
|
|
1454
1455
|
hasLightDismiss: true,
|
|
1455
1456
|
hasCloseButton: false,
|
|
@@ -1546,21 +1547,27 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1546
1547
|
// Announce the filtered result count from the query-change handler (matching
|
|
1547
1548
|
// BaseTypeahead) rather than a reactive effect: computing the count for the
|
|
1548
1549
|
// next query here fires the announcement exactly once per keystroke and does
|
|
1549
|
-
// not re-speak on unrelated re-renders.
|
|
1550
|
-
//
|
|
1550
|
+
// not re-speak on unrelated re-renders. The count, the create row and the
|
|
1551
|
+
// empty panel's message state one thing, how the search ended, so they go
|
|
1552
|
+
// through the search's own `hasReplace` announcer: one the next keystroke
|
|
1553
|
+
// replaces before it is exposed is withdrawn rather than joined
|
|
1554
|
+
// (`spec:AST-086` FR9, FR13), and keystrokes arriving within one frame,
|
|
1555
|
+
// from a scanner, dictation or automation, say only the last query's.
|
|
1556
|
+
// Selection changes keep the plain announcer above, so they still join.
|
|
1557
|
+
const announceSearch = useAnnounce({hasReplace: true});
|
|
1551
1558
|
const handleSearchChange = useCallback(
|
|
1552
1559
|
(nextQuery: string) => {
|
|
1553
1560
|
setSearchQuery(nextQuery);
|
|
1554
1561
|
if (nextQuery.length === 0) {
|
|
1555
1562
|
// Emptying the query clears the region rather than announcing a count.
|
|
1556
|
-
|
|
1563
|
+
announceSearch('');
|
|
1557
1564
|
return;
|
|
1558
1565
|
}
|
|
1559
1566
|
// While isLoading the panel deliberately shows nothing, so announcing a
|
|
1560
1567
|
// result would put a claim in the one channel the screen has gone quiet
|
|
1561
1568
|
// for.
|
|
1562
1569
|
if (isLoading) {
|
|
1563
|
-
|
|
1570
|
+
announceSearch('');
|
|
1564
1571
|
return;
|
|
1565
1572
|
}
|
|
1566
1573
|
const count = filterOptionsByQuery(selectableItems, nextQuery).length;
|
|
@@ -1570,7 +1577,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1570
1577
|
// The create row is the one result on screen, so the panel is not
|
|
1571
1578
|
// empty and the rendered-message route below stays silent; say the
|
|
1572
1579
|
// row.
|
|
1573
|
-
|
|
1580
|
+
announceSearch(
|
|
1574
1581
|
t('@astryx.multiSelector.createOption', {query: nextCreateQuery}),
|
|
1575
1582
|
);
|
|
1576
1583
|
return;
|
|
@@ -1581,9 +1588,9 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1581
1588
|
// keystroke — an async load landing with nothing that matches.
|
|
1582
1589
|
return;
|
|
1583
1590
|
}
|
|
1584
|
-
|
|
1591
|
+
announceSearch(t('@astryx.multiSelector.resultCount', {count}));
|
|
1585
1592
|
},
|
|
1586
|
-
[
|
|
1593
|
+
[announceSearch, isLoading, selectableItems, getCreateQuery, t],
|
|
1587
1594
|
);
|
|
1588
1595
|
|
|
1589
1596
|
// The panel's empty message is role="presentation" — role="listbox" permits
|
|
@@ -1608,7 +1615,12 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1608
1615
|
const realItemCount = sortedItems.length - leadingCount;
|
|
1609
1616
|
const isPanelEmpty =
|
|
1610
1617
|
surface.isOpen && !isLoading && realItemCount === 0 && !hasCreateRow;
|
|
1611
|
-
|
|
1618
|
+
// Through the search's announcer: the empty panel is how the search ended,
|
|
1619
|
+
// so it and the count replace each other while not yet exposed
|
|
1620
|
+
// (`spec:AST-086` FR13).
|
|
1621
|
+
useAnnounceRenderedText(emptyStateRef, isPanelEmpty, searchQuery, {
|
|
1622
|
+
announce: announceSearch,
|
|
1623
|
+
});
|
|
1612
1624
|
|
|
1613
1625
|
// Handle toggle
|
|
1614
1626
|
// Clear all selected values. Shared by the clear button and the keyboard
|
|
@@ -1890,7 +1902,9 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1890
1902
|
return (
|
|
1891
1903
|
<span {...stylex.props(styles.triggerText)}>
|
|
1892
1904
|
{formatValue?.(selectedItems) ??
|
|
1893
|
-
|
|
1905
|
+
t('@astryx.multiSelector.selectedCount', {
|
|
1906
|
+
count: optimisticValue.length,
|
|
1907
|
+
})}
|
|
1894
1908
|
</span>
|
|
1895
1909
|
);
|
|
1896
1910
|
|
|
@@ -1933,6 +1947,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1933
1947
|
placeholder,
|
|
1934
1948
|
formatValue,
|
|
1935
1949
|
maxBadges,
|
|
1950
|
+
t,
|
|
1936
1951
|
]);
|
|
1937
1952
|
|
|
1938
1953
|
// Render search input
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import {cleanup, render, screen, within} from '@testing-library/react';
|
|
11
11
|
import userEvent from '@testing-library/user-event';
|
|
12
|
-
import {describe, expect, it} from 'vitest';
|
|
12
|
+
import {beforeAll, describe, expect, it} from 'vitest';
|
|
13
13
|
import {
|
|
14
14
|
expectAccessibilitySpec,
|
|
15
15
|
LISTBOX_PATTERN,
|
|
@@ -23,6 +23,13 @@ import {installListboxDialogStubs} from '../../Selector/__tests__/Listbox.a11y.d
|
|
|
23
23
|
|
|
24
24
|
installListboxDialogStubs();
|
|
25
25
|
|
|
26
|
+
// The bottom sheet is loaded on demand (SelectorBottomSheet's lazy import).
|
|
27
|
+
// Loading its module is not part of an open, so the file waits for it once
|
|
28
|
+
// here, and each scenario's open waits only for the open.
|
|
29
|
+
beforeAll(async () => {
|
|
30
|
+
await import('../../BottomSheet/BottomSheet');
|
|
31
|
+
});
|
|
32
|
+
|
|
26
33
|
const scenarios = LISTBOX_SCENARIOS.filter(
|
|
27
34
|
scenario => scenario.component === 'MultiSelector',
|
|
28
35
|
);
|
|
@@ -44,7 +44,7 @@ system_specs: [spec:AST-005/DEC-1, spec:AST-020, spec:AST-085]
|
|
|
44
44
|
| Behavior | A `role="menu"` list of `menuitem` rows inside a heading popover, with arrow, Home, End, typeahead, Enter, Space, and Escape support. Selecting an enabled item runs it and, where the host heading supplies a close action, closes the heading menu. |
|
|
45
45
|
| End-user impact | Users switch products or accounts from the heading menu by pointer or keyboard; disabled rows are skipped by keyboard and typeahead. |
|
|
46
46
|
| Builder impact | None. |
|
|
47
|
-
| Compatibility/readiness | Records shipped behavior; no public change.
|
|
47
|
+
| Compatibility/readiness | Records shipped behavior; no public change. Five implementation gaps (see Current-state impact). |
|
|
48
48
|
| Review checks | Reject losing `menu`/`menuitem` roles to caller props, keyboard focus landing on a disabled item, or a caller `onKeyDown` that silently disables the keyboard model. |
|
|
49
49
|
| Governing rules | `family:navigation-destinations` FR7; `architecture:interaction-modality`; `architecture:component-theming-surface`; `architecture:layer-runtime`. |
|
|
50
50
|
|
|
@@ -81,10 +81,6 @@ Consumer migration instructions belong in consumer docs and release notes.
|
|
|
81
81
|
`role="menu"` sits directly inside the heading's own `role="menu"` wrapper.
|
|
82
82
|
WAI-ARIA 1.2 allows a `menu` to own only `menuitem`-family items and groups,
|
|
83
83
|
so assistive technology meets a menu nested in a menu.
|
|
84
|
-
- **Inside a heading, arrow keys move twice.** Inside SideNavHeading or
|
|
85
|
-
TopNavHeading, the heading's own list handler also moves focus over every
|
|
86
|
-
`menuitem`, so one ArrowDown moves two items, and in a two-item menu ArrowDown
|
|
87
|
-
never moves focus. The APG menu pattern moves focus one item per arrow press.
|
|
88
84
|
- **The menu has no name of its own.** NavHeadingMenu's `menu` carries no
|
|
89
85
|
accessible name unless the caller passes `aria-label`; inside a heading only
|
|
90
86
|
the heading's wrapper `menu` is named. The APG menu pattern, which
|