@astryxdesign/core 0.7.5 → 0.7.6-canary.00507ba
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/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +183 -5
- package/dist/AppShell/observeMainOverflow.d.ts +9 -0
- package/dist/AppShell/observeMainOverflow.d.ts.map +1 -0
- package/dist/AppShell/observeMainOverflow.js +125 -0
- 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/Carousel/Carousel.d.ts +3 -1
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +12 -3
- 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 +21 -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/scrollKeyboardDelegation.d.ts.map +1 -1
- package/dist/hooks/scrollKeyboardDelegation.js +7 -3
- 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/hooks/useScrollOverflow.d.ts +2 -1
- package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
- package/dist/hooks/useScrollOverflow.js +56 -10
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +6 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +6 -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/sharedMutationObserver.d.ts +19 -11
- package/dist/utils/sharedMutationObserver.d.ts.map +1 -1
- package/dist/utils/sharedMutationObserver.js +66 -26
- 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 +27 -3
- package/locales/pseudo.json +18 -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/AppShell/AppShell.doc.mjs +1 -1
- package/src/AppShell/AppShell.spec.md +9 -12
- package/src/AppShell/AppShell.test.tsx +164 -2
- package/src/AppShell/AppShell.tsx +225 -5
- package/src/AppShell/__tests__/AppShellMainKeyboard.a11y.browser.spec.ts +534 -0
- package/src/AppShell/observeMainOverflow.test.ts +173 -0
- package/src/AppShell/observeMainOverflow.ts +143 -0
- 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/Carousel.doc.mjs +1 -1
- package/src/Carousel/Carousel.spec.md +9 -9
- package/src/Carousel/Carousel.test.tsx +124 -3
- package/src/Carousel/Carousel.tsx +10 -2
- package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +21 -7
- package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +1 -16
- package/src/Chat/ChatComposerTokenElement.spec.md +45 -33
- 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/scrollKeyboardDelegation.test.ts +33 -0
- package/src/hooks/scrollKeyboardDelegation.ts +9 -3
- 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/hooks/useScrollOverflow.doc.mjs +1 -1
- package/src/hooks/useScrollOverflow.test.tsx +102 -1
- package/src/hooks/useScrollOverflow.ts +59 -10
- package/src/i18n/generated-locales/en.generated.ts +7 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +7 -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/sharedMutationObserver.test.ts +54 -1
- package/src/utils/sharedMutationObserver.ts +87 -29
- 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
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file AppShellMainKeyboard.a11y.browser.spec.ts
|
|
5
|
+
* @input The `Core/AppShell` stories in a built Storybook, and real Chromium
|
|
6
|
+
* and WebKit keyboard input.
|
|
7
|
+
* @output Browser evidence that the main region of a `fill` shell is reachable
|
|
8
|
+
* from the keyboard while it scrolls overflowing content (AST-025 FR12): Tab
|
|
9
|
+
* reaches it when nothing inside is focusable and Page Down then scrolls it,
|
|
10
|
+
* and the stop it reaches is exposed as the main landmark (its role names it);
|
|
11
|
+
* Tab entry moves on to its first link when there is one; keyboard focus
|
|
12
|
+
* draws the focus ring inset, following the elevated surface's rounded
|
|
13
|
+
* corner (also after Shift+Tab from the page start, and for as long as that
|
|
14
|
+
* focus stays when the content shrinks to fit), and pointer focus, the skip
|
|
15
|
+
* link's focus move and a script's focus() draw none; an
|
|
16
|
+
* overflowing main rounds its corner only while its ring shows; a main
|
|
17
|
+
* region that fits stays out of the tab order and keeps its square corner.
|
|
18
|
+
* @position Cross-browser proof; jsdom has no layout, so AppShell.test.tsx can
|
|
19
|
+
* only assert the tab stop against mocked geometry.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {chromium, expect, test, type Page} from '@playwright/test';
|
|
23
|
+
import {
|
|
24
|
+
DEFAULT_STORYBOOK_DIR,
|
|
25
|
+
serveStorybook,
|
|
26
|
+
type StaticServer,
|
|
27
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
28
|
+
|
|
29
|
+
let storybook: StaticServer;
|
|
30
|
+
test.beforeAll(async () => {
|
|
31
|
+
storybook = await serveStorybook(
|
|
32
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
test.afterAll(async () => {
|
|
36
|
+
await storybook?.close();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test.use({viewport: {width: 1280, height: 720}});
|
|
40
|
+
|
|
41
|
+
async function openStory(page: Page, story: string) {
|
|
42
|
+
await page.goto(
|
|
43
|
+
`${storybook.origin}/iframe.html?id=core-appshell--${story}&viewMode=story`,
|
|
44
|
+
);
|
|
45
|
+
await expect(page.getByRole('main')).toBeVisible();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Presses Tab from the top of a freshly opened story until focus reaches
|
|
50
|
+
* `isTarget`, or gives up after `limit` presses. Returns how many presses it
|
|
51
|
+
* took, or -1.
|
|
52
|
+
*/
|
|
53
|
+
async function tabUntil(
|
|
54
|
+
page: Page,
|
|
55
|
+
isTarget: () => boolean,
|
|
56
|
+
limit = 60,
|
|
57
|
+
): Promise<number> {
|
|
58
|
+
for (let presses = 1; presses <= limit; presses += 1) {
|
|
59
|
+
await page.keyboard.press('Tab');
|
|
60
|
+
if (await page.evaluate(isTarget)) {
|
|
61
|
+
return presses;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return -1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
test('Tab reaches an overflowing main region with nothing focusable in it, and Page Down scrolls it', async ({
|
|
68
|
+
page,
|
|
69
|
+
}) => {
|
|
70
|
+
await openStory(page, 'long-content');
|
|
71
|
+
const main = page.getByRole('main');
|
|
72
|
+
await expect(main).toHaveAttribute('tabindex', '0');
|
|
73
|
+
|
|
74
|
+
const presses = await tabUntil(
|
|
75
|
+
page,
|
|
76
|
+
() => document.activeElement?.getAttribute('role') === 'main',
|
|
77
|
+
);
|
|
78
|
+
expect(presses).toBeGreaterThan(0);
|
|
79
|
+
|
|
80
|
+
await page.keyboard.press('PageDown');
|
|
81
|
+
await expect
|
|
82
|
+
.poll(async () => main.evaluate(element => element.scrollTop))
|
|
83
|
+
.toBeGreaterThan(100);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('Tab onto an overflowing main with no name of its own exposes the main landmark', async ({
|
|
87
|
+
page,
|
|
88
|
+
browserName,
|
|
89
|
+
}) => {
|
|
90
|
+
await openStory(page, 'long-content');
|
|
91
|
+
const presses = await tabUntil(
|
|
92
|
+
page,
|
|
93
|
+
() => document.activeElement?.getAttribute('role') === 'main',
|
|
94
|
+
);
|
|
95
|
+
expect(presses).toBeGreaterThan(0);
|
|
96
|
+
|
|
97
|
+
// The stop has no name of its own: its role, the main landmark, is what a
|
|
98
|
+
// screen reader announces (AST-025 FR12 lets the single main rely on it).
|
|
99
|
+
const focused = page.locator(':focus');
|
|
100
|
+
await expect(focused).toHaveRole('main');
|
|
101
|
+
await expect(focused).toHaveAccessibleName('');
|
|
102
|
+
if (browserName !== 'chromium') {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Chromium's own accessibility tree, the one screen readers read: the
|
|
106
|
+
// focused element's node is the main landmark, exposed rather than ignored.
|
|
107
|
+
const cdp = await page.context().newCDPSession(page);
|
|
108
|
+
await cdp.send('Accessibility.enable');
|
|
109
|
+
const {result} = await cdp.send('Runtime.evaluate', {
|
|
110
|
+
expression: 'document.activeElement',
|
|
111
|
+
});
|
|
112
|
+
const {node} = await cdp.send('DOM.describeNode', {
|
|
113
|
+
objectId: result.objectId,
|
|
114
|
+
});
|
|
115
|
+
const {nodes} = await cdp.send('Accessibility.getPartialAXTree', {
|
|
116
|
+
backendNodeId: node.backendNodeId,
|
|
117
|
+
fetchRelatives: false,
|
|
118
|
+
});
|
|
119
|
+
await cdp.detach();
|
|
120
|
+
expect(
|
|
121
|
+
nodes.map(axNode => ({
|
|
122
|
+
role: axNode.role?.value,
|
|
123
|
+
name: axNode.name?.value ?? '',
|
|
124
|
+
ignored: axNode.ignored,
|
|
125
|
+
focused: axNode.properties?.some(
|
|
126
|
+
property =>
|
|
127
|
+
property.name === 'focused' && property.value.value === true,
|
|
128
|
+
),
|
|
129
|
+
})),
|
|
130
|
+
).toEqual([{role: 'main', name: '', ignored: false, focused: true}]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('Tab entry moves on to the first link of an overflowing main region, and Shift+Tab skips the region', async ({
|
|
134
|
+
page,
|
|
135
|
+
}) => {
|
|
136
|
+
await openStory(page, 'long-content');
|
|
137
|
+
const toMain = await tabUntil(
|
|
138
|
+
page,
|
|
139
|
+
() => document.activeElement?.getAttribute('role') === 'main',
|
|
140
|
+
);
|
|
141
|
+
expect(toMain).toBeGreaterThan(0);
|
|
142
|
+
|
|
143
|
+
// A fresh load, so Tab starts from the top again.
|
|
144
|
+
await openStory(page, 'long-content');
|
|
145
|
+
await page.getByRole('main').evaluate(main => {
|
|
146
|
+
const link = document.createElement('a');
|
|
147
|
+
link.href = '#details';
|
|
148
|
+
link.textContent = 'Details';
|
|
149
|
+
main.prepend(link);
|
|
150
|
+
});
|
|
151
|
+
// The press that entered main now lands on its first link: no extra stop.
|
|
152
|
+
const toLink = await tabUntil(
|
|
153
|
+
page,
|
|
154
|
+
() => document.activeElement?.textContent === 'Details',
|
|
155
|
+
);
|
|
156
|
+
expect(toLink).toBe(toMain);
|
|
157
|
+
|
|
158
|
+
await page.keyboard.press('Shift+Tab');
|
|
159
|
+
expect(
|
|
160
|
+
await page.evaluate(() => document.activeElement?.getAttribute('role')),
|
|
161
|
+
).not.toBe('main');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('keyboard focus draws the focus ring inside the overflowing main region, and pointer focus draws none', async ({
|
|
165
|
+
page,
|
|
166
|
+
}) => {
|
|
167
|
+
await openStory(page, 'long-content');
|
|
168
|
+
const main = page.getByRole('main');
|
|
169
|
+
const presses = await tabUntil(
|
|
170
|
+
page,
|
|
171
|
+
() => document.activeElement?.getAttribute('role') === 'main',
|
|
172
|
+
);
|
|
173
|
+
expect(presses).toBeGreaterThan(0);
|
|
174
|
+
const ring = await main.evaluate(element => {
|
|
175
|
+
const style = getComputedStyle(element);
|
|
176
|
+
return {
|
|
177
|
+
style: style.outlineStyle,
|
|
178
|
+
width: Number.parseFloat(style.outlineWidth),
|
|
179
|
+
offset: Number.parseFloat(style.outlineOffset),
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
expect(ring.style).not.toBe('none');
|
|
183
|
+
expect(ring.width).toBeGreaterThan(0);
|
|
184
|
+
// Inset by its own width, so the shell's clipping around main cannot cut it.
|
|
185
|
+
expect(ring.offset).toBeCloseTo(-ring.width, 1);
|
|
186
|
+
// The ring follows the shape of the surface main sits on: with a top nav
|
|
187
|
+
// and an inline side nav, the elevated surface rounds its top-start corner.
|
|
188
|
+
const corners = await main.evaluate(element => {
|
|
189
|
+
const surface = element.parentElement?.firstElementChild;
|
|
190
|
+
if (surface == null || surface === element) {
|
|
191
|
+
throw new Error('no elevated surface behind main');
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
main: getComputedStyle(element).borderTopLeftRadius,
|
|
195
|
+
surface: getComputedStyle(surface).borderTopLeftRadius,
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
expect(Number.parseFloat(corners.surface)).toBeGreaterThan(0);
|
|
199
|
+
expect(corners.main).toBe(corners.surface);
|
|
200
|
+
|
|
201
|
+
// A pointer press inside main focuses it too, without a ring.
|
|
202
|
+
await openStory(page, 'long-content');
|
|
203
|
+
const box = await main.boundingBox();
|
|
204
|
+
if (box == null) {
|
|
205
|
+
throw new Error('no main box');
|
|
206
|
+
}
|
|
207
|
+
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
|
|
208
|
+
expect(
|
|
209
|
+
await page.evaluate(() => document.activeElement?.getAttribute('role')),
|
|
210
|
+
).toBe('main');
|
|
211
|
+
expect(
|
|
212
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
213
|
+
).toBe('none');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('the skip link moves focus to an overflowing main without drawing the ring', async ({
|
|
217
|
+
page,
|
|
218
|
+
}) => {
|
|
219
|
+
await openStory(page, 'long-content');
|
|
220
|
+
const main = page.getByRole('main');
|
|
221
|
+
const presses = await tabUntil(
|
|
222
|
+
page,
|
|
223
|
+
() =>
|
|
224
|
+
document.activeElement?.getAttribute('data-testid') === 'skip-to-content',
|
|
225
|
+
);
|
|
226
|
+
expect(presses).toBeGreaterThan(0);
|
|
227
|
+
|
|
228
|
+
await page.keyboard.press('Enter');
|
|
229
|
+
expect(
|
|
230
|
+
await page.evaluate(() => document.activeElement?.getAttribute('role')),
|
|
231
|
+
).toBe('main');
|
|
232
|
+
expect(
|
|
233
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
234
|
+
).toBe('none');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("a script's focus on an overflowing main after keyboard input draws no ring", async ({
|
|
238
|
+
page,
|
|
239
|
+
}) => {
|
|
240
|
+
await openStory(page, 'long-content');
|
|
241
|
+
const main = page.getByRole('main');
|
|
242
|
+
await page.keyboard.press('Tab');
|
|
243
|
+
await main.evaluate(element => element.focus());
|
|
244
|
+
|
|
245
|
+
expect(
|
|
246
|
+
await page.evaluate(() => document.activeElement?.getAttribute('role')),
|
|
247
|
+
).toBe('main');
|
|
248
|
+
expect(
|
|
249
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
250
|
+
).toBe('none');
|
|
251
|
+
expect(
|
|
252
|
+
await main.evaluate(
|
|
253
|
+
element => getComputedStyle(element).borderTopLeftRadius,
|
|
254
|
+
),
|
|
255
|
+
).toBe('0px');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test('Shift+Tab from the start of the page reaches an overflowing main with its ring', async ({
|
|
259
|
+
page,
|
|
260
|
+
}) => {
|
|
261
|
+
await openStory(page, 'long-content');
|
|
262
|
+
const main = page.getByRole('main');
|
|
263
|
+
let presses = -1;
|
|
264
|
+
for (let press = 1; press <= 60; press += 1) {
|
|
265
|
+
await page.keyboard.press('Shift+Tab');
|
|
266
|
+
if (
|
|
267
|
+
await page.evaluate(
|
|
268
|
+
() => document.activeElement?.getAttribute('role') === 'main',
|
|
269
|
+
)
|
|
270
|
+
) {
|
|
271
|
+
presses = press;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
expect(presses).toBeGreaterThan(0);
|
|
276
|
+
expect(
|
|
277
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
278
|
+
).toBe('solid');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('a Tab-focused main keeps its ring when its content shrinks to fit', async ({
|
|
282
|
+
page,
|
|
283
|
+
}) => {
|
|
284
|
+
await openStory(page, 'long-content');
|
|
285
|
+
const main = page.getByRole('main');
|
|
286
|
+
const presses = await tabUntil(
|
|
287
|
+
page,
|
|
288
|
+
() => document.activeElement?.getAttribute('role') === 'main',
|
|
289
|
+
);
|
|
290
|
+
expect(presses).toBeGreaterThan(0);
|
|
291
|
+
expect(
|
|
292
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
293
|
+
).toBe('solid');
|
|
294
|
+
|
|
295
|
+
await main.evaluate(element => {
|
|
296
|
+
for (const child of element.children) {
|
|
297
|
+
(child as HTMLElement).style.display = 'none';
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
await expect(main).toHaveAttribute('tabindex', '-1');
|
|
301
|
+
expect(
|
|
302
|
+
await page.evaluate(() => document.activeElement?.getAttribute('role')),
|
|
303
|
+
).toBe('main');
|
|
304
|
+
expect(
|
|
305
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
306
|
+
).toBe('solid');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test('keyboard focus entering from the embedding page draws the ring on main', async ({
|
|
310
|
+
page,
|
|
311
|
+
}) => {
|
|
312
|
+
// Shift+Tab from a control after the frame: the key goes to the embedding
|
|
313
|
+
// page, so main's document sees focus arrive with no Tab keydown of its own.
|
|
314
|
+
await page.setContent(
|
|
315
|
+
`<iframe title="App" style="width: 1280px; height: 720px; border: 0" src="${storybook.origin}/iframe.html?id=core-appshell--long-content&viewMode=story"></iframe><button>After the frame</button>`,
|
|
316
|
+
);
|
|
317
|
+
const frame = page.frameLocator('iframe');
|
|
318
|
+
const main = frame.getByRole('main');
|
|
319
|
+
await expect(main).toBeVisible();
|
|
320
|
+
await expect(main).toHaveAttribute('tabindex', '0');
|
|
321
|
+
|
|
322
|
+
await page.getByRole('button', {name: 'After the frame'}).focus();
|
|
323
|
+
await page.keyboard.press('Shift+Tab');
|
|
324
|
+
|
|
325
|
+
await expect
|
|
326
|
+
.poll(async () =>
|
|
327
|
+
main.evaluate(element => element.ownerDocument.activeElement === element),
|
|
328
|
+
)
|
|
329
|
+
.toBe(true);
|
|
330
|
+
expect(
|
|
331
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
332
|
+
).toBe('solid');
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("a script's focus on main after the embedding page focuses the frame draws no ring", async ({
|
|
336
|
+
page,
|
|
337
|
+
}) => {
|
|
338
|
+
// The window gains focus with nothing focused in it; a later focus() is
|
|
339
|
+
// programmatic (FR4), not the focus move that entered the document.
|
|
340
|
+
await page.setContent(
|
|
341
|
+
`<button>Before the frame</button><iframe title="App" style="width: 1280px; height: 720px; border: 0" src="${storybook.origin}/iframe.html?id=core-appshell--long-content&viewMode=story"></iframe>`,
|
|
342
|
+
);
|
|
343
|
+
const main = page.frameLocator('iframe').getByRole('main');
|
|
344
|
+
await expect(main).toHaveAttribute('tabindex', '0');
|
|
345
|
+
await page.getByRole('button', {name: 'Before the frame'}).focus();
|
|
346
|
+
await page.evaluate(() => {
|
|
347
|
+
document.querySelector('iframe')?.contentWindow?.focus();
|
|
348
|
+
});
|
|
349
|
+
await page.waitForTimeout(50);
|
|
350
|
+
|
|
351
|
+
await main.evaluate(element => (element as HTMLElement).focus());
|
|
352
|
+
|
|
353
|
+
await expect
|
|
354
|
+
.poll(async () =>
|
|
355
|
+
main.evaluate(element => element.ownerDocument.activeElement === element),
|
|
356
|
+
)
|
|
357
|
+
.toBe(true);
|
|
358
|
+
expect(
|
|
359
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
360
|
+
).not.toBe('solid');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("a script's focus on main inside a Tab keydown handler draws no ring", async ({
|
|
364
|
+
page,
|
|
365
|
+
}) => {
|
|
366
|
+
await openStory(page, 'long-content');
|
|
367
|
+
const main = page.getByRole('main');
|
|
368
|
+
await expect(main).toHaveAttribute('tabindex', '0');
|
|
369
|
+
// A page's own Tab handler moves focus itself: programmatic focus (FR4),
|
|
370
|
+
// even though a Tab key is down.
|
|
371
|
+
await page.evaluate(() => {
|
|
372
|
+
document.addEventListener('keydown', event => {
|
|
373
|
+
if (event.key === 'Tab') {
|
|
374
|
+
event.preventDefault();
|
|
375
|
+
document.querySelector<HTMLElement>('[role="main"]')?.focus();
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
await page.keyboard.press('Tab');
|
|
381
|
+
|
|
382
|
+
await expect
|
|
383
|
+
.poll(async () =>
|
|
384
|
+
page.evaluate(() => document.activeElement?.getAttribute('role')),
|
|
385
|
+
)
|
|
386
|
+
.toBe('main');
|
|
387
|
+
expect(
|
|
388
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
389
|
+
).not.toBe('solid');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("a script's focus on main inside the frame window's own focus handler draws no ring", async ({
|
|
393
|
+
page,
|
|
394
|
+
}) => {
|
|
395
|
+
await page.setContent(
|
|
396
|
+
`<button>Before the frame</button><iframe title="App" style="width: 1280px; height: 720px; border: 0" src="${storybook.origin}/iframe.html?id=core-appshell--long-content&viewMode=story"></iframe>`,
|
|
397
|
+
);
|
|
398
|
+
const main = page.frameLocator('iframe').getByRole('main');
|
|
399
|
+
await expect(main).toHaveAttribute('tabindex', '0');
|
|
400
|
+
const frame = page.frame({url: /iframe\.html/});
|
|
401
|
+
expect(frame).not.toBeNull();
|
|
402
|
+
await frame?.evaluate(() => {
|
|
403
|
+
window.addEventListener('focus', () => {
|
|
404
|
+
document.querySelector<HTMLElement>('[role="main"]')?.focus();
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
await page.getByRole('button', {name: 'Before the frame'}).focus();
|
|
408
|
+
|
|
409
|
+
await page.evaluate(() => {
|
|
410
|
+
document.querySelector('iframe')?.contentWindow?.focus();
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
await expect
|
|
414
|
+
.poll(async () =>
|
|
415
|
+
main.evaluate(element => element.ownerDocument.activeElement === element),
|
|
416
|
+
)
|
|
417
|
+
.toBe(true);
|
|
418
|
+
expect(
|
|
419
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
420
|
+
).not.toBe('solid');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test('pointer focus entering from the embedding page draws no ring on main', async ({
|
|
424
|
+
page,
|
|
425
|
+
}) => {
|
|
426
|
+
await page.setContent(
|
|
427
|
+
`<button>Before the frame</button><iframe title="App" style="width: 1280px; height: 720px; border: 0" src="${storybook.origin}/iframe.html?id=core-appshell--long-content&viewMode=story"></iframe>`,
|
|
428
|
+
);
|
|
429
|
+
const main = page.frameLocator('iframe').getByRole('main');
|
|
430
|
+
await expect(main).toHaveAttribute('tabindex', '0');
|
|
431
|
+
await page.getByRole('button', {name: 'Before the frame'}).focus();
|
|
432
|
+
|
|
433
|
+
await main.click({position: {x: 40, y: 40}});
|
|
434
|
+
|
|
435
|
+
await expect
|
|
436
|
+
.poll(async () =>
|
|
437
|
+
main.evaluate(element => element.ownerDocument.activeElement === element),
|
|
438
|
+
)
|
|
439
|
+
.toBe(true);
|
|
440
|
+
expect(
|
|
441
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
442
|
+
).not.toBe('solid');
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test('a Tab-focused main keeps its ring after a window or tab switch', async ({
|
|
446
|
+
browserName,
|
|
447
|
+
}) => {
|
|
448
|
+
test.skip(
|
|
449
|
+
browserName !== 'chromium',
|
|
450
|
+
'a real window switch needs full Chromium (launched below)',
|
|
451
|
+
);
|
|
452
|
+
// The headless shell never takes focus away from a page; full Chromium in
|
|
453
|
+
// its headless mode does once focus emulation is off, firing what a real
|
|
454
|
+
// switch fires: blur and focusout on main while main stays the active
|
|
455
|
+
// element, then focus and focusin on return.
|
|
456
|
+
const browser = await chromium.launch({channel: 'chromium'});
|
|
457
|
+
try {
|
|
458
|
+
const context = await browser.newContext({
|
|
459
|
+
viewport: {width: 1280, height: 720},
|
|
460
|
+
});
|
|
461
|
+
const page = await context.newPage();
|
|
462
|
+
await openStory(page, 'long-content');
|
|
463
|
+
const main = page.getByRole('main');
|
|
464
|
+
const presses = await tabUntil(
|
|
465
|
+
page,
|
|
466
|
+
() => document.activeElement?.getAttribute('role') === 'main',
|
|
467
|
+
);
|
|
468
|
+
expect(presses).toBeGreaterThan(0);
|
|
469
|
+
expect(
|
|
470
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
471
|
+
).toBe('solid');
|
|
472
|
+
|
|
473
|
+
const cdp = await context.newCDPSession(page);
|
|
474
|
+
await cdp.send('Emulation.setFocusEmulationEnabled', {enabled: false});
|
|
475
|
+
const other = await context.newPage();
|
|
476
|
+
await other.bringToFront();
|
|
477
|
+
await expect
|
|
478
|
+
.poll(async () => page.evaluate(() => document.hasFocus()))
|
|
479
|
+
.toBe(false);
|
|
480
|
+
await page.bringToFront();
|
|
481
|
+
await expect
|
|
482
|
+
.poll(async () => page.evaluate(() => document.hasFocus()))
|
|
483
|
+
.toBe(true);
|
|
484
|
+
|
|
485
|
+
expect(
|
|
486
|
+
await page.evaluate(() => document.activeElement?.getAttribute('role')),
|
|
487
|
+
).toBe('main');
|
|
488
|
+
expect(
|
|
489
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
490
|
+
).toBe('solid');
|
|
491
|
+
|
|
492
|
+
// A real focus move still ends the ring.
|
|
493
|
+
await page.keyboard.press('Shift+Tab');
|
|
494
|
+
expect(
|
|
495
|
+
await main.evaluate(element => getComputedStyle(element).outlineStyle),
|
|
496
|
+
).not.toBe('solid');
|
|
497
|
+
} finally {
|
|
498
|
+
await browser.close();
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test('an overflowing main rounds its corner only while its ring shows', async ({
|
|
503
|
+
page,
|
|
504
|
+
}) => {
|
|
505
|
+
await openStory(page, 'long-content');
|
|
506
|
+
const main = page.getByRole('main');
|
|
507
|
+
await expect(main).toHaveAttribute('tabindex', '0');
|
|
508
|
+
expect(
|
|
509
|
+
await main.evaluate(
|
|
510
|
+
element => getComputedStyle(element).borderTopLeftRadius,
|
|
511
|
+
),
|
|
512
|
+
).toBe('0px');
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test('a main region that fits stays out of the tab order and keeps its square corner', async ({
|
|
516
|
+
page,
|
|
517
|
+
}) => {
|
|
518
|
+
await openStory(page, 'playground');
|
|
519
|
+
const main = page.getByRole('main');
|
|
520
|
+
await expect(main).toHaveAttribute('tabindex', '-1');
|
|
521
|
+
// The rounded corner only shapes the ring of a main that is a stop.
|
|
522
|
+
expect(
|
|
523
|
+
await main.evaluate(
|
|
524
|
+
element => getComputedStyle(element).borderTopLeftRadius,
|
|
525
|
+
),
|
|
526
|
+
).toBe('0px');
|
|
527
|
+
|
|
528
|
+
const presses = await tabUntil(
|
|
529
|
+
page,
|
|
530
|
+
() => document.activeElement?.getAttribute('role') === 'main',
|
|
531
|
+
40,
|
|
532
|
+
);
|
|
533
|
+
expect(presses).toBe(-1);
|
|
534
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file observeMainOverflow.test.ts
|
|
5
|
+
* @input observeMainOverflow on a main element with mocked geometry, a
|
|
6
|
+
* recorded resize observer, and frames run by hand
|
|
7
|
+
* @output Regression coverage for measuring a scroll container that holds its
|
|
8
|
+
* content as direct children: the children's sizes, the children coming and
|
|
9
|
+
* going, unknown geometry, and stopping
|
|
10
|
+
* @position Unit contract of AppShell's internal overflow observer;
|
|
11
|
+
* AppShell.test.tsx covers main becoming a keyboard stop
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
15
|
+
import {observeMainOverflow} from './observeMainOverflow';
|
|
16
|
+
|
|
17
|
+
const observed = vi.hoisted(
|
|
18
|
+
() => new Map<Element, {callback: () => void; stopped: boolean}>(),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
vi.mock('../utils/sharedResizeObserver', () => ({
|
|
22
|
+
observeResize: (element: Element, callback: () => void) => {
|
|
23
|
+
const entry = {callback, stopped: false};
|
|
24
|
+
observed.set(element, entry);
|
|
25
|
+
return () => {
|
|
26
|
+
entry.stopped = true;
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
function setGeometry(element: HTMLElement, client: number, scroll: number) {
|
|
32
|
+
for (const [key, value] of [
|
|
33
|
+
['clientWidth', 800],
|
|
34
|
+
['scrollWidth', 800],
|
|
35
|
+
['clientHeight', client],
|
|
36
|
+
['scrollHeight', scroll],
|
|
37
|
+
['scrollTop', 0],
|
|
38
|
+
['scrollLeft', 0],
|
|
39
|
+
] as const) {
|
|
40
|
+
Object.defineProperty(element, key, {
|
|
41
|
+
configurable: true,
|
|
42
|
+
value,
|
|
43
|
+
writable: true,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('observeMainOverflow', () => {
|
|
49
|
+
let frames: FrameRequestCallback[];
|
|
50
|
+
let main: HTMLElement;
|
|
51
|
+
let stop: (() => void) | null;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
observed.clear();
|
|
55
|
+
frames = [];
|
|
56
|
+
stop = null;
|
|
57
|
+
vi.stubGlobal(
|
|
58
|
+
'requestAnimationFrame',
|
|
59
|
+
vi.fn((callback: FrameRequestCallback) => {
|
|
60
|
+
frames.push(callback);
|
|
61
|
+
return frames.length;
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
vi.stubGlobal('cancelAnimationFrame', vi.fn());
|
|
65
|
+
// jsdom computes no overflow longhands; main scrolls (`overflow: auto`).
|
|
66
|
+
const nativeGetComputedStyle = window.getComputedStyle.bind(window);
|
|
67
|
+
vi.spyOn(window, 'getComputedStyle').mockImplementation(element => {
|
|
68
|
+
const computed = nativeGetComputedStyle(element);
|
|
69
|
+
return new Proxy(computed, {
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/promise-function-async -- Proxy traps must remain synchronous
|
|
71
|
+
get(target, property) {
|
|
72
|
+
if (property === 'overflowX' || property === 'overflowY') {
|
|
73
|
+
return 'auto';
|
|
74
|
+
}
|
|
75
|
+
return Reflect.get(target, property, target);
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
main = document.createElement('div');
|
|
80
|
+
main.innerHTML = '<p id="first">First</p><p id="second">Second</p>';
|
|
81
|
+
document.body.append(main);
|
|
82
|
+
setGeometry(main, 300, 300);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
afterEach(() => {
|
|
86
|
+
stop?.();
|
|
87
|
+
main.remove();
|
|
88
|
+
vi.restoreAllMocks();
|
|
89
|
+
vi.unstubAllGlobals();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
function runFrames() {
|
|
93
|
+
const pending = frames.splice(0);
|
|
94
|
+
pending.forEach(callback => callback(performance.now()));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
it('reports the first measurement at once, then only changes', () => {
|
|
98
|
+
const onChange = vi.fn();
|
|
99
|
+
stop = observeMainOverflow(main, onChange);
|
|
100
|
+
expect(onChange.mock.calls).toEqual([[false]]);
|
|
101
|
+
|
|
102
|
+
window.dispatchEvent(new Event('resize'));
|
|
103
|
+
runFrames();
|
|
104
|
+
expect(onChange.mock.calls).toEqual([[false]]);
|
|
105
|
+
|
|
106
|
+
setGeometry(main, 300, 1200);
|
|
107
|
+
window.dispatchEvent(new Event('resize'));
|
|
108
|
+
runFrames();
|
|
109
|
+
expect(onChange.mock.calls).toEqual([[false], [true]]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('watches each direct child, so content growing inside main is measured', () => {
|
|
113
|
+
const onChange = vi.fn();
|
|
114
|
+
stop = observeMainOverflow(main, onChange);
|
|
115
|
+
const second = main.querySelector('#second');
|
|
116
|
+
expect(observed.has(main)).toBe(true);
|
|
117
|
+
expect(observed.has(main.querySelector('#first') as Element)).toBe(true);
|
|
118
|
+
expect(second != null && observed.has(second)).toBe(true);
|
|
119
|
+
|
|
120
|
+
// The child grows; main keeps its own size.
|
|
121
|
+
setGeometry(main, 300, 1200);
|
|
122
|
+
observed.get(second as Element)?.callback();
|
|
123
|
+
runFrames();
|
|
124
|
+
|
|
125
|
+
expect(onChange).toHaveBeenLastCalledWith(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('follows the children as they come and go', async () => {
|
|
129
|
+
stop = observeMainOverflow(main, vi.fn());
|
|
130
|
+
const first = main.querySelector('#first') as Element;
|
|
131
|
+
|
|
132
|
+
const replacement = document.createElement('section');
|
|
133
|
+
first.replaceWith(replacement);
|
|
134
|
+
// The mutation observer reports the change asynchronously.
|
|
135
|
+
await Promise.resolve();
|
|
136
|
+
|
|
137
|
+
expect(observed.get(first)?.stopped).toBe(true);
|
|
138
|
+
expect(observed.has(replacement)).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('keeps the last answer while main cannot be measured', () => {
|
|
142
|
+
const onChange = vi.fn();
|
|
143
|
+
setGeometry(main, 300, 1200);
|
|
144
|
+
stop = observeMainOverflow(main, onChange);
|
|
145
|
+
expect(onChange.mock.calls).toEqual([[true]]);
|
|
146
|
+
|
|
147
|
+
// A hidden or zero-size main is unknown, not fitting.
|
|
148
|
+
setGeometry(main, 0, 0);
|
|
149
|
+
window.dispatchEvent(new Event('resize'));
|
|
150
|
+
runFrames();
|
|
151
|
+
expect(onChange.mock.calls).toEqual([[true]]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('stops every observer and listener, and measures nothing after', () => {
|
|
155
|
+
const onChange = vi.fn();
|
|
156
|
+
stop = observeMainOverflow(main, onChange);
|
|
157
|
+
const children = Array.from(main.children);
|
|
158
|
+
window.dispatchEvent(new Event('resize'));
|
|
159
|
+
stop();
|
|
160
|
+
stop = null;
|
|
161
|
+
|
|
162
|
+
expect(cancelAnimationFrame).toHaveBeenCalled();
|
|
163
|
+
expect(observed.get(main)?.stopped).toBe(true);
|
|
164
|
+
for (const child of children) {
|
|
165
|
+
expect(observed.get(child)?.stopped).toBe(true);
|
|
166
|
+
}
|
|
167
|
+
setGeometry(main, 300, 1200);
|
|
168
|
+
window.dispatchEvent(new Event('resize'));
|
|
169
|
+
runFrames();
|
|
170
|
+
expect(frames).toEqual([]);
|
|
171
|
+
expect(onChange.mock.calls).toEqual([[false]]);
|
|
172
|
+
});
|
|
173
|
+
});
|