@astryxdesign/core 0.7.5 → 0.7.6-canary.bb18c81
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/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/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +4 -1
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +101 -15
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +1 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +4 -1
- 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.map +1 -1
- package/dist/Drawer/Drawer.js +5 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +4 -1
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -1
- package/dist/DropdownMenu/MenuBottomSheetActionList.js +6 -5
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +10 -1
- package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -1
- package/dist/DropdownMenu/lazyMenuBottomSheet.js +16 -2
- 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/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/MultiSelector.d.ts +1 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +5 -2
- 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/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/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +7 -1
- package/dist/Spinner/Spinner.js +2 -2
- 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/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +2 -2
- package/dist/Timestamp/TimestampHoverCard.js +4 -4
- 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/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 +14 -1
- package/dist/gestures/PinchGestureRecognizer.d.ts +30 -0
- package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -0
- package/dist/gestures/PinchGestureRecognizer.js +125 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +70 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.js +220 -0
- package/dist/gestures/SwipeGestureRecognizer.d.ts +36 -0
- package/dist/gestures/SwipeGestureRecognizer.d.ts.map +1 -0
- package/dist/gestures/SwipeGestureRecognizer.js +145 -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/types.d.ts +11 -7
- package/dist/gestures/types.d.ts.map +1 -1
- package/dist/gestures/types.js +22 -2
- package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -1
- package/dist/hooks/useAdaptivePresentation.js +4 -1
- 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 +3 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +3 -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.js +1 -1
- 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/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/presentation.d.ts +50 -0
- package/dist/utils/presentation.d.ts.map +1 -0
- package/dist/utils/presentation.js +125 -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/locales/en.json +13 -1
- package/locales/pseudo.json +9 -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/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/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/ContextMenu/ContextMenu.test.tsx +35 -0
- package/src/DateInput/DateInput.test.tsx +53 -2
- package/src/DateInput/DateInput.tsx +15 -11
- package/src/DateInput/MonthScroller.tsx +106 -20
- package/src/DateInput/MonthScrollerSettle.test.tsx +165 -0
- 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.spec.md +71 -48
- package/src/DateTimeInput/DateTimeInput.test.tsx +61 -0
- package/src/DateTimeInput/DateTimeInput.tsx +15 -11
- 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 +22 -0
- package/src/Drawer/Drawer.tsx +5 -0
- package/src/Drawer/DrawerHeader.spec.md +181 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +41 -0
- package/src/DropdownMenu/DropdownMenu.tsx +7 -1
- 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 +53 -0
- package/src/DropdownMenu/lazyMenuBottomSheet.ts +23 -6
- 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/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.tsx +3 -3
- 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/MultiSelector.spec.md +7 -4
- package/src/MultiSelector/MultiSelector.test.tsx +118 -1
- package/src/MultiSelector/MultiSelector.tsx +6 -2
- 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/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/Slider/Slider.test.tsx +12 -0
- package/src/Slider/Slider.tsx +4 -1
- package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +19 -7
- package/src/Spinner/Spinner.tsx +2 -2
- package/src/Stack/Stack.tsx +1 -1
- package/src/Stack/StackItem.tsx +1 -1
- package/src/TabList/TabList.test.tsx +52 -1
- 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/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 +7 -10
- package/src/Toast/ToastViewport.test.tsx +575 -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/Typeahead/Typeahead.test.tsx +40 -0
- package/src/Typeahead/Typeahead.tsx +12 -1
- package/src/gestures/PinchGestureRecognizer.ts +144 -0
- package/src/gestures/ScreenEdgePanGestureRecognizer.ts +252 -0
- package/src/gestures/SwipeGestureRecognizer.ts +193 -0
- package/src/gestures/VelocityTracker.ts +10 -3
- 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__/arenaPinchSwipeEdge.test.ts +188 -0
- package/src/gestures/__tests__/harness.ts +21 -2
- package/src/gestures/types.ts +23 -9
- package/src/hooks/useAdaptivePresentation.ts +4 -1
- package/src/hooks/useMenuHover.ts +15 -1
- package/src/i18n/generated-locales/en.generated.ts +4 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +4 -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/defineTheme.ts +1 -1
- 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/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/presentation.test.ts +380 -0
- package/src/utils/presentation.ts +180 -0
- package/src/utils/pressPaintPriority.test.ts +80 -0
- package/src/utils/touchPresentation.stylex.ts +43 -0
- package/src/utils/touchPresentation.test.ts +140 -1
- package/src/utils/touchPresentation.ts +5 -3
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file arenaPinchSwipeEdge.test.ts
|
|
5
|
+
* @input GestureArena with pinches, swipes, edge pans and pans, driven by
|
|
6
|
+
* synthetic touches with explicit timestamps
|
|
7
|
+
* @output Simultaneous recognition (pinch + pan), the first to recognize
|
|
8
|
+
* winning without permission, edge pan vs inner pan, swipe vs pan, and a
|
|
9
|
+
* delayed touch delivery
|
|
10
|
+
* @position Unit tests of packages/core/src/gestures/GestureArena.ts
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {describe, expect, it} from 'vitest';
|
|
14
|
+
import {UIKIT_PINCH, UIKIT_SWIPE} from '../../motion/uikit/gestures';
|
|
15
|
+
import {GestureRecognizer} from '../GestureRecognizer';
|
|
16
|
+
import {PanGestureRecognizer} from '../PanGestureRecognizer';
|
|
17
|
+
import {PinchGestureRecognizer} from '../PinchGestureRecognizer';
|
|
18
|
+
import {ScreenEdgePanGestureRecognizer} from '../ScreenEdgePanGestureRecognizer';
|
|
19
|
+
import {SwipeGestureRecognizer} from '../SwipeGestureRecognizer';
|
|
20
|
+
import {createHarness, RecordingConsumer} from './harness';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A recognizer that stays possible, even past its finger's lift, until the
|
|
24
|
+
* test fails it: something another recognizer can wait on.
|
|
25
|
+
*/
|
|
26
|
+
class Held extends GestureRecognizer {
|
|
27
|
+
private isWaiting = true;
|
|
28
|
+
|
|
29
|
+
override isInvolved(): boolean {
|
|
30
|
+
return super.isInvolved() || this.isWaiting;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fail(): void {
|
|
34
|
+
this.isWaiting = false;
|
|
35
|
+
this.setState('failed');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('conflict matrix', () => {
|
|
40
|
+
it('pinch + pan recognize simultaneously when allowed', () => {
|
|
41
|
+
const h = createHarness();
|
|
42
|
+
const pinch = new PinchGestureRecognizer({name: 'pinch'});
|
|
43
|
+
const pan = new PanGestureRecognizer({name: 'pan'});
|
|
44
|
+
pinch.recognizeSimultaneouslyWith(pan);
|
|
45
|
+
h.finger(1).down(100, 100, 0, {recognizers: [pinch, pan]});
|
|
46
|
+
h.finger(2).down(200, 100, 0, {recognizers: [pinch, pan]});
|
|
47
|
+
h.finger(1).move(80, 120, 16);
|
|
48
|
+
h.finger(2).move(240, 120, 16);
|
|
49
|
+
expect(h.log.slice(0, 2)).toEqual(['pinch:began@16', 'pan:began@16']);
|
|
50
|
+
// Span 100 -> 160, measured against 100 + 8.
|
|
51
|
+
expect(pinch.scale).toBeCloseTo(160 / 108, 5);
|
|
52
|
+
// The pan began at the first finger's move, centroid (-10, 10), which
|
|
53
|
+
// the hysteresis swallows; the second finger's move brought it to (10, 20).
|
|
54
|
+
expect(pan.translation()).toEqual({x: 20, y: 10});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('pinch vs pan without permission: the first to recognize wins', () => {
|
|
58
|
+
const h = createHarness();
|
|
59
|
+
const pinch = new PinchGestureRecognizer({name: 'pinch'});
|
|
60
|
+
const pan = new PanGestureRecognizer({name: 'pan'});
|
|
61
|
+
h.finger(1).down(100, 100, 0, {recognizers: [pinch, pan]});
|
|
62
|
+
h.finger(2).down(200, 100, 0, {recognizers: [pinch, pan]});
|
|
63
|
+
h.finger(1).move(80, 120, 16);
|
|
64
|
+
h.finger(2).move(240, 120, 16);
|
|
65
|
+
expect(h.log.slice(0, 2)).toEqual(['pinch:began@16', 'pan:failed@16']);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('edge pan vs inner pan: in the band the edge pan wins, elsewhere the inner pan runs undelayed', () => {
|
|
69
|
+
const band = createHarness();
|
|
70
|
+
const edge = new ScreenEdgePanGestureRecognizer({name: 'edge'});
|
|
71
|
+
const inner = new PanGestureRecognizer({
|
|
72
|
+
name: 'inner',
|
|
73
|
+
direction: 'horizontal',
|
|
74
|
+
});
|
|
75
|
+
inner.requireToFail(edge);
|
|
76
|
+
// A thin edge view: the edge zone, where the pan takes its axis.
|
|
77
|
+
band.environment.view = {width: edge.edgeRegion, height: 844};
|
|
78
|
+
band.finger(1).down(8, 300, 0, {recognizers: [inner, edge]});
|
|
79
|
+
band.finger(1).drag(60, 2, 60, 6);
|
|
80
|
+
// The edge pan's own hysteresis is 15 px: the second 10 px step.
|
|
81
|
+
expect(band.log.slice(0, 2)).toEqual(['edge:began@20', 'inner:failed@20']);
|
|
82
|
+
|
|
83
|
+
const middle = createHarness();
|
|
84
|
+
const edge2 = new ScreenEdgePanGestureRecognizer({name: 'edge'});
|
|
85
|
+
const inner2 = new PanGestureRecognizer({
|
|
86
|
+
name: 'inner',
|
|
87
|
+
direction: 'horizontal',
|
|
88
|
+
});
|
|
89
|
+
inner2.requireToFail(edge2);
|
|
90
|
+
middle.finger(1).down(150, 300, 0, {recognizers: [inner2, edge2]});
|
|
91
|
+
// Failed at touch-down, before any movement: nothing waits on it.
|
|
92
|
+
expect(middle.log).toEqual(['edge:failed@0']);
|
|
93
|
+
middle.finger(1).drag(60, 2, 60, 6);
|
|
94
|
+
expect(middle.log[1]).toBe('inner:began@10');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('swipe vs pan: the pan wins by default, the swipe when the pan requires it to fail', () => {
|
|
98
|
+
const plain = createHarness();
|
|
99
|
+
const swipe = new SwipeGestureRecognizer({name: 'swipe'});
|
|
100
|
+
const pan = new PanGestureRecognizer({name: 'pan'});
|
|
101
|
+
plain
|
|
102
|
+
.finger(1)
|
|
103
|
+
.down(0, 0, 0, {recognizers: [swipe, pan]})
|
|
104
|
+
.drag(80, 0, 60, 6);
|
|
105
|
+
expect(plain.log.slice(0, 2)).toEqual(['pan:began@10', 'swipe:failed@10']);
|
|
106
|
+
|
|
107
|
+
const ordered = createHarness();
|
|
108
|
+
const swipe2 = new SwipeGestureRecognizer({name: 'swipe'});
|
|
109
|
+
const pan2 = new PanGestureRecognizer({name: 'pan'});
|
|
110
|
+
pan2.requireToFail(swipe2);
|
|
111
|
+
ordered
|
|
112
|
+
.finger(1)
|
|
113
|
+
.down(0, 0, 0, {recognizers: [swipe2, pan2]})
|
|
114
|
+
.drag(80, 0, 60, 8);
|
|
115
|
+
expect(ordered.log.slice(0, 2)).toEqual([
|
|
116
|
+
'swipe:ended@37.5',
|
|
117
|
+
'pan:failed@37.5',
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
const slow = createHarness();
|
|
121
|
+
const swipe3 = new SwipeGestureRecognizer({name: 'swipe'});
|
|
122
|
+
const pan3 = new PanGestureRecognizer({name: 'pan'});
|
|
123
|
+
pan3.requireToFail(swipe3);
|
|
124
|
+
slow
|
|
125
|
+
.finger(1)
|
|
126
|
+
.down(0, 0, 0, {recognizers: [swipe3, pan3]})
|
|
127
|
+
.drag(30, 0, 300, 3);
|
|
128
|
+
expect(pan3.state).toBe('possible');
|
|
129
|
+
slow.advance(500);
|
|
130
|
+
// The swipe ran out of time; the waiting pan begins with what it has.
|
|
131
|
+
expect(slow.log.slice(0, 2)).toEqual(['swipe:failed@500', 'pan:began@500']);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('a recognizer waiting on another', () => {
|
|
136
|
+
it('a swipe holds its recognition past its lift and its deadline (spec:AST-094 FR15)', () => {
|
|
137
|
+
const h = createHarness();
|
|
138
|
+
const held = new Held({name: 'held'});
|
|
139
|
+
const swipe = new SwipeGestureRecognizer({name: 'swipe'});
|
|
140
|
+
swipe.requireToFail(held);
|
|
141
|
+
h.finger(1)
|
|
142
|
+
.down(0, 0, 0, {recognizers: [swipe, held]})
|
|
143
|
+
.drag(80, 0, 60, 8)
|
|
144
|
+
.up(70);
|
|
145
|
+
h.advance(UIKIT_SWIPE.maximumDuration.value + 100);
|
|
146
|
+
expect(swipe.state).toBe('possible');
|
|
147
|
+
expect(h.log.some(entry => entry.startsWith('swipe:failed'))).toBe(false);
|
|
148
|
+
held.fail();
|
|
149
|
+
expect(h.log).toContain(
|
|
150
|
+
`swipe:ended@${UIKIT_SWIPE.maximumDuration.value + 100}`,
|
|
151
|
+
);
|
|
152
|
+
expect(swipe.recognizedDirection).toBe('right');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('a pinch keeps its start once it passes the hysteresis, so it begins at the scale its fingers show (spec:AST-094 FR15)', () => {
|
|
156
|
+
const h = createHarness();
|
|
157
|
+
const held = new Held({name: 'held'});
|
|
158
|
+
const pinch = new PinchGestureRecognizer({name: 'pinch'});
|
|
159
|
+
pinch.requireToFail(held);
|
|
160
|
+
h.finger(1).down(100, 100, 0, {recognizers: [pinch, held]});
|
|
161
|
+
h.finger(2).down(200, 100, 0, {recognizers: [pinch, held]});
|
|
162
|
+
// Span 100 -> 120 -> 140 -> 160 while the pinch waits.
|
|
163
|
+
h.finger(2).move(220, 100, 16);
|
|
164
|
+
h.finger(2).move(240, 100, 32);
|
|
165
|
+
h.finger(2).move(260, 100, 48);
|
|
166
|
+
expect(pinch.state).toBe('possible');
|
|
167
|
+
held.fail();
|
|
168
|
+
expect(h.log).toContain('pinch:began@48');
|
|
169
|
+
// Measured against 100 + 8 once, not re-based on every move.
|
|
170
|
+
const hysteresis = UIKIT_PINCH.hysteresis.value;
|
|
171
|
+
expect(pinch.scale).toBeCloseTo(160 / (100 + hysteresis), 5);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('delivery to the view', () => {
|
|
176
|
+
it('holds the began with delaysTouchesBegan until the recognizer fails', () => {
|
|
177
|
+
const h = createHarness();
|
|
178
|
+
const consumer = new RecordingConsumer();
|
|
179
|
+
const swipe = new SwipeGestureRecognizer({
|
|
180
|
+
name: 'swipe',
|
|
181
|
+
delaysTouchesBegan: true,
|
|
182
|
+
});
|
|
183
|
+
h.finger(1).down(0, 0, 0, {recognizers: [swipe], consumer});
|
|
184
|
+
expect(consumer.events).toEqual([]);
|
|
185
|
+
h.advance(UIKIT_SWIPE.maximumDuration.value);
|
|
186
|
+
expect(consumer.events).toEqual(['began:1']);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
@@ -40,7 +40,13 @@ export interface Harness {
|
|
|
40
40
|
claims: number[];
|
|
41
41
|
/** Deferred clicks as they resolved. */
|
|
42
42
|
deferredClicks: {id: number; allowed: boolean; time: number}[];
|
|
43
|
-
environment: {
|
|
43
|
+
environment: {
|
|
44
|
+
width: number;
|
|
45
|
+
height: number;
|
|
46
|
+
rtl: boolean;
|
|
47
|
+
/** The box every recognizer's view measures, when a test sets one. */
|
|
48
|
+
view?: {left?: number; top?: number; width: number; height: number};
|
|
49
|
+
};
|
|
44
50
|
finger(id: number): Finger;
|
|
45
51
|
/** Advance the clock and run every timer due by `time`. */
|
|
46
52
|
advance(time: number): void;
|
|
@@ -51,7 +57,11 @@ export function createHarness(): Harness {
|
|
|
51
57
|
const log: string[] = [];
|
|
52
58
|
const claims: number[] = [];
|
|
53
59
|
const deferredClicks: Harness['deferredClicks'] = [];
|
|
54
|
-
const environment = {
|
|
60
|
+
const environment: Harness['environment'] = {
|
|
61
|
+
width: 390,
|
|
62
|
+
height: 844,
|
|
63
|
+
rtl: false,
|
|
64
|
+
};
|
|
55
65
|
const fingers = new Map<number, Finger>();
|
|
56
66
|
const env: GestureEnvironment = {
|
|
57
67
|
viewportSize: () => ({
|
|
@@ -59,6 +69,15 @@ export function createHarness(): Harness {
|
|
|
59
69
|
height: environment.height,
|
|
60
70
|
}),
|
|
61
71
|
isRtl: () => environment.rtl,
|
|
72
|
+
viewRect: () =>
|
|
73
|
+
environment.view == null
|
|
74
|
+
? null
|
|
75
|
+
: {
|
|
76
|
+
left: environment.view.left ?? 0,
|
|
77
|
+
top: environment.view.top ?? 0,
|
|
78
|
+
width: environment.view.width,
|
|
79
|
+
height: environment.view.height,
|
|
80
|
+
},
|
|
62
81
|
};
|
|
63
82
|
const arena: GestureArena = new GestureArena({
|
|
64
83
|
clock,
|
package/src/gestures/types.ts
CHANGED
|
@@ -46,13 +46,6 @@ export interface NativeScrollProbe {
|
|
|
46
46
|
* lets the browser pan that axis).
|
|
47
47
|
*/
|
|
48
48
|
canScroll(dx: number, dy: number): boolean;
|
|
49
|
-
/**
|
|
50
|
-
* How much further (px) the scroller that would move for a finger moving
|
|
51
|
-
* by (dx, dy) can scroll before it reaches its edge; 0 when none would.
|
|
52
|
-
* Lets a reader place the point where the content runs out between two
|
|
53
|
-
* samples instead of at the next one.
|
|
54
|
-
*/
|
|
55
|
-
distanceToEdge?(dx: number, dy: number): number;
|
|
56
49
|
/** Whether any element under the touch can scroll natively at all. */
|
|
57
50
|
readonly isInsideScroller: boolean;
|
|
58
51
|
/**
|
|
@@ -127,6 +120,17 @@ export interface GestureEventContext {
|
|
|
127
120
|
export interface GestureEnvironment {
|
|
128
121
|
viewportSize(): {readonly width: number; readonly height: number};
|
|
129
122
|
isRtl(view: unknown): boolean;
|
|
123
|
+
/**
|
|
124
|
+
* The box of a recognizer's view in viewport coordinates, or null when it
|
|
125
|
+
* has none to measure. A screen-edge pan reads it to tell a view within
|
|
126
|
+
* its edge band from any other.
|
|
127
|
+
*/
|
|
128
|
+
viewRect?(view: unknown): {
|
|
129
|
+
readonly left: number;
|
|
130
|
+
readonly top: number;
|
|
131
|
+
readonly width: number;
|
|
132
|
+
readonly height: number;
|
|
133
|
+
} | null;
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
export const DEFAULT_ENVIRONMENT: GestureEnvironment = {
|
|
@@ -136,8 +140,18 @@ export const DEFAULT_ENVIRONMENT: GestureEnvironment = {
|
|
|
136
140
|
}
|
|
137
141
|
return {width: window.innerWidth, height: window.innerHeight};
|
|
138
142
|
},
|
|
139
|
-
isRtl() {
|
|
140
|
-
|
|
143
|
+
isRtl(view) {
|
|
144
|
+
if (typeof window === 'undefined' || !(view instanceof Element)) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return window.getComputedStyle(view).direction === 'rtl';
|
|
148
|
+
},
|
|
149
|
+
viewRect(view) {
|
|
150
|
+
if (typeof Element === 'undefined' || !(view instanceof Element)) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
const {left, top, width, height} = view.getBoundingClientRect();
|
|
154
|
+
return {left, top, width, height};
|
|
141
155
|
},
|
|
142
156
|
};
|
|
143
157
|
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* @input Uses React's external-store subscription over window.matchMedia
|
|
8
8
|
* and the touch presentation's compact-touch query
|
|
9
9
|
* @output Resolves popover/bottom-sheet presentation policy
|
|
10
|
-
* @position Internal overlay policy shared across component families
|
|
10
|
+
* @position Internal overlay policy shared across component families. A
|
|
11
|
+
* component that also switches input modes under a finger resolves its
|
|
12
|
+
* `presentation` with usePresentation (utils/presentation.ts), whose
|
|
13
|
+
* surface is this one's
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
import {useCallback, useSyncExternalStore} from 'react';
|
|
@@ -288,6 +288,20 @@ export function useMenuHover<T extends HTMLElement = HTMLElement>(
|
|
|
288
288
|
}
|
|
289
289
|
}, [hide, menuRef]);
|
|
290
290
|
|
|
291
|
+
// A menu placed in the content can own its own list keys (NavHeadingMenu
|
|
292
|
+
// runs useListFocus over the same `menuitem`s); a key it consumed must not
|
|
293
|
+
// move focus a second time here as it bubbles out (APG menu: one item per
|
|
294
|
+
// arrow press).
|
|
295
|
+
const handleContentKeyDown = useCallback(
|
|
296
|
+
(event: React.KeyboardEvent) => {
|
|
297
|
+
if (event.defaultPrevented) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
handleListKeyDown(event);
|
|
301
|
+
},
|
|
302
|
+
[handleListKeyDown],
|
|
303
|
+
);
|
|
304
|
+
|
|
291
305
|
useEffect(() => {
|
|
292
306
|
escapeHandlerRef.current = () => {
|
|
293
307
|
clearTimeouts();
|
|
@@ -535,7 +549,7 @@ export function useMenuHover<T extends HTMLElement = HTMLElement>(
|
|
|
535
549
|
contentProps: {
|
|
536
550
|
onMouseEnter: handleContentMouseEnter,
|
|
537
551
|
onMouseLeave: handleMouseLeave,
|
|
538
|
-
onKeyDown:
|
|
552
|
+
onKeyDown: handleContentKeyDown,
|
|
539
553
|
},
|
|
540
554
|
menuRef,
|
|
541
555
|
focusFirst,
|
|
@@ -233,6 +233,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
233
233
|
'@astryx.tabList.label': 'Tabs',
|
|
234
234
|
'@astryx.table.label': 'Table',
|
|
235
235
|
'@astryx.table.noData': 'No data',
|
|
236
|
+
'@astryx.table.columnResize.resizeColumn': 'Resize column {label}',
|
|
236
237
|
'@astryx.table.filter.allPlaceholder': 'All',
|
|
237
238
|
'@astryx.table.filter.reset': 'Reset',
|
|
238
239
|
'@astryx.table.filter.apply': 'Apply',
|
|
@@ -427,6 +428,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
427
428
|
'@astryx.tableTree.expandRow': 'Expand row',
|
|
428
429
|
'@astryx.tableTree.collapseAllRows': 'Collapse all rows',
|
|
429
430
|
'@astryx.tableTree.expandAllRows': 'Expand all rows',
|
|
431
|
+
'@astryx.textArea.counter': '{count, number}/{limit, number}',
|
|
430
432
|
'@astryx.textArea.charactersRemaining':
|
|
431
433
|
'{count, number} {count, plural, one {character} other {characters}} remaining',
|
|
432
434
|
'@astryx.textArea.charactersOverLimit':
|
|
@@ -449,6 +451,8 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
449
451
|
'@astryx.multiSelector.allSelected': 'All selected',
|
|
450
452
|
'@astryx.multiSelector.selectionCount':
|
|
451
453
|
'{count, number} of {total, number} selected',
|
|
454
|
+
'@astryx.multiSelector.selectedCount':
|
|
455
|
+
'{count, plural, one {# selected} other {# selected}}',
|
|
452
456
|
'@astryx.multiSelector.createOption': 'Create "{query}"',
|
|
453
457
|
'@astryx.multiSelector.optionCreated': '{label} created',
|
|
454
458
|
'@astryx.multiSelector.emptySearchResults': 'No results found',
|
|
@@ -239,6 +239,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
239
239
|
'@astryx.tabList.label': '⟦Ţàƀš⟧',
|
|
240
240
|
'@astryx.table.label': '⟦Ţàƀłé⟧',
|
|
241
241
|
'@astryx.table.noData': '⟦Ñó ðàţà⟧',
|
|
242
|
+
'@astryx.table.columnResize.resizeColumn': '⟦Řéšížé çółúɱñ {label}⟧',
|
|
242
243
|
'@astryx.table.filter.allPlaceholder': '⟦Àłł⟧',
|
|
243
244
|
'@astryx.table.filter.reset': '⟦Řéšéţ⟧',
|
|
244
245
|
'@astryx.table.filter.apply': '⟦Àþþłý⟧',
|
|
@@ -436,6 +437,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
436
437
|
'@astryx.tableTree.expandRow': '⟦Éẋþàñð řóŵ⟧',
|
|
437
438
|
'@astryx.tableTree.collapseAllRows': '⟦Çółłàþšé àłł řóŵš⟧',
|
|
438
439
|
'@astryx.tableTree.expandAllRows': '⟦Éẋþàñð àłł řóŵš⟧',
|
|
440
|
+
'@astryx.textArea.counter': '⟦{count, number}/{limit, number}⟧',
|
|
439
441
|
'@astryx.textArea.charactersRemaining':
|
|
440
442
|
'⟦{count, number} {count, plural, one {character} other {characters}} řéɱàíñíñĝ⟧',
|
|
441
443
|
'@astryx.textArea.charactersOverLimit':
|
|
@@ -458,6 +460,8 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
458
460
|
'@astryx.multiSelector.allSelected': '⟦Àłł šéłéçţéð⟧',
|
|
459
461
|
'@astryx.multiSelector.selectionCount':
|
|
460
462
|
'⟦{count, number} óƒ {total, number} šéłéçţéð⟧',
|
|
463
|
+
'@astryx.multiSelector.selectedCount':
|
|
464
|
+
'⟦{count, plural, one {# selected} other {# selected}}⟧',
|
|
461
465
|
'@astryx.multiSelector.createOption': '⟦Çřéàţé "{query}"⟧',
|
|
462
466
|
'@astryx.multiSelector.optionCreated': '⟦{label} çřéàţéð⟧',
|
|
463
467
|
'@astryx.multiSelector.emptySearchResults': '⟦Ñó řéšúłţš ƒóúñð⟧',
|
package/src/motion/README.md
CHANGED
|
@@ -81,7 +81,10 @@ await handle.finished; // true if it ran to the end, false if interrupted
|
|
|
81
81
|
- Promotion: every animation the animator creates, and every `set`, gives its
|
|
82
82
|
channels a compositing layer in the same task (inline `will-change`, merged
|
|
83
83
|
with the element's own), cleared 500 ms after the last motion. Mobile Safari
|
|
84
|
-
needs it to run a curve through a blocked main thread.
|
|
84
|
+
needs it to run a curve through a blocked main thread. A pseudo-element is
|
|
85
|
+
promoted through `--astryx-motion-will-change-<pseudo>` on its host; its
|
|
86
|
+
style opts in with
|
|
87
|
+
`willChange: 'var(--astryx-motion-will-change-before, auto)'`.
|
|
85
88
|
- `atRest(properties)` resolves once none of them is moving, after the last
|
|
86
89
|
curve ends, whoever started it.
|
|
87
90
|
- `rebase({y: -shift})` moves properties at once, as a layout shift does; a
|
|
@@ -121,6 +124,13 @@ await handle.finished; // true if it ran to the end, false if interrupted
|
|
|
121
124
|
- `animatorFor(element)` returns the one animator for an element, so code
|
|
122
125
|
that animates it from several places (a swipe on x, a removal on y)
|
|
123
126
|
composes in one `translate` track.
|
|
127
|
+
- `createAnimator(dialog, {pseudoElement: '::backdrop'})` drives a
|
|
128
|
+
pseudo-element; with no inline style to hold it, a static filling animation
|
|
129
|
+
holds its end value until the next animation replaces it, while the
|
|
130
|
+
pseudo-element exists. A dialog's `::backdrop` is made again each time the
|
|
131
|
+
dialog opens, so a presentation `set`s its starting value right after
|
|
132
|
+
`showModal()`, in the same task (a `set` before it lands on the backdrop
|
|
133
|
+
the open replaces).
|
|
124
134
|
|
|
125
135
|
### How it stays on the compositor
|
|
126
136
|
|
|
@@ -67,18 +67,23 @@ const requestFrame = (callback: () => void) => {
|
|
|
67
67
|
* properties, so the page is modeled here).
|
|
68
68
|
*/
|
|
69
69
|
let stylesheet: Record<string, string> = {};
|
|
70
|
+
/** The stylesheet's values for a pseudo-element of the element. */
|
|
71
|
+
let pseudoStylesheet: Record<string, string> = {};
|
|
70
72
|
|
|
71
73
|
beforeEach(() => {
|
|
72
74
|
clock = 1000;
|
|
73
75
|
recorded = [];
|
|
74
76
|
frameQueue = [];
|
|
75
77
|
stylesheet = {};
|
|
78
|
+
pseudoStylesheet = {};
|
|
76
79
|
vi.spyOn(window, 'getComputedStyle').mockImplementation(
|
|
77
|
-
element =>
|
|
80
|
+
(element, pseudo) =>
|
|
78
81
|
({
|
|
79
82
|
getPropertyValue: (property: string) =>
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
pseudo != null
|
|
84
|
+
? (pseudoStylesheet[property] ?? '')
|
|
85
|
+
: (element as HTMLElement).style.getPropertyValue(property) ||
|
|
86
|
+
(stylesheet[property] ?? ''),
|
|
82
87
|
}) as unknown as CSSStyleDeclaration,
|
|
83
88
|
);
|
|
84
89
|
vi.useFakeTimers();
|
|
@@ -799,6 +804,12 @@ describe('animatorFor: one animator per element', () => {
|
|
|
799
804
|
const b = document.createElement('div');
|
|
800
805
|
expect(animatorFor(a)).toBe(animatorFor(a));
|
|
801
806
|
expect(animatorFor(a)).not.toBe(animatorFor(b));
|
|
807
|
+
expect(animatorFor(a, {pseudoElement: '::backdrop'})).toBe(
|
|
808
|
+
animatorFor(a, {pseudoElement: '::backdrop'}),
|
|
809
|
+
);
|
|
810
|
+
expect(animatorFor(a, {pseudoElement: '::backdrop'})).not.toBe(
|
|
811
|
+
animatorFor(a),
|
|
812
|
+
);
|
|
802
813
|
});
|
|
803
814
|
|
|
804
815
|
it('composes two callers’ axes in one translate track', async () => {
|
|
@@ -902,6 +913,24 @@ describe('promotion: a compositing layer while it moves', () => {
|
|
|
902
913
|
expect(element.style.willChange).toBe('');
|
|
903
914
|
});
|
|
904
915
|
|
|
916
|
+
it('promotes a pseudo-element through a custom property on its host', async () => {
|
|
917
|
+
const {animator, element} = setup({pseudoElement: '::backdrop'});
|
|
918
|
+
animator.set({opacity: 0});
|
|
919
|
+
// set() promotes a pseudo-element too.
|
|
920
|
+
expect(
|
|
921
|
+
element.style.getPropertyValue('--astryx-motion-will-change-backdrop'),
|
|
922
|
+
).toBe('opacity');
|
|
923
|
+
animator.animate({opacity: 1}, {spring: springs.alertPresent});
|
|
924
|
+
expect(
|
|
925
|
+
element.style.getPropertyValue('--astryx-motion-will-change-backdrop'),
|
|
926
|
+
).toBe('opacity');
|
|
927
|
+
expect(element.style.willChange).toBe('');
|
|
928
|
+
await advance(5000 + PROMOTION_LINGER_MS);
|
|
929
|
+
expect(
|
|
930
|
+
element.style.getPropertyValue('--astryx-motion-will-change-backdrop'),
|
|
931
|
+
).toBe('');
|
|
932
|
+
});
|
|
933
|
+
|
|
905
934
|
it('ends promotion on the element it leaves', () => {
|
|
906
935
|
const {animator, element} = setup();
|
|
907
936
|
animator.animate({x: 50}, {spring: springs.settle});
|
|
@@ -1937,6 +1966,75 @@ describe('Reduced Motion (design:reduced-motion)', () => {
|
|
|
1937
1966
|
expect(element.style.translate).toBe('100px 0px');
|
|
1938
1967
|
});
|
|
1939
1968
|
|
|
1969
|
+
it("a pseudo-element's dip borrows its own opacity and hands it back without touching the host", async () => {
|
|
1970
|
+
for (const role of [undefined, 'exit'] as const) {
|
|
1971
|
+
const {animator, element} = setup({
|
|
1972
|
+
reduced: true,
|
|
1973
|
+
coarse: true,
|
|
1974
|
+
pseudoElement: '::before',
|
|
1975
|
+
});
|
|
1976
|
+
element.style.opacity = '0.5'; // the host's own, untouched
|
|
1977
|
+
pseudoStylesheet = {opacity: '0.3'};
|
|
1978
|
+
animator.animate(
|
|
1979
|
+
{x: 100},
|
|
1980
|
+
{spring: springs.navigationPush},
|
|
1981
|
+
role == null ? {} : {crossfadeAs: role},
|
|
1982
|
+
);
|
|
1983
|
+
await commitFrame(clock);
|
|
1984
|
+
const [fadeLayer] = live('opacity');
|
|
1985
|
+
const values = fadeLayer.keyframes.map(k => Number(k.opacity));
|
|
1986
|
+
expect(Math.max(...values)).toBeCloseTo(0.3, 9);
|
|
1987
|
+
await advance(fade * 2 + END_HOLD_MS + 1);
|
|
1988
|
+
expect(animator.get('x')).toBe(100);
|
|
1989
|
+
// The pseudo-element's stylesheet owns its opacity again; the host
|
|
1990
|
+
// keeps its own.
|
|
1991
|
+
expect(live('opacity')).toEqual([]);
|
|
1992
|
+
expect(element.style.opacity).toBe('0.5');
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1995
|
+
|
|
1996
|
+
it("a pseudo-element's identity hold follows a node replacement", () => {
|
|
1997
|
+
const {animator, element} = setup({pseudoElement: '::backdrop'});
|
|
1998
|
+
pseudoStylesheet = {opacity: '0'};
|
|
1999
|
+
animator.set({opacity: 1});
|
|
2000
|
+
animator.attach(null);
|
|
2001
|
+
const next = document.createElement('div');
|
|
2002
|
+
document.body.append(next);
|
|
2003
|
+
animator.attach(next);
|
|
2004
|
+
const held = live('opacity').filter(
|
|
2005
|
+
animation => animation.element === next,
|
|
2006
|
+
);
|
|
2007
|
+
expect(held).toHaveLength(1);
|
|
2008
|
+
expect(Number(held[0].keyframes.at(-1)!.opacity)).toBe(1);
|
|
2009
|
+
expect(live('opacity').some(a => a.element === element)).toBe(false);
|
|
2010
|
+
next.remove();
|
|
2011
|
+
});
|
|
2012
|
+
|
|
2013
|
+
it('a pseudo-element shown over a hiding stylesheet keeps its own opacity through a move', async () => {
|
|
2014
|
+
for (const role of [undefined, 'entrance'] as const) {
|
|
2015
|
+
const {animator} = setup({
|
|
2016
|
+
reduced: true,
|
|
2017
|
+
coarse: true,
|
|
2018
|
+
pseudoElement: '::backdrop',
|
|
2019
|
+
});
|
|
2020
|
+
pseudoStylesheet = {opacity: '0'};
|
|
2021
|
+
animator.set({opacity: 1});
|
|
2022
|
+
animator.animate(
|
|
2023
|
+
{x: 100},
|
|
2024
|
+
{spring: springs.navigationPush},
|
|
2025
|
+
role == null ? {} : {crossfadeAs: role},
|
|
2026
|
+
);
|
|
2027
|
+
await commitFrame(clock);
|
|
2028
|
+
for (let step = 0; step < 8; step++) {
|
|
2029
|
+
await advance(fade / 4);
|
|
2030
|
+
}
|
|
2031
|
+
expect(animator.get('x')).toBe(100);
|
|
2032
|
+
expect(animator.get('opacity')).toBe(1);
|
|
2033
|
+
const [held] = live('opacity');
|
|
2034
|
+
expect(Number(held.keyframes.at(-1)?.opacity)).toBe(1);
|
|
2035
|
+
}
|
|
2036
|
+
});
|
|
2037
|
+
|
|
1940
2038
|
it('a move dips: fades out, lands while transparent, fades back in', async () => {
|
|
1941
2039
|
const {animator, element} = reduced();
|
|
1942
2040
|
animator.animate({x: 100}, {spring: springs.navigationPush});
|
|
@@ -2555,6 +2653,56 @@ describe('decay: momentum curves (a coast)', () => {
|
|
|
2555
2653
|
});
|
|
2556
2654
|
});
|
|
2557
2655
|
|
|
2656
|
+
describe('pseudoElement: a dialog\u2019s ::backdrop', () => {
|
|
2657
|
+
it('targets the pseudo-element, writes no inline style and holds the end state', async () => {
|
|
2658
|
+
const {animator, element} = setup({pseudoElement: '::backdrop'});
|
|
2659
|
+
animator.set({opacity: 0});
|
|
2660
|
+
const [held] = live('opacity');
|
|
2661
|
+
expect(held.options).toMatchObject({
|
|
2662
|
+
fill: 'both',
|
|
2663
|
+
pseudoElement: '::backdrop',
|
|
2664
|
+
});
|
|
2665
|
+
expect(held.keyframes.map(k => k.opacity)).toEqual(['0', '0']);
|
|
2666
|
+
expect(element.style.opacity).toBe('');
|
|
2667
|
+
|
|
2668
|
+
animator.animate({opacity: 1}, {spring: springs.alertPresent});
|
|
2669
|
+
// The new animation exists before the hold goes: no frame in between.
|
|
2670
|
+
expect(held.cancelled).toBe(true);
|
|
2671
|
+
const [fade] = live('opacity');
|
|
2672
|
+
expect(fade.options).toMatchObject({
|
|
2673
|
+
fill: 'both',
|
|
2674
|
+
pseudoElement: '::backdrop',
|
|
2675
|
+
});
|
|
2676
|
+
expect(fade.keyframes[fade.keyframes.length - 1].opacity).toBe('1');
|
|
2677
|
+
expect(element.style.opacity).toBe('');
|
|
2678
|
+
await advance(Number(fade.options.duration) + 1000);
|
|
2679
|
+
// Past the curve's end, a static animation holds the end value.
|
|
2680
|
+
expect(fade.cancelled).toBe(true);
|
|
2681
|
+
const [rest] = live('opacity');
|
|
2682
|
+
expect(rest.options).toMatchObject({
|
|
2683
|
+
fill: 'both',
|
|
2684
|
+
pseudoElement: '::backdrop',
|
|
2685
|
+
});
|
|
2686
|
+
expect(rest.keyframes.map(k => k.opacity)).toEqual(['1', '1']);
|
|
2687
|
+
expect(animator.get('opacity')).toBe(1);
|
|
2688
|
+
});
|
|
2689
|
+
|
|
2690
|
+
it('an exit holds 0 until the next animation replaces it', async () => {
|
|
2691
|
+
const {animator} = setup({
|
|
2692
|
+
pseudoElement: '::backdrop',
|
|
2693
|
+
initial: {opacity: 1},
|
|
2694
|
+
});
|
|
2695
|
+
animator.animate({opacity: 0}, {spring: springs.alertDismiss});
|
|
2696
|
+
const [fade] = live('opacity');
|
|
2697
|
+
await advance(Number(fade.options.duration) + 1000);
|
|
2698
|
+
const [rest] = live('opacity');
|
|
2699
|
+
expect(rest.keyframes.map(k => k.opacity)).toEqual(['0', '0']);
|
|
2700
|
+
animator.animate({opacity: 1}, {spring: springs.alertPresent});
|
|
2701
|
+
expect(rest.cancelled).toBe(true);
|
|
2702
|
+
expect(live('opacity')).toHaveLength(1);
|
|
2703
|
+
});
|
|
2704
|
+
});
|
|
2705
|
+
|
|
2558
2706
|
describe('attach', () => {
|
|
2559
2707
|
it('keeps state without an element and resumes the curve on attach', () => {
|
|
2560
2708
|
const animator = createAnimator(null, {
|