@astryxdesign/core 0.7.5 → 0.7.6-canary.5cd7a15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AlertDialog/AlertDialog.d.ts +1 -1
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +5 -5
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +12 -3
- package/dist/Badge/Badge.d.ts.map +1 -1
- package/dist/Badge/Badge.js +7 -1
- package/dist/BottomSheet/BottomSheet.js +1 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +2 -1
- package/dist/BottomSheet/useSheetGestures.d.ts +12 -0
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +30 -3
- package/dist/Button/Button.js +4 -4
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +6 -5
- package/dist/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 +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/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/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/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/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 +15 -1
- 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/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/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/primaryClick.d.ts +11 -0
- package/dist/utils/primaryClick.d.ts.map +1 -0
- package/dist/utils/primaryClick.js +25 -0
- package/dist/utils/timeFormat.d.ts +47 -0
- package/dist/utils/timeFormat.d.ts.map +1 -0
- package/dist/utils/timeFormat.js +240 -0
- package/dist/utils/touchPresentation.d.ts.map +1 -1
- package/dist/utils/touchPresentation.js +5 -3
- package/dist/utils/touchPresentation.stylex.d.ts +15 -0
- package/dist/utils/touchPresentation.stylex.d.ts.map +1 -0
- package/dist/utils/touchPresentation.stylex.js +33 -0
- package/dist/utils/unicodeDigits.d.ts +20 -0
- package/dist/utils/unicodeDigits.d.ts.map +1 -0
- package/dist/utils/unicodeDigits.js +70 -0
- package/locales/en.json +15 -3
- 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/Badge/Badge.doc.mjs +1 -1
- package/src/Badge/Badge.tsx +19 -2
- package/src/Badge/__tests__/BadgeLargerText.a11y.chromium.spec.ts +260 -0
- package/src/BottomSheet/BottomSheet.test.tsx +46 -0
- package/src/BottomSheet/BottomSheet.tsx +1 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +2 -1
- package/src/BottomSheet/useSheetGestures.test.ts +52 -0
- package/src/BottomSheet/useSheetGestures.ts +37 -6
- package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.chromium.spec.ts +18 -6
- package/src/Button/Button.tsx +4 -4
- package/src/Calendar/Calendar.spec.md +87 -48
- package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +21 -7
- package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +21 -7
- package/src/CheckboxInput/CheckboxInput.tsx +6 -5
- package/src/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 +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.spec.md +205 -0
- 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/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/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 +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/Typeahead/Typeahead.test.tsx +40 -0
- package/src/Typeahead/Typeahead.tsx +12 -1
- 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/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/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/pressPaintPriority.test.ts +80 -0
- package/src/utils/primaryClick.test.ts +38 -0
- package/src/utils/primaryClick.ts +28 -0
- package/src/utils/timeFormat.test.ts +204 -0
- package/src/utils/timeFormat.ts +314 -0
- package/src/utils/touchPresentation.stylex.ts +43 -0
- package/src/utils/touchPresentation.test.ts +140 -1
- package/src/utils/touchPresentation.ts +5 -3
- package/src/utils/unicodeDigits.test.ts +44 -0
- package/src/utils/unicodeDigits.ts +71 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file haptics.ts
|
|
5
|
+
* @input A feedback kind, or a label element a real finger will tap
|
|
6
|
+
* @output Exports playHaptic (vibration where the platform has it),
|
|
7
|
+
* attachTapHaptic (iOS Safari's tap-moment light impact) and the support
|
|
8
|
+
* probes
|
|
9
|
+
* @position Haptics of packages/core/src/gestures; the kinds and the
|
|
10
|
+
* vibration patterns live in the constants table (motion/uikit/gestures.ts)
|
|
11
|
+
*
|
|
12
|
+
* The web has one haptics API, `navigator.vibrate`, and iOS Safari does not
|
|
13
|
+
* implement it. iOS Safari does play a haptic in exactly one place: when a
|
|
14
|
+
* REAL tap toggles an `<input type="checkbox" switch>`, by landing on the
|
|
15
|
+
* switch or on a `<label>` tied to it (and during a native drag-flip of the
|
|
16
|
+
* switch). That haptic is UIImpactFeedbackGenerator(.light). WebKit requires
|
|
17
|
+
* a user gesture AND a trusted event for it: a `label.click()` from script
|
|
18
|
+
* does not play it (WebKit bug 309082, May 2026), so nothing can fire a
|
|
19
|
+
* haptic later from JavaScript, and mid-drag haptics (detent snaps, picker
|
|
20
|
+
* ticks) cannot exist in iOS Safari.
|
|
21
|
+
*
|
|
22
|
+
* So there are two tools:
|
|
23
|
+
*
|
|
24
|
+
* - `attachTapHaptic(label)` puts a visually hidden switch inside a label the
|
|
25
|
+
* control renders as its tap target: every real tap on the label plays the
|
|
26
|
+
* light impact at the moment of the tap. The switch itself never takes the
|
|
27
|
+
* touch (`pointer-events: none`; WebKit marks a switch's `touchstart`
|
|
28
|
+
* handled, which would stop a pan starting on it from scrolling the page),
|
|
29
|
+
* and its own events never reach the page.
|
|
30
|
+
* - `playHaptic(kind)` vibrates where `navigator.vibrate` exists (Android
|
|
31
|
+
* browsers) with the kind's pattern, and does nothing elsewhere.
|
|
32
|
+
*
|
|
33
|
+
* UIKit's haptics follow the system setting, not Reduce Motion, so neither
|
|
34
|
+
* tool consults `prefers-reduced-motion`.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import {
|
|
38
|
+
HAPTIC_VIBRATION_PATTERNS,
|
|
39
|
+
type HapticFeedbackKind,
|
|
40
|
+
} from '../motion/uikit/gestures';
|
|
41
|
+
|
|
42
|
+
/** Marks the hidden switch, so click handlers can recognize and ignore it. */
|
|
43
|
+
export const HAPTIC_SWITCH_ATTRIBUTE = 'data-astryx-haptic-switch';
|
|
44
|
+
|
|
45
|
+
/** Whether this browser vibrates at all. */
|
|
46
|
+
export function canVibrate(): boolean {
|
|
47
|
+
return (
|
|
48
|
+
typeof navigator !== 'undefined' && typeof navigator.vibrate === 'function'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Whether this browser knows the switch control (Safari 17.4 and later). */
|
|
53
|
+
export function supportsSwitchControl(doc?: Document): boolean {
|
|
54
|
+
const target = doc ?? (typeof document === 'undefined' ? null : document);
|
|
55
|
+
if (target == null) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
const input = target.createElement('input');
|
|
59
|
+
input.type = 'checkbox';
|
|
60
|
+
return 'switch' in input;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Vibrate for a feedback kind where `navigator.vibrate` exists. Returns
|
|
65
|
+
* whether anything was played. Never plays on iOS, which has no vibration
|
|
66
|
+
* API; use `attachTapHaptic` for the tap moment there.
|
|
67
|
+
*/
|
|
68
|
+
export function playHaptic(kind: HapticFeedbackKind): boolean {
|
|
69
|
+
if (!canVibrate()) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return navigator.vibrate([...HAPTIC_VIBRATION_PATTERNS[kind].value]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const VISUALLY_HIDDEN =
|
|
76
|
+
'position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;' +
|
|
77
|
+
'overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;' +
|
|
78
|
+
'opacity:0;pointer-events:none;';
|
|
79
|
+
|
|
80
|
+
let nextLabelId = 0;
|
|
81
|
+
|
|
82
|
+
/** A native disabled control, or one marked `aria-disabled`. */
|
|
83
|
+
function isDisabledControl(control: HTMLElement): boolean {
|
|
84
|
+
return (
|
|
85
|
+
control.matches(':disabled') ||
|
|
86
|
+
control.getAttribute('aria-disabled') === 'true'
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Whether `input` is a switch checkbox, which plays the tap haptic itself. */
|
|
91
|
+
function isSwitch(input: HTMLInputElement): boolean {
|
|
92
|
+
return input.type === 'checkbox' && input.hasAttribute('switch');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function swallow(event: Event): void {
|
|
96
|
+
event.stopPropagation();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Make every real tap on `label` play iOS Safari's light impact, by giving it
|
|
101
|
+
* a hidden switch to toggle. Returns a function that removes the switch.
|
|
102
|
+
* Harmless where switches have no haptics.
|
|
103
|
+
*
|
|
104
|
+
* - The switch never keeps the focus: where a label's activation focuses
|
|
105
|
+
* its control (Chromium), the focus goes on to the label's own control,
|
|
106
|
+
* or back where it was, so it never lands on an `aria-hidden` element.
|
|
107
|
+
* - A label that already has a control keeps it: the switch takes the
|
|
108
|
+
* label's activation (it goes first) and hands the click on to that
|
|
109
|
+
* control, so the tap plays and the control still toggles. The control
|
|
110
|
+
* keeps its name too: while the switch is attached it is labelled by the
|
|
111
|
+
* label through `aria-labelledby`, unless it already names itself. A control
|
|
112
|
+
* that is itself a switch plays on its own, and a label tied to its
|
|
113
|
+
* control by `for` cannot be given a second one: both are left as they
|
|
114
|
+
* are.
|
|
115
|
+
*/
|
|
116
|
+
export function attachTapHaptic(label: HTMLLabelElement): () => void {
|
|
117
|
+
const existing = label.control;
|
|
118
|
+
if (
|
|
119
|
+
(existing instanceof HTMLInputElement && isSwitch(existing)) ||
|
|
120
|
+
(existing != null && label.htmlFor !== '')
|
|
121
|
+
) {
|
|
122
|
+
return () => {};
|
|
123
|
+
}
|
|
124
|
+
const doc = label.ownerDocument;
|
|
125
|
+
const input = doc.createElement('input');
|
|
126
|
+
input.type = 'checkbox';
|
|
127
|
+
input.setAttribute('switch', '');
|
|
128
|
+
input.setAttribute(HAPTIC_SWITCH_ATTRIBUTE, '');
|
|
129
|
+
input.setAttribute('aria-hidden', 'true');
|
|
130
|
+
input.tabIndex = -1;
|
|
131
|
+
input.style.cssText = VISUALLY_HIDDEN;
|
|
132
|
+
// The label's activation clicks the switch, and that click would bubble
|
|
133
|
+
// through the control as a second click: keep it, and the switch's
|
|
134
|
+
// change events, inside. A label with a control of its own gets that
|
|
135
|
+
// click handed on, so the control still toggles.
|
|
136
|
+
// A disabled control gives no feedback (spec:AST-094 FR23): its label's
|
|
137
|
+
// activation neither toggles the switch, so nothing plays, nor reaches
|
|
138
|
+
// the control.
|
|
139
|
+
const onClick = (event: Event) => {
|
|
140
|
+
event.stopPropagation();
|
|
141
|
+
if (existing != null && isDisabledControl(existing)) {
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
existing?.click();
|
|
146
|
+
};
|
|
147
|
+
const onFocus = (event: FocusEvent) => {
|
|
148
|
+
const previous = event.relatedTarget;
|
|
149
|
+
if (
|
|
150
|
+
existing instanceof HTMLElement &&
|
|
151
|
+
existing.tabIndex >= 0 &&
|
|
152
|
+
!isDisabledControl(existing)
|
|
153
|
+
) {
|
|
154
|
+
existing.focus({preventScroll: true});
|
|
155
|
+
}
|
|
156
|
+
if (doc.activeElement !== input) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (previous instanceof HTMLElement && previous.isConnected) {
|
|
160
|
+
previous.focus({preventScroll: true});
|
|
161
|
+
}
|
|
162
|
+
if (doc.activeElement === input) {
|
|
163
|
+
input.blur();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
input.addEventListener('click', onClick);
|
|
167
|
+
input.addEventListener('change', swallow);
|
|
168
|
+
input.addEventListener('input', swallow);
|
|
169
|
+
input.addEventListener('focus', onFocus);
|
|
170
|
+
// The switch becomes the label's control, so the control the label named
|
|
171
|
+
// is named through `aria-labelledby` instead (the switch is aria-hidden,
|
|
172
|
+
// so the label's text is the name).
|
|
173
|
+
let labelledControl: HTMLElement | null = null;
|
|
174
|
+
let addedLabelId = false;
|
|
175
|
+
if (existing != null) {
|
|
176
|
+
label.prepend(input);
|
|
177
|
+
if (
|
|
178
|
+
existing instanceof HTMLElement &&
|
|
179
|
+
!existing.hasAttribute('aria-labelledby') &&
|
|
180
|
+
!existing.hasAttribute('aria-label')
|
|
181
|
+
) {
|
|
182
|
+
if (label.id === '') {
|
|
183
|
+
label.id = `astryx-haptic-label-${(nextLabelId += 1)}`;
|
|
184
|
+
addedLabelId = true;
|
|
185
|
+
}
|
|
186
|
+
existing.setAttribute('aria-labelledby', label.id);
|
|
187
|
+
labelledControl = existing;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
label.appendChild(input);
|
|
191
|
+
}
|
|
192
|
+
return () => {
|
|
193
|
+
labelledControl?.removeAttribute('aria-labelledby');
|
|
194
|
+
if (addedLabelId) {
|
|
195
|
+
label.removeAttribute('id');
|
|
196
|
+
}
|
|
197
|
+
input.removeEventListener('click', onClick);
|
|
198
|
+
input.removeEventListener('change', swallow);
|
|
199
|
+
input.removeEventListener('input', swallow);
|
|
200
|
+
input.removeEventListener('focus', onFocus);
|
|
201
|
+
input.remove();
|
|
202
|
+
};
|
|
203
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file nativeScroll.ts
|
|
5
|
+
* @input An element a touch landed on
|
|
6
|
+
* @output Exports createNativeScrollProbe (the NativeScrollProbe a host gives
|
|
7
|
+
* each touch) and parseTouchAction
|
|
8
|
+
* @position DOM half of the scroll coordination in packages/core/src/gestures
|
|
9
|
+
*
|
|
10
|
+
* Answers one question for a recognizer without letting it touch the DOM:
|
|
11
|
+
* would the browser scroll something if this finger moved that way?
|
|
12
|
+
*
|
|
13
|
+
* The browser scrolls the innermost scroller that has room, then chains
|
|
14
|
+
* outward, so the answer is "yes" while ANY scroller under the touch can
|
|
15
|
+
* still move in that direction, with three narrowings that match the
|
|
16
|
+
* browser's own:
|
|
17
|
+
*
|
|
18
|
+
* - `touch-action` between the touch target and a scroller must allow the
|
|
19
|
+
* axis, or the browser will not pan that scroller with a finger;
|
|
20
|
+
* - an `overscroll-behavior` other than `auto` on the axis stops the chain at
|
|
21
|
+
* that scroller (a sheet body with `contain` never scrolls the page);
|
|
22
|
+
* - a finger moving down scrolls content toward its START (scrollTop
|
|
23
|
+
* decreasing), and horizontally the range follows the writing direction
|
|
24
|
+
* (RTL `scrollLeft` runs from minus the overflow to zero).
|
|
25
|
+
*
|
|
26
|
+
* Styles are read once per touch, on the first question; scroll offsets are
|
|
27
|
+
* read fresh on every question, since a native scroll moves them.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type {NativeScrollProbe} from './types';
|
|
31
|
+
|
|
32
|
+
export interface AllowedPans {
|
|
33
|
+
readonly x: boolean;
|
|
34
|
+
readonly y: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Which pans a `touch-action` value lets the browser perform. */
|
|
38
|
+
export function parseTouchAction(value: string): AllowedPans {
|
|
39
|
+
const tokens = value.trim().split(/\s+/);
|
|
40
|
+
if (tokens.includes('auto') || tokens.includes('manipulation')) {
|
|
41
|
+
return {x: true, y: true};
|
|
42
|
+
}
|
|
43
|
+
if (tokens.includes('none')) {
|
|
44
|
+
return {x: false, y: false};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
x: tokens.some(
|
|
48
|
+
token =>
|
|
49
|
+
token === 'pan-x' || token === 'pan-left' || token === 'pan-right',
|
|
50
|
+
),
|
|
51
|
+
y: tokens.some(
|
|
52
|
+
token => token === 'pan-y' || token === 'pan-up' || token === 'pan-down',
|
|
53
|
+
),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface ChainEntry {
|
|
58
|
+
readonly element: Element;
|
|
59
|
+
readonly x: boolean;
|
|
60
|
+
readonly y: boolean;
|
|
61
|
+
readonly isRtl: boolean;
|
|
62
|
+
readonly stopsX: boolean;
|
|
63
|
+
readonly stopsY: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* The scroller sits beyond a `position: fixed` ancestor of the touch: it
|
|
66
|
+
* still scrolls under the finger, but it does not carry the touched
|
|
67
|
+
* element, so the element is not inside a scroll view (spec:AST-094 FR11).
|
|
68
|
+
*/
|
|
69
|
+
readonly isBeyondFixed: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const SCROLLABLE = new Set(['auto', 'scroll', 'overlay']);
|
|
73
|
+
const EDGE_TOLERANCE = 1;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The node's parent in the flat tree: a slotted node's slot, then out of
|
|
77
|
+
* shadow roots to their hosts, so a scroller inside a shadow tree around the
|
|
78
|
+
* slot is found.
|
|
79
|
+
*/
|
|
80
|
+
export function parentAcrossShadow(node: Node): Node | null {
|
|
81
|
+
const slot =
|
|
82
|
+
node instanceof Element || node instanceof Text ? node.assignedSlot : null;
|
|
83
|
+
if (slot != null) {
|
|
84
|
+
return slot;
|
|
85
|
+
}
|
|
86
|
+
if (node.parentNode != null) {
|
|
87
|
+
return node.parentNode instanceof ShadowRoot
|
|
88
|
+
? node.parentNode.host
|
|
89
|
+
: node.parentNode;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface Chain {
|
|
95
|
+
readonly entries: ChainEntry[];
|
|
96
|
+
/** touch-action allows some pan all the way up. */
|
|
97
|
+
readonly allowsPan: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildChain(target: Element): Chain {
|
|
101
|
+
const doc = target.ownerDocument;
|
|
102
|
+
const view = doc.defaultView;
|
|
103
|
+
if (view == null) {
|
|
104
|
+
return {entries: [], allowsPan: false};
|
|
105
|
+
}
|
|
106
|
+
const root = doc.scrollingElement ?? doc.documentElement;
|
|
107
|
+
const chain: ChainEntry[] = [];
|
|
108
|
+
let allowX = true;
|
|
109
|
+
let allowY = true;
|
|
110
|
+
let reachedRoot = false;
|
|
111
|
+
let isBeyondFixed = false;
|
|
112
|
+
for (
|
|
113
|
+
let node: Node | null = target;
|
|
114
|
+
node != null;
|
|
115
|
+
node = parentAcrossShadow(node)
|
|
116
|
+
) {
|
|
117
|
+
if (!(node instanceof Element)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const style = view.getComputedStyle(node);
|
|
121
|
+
const pans = parseTouchAction(style.touchAction || 'auto');
|
|
122
|
+
allowX &&= pans.x;
|
|
123
|
+
allowY &&= pans.y;
|
|
124
|
+
const isRoot = node === root || node === doc.documentElement;
|
|
125
|
+
if (isRoot) {
|
|
126
|
+
reachedRoot = true;
|
|
127
|
+
}
|
|
128
|
+
const overflowX = isRoot ? rootOverflow(doc, 'x') : style.overflowX;
|
|
129
|
+
const overflowY = isRoot ? rootOverflow(doc, 'y') : style.overflowY;
|
|
130
|
+
const scrollsX = SCROLLABLE.has(overflowX);
|
|
131
|
+
const scrollsY = SCROLLABLE.has(overflowY);
|
|
132
|
+
if (scrollsX || scrollsY) {
|
|
133
|
+
const element = isRoot ? root : node;
|
|
134
|
+
const [behaviorX, behaviorY] = overscrollBehavior(style);
|
|
135
|
+
chain.push({
|
|
136
|
+
element,
|
|
137
|
+
x: scrollsX && allowX,
|
|
138
|
+
y: scrollsY && allowY,
|
|
139
|
+
isRtl: style.direction === 'rtl',
|
|
140
|
+
stopsX: behaviorX !== 'auto',
|
|
141
|
+
stopsY: behaviorY !== 'auto',
|
|
142
|
+
isBeyondFixed,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (isRoot) {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
if (style.position === 'fixed') {
|
|
149
|
+
isBeyondFixed = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!reachedRoot && root instanceof Element) {
|
|
153
|
+
chain.push({
|
|
154
|
+
element: root,
|
|
155
|
+
x: allowX && SCROLLABLE.has(rootOverflow(doc, 'x')),
|
|
156
|
+
y: allowY && SCROLLABLE.has(rootOverflow(doc, 'y')),
|
|
157
|
+
isRtl: false,
|
|
158
|
+
stopsX: false,
|
|
159
|
+
stopsY: false,
|
|
160
|
+
isBeyondFixed,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return {entries: chain, allowsPan: allowX || allowY};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** `overscroll-behavior` per axis, from the longhands or the shorthand. */
|
|
167
|
+
function overscrollBehavior(style: CSSStyleDeclaration): [string, string] {
|
|
168
|
+
const x = style.overscrollBehaviorX;
|
|
169
|
+
const y = style.overscrollBehaviorY;
|
|
170
|
+
if (x !== '' && y !== '' && x != null && y != null) {
|
|
171
|
+
return [x, y];
|
|
172
|
+
}
|
|
173
|
+
const [first = 'auto', second = first] = (
|
|
174
|
+
style.getPropertyValue('overscroll-behavior') || 'auto'
|
|
175
|
+
)
|
|
176
|
+
.trim()
|
|
177
|
+
.split(/\s+/);
|
|
178
|
+
return [x || first, y || second];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The viewport scrolls unless the root or the body clips it (a scroll lock
|
|
183
|
+
* sets `overflow: hidden` on one of them).
|
|
184
|
+
*/
|
|
185
|
+
function rootOverflow(doc: Document, axis: 'x' | 'y'): string {
|
|
186
|
+
const view = doc.defaultView;
|
|
187
|
+
if (view == null) {
|
|
188
|
+
return 'hidden';
|
|
189
|
+
}
|
|
190
|
+
for (const element of [doc.documentElement, doc.body]) {
|
|
191
|
+
if (element == null) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const style = view.getComputedStyle(element);
|
|
195
|
+
const value = axis === 'x' ? style.overflowX : style.overflowY;
|
|
196
|
+
if (value === 'hidden' || value === 'clip') {
|
|
197
|
+
return 'hidden';
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return 'auto';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function canMoveY(element: Element, dy: number): boolean {
|
|
204
|
+
if (element.scrollHeight - element.clientHeight <= EDGE_TOLERANCE) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return dy > 0
|
|
208
|
+
? element.scrollTop > EDGE_TOLERANCE - 0.5
|
|
209
|
+
: element.scrollTop <
|
|
210
|
+
element.scrollHeight - element.clientHeight - EDGE_TOLERANCE + 0.5;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function canMoveX(entry: ChainEntry, dx: number): boolean {
|
|
214
|
+
const {element} = entry;
|
|
215
|
+
const overflow = element.scrollWidth - element.clientWidth;
|
|
216
|
+
if (overflow <= EDGE_TOLERANCE) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
const min = entry.isRtl ? -overflow : 0;
|
|
220
|
+
const max = entry.isRtl ? 0 : overflow;
|
|
221
|
+
// A finger moving right drags the content right: scrollLeft decreases.
|
|
222
|
+
return dx > 0
|
|
223
|
+
? element.scrollLeft > min + EDGE_TOLERANCE - 0.5
|
|
224
|
+
: element.scrollLeft < max - EDGE_TOLERANCE + 0.5;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** A probe of the native scrollers under `target`. */
|
|
228
|
+
export function createNativeScrollProbe(target: unknown): NativeScrollProbe {
|
|
229
|
+
let chain: Chain | null = null;
|
|
230
|
+
const getChain = (): ChainEntry[] => {
|
|
231
|
+
chain ??=
|
|
232
|
+
target instanceof Element
|
|
233
|
+
? buildChain(target)
|
|
234
|
+
: {entries: [], allowsPan: false};
|
|
235
|
+
return chain.entries;
|
|
236
|
+
};
|
|
237
|
+
return {
|
|
238
|
+
canScroll(dx: number, dy: number): boolean {
|
|
239
|
+
const ax = Math.abs(dx);
|
|
240
|
+
const ay = Math.abs(dy);
|
|
241
|
+
// The minor axis of a mostly-straight drag is the hand's wobble, not
|
|
242
|
+
// a direction: the browser locks to the major one.
|
|
243
|
+
const considerX = ax > 0 && ax >= ay / 2;
|
|
244
|
+
const considerY = ay > 0 && ay >= ax / 2;
|
|
245
|
+
for (const entry of getChain()) {
|
|
246
|
+
if (considerY && entry.y && canMoveY(entry.element, dy)) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
if (considerX && entry.x && canMoveX(entry, dx)) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
const stops =
|
|
253
|
+
(!considerX || entry.stopsX) && (!considerY || entry.stopsY);
|
|
254
|
+
if (stops && (entry.stopsX || entry.stopsY)) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
},
|
|
260
|
+
get allowsNativePan(): boolean {
|
|
261
|
+
getChain();
|
|
262
|
+
return chain?.allowsPan === true;
|
|
263
|
+
},
|
|
264
|
+
get isInsideScroller(): boolean {
|
|
265
|
+
return getChain().some(
|
|
266
|
+
entry =>
|
|
267
|
+
!entry.isBeyondFixed &&
|
|
268
|
+
((entry.y &&
|
|
269
|
+
entry.element.scrollHeight - entry.element.clientHeight >
|
|
270
|
+
EDGE_TOLERANCE) ||
|
|
271
|
+
(entry.x &&
|
|
272
|
+
entry.element.scrollWidth - entry.element.clientWidth >
|
|
273
|
+
EDGE_TOLERANCE)),
|
|
274
|
+
);
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file touchAction.ts
|
|
5
|
+
* @input The recognizers attached to one element
|
|
6
|
+
* @output Exports deriveTouchAction (the element's `touch-action`),
|
|
7
|
+
* gestureHostStyle (the inline style a host sets for its recognizers),
|
|
8
|
+
* intersectTouchAction (what both a derived and a caller's value allow) and
|
|
9
|
+
* holdSelectionStyle (the selection and callout lock for one held press)
|
|
10
|
+
* @position Browser negotiation of packages/core/src/gestures, pure so a
|
|
11
|
+
* server render can emit the same style the client would set
|
|
12
|
+
*
|
|
13
|
+
* `touch-action` is decided when the finger lands and cannot change during
|
|
14
|
+
* the gesture, so it must say up front what the browser may still do: the
|
|
15
|
+
* intersection of what every recognizer on the element leaves it.
|
|
16
|
+
*
|
|
17
|
+
* - A tap or long press only removes double-tap zoom (`manipulation`): no
|
|
18
|
+
* 300 ms click delay, scrolling and pinch-zoom untouched.
|
|
19
|
+
* - A one-axis pan or swipe gives the browser the other axis.
|
|
20
|
+
* - A pan that coordinates with native scrolling (`scrollEdge`) keeps both
|
|
21
|
+
* axes: who gets the drag is decided once, when it begins, and a drag the
|
|
22
|
+
* browser began scrolling stays the browser's to the lift (`spec:AST-094`
|
|
23
|
+
* FR14).
|
|
24
|
+
* - `pinch-zoom` stays in every value unless a pinch recognizer is attached,
|
|
25
|
+
* so the page stays zoomable (WCAG 1.4.4).
|
|
26
|
+
*
|
|
27
|
+
* WebKit pans a drag with any vertical component even under `pan-y`, so the
|
|
28
|
+
* value alone does not protect a horizontal pan there: the host also cancels
|
|
29
|
+
* `touchmove` while a recognizer owns the touch (see GestureHost.ts).
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import type {GestureRecognizer, NativeTouchBehavior} from './GestureRecognizer';
|
|
33
|
+
|
|
34
|
+
/** The intersection of what each touch recognizer leaves the browser. */
|
|
35
|
+
export function combinedNativeBehavior(
|
|
36
|
+
recognizers: ReadonlyArray<GestureRecognizer>,
|
|
37
|
+
): NativeTouchBehavior {
|
|
38
|
+
let panX = true;
|
|
39
|
+
let panY = true;
|
|
40
|
+
let pinchZoom = true;
|
|
41
|
+
let doubleTapZoom = true;
|
|
42
|
+
for (const recognizer of recognizers) {
|
|
43
|
+
if (!recognizer.acceptsTouchInput) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const behavior = recognizer.nativeTouchBehavior;
|
|
47
|
+
panX &&= behavior.panX;
|
|
48
|
+
panY &&= behavior.panY;
|
|
49
|
+
pinchZoom &&= behavior.pinchZoom;
|
|
50
|
+
doubleTapZoom &&= behavior.doubleTapZoom;
|
|
51
|
+
}
|
|
52
|
+
return {panX, panY, pinchZoom, doubleTapZoom};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The `touch-action` value for a set of recognizers, or null for none needed. */
|
|
56
|
+
export function deriveTouchAction(
|
|
57
|
+
recognizers: ReadonlyArray<GestureRecognizer>,
|
|
58
|
+
): string | null {
|
|
59
|
+
const behavior = combinedNativeBehavior(recognizers);
|
|
60
|
+
if (
|
|
61
|
+
behavior.panX &&
|
|
62
|
+
behavior.panY &&
|
|
63
|
+
behavior.pinchZoom &&
|
|
64
|
+
behavior.doubleTapZoom
|
|
65
|
+
) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (behavior.panX && behavior.panY && behavior.pinchZoom) {
|
|
69
|
+
return 'manipulation';
|
|
70
|
+
}
|
|
71
|
+
const tokens: string[] = [];
|
|
72
|
+
if (behavior.panX) {
|
|
73
|
+
tokens.push('pan-x');
|
|
74
|
+
}
|
|
75
|
+
if (behavior.panY) {
|
|
76
|
+
tokens.push('pan-y');
|
|
77
|
+
}
|
|
78
|
+
if (behavior.pinchZoom) {
|
|
79
|
+
tokens.push('pinch-zoom');
|
|
80
|
+
}
|
|
81
|
+
return tokens.length === 0 ? 'none' : tokens.join(' ');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface GestureHostStyle {
|
|
85
|
+
touchAction?: string;
|
|
86
|
+
WebkitTouchCallout?: string;
|
|
87
|
+
WebkitUserSelect?: string;
|
|
88
|
+
userSelect?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The style a host sets for these recognizers while they are attached: the
|
|
93
|
+
* `touch-action`. Text selection is not locked here: a mouse, a keyboard and
|
|
94
|
+
* assistive technology keep selecting as they do today (spec:AST-017 FR59,
|
|
95
|
+
* spec:AST-094 FR41).
|
|
96
|
+
*/
|
|
97
|
+
export function gestureHostStyle(
|
|
98
|
+
recognizers: ReadonlyArray<GestureRecognizer>,
|
|
99
|
+
): GestureHostStyle {
|
|
100
|
+
const style: GestureHostStyle = {};
|
|
101
|
+
const touchAction = deriveTouchAction(recognizers);
|
|
102
|
+
if (touchAction != null) {
|
|
103
|
+
style.touchAction = touchAction;
|
|
104
|
+
}
|
|
105
|
+
return style;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** What a `touch-action` value lets the browser do, as tokens. */
|
|
109
|
+
const ALL_PANS = [
|
|
110
|
+
'pan-left',
|
|
111
|
+
'pan-right',
|
|
112
|
+
'pan-up',
|
|
113
|
+
'pan-down',
|
|
114
|
+
'pinch-zoom',
|
|
115
|
+
] as const;
|
|
116
|
+
|
|
117
|
+
function touchActionTokens(value: string): Set<string> {
|
|
118
|
+
const parts = value
|
|
119
|
+
.trim()
|
|
120
|
+
.split(/\s+/)
|
|
121
|
+
.filter(part => part !== '');
|
|
122
|
+
const tokens = new Set<string>();
|
|
123
|
+
if (parts.length === 0) {
|
|
124
|
+
parts.push('auto');
|
|
125
|
+
}
|
|
126
|
+
for (const part of parts) {
|
|
127
|
+
if (part === 'auto') {
|
|
128
|
+
ALL_PANS.forEach(token => tokens.add(token));
|
|
129
|
+
tokens.add('double-tap-zoom');
|
|
130
|
+
} else if (part === 'manipulation') {
|
|
131
|
+
ALL_PANS.forEach(token => tokens.add(token));
|
|
132
|
+
} else if (part === 'pan-x') {
|
|
133
|
+
tokens.add('pan-left').add('pan-right');
|
|
134
|
+
} else if (part === 'pan-y') {
|
|
135
|
+
tokens.add('pan-up').add('pan-down');
|
|
136
|
+
} else if ((ALL_PANS as ReadonlyArray<string>).includes(part)) {
|
|
137
|
+
tokens.add(part);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return tokens;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The `touch-action` that allows only what both values allow: the host's
|
|
145
|
+
* derived value never overrides a caller's narrower one.
|
|
146
|
+
*/
|
|
147
|
+
export function intersectTouchAction(derived: string, caller: string): string {
|
|
148
|
+
const own = touchActionTokens(caller);
|
|
149
|
+
const both = new Set(
|
|
150
|
+
[...touchActionTokens(derived)].filter(token => own.has(token)),
|
|
151
|
+
);
|
|
152
|
+
if (ALL_PANS.every(token => both.has(token))) {
|
|
153
|
+
return both.has('double-tap-zoom') ? 'auto' : 'manipulation';
|
|
154
|
+
}
|
|
155
|
+
const parts: string[] = [];
|
|
156
|
+
for (const [axis, start, end] of [
|
|
157
|
+
['pan-x', 'pan-left', 'pan-right'],
|
|
158
|
+
['pan-y', 'pan-up', 'pan-down'],
|
|
159
|
+
] as const) {
|
|
160
|
+
if (both.has(start) && both.has(end)) {
|
|
161
|
+
parts.push(axis);
|
|
162
|
+
} else if (both.has(start)) {
|
|
163
|
+
parts.push(start);
|
|
164
|
+
} else if (both.has(end)) {
|
|
165
|
+
parts.push(end);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (both.has('pinch-zoom')) {
|
|
169
|
+
parts.push('pinch-zoom');
|
|
170
|
+
}
|
|
171
|
+
return parts.length === 0 ? 'none' : parts.join(' ');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The lock a host applies for one press only, from touch-down to the lift or
|
|
176
|
+
* a cancel, on an element whose hold does something (a long press): no iOS
|
|
177
|
+
* callout and no text-selection start under the held finger, as a
|
|
178
|
+
* long-pressed row in UIKit selects no text (spec:AST-094 FR41). Null where
|
|
179
|
+
* the hold does nothing, so a long press there selects natively.
|
|
180
|
+
*/
|
|
181
|
+
export function holdSelectionStyle(
|
|
182
|
+
recognizers: ReadonlyArray<GestureRecognizer>,
|
|
183
|
+
): GestureHostStyle | null {
|
|
184
|
+
const holds = recognizers.some(
|
|
185
|
+
recognizer =>
|
|
186
|
+
recognizer.kind === 'longPress' && recognizer.acceptsTouchInput,
|
|
187
|
+
);
|
|
188
|
+
return holds
|
|
189
|
+
? {WebkitTouchCallout: 'none', WebkitUserSelect: 'none', userSelect: 'none'}
|
|
190
|
+
: null;
|
|
191
|
+
}
|