@astryxdesign/core 0.7.6 → 0.7.7-canary.0eb44a2
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/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +23 -3
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +1 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +16 -9
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +2 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +3 -2
- package/dist/Chat/ChatComposerInput.d.ts +9 -0
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +2 -0
- package/dist/Chat/ChatMessage.js +2 -2
- package/dist/Chat/chatMessage.vars.stylex.d.ts +8 -8
- package/dist/Chat/chatMessage.vars.stylex.js +8 -8
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +8 -1
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +72 -2
- package/dist/DateInput/Wheel.d.ts +17 -1
- package/dist/DateInput/Wheel.d.ts.map +1 -1
- package/dist/DateInput/Wheel.js +179 -19
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +63 -49
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +37 -1
- package/dist/Drawer/Drawer.d.ts.map +1 -1
- package/dist/Drawer/Drawer.js +6 -0
- package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
- package/dist/Drawer/useDrawerDialogPresence.js +4 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +20 -4
- package/dist/DropdownMenu/useMenuOverflow.d.ts +13 -3
- package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -1
- package/dist/DropdownMenu/useMenuOverflow.js +31 -8
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +8 -1
- package/dist/Field/textEntryTraits.d.ts +6 -6
- package/dist/Field/textEntryTraits.js +6 -6
- package/dist/Layer/layerClickBoundary.d.ts +7 -1
- package/dist/Layer/layerClickBoundary.d.ts.map +1 -1
- package/dist/Layer/layerClickBoundary.js +2 -2
- package/dist/Layer/usePressPin.d.ts +95 -0
- package/dist/Layer/usePressPin.d.ts.map +1 -0
- package/dist/Layer/usePressPin.js +365 -0
- package/dist/Layer/useTouchTrigger.d.ts +23 -1
- package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
- package/dist/Layer/useTouchTrigger.js +45 -7
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +2 -1
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +1 -0
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +56 -7
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +38 -30
- package/dist/ProgressBar/ProgressBar.d.ts +1 -1
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +52 -14
- package/dist/ProgressBar/ProgressBarTouch.d.ts +29 -0
- package/dist/ProgressBar/ProgressBarTouch.d.ts.map +1 -0
- package/dist/ProgressBar/ProgressBarTouch.js +91 -0
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +2 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +25 -13
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +404 -131
- package/dist/Slider/sliderTouchController.d.ts +189 -0
- package/dist/Slider/sliderTouchController.d.ts.map +1 -0
- package/dist/Slider/sliderTouchController.js +1017 -0
- package/dist/Slider/useSliderTouch.d.ts +22 -0
- package/dist/Slider/useSliderTouch.d.ts.map +1 -0
- package/dist/Slider/useSliderTouch.js +56 -0
- package/dist/Switch/Switch.d.ts +1 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +200 -46
- package/dist/Switch/switchFill.d.ts +31 -0
- package/dist/Switch/switchFill.d.ts.map +1 -0
- package/dist/Switch/switchFill.js +88 -0
- package/dist/Switch/switchTouch.d.ts +81 -0
- package/dist/Switch/switchTouch.d.ts.map +1 -0
- package/dist/Switch/switchTouch.js +133 -0
- package/dist/Switch/useSwitchTouch.d.ts +285 -0
- package/dist/Switch/useSwitchTouch.d.ts.map +1 -0
- package/dist/Switch/useSwitchTouch.js +998 -0
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +4 -3
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +12 -5
- package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts +3 -0
- package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts.map +1 -0
- package/dist/Tokenizer/TokenizerItemRemovableContext.js +17 -0
- package/dist/Tooltip/Tooltip.d.ts +30 -6
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.js +5 -0
- package/dist/Tooltip/index.d.ts +1 -1
- package/dist/Tooltip/index.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.d.ts +31 -5
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +59 -18
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +19 -9
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +26 -15
- package/dist/astryx.css +91 -8
- package/dist/gestures/GestureArena.d.ts +21 -2
- package/dist/gestures/GestureArena.d.ts.map +1 -1
- package/dist/gestures/GestureArena.js +23 -3
- package/dist/gestures/GestureHost.d.ts +123 -3
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +797 -15
- package/dist/gestures/GestureRecognizer.d.ts +15 -1
- package/dist/gestures/GestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/GestureRecognizer.js +12 -0
- package/dist/gestures/PanGestureRecognizer.d.ts +8 -0
- package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/PinchGestureRecognizer.d.ts +5 -1
- package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/PinchGestureRecognizer.js +10 -0
- package/dist/gestures/PressTracker.d.ts +6 -0
- package/dist/gestures/PressTracker.d.ts.map +1 -1
- package/dist/gestures/PressTracker.js +3 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +3 -3
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/ScreenEdgePanGestureRecognizer.js +7 -5
- package/dist/gestures/nativeScroll.d.ts +16 -2
- package/dist/gestures/nativeScroll.d.ts.map +1 -1
- package/dist/gestures/nativeScroll.js +39 -8
- package/dist/gestures/recognizerInternals.d.ts +4 -2
- package/dist/gestures/recognizerInternals.d.ts.map +1 -1
- package/dist/gestures/types.d.ts +17 -0
- package/dist/gestures/types.d.ts.map +1 -1
- package/dist/gestures/types.js +7 -0
- package/dist/gestures/usePressTracker.d.ts.map +1 -1
- package/dist/gestures/usePressTracker.js +1 -0
- package/dist/gestures/wheelSegmenter.d.ts +56 -0
- package/dist/gestures/wheelSegmenter.d.ts.map +1 -0
- package/dist/gestures/wheelSegmenter.js +122 -0
- package/dist/hooks/useAttachment.d.ts +16 -8
- package/dist/hooks/useAttachment.d.ts.map +1 -1
- package/dist/hooks/useAttachment.js +35 -15
- package/dist/hooks/useFocusTrap.d.ts +15 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +13 -2
- package/dist/hooks/usePager.d.ts +261 -0
- package/dist/hooks/usePager.d.ts.map +1 -0
- package/dist/hooks/usePager.js +1169 -0
- package/dist/motion/animator.d.ts.map +1 -1
- package/dist/motion/animator.js +140 -19
- package/dist/motion/paging.d.ts +69 -0
- package/dist/motion/paging.d.ts.map +1 -0
- package/dist/motion/paging.js +148 -0
- package/dist/motion/uikit/index.d.ts +8 -4
- package/dist/motion/uikit/index.d.ts.map +1 -1
- package/dist/motion/uikit/index.js +8 -4
- package/dist/motion/uikit/paging.d.ts +171 -0
- package/dist/motion/uikit/paging.d.ts.map +1 -0
- package/dist/motion/uikit/paging.js +192 -0
- package/dist/motion/uikit/progressBar.d.ts +79 -0
- package/dist/motion/uikit/progressBar.d.ts.map +1 -0
- package/dist/motion/uikit/progressBar.js +102 -0
- package/dist/motion/uikit/scroll.d.ts +0 -19
- package/dist/motion/uikit/scroll.d.ts.map +1 -1
- package/dist/motion/uikit/scroll.js +2 -21
- package/dist/motion/uikit/slider.d.ts +97 -0
- package/dist/motion/uikit/slider.d.ts.map +1 -0
- package/dist/motion/uikit/slider.js +111 -0
- package/dist/motion/uikit/springs.d.ts +59 -30
- package/dist/motion/uikit/springs.d.ts.map +1 -1
- package/dist/motion/uikit/springs.js +64 -34
- package/dist/motion/uikit/switch.d.ts +166 -0
- package/dist/motion/uikit/switch.d.ts.map +1 -0
- package/dist/motion/uikit/switch.js +186 -0
- package/dist/privateAttributes/pressHoldsDrag.stylex.d.ts +18 -0
- package/dist/privateAttributes/pressHoldsDrag.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/pressHoldsDrag.stylex.js +28 -0
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +5 -0
- package/dist/utils/selectionLock.d.ts +43 -0
- package/dist/utils/selectionLock.d.ts.map +1 -1
- package/dist/utils/selectionLock.js +394 -6
- package/package.json +5 -5
- package/src/AppShell/AppShell.doc.mjs +10 -0
- package/src/AppShell/AppShell.tsx +48 -2
- package/src/Avatar/Avatar.tsx +3 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +16 -9
- package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
- package/src/Button/Button.tsx +3 -2
- package/src/Chat/Chat.doc.mjs +1 -0
- package/src/Chat/ChatComposerInput.doc.mjs +11 -0
- package/src/Chat/ChatComposerInput.tsx +11 -0
- package/src/Chat/ChatMessage.tsx +2 -2
- package/src/Chat/chatMessage.vars.stylex.ts +8 -8
- package/src/CommandPalette/CommandPaletteInput.tsx +6 -0
- package/src/DateInput/MonthScroller.tsx +63 -5
- package/src/DateInput/Wheel.tsx +174 -14
- package/src/DateRangeInput/DateRangeInput.doc.mjs +3 -2
- package/src/DateRangeInput/DateRangeInput.tsx +117 -71
- package/src/Dialog/Dialog.tsx +37 -1
- package/src/Drawer/Drawer.tsx +6 -0
- package/src/Drawer/useDrawerDialogPresence.ts +4 -0
- package/src/DropdownMenu/DropdownMenu.tsx +28 -6
- package/src/DropdownMenu/useMenuOverflow.ts +44 -8
- package/src/Field/PanelSearchInput.tsx +6 -0
- package/src/Field/textEntryTraits.ts +6 -6
- package/src/Layer/layerClickBoundary.ts +2 -2
- package/src/Layer/usePressPin.ts +516 -0
- package/src/Layer/useTouchTrigger.ts +56 -6
- package/src/Lightbox/Lightbox.tsx +2 -1
- package/src/Link/Link.tsx +3 -0
- package/src/Popover/usePopover.tsx +71 -9
- package/src/PowerSearch/PowerSearch.tsx +38 -31
- package/src/ProgressBar/ProgressBar.doc.mjs +7 -5
- package/src/ProgressBar/ProgressBar.tsx +147 -16
- package/src/ProgressBar/ProgressBarTouch.ts +109 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +2 -1
- package/src/Selector/Selector.tsx +29 -2
- package/src/Slider/Slider.doc.mjs +11 -7
- package/src/Slider/Slider.tsx +539 -132
- package/src/Slider/sliderTouchController.ts +1209 -0
- package/src/Slider/useSliderTouch.ts +78 -0
- package/src/Switch/Switch.doc.mjs +3 -3
- package/src/Switch/Switch.tsx +412 -86
- package/src/Switch/switchFill.ts +106 -0
- package/src/Switch/switchTouch.ts +194 -0
- package/src/Switch/useSwitchTouch.ts +1121 -0
- package/src/TabList/Tab.tsx +5 -3
- package/src/Tokenizer/Tokenizer.tsx +15 -5
- package/src/Tokenizer/TokenizerItemRemovableContext.ts +22 -0
- package/src/Tooltip/Tooltip.doc.mjs +21 -6
- package/src/Tooltip/Tooltip.tsx +42 -5
- package/src/Tooltip/index.ts +1 -0
- package/src/Tooltip/useTooltip.doc.mjs +4 -2
- package/src/Tooltip/useTooltip.tsx +84 -21
- package/src/Typeahead/BaseTypeahead.tsx +16 -8
- package/src/Typeahead/Typeahead.tsx +29 -15
- package/src/gestures/GestureArena.ts +45 -4
- package/src/gestures/GestureHost.ts +958 -15
- package/src/gestures/GestureRecognizer.ts +28 -0
- package/src/gestures/PanGestureRecognizer.ts +8 -0
- package/src/gestures/PinchGestureRecognizer.ts +16 -1
- package/src/gestures/PressTracker.ts +10 -0
- package/src/gestures/README.md +82 -3
- package/src/gestures/ScreenEdgePanGestureRecognizer.ts +7 -5
- package/src/gestures/nativeScroll.ts +49 -9
- package/src/gestures/recognizerInternals.ts +9 -2
- package/src/gestures/types.ts +18 -0
- package/src/gestures/usePressTracker.ts +2 -0
- package/src/gestures/wheelSegmenter.ts +143 -0
- package/src/hooks/useAttachment.ts +56 -14
- package/src/hooks/useFocusTrap.ts +26 -2
- package/src/hooks/usePager.ts +1446 -0
- package/src/motion/MEASUREMENTS.md +36 -27
- package/src/motion/README.md +7 -3
- package/src/motion/animator.ts +193 -25
- package/src/motion/paging.ts +161 -0
- package/src/motion/uikit/index.ts +7 -4
- package/src/motion/uikit/paging.ts +193 -0
- package/src/motion/uikit/progressBar.ts +102 -0
- package/src/motion/uikit/scroll.ts +2 -18
- package/src/motion/uikit/slider.ts +105 -0
- package/src/motion/uikit/springs.ts +53 -32
- package/src/motion/uikit/switch.ts +174 -0
- package/src/privateAttributes/pressHoldsDrag.stylex.ts +28 -0
- package/src/utils/pressFeedback.ts +5 -0
- package/src/utils/selectionLock.ts +501 -6
- package/dist/motion/selectPage.d.ts +0 -26
- package/dist/motion/selectPage.d.ts.map +0 -1
- package/dist/motion/selectPage.js +0 -55
- package/src/motion/selectPage.ts +0 -55
|
@@ -36,6 +36,7 @@ import type {
|
|
|
36
36
|
GestureTouch,
|
|
37
37
|
Point,
|
|
38
38
|
PointerKind,
|
|
39
|
+
ScrollType,
|
|
39
40
|
} from './types';
|
|
40
41
|
import {DEFAULT_ENVIRONMENT} from './types';
|
|
41
42
|
|
|
@@ -81,6 +82,16 @@ export interface GestureRecognizerOptions<R = GestureRecognizer> {
|
|
|
81
82
|
delaysTouchesEnded?: boolean;
|
|
82
83
|
/** Pointer kinds this recognizer listens to (UIKit's `allowedTouchTypes`). @default all */
|
|
83
84
|
allowedPointerTypes?: ReadonlyArray<PointerKind>;
|
|
85
|
+
/**
|
|
86
|
+
* Scroll input this recognizer takes, as UIKit's `allowedScrollTypesMask`.
|
|
87
|
+
* A pan follows a trackpad's two-finger scroll (`continuous`), never a
|
|
88
|
+
* mouse wheel's notches. It moves as a contact with the content, along its
|
|
89
|
+
* own axis, after the scrollers under the pointer. A pinch takes a
|
|
90
|
+
* ctrl-wheel of either, as a trackpad pinches.
|
|
91
|
+
* @default none; a pinch takes both, the screen-edge pan none whatever a
|
|
92
|
+
* caller passes
|
|
93
|
+
*/
|
|
94
|
+
allowedScrollTypes?: ReadonlyArray<ScrollType>;
|
|
84
95
|
/** Ignore a touch of a different kind than the ones already tracked. @default true */
|
|
85
96
|
requiresExclusiveTouchType?: boolean;
|
|
86
97
|
/**
|
|
@@ -202,6 +213,8 @@ export abstract class GestureRecognizer {
|
|
|
202
213
|
delaysTouchesBegan: boolean;
|
|
203
214
|
delaysTouchesEnded: boolean;
|
|
204
215
|
allowedPointerTypes: ReadonlySet<PointerKind> | null;
|
|
216
|
+
/** See GestureRecognizerOptions.allowedScrollTypes. */
|
|
217
|
+
readonly allowedScrollTypes: ReadonlySet<ScrollType>;
|
|
205
218
|
requiresExclusiveTouchType: boolean;
|
|
206
219
|
/**
|
|
207
220
|
* The element (or any object) the recognizer is attached to. The DOM host
|
|
@@ -247,6 +260,9 @@ export abstract class GestureRecognizer {
|
|
|
247
260
|
options.allowedPointerTypes == null
|
|
248
261
|
? null
|
|
249
262
|
: new Set(options.allowedPointerTypes);
|
|
263
|
+
this.allowedScrollTypes = new Set(
|
|
264
|
+
options.allowedScrollTypes ?? this.defaultScrollTypes,
|
|
265
|
+
);
|
|
250
266
|
this.requiresExclusiveTouchType =
|
|
251
267
|
options.requiresExclusiveTouchType ?? true;
|
|
252
268
|
}
|
|
@@ -479,6 +495,11 @@ export abstract class GestureRecognizer {
|
|
|
479
495
|
/** Clear per-gesture state before the next attempt. */
|
|
480
496
|
protected reset(): void {}
|
|
481
497
|
|
|
498
|
+
/** The scroll types taken when the options name none (UIKit's: none). */
|
|
499
|
+
protected get defaultScrollTypes(): ReadonlyArray<ScrollType> {
|
|
500
|
+
return [];
|
|
501
|
+
}
|
|
502
|
+
|
|
482
503
|
/**
|
|
483
504
|
* Whether a touch may join. The base checks enabled, pointer kind and
|
|
484
505
|
* exclusivity of kind; subclasses narrow it (a one-finger pan ignores a
|
|
@@ -488,6 +509,13 @@ export abstract class GestureRecognizer {
|
|
|
488
509
|
if (!this.isEnabled) {
|
|
489
510
|
return false;
|
|
490
511
|
}
|
|
512
|
+
if (touch.scrollType != null) {
|
|
513
|
+
// Scroll input is no pointer: only the scroll types allowed count.
|
|
514
|
+
return (
|
|
515
|
+
this.allowedScrollTypes.has(touch.scrollType) &&
|
|
516
|
+
(this.options.shouldReceiveTouch?.(this, touch) ?? true)
|
|
517
|
+
);
|
|
518
|
+
}
|
|
491
519
|
if (
|
|
492
520
|
this.allowedPointerTypes != null &&
|
|
493
521
|
!this.allowedPointerTypes.has(touch.pointerType)
|
|
@@ -73,6 +73,14 @@ export interface PanGestureRecognizerOptions extends GestureRecognizerOptions<Pa
|
|
|
73
73
|
* at the start only (UIScrollView's directional lock).
|
|
74
74
|
*/
|
|
75
75
|
lockAxis?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* A trackpad's two-finger scroll (`continuous`) drives the pan where its
|
|
78
|
+
* owner allows it. It moves as a contact with the content, along its own
|
|
79
|
+
* axis, after the scrollers under the pointer. A mouse wheel's notches
|
|
80
|
+
* never drive a pan; they scroll what is under the pointer.
|
|
81
|
+
* @default [] (none)
|
|
82
|
+
*/
|
|
83
|
+
allowedScrollTypes?: ReadonlyArray<'continuous'>;
|
|
76
84
|
/** Begin only when the native scrollers under the touch are at the edge. */
|
|
77
85
|
scrollEdge?: boolean;
|
|
78
86
|
}
|
|
@@ -27,7 +27,12 @@ import {
|
|
|
27
27
|
type GestureRecognizerOptions,
|
|
28
28
|
type NativeTouchBehavior,
|
|
29
29
|
} from './GestureRecognizer';
|
|
30
|
-
import type {
|
|
30
|
+
import type {
|
|
31
|
+
GestureEventContext,
|
|
32
|
+
GestureTouch,
|
|
33
|
+
Point,
|
|
34
|
+
ScrollType,
|
|
35
|
+
} from './types';
|
|
31
36
|
import {VelocityTracker} from './VelocityTracker';
|
|
32
37
|
|
|
33
38
|
export interface PinchGestureRecognizerOptions extends GestureRecognizerOptions<PinchGestureRecognizer> {
|
|
@@ -51,6 +56,16 @@ export class PinchGestureRecognizer extends GestureRecognizer {
|
|
|
51
56
|
this.hysteresis = options.hysteresis ?? UIKIT_PINCH.hysteresis.value;
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
/** @internal The host's brand: a ctrl-wheel drives a pinch (recognizerInternals). */
|
|
60
|
+
protected get family(): 'pinch' {
|
|
61
|
+
return 'pinch';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A ctrl-wheel drives a pinch, from a trackpad or a mouse. */
|
|
65
|
+
protected override get defaultScrollTypes(): ReadonlyArray<ScrollType> {
|
|
66
|
+
return ['continuous', 'discrete'];
|
|
67
|
+
}
|
|
68
|
+
|
|
54
69
|
/** Current span over the span the fingers started at. */
|
|
55
70
|
get scale(): number {
|
|
56
71
|
return this.scaleValue;
|
|
@@ -95,6 +95,12 @@ export interface PressTrackerOptions {
|
|
|
95
95
|
* does nothing passes none. @default undefined
|
|
96
96
|
*/
|
|
97
97
|
selectionLock?: SelectionLock;
|
|
98
|
+
/**
|
|
99
|
+
* Whether the press of `touchId` holds the browser's context menu closed
|
|
100
|
+
* now; the host then cancels it. For a control whose hold acts, such as a
|
|
101
|
+
* switch. @default undefined
|
|
102
|
+
*/
|
|
103
|
+
holdsContextMenu?: (touchId: number) => boolean;
|
|
98
104
|
trackingExpansion?: number;
|
|
99
105
|
/**
|
|
100
106
|
* When the highlight waits `contentTouchDelay`: inside a scroller (a
|
|
@@ -312,6 +318,10 @@ export function createPressTracker(options: PressTrackerOptions): PressTracker {
|
|
|
312
318
|
return options.isEnabled?.() !== false;
|
|
313
319
|
},
|
|
314
320
|
|
|
321
|
+
holdsContextMenu(id: number): boolean {
|
|
322
|
+
return options.holdsContextMenu?.(id) === true;
|
|
323
|
+
},
|
|
324
|
+
|
|
315
325
|
touchBegan(touch: GestureTouch): void {
|
|
316
326
|
if (
|
|
317
327
|
touchId != null ||
|
package/src/gestures/README.md
CHANGED
|
@@ -304,9 +304,25 @@ presses nothing.
|
|
|
304
304
|
controller's surfaces (`usePressFeedback` rows), keep the browser's
|
|
305
305
|
long-press selection; a tap never selects. A non-text input under the finger inside such an element (a checkbox, a radio, a switch's input) is locked too: WebKit's own stylesheet makes every input selectable, which no ancestor's lock reaches. A text field keeps its selection, since a held finger places its caret. A
|
|
306
306
|
finger or a pen takes the lock under any primary pointer, a touchscreen
|
|
307
|
-
laptop and a desktop pen tablet included; a mouse never does
|
|
308
|
-
|
|
309
|
-
|
|
307
|
+
laptop and a desktop pen tablet included; a mouse never does
|
|
308
|
+
(`spec:AST-017` FR59).
|
|
309
|
+
|
|
310
|
+
A mouse or pen drag that a recognizer claims takes no new text selection
|
|
311
|
+
and never clears the page's (`spec:AST-094` FR41): from the claim until a
|
|
312
|
+
task after the lift (the press's own `mouseup` and `click` come in its
|
|
313
|
+
task), the claiming elements and the document refuse a selection, and the
|
|
314
|
+
selection the page had when the press began is put back over whatever the
|
|
315
|
+
press or the drag made of it. The hold starts at the claim of whichever
|
|
316
|
+
recognizer makes it, whatever its state: a swipe recognizes as `ended`
|
|
317
|
+
while the button is still down, and a recognizer that waits on another's
|
|
318
|
+
failure claims while still `possible`. A claim given back to the browser
|
|
319
|
+
(that waiting recognizer failing or cancelled, with no other owner) lets
|
|
320
|
+
the hold go, and the drag selects again. A drag that starts on selectable text (a
|
|
321
|
+
character of text that allows a selection, or a text field) is not
|
|
322
|
+
claimed: no recognizer recognizes with it while it is down, so it selects
|
|
323
|
+
text, and content under a sheet's or a pager's pan stays selectable. A
|
|
324
|
+
press a recognizer claims at its own pointerdown, before it moves, is no
|
|
325
|
+
drag, and a tap claims only at its lift.
|
|
310
326
|
|
|
311
327
|
- **Claiming.** When a recognizer recognizes, it owns its touches. Each
|
|
312
328
|
element with a recognizer that can claim a moving touch (a pan, a swipe, a
|
|
@@ -452,6 +468,69 @@ anywhere (often on a plain row no recognizer is on) fails a pan limited to
|
|
|
452
468
|
one finger, so the two fingers zoom the page (WCAG 1.4.4) instead of the pan
|
|
453
469
|
claiming them midway.
|
|
454
470
|
|
|
471
|
+
## Wheel and trackpad
|
|
472
|
+
|
|
473
|
+
A recognizer takes scroll input only where it allows that scroll type
|
|
474
|
+
(`allowedScrollTypes`, UIKit's `allowedScrollTypesMask`): `continuous`
|
|
475
|
+
for a trackpad's two-finger scroll or pinch, `discrete` for a mouse wheel's
|
|
476
|
+
notches. A pan follows a trackpad, never a mouse wheel (`spec:AST-094` FR43,
|
|
477
|
+
DEC-24): it allows only `continuous`, and none by default, so it takes a
|
|
478
|
+
trackpad's scroll only where its owner allows it, its element then getting
|
|
479
|
+
the one wheel listener, and a mouse wheel's notches always scroll what is
|
|
480
|
+
under the pointer. A pinch allows both, for a ctrl-wheel from a trackpad or
|
|
481
|
+
a mouse. The screen-edge pan takes none, as it takes no mouse, trackpad or
|
|
482
|
+
pen (FR43). A control's thumb, a slider's or a switch's, tracks as a control,
|
|
483
|
+
not a pan, and takes no wheel, so a page scroll over it never changes its
|
|
484
|
+
value (DEC-23). The web reports no wheel phases or device, so the deltas
|
|
485
|
+
tell, and what they cannot tell apart reads as a mouse wheel: a line- or
|
|
486
|
+
page-mode delta; a scroll with Shift held (Shift+wheel); a pixel delta a hair
|
|
487
|
+
off a whole pixel (a mouse wheel on Windows); a scroll with no legacy `wheelDelta` (an engine that
|
|
488
|
+
sends none); a delta that is not a third of its legacy `wheelDelta`; or a 120
|
|
489
|
+
tick over a 40 px delta. A trackpad's precise deltas, a third of their legacy
|
|
490
|
+
`wheelDelta` (Chromium and WebKit), read as a trackpad.
|
|
491
|
+
|
|
492
|
+
A wheel gesture drives a pan as a contact moving with the content, along the
|
|
493
|
+
pan's own axis, and only once every scroller between the pointer and the
|
|
494
|
+
pan's element is at its edge that way, decided at the gesture's first wheel:
|
|
495
|
+
a wheel the pan does not take is never prevented. A watcher
|
|
496
|
+
(`claimsTouches: false`) never takes one, and a gesture whose first wheel
|
|
497
|
+
another handler already prevented, or that cannot be cancelled, stays the
|
|
498
|
+
page's. A scroll-edge pan (a pull to
|
|
499
|
+
refresh) takes one only if no scroller under the pointer could scroll for it,
|
|
500
|
+
its own scroll owner and the page included, as its finger's rule reads them
|
|
501
|
+
(`spec:AST-094` FR19); `touch-action` narrows only a finger's pan, so a wheel
|
|
502
|
+
reads every scroller past it. A ctrl-wheel (a trackpad
|
|
503
|
+
pinch, or Control and a mouse wheel) drives a pinch, whose scale follows
|
|
504
|
+
`exp(-deltaY / 100)`; a pinch takes it by default and ends 100 ms after its
|
|
505
|
+
last wheel. While the fingers move, a trackpad scroll ends 180 ms after its
|
|
506
|
+
last wheel vertically and 220 ms horizontally, whoever keeps it (a pan, a
|
|
507
|
+
scroller under the pointer, or the page), as a slow drag pauses between
|
|
508
|
+
wheels (DEC-24). A gesture's release is never stamped
|
|
509
|
+
later than an input that ends it sooner (FR16).
|
|
510
|
+
|
|
511
|
+
The web reports no trackpad contact or phase either, so one splitter reads
|
|
512
|
+
every trackpad stream (`wheelSegmenter.ts`, DEC-24): at the fourth or later
|
|
513
|
+
delta of a shrinking run, once the run has lost a fifth of its size, the
|
|
514
|
+
fingers lifted and momentum began. Until it decides,
|
|
515
|
+
the shrinking deltas wait, and a pan it follows moves on at the fingers' last
|
|
516
|
+
speed, never past what that speed projects. At the lift the pan keeps that
|
|
517
|
+
travel and lets go there, still moving at that speed, and the momentum after
|
|
518
|
+
it never moves it; when the fingers carry on instead, their moves absorb
|
|
519
|
+
what the pan showed past their travel, or short of it, so it never steps
|
|
520
|
+
back and ends on their travel. Fingers that stop release still, once the end
|
|
521
|
+
time passes. In momentum, a reversal, a delta that grows
|
|
522
|
+
against the decay, or a 50 ms pause starts a new gesture. A finger, mouse or
|
|
523
|
+
pen that comes down ends it, and a wheel claim keeps the page's text
|
|
524
|
+
selection. A gesture a
|
|
525
|
+
recognizer took is latched to it, as a browser latches a scroller: while it
|
|
526
|
+
lasts, the host hears every step first, through the shared input stream's
|
|
527
|
+
window-capture listener, wherever it lands, so a pan whose element moves from
|
|
528
|
+
under a still pointer keeps the gesture. A page that loses focus or hides
|
|
529
|
+
cancels a trackpad pan a recognizer still drives, as it cancels every touch;
|
|
530
|
+
the rest of that stream is the page's. A ctrl-wheel pinch keeps its stream,
|
|
531
|
+
so the page never zooms for it, and a pan its lift released keeps its
|
|
532
|
+
momentum latched until the stream ends.
|
|
533
|
+
|
|
455
534
|
## Time
|
|
456
535
|
|
|
457
536
|
Every input carries its own timestamp (`event.timeStamp`), and timers are
|
|
@@ -71,12 +71,12 @@ interface EdgeLayout {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* A screen-edge pan is a finger's: it takes no `allowedPointerTypes`
|
|
75
|
-
*
|
|
74
|
+
* A screen-edge pan is a finger's: it takes no `allowedPointerTypes` and no
|
|
75
|
+
* `allowedScrollTypes`, so neither a mouse, a trackpad nor a pen drives it.
|
|
76
76
|
*/
|
|
77
77
|
export interface ScreenEdgePanGestureRecognizerOptions extends Omit<
|
|
78
78
|
PanGestureRecognizerOptions,
|
|
79
|
-
'direction' | 'allowedPointerTypes'
|
|
79
|
+
'direction' | 'allowedPointerTypes' | 'allowedScrollTypes'
|
|
80
80
|
> {
|
|
81
81
|
/** The edges a touch may start from. @default ['leading'] */
|
|
82
82
|
edges?: ReadonlyArray<ScreenEdge>;
|
|
@@ -109,9 +109,11 @@ export class ScreenEdgePanGestureRecognizer extends PanGestureRecognizer {
|
|
|
109
109
|
maximumNumberOfTouches: 1,
|
|
110
110
|
hysteresis: UIKIT_SCREEN_EDGE_PAN.hysteresis.value,
|
|
111
111
|
...options,
|
|
112
|
-
// A finger's only:
|
|
113
|
-
// pan stays off
|
|
112
|
+
// A finger's only (`spec:AST-094` FR43). Under a mouse, a trackpad or
|
|
113
|
+
// a pen, the screen-edge pan stays off, whatever a caller passes. It
|
|
114
|
+
// takes no wheel or trackpad scrolling.
|
|
114
115
|
allowedPointerTypes: ['touch'],
|
|
116
|
+
allowedScrollTypes: [],
|
|
115
117
|
});
|
|
116
118
|
this.edges = options.edges ?? ['leading'];
|
|
117
119
|
this.edgeRegion =
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* @file nativeScroll.ts
|
|
5
5
|
* @input An element a touch landed on
|
|
6
6
|
* @output Exports createNativeScrollProbe (the NativeScrollProbe a host gives
|
|
7
|
-
* each touch
|
|
7
|
+
* each touch, and each wheel gesture without the touch-action narrowing)
|
|
8
|
+
* and parseTouchAction
|
|
8
9
|
* @position DOM half of the scroll coordination in packages/core/src/gestures
|
|
9
10
|
*
|
|
10
11
|
* Answers one question for a recognizer without letting it touch the DOM:
|
|
@@ -82,7 +83,7 @@ interface Chain {
|
|
|
82
83
|
readonly allowsPan: boolean;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
function buildChain(target: Element): Chain {
|
|
86
|
+
function buildChain(target: Element, readsTouchAction: boolean): Chain {
|
|
86
87
|
const doc = target.ownerDocument;
|
|
87
88
|
const view = doc.defaultView;
|
|
88
89
|
if (view == null) {
|
|
@@ -103,9 +104,11 @@ function buildChain(target: Element): Chain {
|
|
|
103
104
|
continue;
|
|
104
105
|
}
|
|
105
106
|
const style = view.getComputedStyle(node);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
if (readsTouchAction) {
|
|
108
|
+
const pans = parseTouchAction(style.touchAction || 'auto');
|
|
109
|
+
allowX &&= pans.x;
|
|
110
|
+
allowY &&= pans.y;
|
|
111
|
+
}
|
|
109
112
|
const isRoot = node === root || node === doc.documentElement;
|
|
110
113
|
if (isRoot) {
|
|
111
114
|
reachedRoot = true;
|
|
@@ -196,7 +199,10 @@ function canMoveY(element: Element, dy: number): boolean {
|
|
|
196
199
|
element.scrollHeight - element.clientHeight - EDGE_TOLERANCE + 0.5;
|
|
197
200
|
}
|
|
198
201
|
|
|
199
|
-
function canMoveX(
|
|
202
|
+
function canMoveX(
|
|
203
|
+
entry: Pick<ChainEntry, 'element' | 'isRtl'>,
|
|
204
|
+
dx: number,
|
|
205
|
+
): boolean {
|
|
200
206
|
const {element} = entry;
|
|
201
207
|
const overflow = element.scrollWidth - element.clientWidth;
|
|
202
208
|
if (overflow <= EDGE_TOLERANCE) {
|
|
@@ -210,13 +216,47 @@ function canMoveX(entry: ChainEntry, dx: number): boolean {
|
|
|
210
216
|
: element.scrollLeft < max - EDGE_TOLERANCE + 0.5;
|
|
211
217
|
}
|
|
212
218
|
|
|
213
|
-
/**
|
|
214
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Whether `element` itself scrolls natively now for a finger moving by
|
|
221
|
+
* (dx, dy). It uses the probe's own test of each scroller: overflow that
|
|
222
|
+
* scrolls on that axis, and room past the edge tolerance. The position must
|
|
223
|
+
* also be short of the end it moves toward, a right-to-left range included.
|
|
224
|
+
* A wheel moves the content the other way, so pass its deltas negated.
|
|
225
|
+
*/
|
|
226
|
+
export function canElementScroll(
|
|
227
|
+
element: Element,
|
|
228
|
+
dx: number,
|
|
229
|
+
dy: number,
|
|
230
|
+
): boolean {
|
|
231
|
+
const view = element.ownerDocument.defaultView;
|
|
232
|
+
if (view == null) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
const style = view.getComputedStyle(element);
|
|
236
|
+
if (dy !== 0 && SCROLLABLE.has(style.overflowY) && canMoveY(element, dy)) {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
return (
|
|
240
|
+
dx !== 0 &&
|
|
241
|
+
SCROLLABLE.has(style.overflowX) &&
|
|
242
|
+
canMoveX({element, isRtl: isRtlElement(element as HTMLElement)}, dx)
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* A probe of the native scrollers under `target`. A wheel scrolls whatever
|
|
248
|
+
* `touch-action` says, which narrows only a finger's pan: a wheel's probe
|
|
249
|
+
* passes `readsTouchAction: false`.
|
|
250
|
+
*/
|
|
251
|
+
export function createNativeScrollProbe(
|
|
252
|
+
target: unknown,
|
|
253
|
+
{readsTouchAction = true}: {readsTouchAction?: boolean} = {},
|
|
254
|
+
): NativeScrollProbe {
|
|
215
255
|
let chain: Chain | null = null;
|
|
216
256
|
const getChain = (): ChainEntry[] => {
|
|
217
257
|
chain ??=
|
|
218
258
|
target instanceof Element
|
|
219
|
-
? buildChain(target)
|
|
259
|
+
? buildChain(target, readsTouchAction)
|
|
220
260
|
: {entries: [], allowsPan: false};
|
|
221
261
|
return chain.entries;
|
|
222
262
|
};
|
|
@@ -20,7 +20,12 @@
|
|
|
20
20
|
|
|
21
21
|
import type {GestureArena} from './GestureArena';
|
|
22
22
|
import type {GestureRecognizer} from './GestureRecognizer';
|
|
23
|
-
import type {
|
|
23
|
+
import type {
|
|
24
|
+
GestureEventContext,
|
|
25
|
+
GestureState,
|
|
26
|
+
GestureTouch,
|
|
27
|
+
Point,
|
|
28
|
+
} from './types';
|
|
24
29
|
|
|
25
30
|
/**
|
|
26
31
|
* The kinds of recognizer the host and touch-action treat specially. Each of
|
|
@@ -28,12 +33,14 @@ import type {GestureEventContext, GestureState, GestureTouch} from './types';
|
|
|
28
33
|
* never imports a recognizer class: a page ships only the recognizers it
|
|
29
34
|
* constructs.
|
|
30
35
|
*/
|
|
31
|
-
export type RecognizerFamily = 'tap' | 'longPress' | 'pan';
|
|
36
|
+
export type RecognizerFamily = 'tap' | 'longPress' | 'pan' | 'pinch';
|
|
32
37
|
|
|
33
38
|
/** The members of a recognizer only the gesture system calls. */
|
|
34
39
|
export interface RecognizerInternals {
|
|
35
40
|
/** The recognizer's family, when the host treats it specially. */
|
|
36
41
|
readonly family?: RecognizerFamily;
|
|
42
|
+
/** A pan's direction rule for a translation, `axisRatio` included. */
|
|
43
|
+
classify?(translation: Point): 'begin' | 'fail' | 'wait';
|
|
37
44
|
arena: GestureArena | null;
|
|
38
45
|
pending: 'began' | 'ended' | null;
|
|
39
46
|
readonly seenTouchIds: Set<number>;
|
package/src/gestures/types.ts
CHANGED
|
@@ -21,6 +21,14 @@ export type GestureState =
|
|
|
21
21
|
/** What made the contact. A pen on an iPad is `pen`, not `touch`. */
|
|
22
22
|
export type PointerKind = 'touch' | 'mouse' | 'pen';
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Scroll input a recognizer can take, as UIKit's `UIScrollTypeMask`: a
|
|
26
|
+
* trackpad's two-finger scroll or pinch (`continuous`), or a mouse wheel's
|
|
27
|
+
* notches (`discrete`). A pan takes only a trackpad's; a pinch takes a
|
|
28
|
+
* ctrl-wheel of either.
|
|
29
|
+
*/
|
|
30
|
+
export type ScrollType = 'continuous' | 'discrete';
|
|
31
|
+
|
|
24
32
|
export interface Point {
|
|
25
33
|
readonly x: number;
|
|
26
34
|
readonly y: number;
|
|
@@ -85,6 +93,8 @@ export interface GestureTouch {
|
|
|
85
93
|
readonly target: unknown;
|
|
86
94
|
/** The native scrollers under the contact, when the host knows them. */
|
|
87
95
|
readonly scroll: NativeScrollProbe | null;
|
|
96
|
+
/** Set for a wheel's or a trackpad's scroll input, which is no contact. */
|
|
97
|
+
readonly scrollType?: ScrollType;
|
|
88
98
|
/** The browser took this contact for its own pan or zoom. */
|
|
89
99
|
takenByBrowser: boolean;
|
|
90
100
|
}
|
|
@@ -104,6 +114,8 @@ export interface TouchBeganInput extends TouchInput {
|
|
|
104
114
|
readonly pointerType: PointerKind;
|
|
105
115
|
readonly target: unknown;
|
|
106
116
|
readonly scroll?: NativeScrollProbe | null;
|
|
117
|
+
/** Set for a wheel's or a trackpad's scroll input. */
|
|
118
|
+
readonly scrollType?: ScrollType;
|
|
107
119
|
}
|
|
108
120
|
|
|
109
121
|
/** What a recognizer is told alongside the touches that changed. */
|
|
@@ -190,6 +202,12 @@ export interface TouchConsumer {
|
|
|
190
202
|
* undefined and keeps the browser's long-press selection.
|
|
191
203
|
*/
|
|
192
204
|
readonly selectionLock?: SelectionLock;
|
|
205
|
+
/**
|
|
206
|
+
* Whether this touch's press holds the browser's context menu closed now.
|
|
207
|
+
* A control whose hold acts uses it (a switch's hold toggles it). The host
|
|
208
|
+
* then cancels the menu, as for a claimed touch.
|
|
209
|
+
*/
|
|
210
|
+
holdsContextMenu?(touchId: number): boolean;
|
|
193
211
|
/**
|
|
194
212
|
* Whether the consumer takes touches now (a disabled control does not).
|
|
195
213
|
* The host listens only while something enabled is attached
|
|
@@ -132,6 +132,8 @@ export function usePressTracker(
|
|
|
132
132
|
recognizer,
|
|
133
133
|
),
|
|
134
134
|
isEnabled: () => optionsRef.current.isEnabled?.() ?? true,
|
|
135
|
+
holdsContextMenu: id =>
|
|
136
|
+
optionsRef.current.holdsContextMenu?.(id) === true,
|
|
135
137
|
// The caller's brake, or the host's: a touch that stops a flick
|
|
136
138
|
// presses nothing, as a UIKit control in a decelerating scroll view.
|
|
137
139
|
isBraking: touch => {
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file wheelSegmenter.ts
|
|
5
|
+
* @input A wheel stream's deltas along one axis, with their event times
|
|
6
|
+
* @output Exports WheelSegmenter and isBunched. WheelSegmenter splits the
|
|
7
|
+
* stream into gestures and reads a trackpad's lift (the onset of momentum).
|
|
8
|
+
* isBunched tells one stall's merge from a frame.
|
|
9
|
+
* @position Read by GestureHost.ts's wheel input: the web reports no
|
|
10
|
+
* trackpad contact or phase, so both are inferred.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type WheelBoundary = 'first' | 'idle' | 'reversal' | 'jump' | 'gap';
|
|
14
|
+
export type WheelPhase = 'finger' | 'momentum';
|
|
15
|
+
|
|
16
|
+
export interface WheelSegment {
|
|
17
|
+
/** Why this event starts a new gesture, or null when it continues one. */
|
|
18
|
+
readonly boundary: WheelBoundary | null;
|
|
19
|
+
readonly phase: WheelPhase;
|
|
20
|
+
/** The gesture's number, counting from 1. */
|
|
21
|
+
readonly gesture: number;
|
|
22
|
+
/** This event is where the fingers lifted (momentum begins with it). */
|
|
23
|
+
readonly liftNow: boolean;
|
|
24
|
+
/** This event shrank, so it may be momentum: hold it until known. */
|
|
25
|
+
readonly decaying: boolean;
|
|
26
|
+
/** This event came so soon after the one before that it is a merge. */
|
|
27
|
+
readonly bunched: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Momentum arrives at frame rate: a pause this long ends it. */
|
|
31
|
+
const MOMENTUM_GAP_MS = 50;
|
|
32
|
+
/**
|
|
33
|
+
* A wheel this close to the one before, ms, is one stall's merge. Frames come
|
|
34
|
+
* further apart at every display rate up to 500 Hz, which is 2 ms.
|
|
35
|
+
*/
|
|
36
|
+
const BUNCHED_MS = 1.5;
|
|
37
|
+
/** Shrinking deltas in a row before the lift is read. */
|
|
38
|
+
const LIFT_RUN = 4;
|
|
39
|
+
/** And how much of its size the run has lost by then. */
|
|
40
|
+
const LIFT_DECAY = 0.8;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether a wheel `intervalMs` after the one before is one stall's merge, not
|
|
44
|
+
* a frame. A stalled page receives the wheels it held back together, within
|
|
45
|
+
* 1.5 ms of each other. The rule reads no history, so slow wheels never make
|
|
46
|
+
* a real frame look like a merge.
|
|
47
|
+
*/
|
|
48
|
+
export function isBunched(intervalMs: number): boolean {
|
|
49
|
+
return intervalMs <= BUNCHED_MS;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A trackpad gesture has a finger phase and, after a fling, a momentum phase.
|
|
54
|
+
* In the finger phase, deltas come while the fingers move, with pauses when
|
|
55
|
+
* they move slowly. In the momentum phase, deltas come at frame rate and
|
|
56
|
+
* shrink smoothly. The lift is the start of a run of shrinking deltas that
|
|
57
|
+
* has lost a fifth of its size. In the finger phase, a pause of `idleMs`
|
|
58
|
+
* ends the gesture. In the momentum phase, a reversal, a jump up against the
|
|
59
|
+
* decay, or a pause of 50 ms starts a new gesture. A bunched wheel is never
|
|
60
|
+
* a jump. The decay reads it by its last step. Momentum never pauses,
|
|
61
|
+
* reverses or grows.
|
|
62
|
+
*/
|
|
63
|
+
export class WheelSegmenter {
|
|
64
|
+
gesture = 0;
|
|
65
|
+
phase: WheelPhase = 'finger';
|
|
66
|
+
private last = -Infinity;
|
|
67
|
+
private previous = 0;
|
|
68
|
+
private sign = 0;
|
|
69
|
+
private count = 0;
|
|
70
|
+
private run = 0;
|
|
71
|
+
private runStart = 0;
|
|
72
|
+
|
|
73
|
+
push(time: number, delta: number, idleMs: number): WheelSegment {
|
|
74
|
+
const size = Math.abs(delta);
|
|
75
|
+
const sign = Math.sign(delta);
|
|
76
|
+
const gap = time - this.last;
|
|
77
|
+
const bunched = this.count > 0 && isBunched(gap);
|
|
78
|
+
let boundary: WheelBoundary | null = null;
|
|
79
|
+
if (this.count === 0) {
|
|
80
|
+
boundary = this.gesture === 0 ? 'first' : 'idle';
|
|
81
|
+
} else if (this.phase === 'finger' && gap >= idleMs) {
|
|
82
|
+
boundary = 'idle';
|
|
83
|
+
} else if (this.phase === 'momentum') {
|
|
84
|
+
if (sign !== 0 && this.sign !== 0 && sign !== this.sign) {
|
|
85
|
+
boundary = 'reversal';
|
|
86
|
+
} else if (gap >= MOMENTUM_GAP_MS) {
|
|
87
|
+
boundary = 'gap';
|
|
88
|
+
} else if (!bunched && size >= 2 && size > this.previous * 1.3 + 0.5) {
|
|
89
|
+
boundary = 'jump';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// A bunched wheel carries the steps of the frames it merged. The decay
|
|
93
|
+
// goes on from its last step, so the steps after it read as they are.
|
|
94
|
+
const steps =
|
|
95
|
+
boundary == null && bunched && this.previous > 0
|
|
96
|
+
? Math.max(1, Math.round(size / this.previous))
|
|
97
|
+
: 1;
|
|
98
|
+
if (boundary != null) {
|
|
99
|
+
this.gesture += 1;
|
|
100
|
+
this.phase = 'finger';
|
|
101
|
+
this.count = 0;
|
|
102
|
+
this.run = 0;
|
|
103
|
+
}
|
|
104
|
+
let liftNow = false;
|
|
105
|
+
let decaying = false;
|
|
106
|
+
if (this.phase === 'finger' && this.count >= 3 && size > 0) {
|
|
107
|
+
// A bunched wheel is read by its last step, as the decay goes on from it.
|
|
108
|
+
const step = size / steps;
|
|
109
|
+
decaying = step <= this.previous * 0.995 && step >= this.previous * 0.5;
|
|
110
|
+
if (decaying) {
|
|
111
|
+
if (this.run === 0) {
|
|
112
|
+
this.runStart = this.previous;
|
|
113
|
+
}
|
|
114
|
+
this.run += 1;
|
|
115
|
+
} else {
|
|
116
|
+
this.run = 0;
|
|
117
|
+
}
|
|
118
|
+
if (this.run >= LIFT_RUN && step <= this.runStart * LIFT_DECAY) {
|
|
119
|
+
this.phase = 'momentum';
|
|
120
|
+
liftNow = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
this.count += 1;
|
|
124
|
+
this.previous = size / steps;
|
|
125
|
+
if (sign !== 0) {
|
|
126
|
+
this.sign = sign;
|
|
127
|
+
}
|
|
128
|
+
this.last = time;
|
|
129
|
+
return {
|
|
130
|
+
boundary,
|
|
131
|
+
phase: this.phase,
|
|
132
|
+
gesture: this.gesture,
|
|
133
|
+
liftNow,
|
|
134
|
+
decaying: decaying && !liftNow,
|
|
135
|
+
bunched: boundary == null && bunched,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The gesture ended on its own (its timer): the next event starts one. */
|
|
140
|
+
end(): void {
|
|
141
|
+
this.count = 0;
|
|
142
|
+
}
|
|
143
|
+
}
|