@astryxdesign/core 0.7.6 → 0.7.7-canary.0d1ddcd
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/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +16 -9
- 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/Drawer/Drawer.d.ts.map +1 -1
- package/dist/Drawer/Drawer.js +6 -0
- 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/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +1 -0
- 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/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +19 -9
- package/dist/astryx.css +33 -6
- package/dist/gestures/GestureArena.d.ts.map +1 -1
- package/dist/gestures/GestureArena.js +4 -1
- package/dist/gestures/GestureHost.d.ts +72 -2
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +643 -5
- 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/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 +11 -0
- package/dist/gestures/types.d.ts.map +1 -1
- package/dist/gestures/types.js +7 -0
- package/dist/gestures/wheelSegmenter.d.ts +45 -0
- package/dist/gestures/wheelSegmenter.d.ts.map +1 -0
- package/dist/gestures/wheelSegmenter.js +98 -0
- package/dist/motion/animator.d.ts.map +1 -1
- package/dist/motion/animator.js +140 -19
- package/dist/motion/paging.d.ts +58 -0
- package/dist/motion/paging.d.ts.map +1 -0
- package/dist/motion/paging.js +128 -0
- package/dist/motion/uikit/index.d.ts +2 -1
- package/dist/motion/uikit/index.d.ts.map +1 -1
- package/dist/motion/uikit/index.js +2 -1
- package/dist/motion/uikit/paging.d.ts +160 -0
- package/dist/motion/uikit/paging.d.ts.map +1 -0
- package/dist/motion/uikit/paging.js +162 -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 +46 -1
- package/dist/motion/uikit/springs.d.ts.map +1 -1
- package/dist/motion/uikit/springs.js +48 -4
- package/package.json +5 -5
- package/src/AppShell/AppShell.doc.mjs +10 -0
- package/src/AppShell/AppShell.tsx +48 -2
- package/src/BottomSheet/BottomSheetPanel.tsx +16 -9
- 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/Drawer/Drawer.tsx +6 -0
- package/src/DropdownMenu/useMenuOverflow.ts +44 -8
- package/src/Field/PanelSearchInput.tsx +6 -0
- package/src/Field/textEntryTraits.ts +6 -6
- package/src/Link/Link.tsx +3 -0
- 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/Typeahead/BaseTypeahead.tsx +16 -8
- package/src/gestures/GestureArena.ts +1 -0
- package/src/gestures/GestureHost.ts +765 -5
- package/src/gestures/GestureRecognizer.ts +28 -0
- package/src/gestures/PanGestureRecognizer.ts +8 -0
- package/src/gestures/PinchGestureRecognizer.ts +16 -1
- package/src/gestures/README.md +63 -0
- 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 +12 -0
- package/src/gestures/wheelSegmenter.ts +114 -0
- package/src/motion/MEASUREMENTS.md +8 -1
- package/src/motion/README.md +3 -2
- package/src/motion/animator.ts +193 -25
- package/src/motion/paging.ts +138 -0
- package/src/motion/uikit/index.ts +2 -1
- package/src/motion/uikit/paging.ts +162 -0
- package/src/motion/uikit/scroll.ts +2 -18
- package/src/motion/uikit/slider.ts +105 -0
- package/src/motion/uikit/springs.ts +37 -2
- 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;
|
package/src/gestures/README.md
CHANGED
|
@@ -452,6 +452,69 @@ anywhere (often on a plain row no recognizer is on) fails a pan limited to
|
|
|
452
452
|
one finger, so the two fingers zoom the page (WCAG 1.4.4) instead of the pan
|
|
453
453
|
claiming them midway.
|
|
454
454
|
|
|
455
|
+
## Wheel and trackpad
|
|
456
|
+
|
|
457
|
+
A recognizer takes scroll input only where it allows that scroll type
|
|
458
|
+
(`allowedScrollTypes`, UIKit's `allowedScrollTypesMask`): `continuous`
|
|
459
|
+
for a trackpad's two-finger scroll or pinch, `discrete` for a mouse wheel's
|
|
460
|
+
notches. A pan follows a trackpad, never a mouse wheel (`spec:AST-094` FR43,
|
|
461
|
+
DEC-24): it allows only `continuous`, and none by default, so it takes a
|
|
462
|
+
trackpad's scroll only where its owner allows it, its element then getting
|
|
463
|
+
the one wheel listener, and a mouse wheel's notches always scroll what is
|
|
464
|
+
under the pointer. A pinch allows both, for a ctrl-wheel from a trackpad or
|
|
465
|
+
a mouse. The screen-edge pan takes none, as it takes no mouse, trackpad or
|
|
466
|
+
pen (FR43). A control's thumb, a slider's or a switch's, tracks as a control,
|
|
467
|
+
not a pan, and takes no wheel, so a page scroll over it never changes its
|
|
468
|
+
value (DEC-23). The web reports no wheel phases or device, so the deltas
|
|
469
|
+
tell, and what they cannot tell apart reads as a mouse wheel: a line- or
|
|
470
|
+
page-mode delta; a scroll with Shift held (Shift+wheel); a pixel delta a hair
|
|
471
|
+
off a whole pixel (a mouse wheel on Windows); a scroll with no legacy `wheelDelta` (an engine that
|
|
472
|
+
sends none); a delta that is not a third of its legacy `wheelDelta`; or a 120
|
|
473
|
+
tick over a 40 px delta. A trackpad's precise deltas, a third of their legacy
|
|
474
|
+
`wheelDelta` (Chromium and WebKit), read as a trackpad.
|
|
475
|
+
|
|
476
|
+
A wheel gesture drives a pan as a contact moving with the content, along the
|
|
477
|
+
pan's own axis, and only once every scroller between the pointer and the
|
|
478
|
+
pan's element is at its edge that way, decided at the gesture's first wheel:
|
|
479
|
+
a wheel the pan does not take is never prevented. A watcher
|
|
480
|
+
(`claimsTouches: false`) never takes one, and a gesture whose first wheel
|
|
481
|
+
another handler already prevented, or that cannot be cancelled, stays the
|
|
482
|
+
page's. A scroll-edge pan (a pull to
|
|
483
|
+
refresh) takes one only if no scroller under the pointer could scroll for it,
|
|
484
|
+
its own scroll owner and the page included, as its finger's rule reads them
|
|
485
|
+
(`spec:AST-094` FR19); `touch-action` narrows only a finger's pan, so a wheel
|
|
486
|
+
reads every scroller past it. A ctrl-wheel (a trackpad
|
|
487
|
+
pinch, or Control and a mouse wheel) drives a pinch, whose scale follows
|
|
488
|
+
`exp(-deltaY / 100)`; a pinch takes it by default and ends 100 ms after its
|
|
489
|
+
last wheel. While the fingers move, a trackpad scroll ends 180 ms after its
|
|
490
|
+
last wheel vertically and 220 ms horizontally, whoever keeps it (a pan, a
|
|
491
|
+
scroller under the pointer, or the page), as a slow drag pauses between
|
|
492
|
+
wheels (DEC-24). A gesture's release is never stamped
|
|
493
|
+
later than an input that ends it sooner (FR16).
|
|
494
|
+
|
|
495
|
+
The web reports no trackpad contact or phase either, so one splitter reads
|
|
496
|
+
every trackpad stream (`wheelSegmenter.ts`, DEC-24): at the fourth or later
|
|
497
|
+
delta of a shrinking run, once the run has lost a fifth of its size, the
|
|
498
|
+
fingers lifted and momentum began. Until it decides,
|
|
499
|
+
the shrinking deltas wait, and a pan it follows moves on at the fingers' last
|
|
500
|
+
speed, never past what that speed projects. At the lift the pan keeps that
|
|
501
|
+
travel and lets go there, still moving at that speed, and the momentum after
|
|
502
|
+
it never moves it; when the fingers carry on instead, their moves absorb
|
|
503
|
+
what the pan showed past their travel, or short of it, so it never steps
|
|
504
|
+
back and ends on their travel. Fingers that stop release still, once the end
|
|
505
|
+
time passes. In momentum, a reversal, a delta that grows
|
|
506
|
+
against the decay, or a 50 ms pause starts a new gesture. A finger, mouse or
|
|
507
|
+
pen that comes down ends it, and a wheel claim keeps the page's text
|
|
508
|
+
selection. A gesture a
|
|
509
|
+
recognizer took is latched to it, as a browser latches a scroller: while it
|
|
510
|
+
lasts, the host hears every step first, through the shared input stream's
|
|
511
|
+
window-capture listener, wherever it lands, so a pan whose element moves from
|
|
512
|
+
under a still pointer keeps the gesture. A page that loses focus or hides
|
|
513
|
+
cancels a trackpad pan a recognizer still drives, as it cancels every touch;
|
|
514
|
+
the rest of that stream is the page's. A ctrl-wheel pinch keeps its stream,
|
|
515
|
+
so the page never zooms for it, and a pan its lift released keeps its
|
|
516
|
+
momentum latched until the stream ends.
|
|
517
|
+
|
|
455
518
|
## Time
|
|
456
519
|
|
|
457
520
|
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. */
|
|
@@ -0,0 +1,114 @@
|
|
|
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: splits the stream into gestures and reads
|
|
7
|
+
* a trackpad's lift (the onset of momentum)
|
|
8
|
+
* @position Read by GestureHost.ts's wheel input: the web reports no
|
|
9
|
+
* trackpad contact or phase, so both are inferred.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type WheelBoundary = 'first' | 'idle' | 'reversal' | 'jump' | 'gap';
|
|
13
|
+
export type WheelPhase = 'finger' | 'momentum';
|
|
14
|
+
|
|
15
|
+
export interface WheelSegment {
|
|
16
|
+
/** Why this event starts a new gesture, or null when it continues one. */
|
|
17
|
+
readonly boundary: WheelBoundary | null;
|
|
18
|
+
readonly phase: WheelPhase;
|
|
19
|
+
/** The gesture's number, counting from 1. */
|
|
20
|
+
readonly gesture: number;
|
|
21
|
+
/** This event is where the fingers lifted (momentum begins with it). */
|
|
22
|
+
readonly liftNow: boolean;
|
|
23
|
+
/** This event shrank, so it may be momentum: hold it until known. */
|
|
24
|
+
readonly decaying: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Momentum arrives at frame rate: a pause this long ends it. */
|
|
28
|
+
const MOMENTUM_GAP_MS = 50;
|
|
29
|
+
/** Shrinking deltas in a row before the lift is read. */
|
|
30
|
+
const LIFT_RUN = 4;
|
|
31
|
+
/** And how much of its size the run has lost by then. */
|
|
32
|
+
const LIFT_DECAY = 0.8;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A trackpad gesture has a finger phase and, after a fling, a momentum phase.
|
|
36
|
+
* In the finger phase, deltas come while the fingers move, with pauses when
|
|
37
|
+
* they move slowly. In the momentum phase, deltas come at frame rate and
|
|
38
|
+
* shrink smoothly. The lift is the start of a run of shrinking deltas that
|
|
39
|
+
* has lost a fifth of its size. In the finger phase, a pause of `idleMs`
|
|
40
|
+
* ends the gesture. In the momentum phase, a reversal, a jump up against the
|
|
41
|
+
* decay, or a pause of 50 ms starts a new gesture. Momentum never pauses,
|
|
42
|
+
* reverses or grows.
|
|
43
|
+
*/
|
|
44
|
+
export class WheelSegmenter {
|
|
45
|
+
gesture = 0;
|
|
46
|
+
phase: WheelPhase = 'finger';
|
|
47
|
+
private last = -Infinity;
|
|
48
|
+
private previous = 0;
|
|
49
|
+
private sign = 0;
|
|
50
|
+
private count = 0;
|
|
51
|
+
private run = 0;
|
|
52
|
+
private runStart = 0;
|
|
53
|
+
|
|
54
|
+
push(time: number, delta: number, idleMs: number): WheelSegment {
|
|
55
|
+
const size = Math.abs(delta);
|
|
56
|
+
const sign = Math.sign(delta);
|
|
57
|
+
const gap = time - this.last;
|
|
58
|
+
let boundary: WheelBoundary | null = null;
|
|
59
|
+
if (this.count === 0) {
|
|
60
|
+
boundary = this.gesture === 0 ? 'first' : 'idle';
|
|
61
|
+
} else if (this.phase === 'finger' && gap >= idleMs) {
|
|
62
|
+
boundary = 'idle';
|
|
63
|
+
} else if (this.phase === 'momentum') {
|
|
64
|
+
if (sign !== 0 && this.sign !== 0 && sign !== this.sign) {
|
|
65
|
+
boundary = 'reversal';
|
|
66
|
+
} else if (gap >= MOMENTUM_GAP_MS) {
|
|
67
|
+
boundary = 'gap';
|
|
68
|
+
} else if (size >= 2 && size > this.previous * 1.3 + 0.5) {
|
|
69
|
+
boundary = 'jump';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (boundary != null) {
|
|
73
|
+
this.gesture += 1;
|
|
74
|
+
this.phase = 'finger';
|
|
75
|
+
this.count = 0;
|
|
76
|
+
this.run = 0;
|
|
77
|
+
}
|
|
78
|
+
let liftNow = false;
|
|
79
|
+
let decaying = false;
|
|
80
|
+
if (this.phase === 'finger' && this.count >= 3 && size > 0) {
|
|
81
|
+
decaying = size <= this.previous * 0.995 && size >= this.previous * 0.5;
|
|
82
|
+
if (decaying) {
|
|
83
|
+
if (this.run === 0) {
|
|
84
|
+
this.runStart = this.previous;
|
|
85
|
+
}
|
|
86
|
+
this.run += 1;
|
|
87
|
+
} else {
|
|
88
|
+
this.run = 0;
|
|
89
|
+
}
|
|
90
|
+
if (this.run >= LIFT_RUN && size <= this.runStart * LIFT_DECAY) {
|
|
91
|
+
this.phase = 'momentum';
|
|
92
|
+
liftNow = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this.count += 1;
|
|
96
|
+
this.previous = size;
|
|
97
|
+
if (sign !== 0) {
|
|
98
|
+
this.sign = sign;
|
|
99
|
+
}
|
|
100
|
+
this.last = time;
|
|
101
|
+
return {
|
|
102
|
+
boundary,
|
|
103
|
+
phase: this.phase,
|
|
104
|
+
gesture: this.gesture,
|
|
105
|
+
liftNow,
|
|
106
|
+
decaying: decaying && !liftNow,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The gesture ended on its own (its timer): the next event starts one. */
|
|
111
|
+
end(): void {
|
|
112
|
+
this.count = 0;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -43,7 +43,7 @@ installed); rows marked † also on iOS 18 for the pre-glass delta. iPhone
|
|
|
43
43
|
| M-S2 | Momentum coast | A long list (Contacts, or a probe table of 500 rows): flicks released at 1000, 2500, 5000 pt/s | contentOffset(t) from release to rest; decay constant; per-millisecond vs per-frame model; the speed at which it stops | C | `scroll.decelerationRate` |
|
|
44
44
|
| M-S3 | Bounce-back spring | Same list: drag 120 pt past the top, hold still, release; a flick into the top edge at 3000 pt/s | contentOffset(t) after release and after edge contact; spring response and damping ratio; velocity hand-off | C | `scroll.bounce` |
|
|
45
45
|
| M-S5 | Rubber band | Same list on an 852 pt view: drag 100, 300, 600 pt past the top; a horizontal pager | displayed overflow against finger overflow; confirm c = 0.55 and d = the view's size on the axis | C | `scroll.rubberBand` |
|
|
46
|
-
| M-PG1 | Paging | Photos full-screen swipes; a probe paging scroll view | page chosen per release position × velocity (thresholds; at most one page?); contentOffset(t) onto the page | B, C | `
|
|
46
|
+
| M-PG1 | Paging | Photos full-screen swipes; a probe paging scroll view | page chosen per release position × velocity (thresholds; at most one page?); contentOffset(t) onto the page | B, C | `paging` |
|
|
47
47
|
| M-ST1 | Scroll to top | Settings scrolled down 500, 2000, 8000 pt; tap the status bar | contentOffset(t); duration against distance; curve or spring fit | B, C | `scroll.scrollToTopRate` |
|
|
48
48
|
| M-N1 | Navigation push | Settings: tap General | incoming leading edge x(t), outgoing x(t) (parallax fraction), dimming alpha(t); animation parameters | A, B | `springs.navigationPush` |
|
|
49
49
|
| M-N2 | Navigation pop | General: back button | same as M-N1 | A, B | `springs.navigationPop` |
|
|
@@ -72,6 +72,13 @@ initial velocity's sign flipped, a curve that overshoots. Push and pop are
|
|
|
72
72
|
not Core Animation animations on iOS 26.2: UIKit writes presentation
|
|
73
73
|
modifiers every frame from the main thread.
|
|
74
74
|
|
|
75
|
+
## Paging (UIScrollView `isPagingEnabled`, UIPageViewController)
|
|
76
|
+
|
|
77
|
+
| ID | What | App and gesture | Extract | Method | Feeds |
|
|
78
|
+
| ------ | --------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------ | ------------------------------ |
|
|
79
|
+
| M-PG12 | End of a settle | Same pager: releases at rest from 10 %, 49 %, 60 % and 90 % of a page, logged at 120 fps | remaining distance and step velocity at the frame the offset lands exactly on the page | C | `paging.integrator.restSpeed` |
|
|
80
|
+
| M-PG13 | Settle velocity floor | Same pager: steady releases from 200 to 250 pt/s in 5 pt/s steps, at 25 % of a page | the slowest release whose `scrollViewWillEndDragging` velocity is not 0 | C | `paging.release.velocityFloor` |
|
|
81
|
+
|
|
75
82
|
## Gestures (UIKit gesture recognizers)
|
|
76
83
|
|
|
77
84
|
Thresholds the gesture system reads (`uikit/gestures.ts`). Safari on iOS runs
|
package/src/motion/README.md
CHANGED
|
@@ -15,8 +15,9 @@ so each keeps its measured value; no shipped component reads the roles yet.
|
|
|
15
15
|
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
16
16
|
| `uikit/` | THE constants table: every spring, duration, deceleration rate, rubber-band constant and curve, with its source. |
|
|
17
17
|
| `uikit/springs.ts` | Interaction springs (navigation, sheets, alerts, menus, controls, keyboard, banners) and the SwiftUI / UIKit presets. |
|
|
18
|
-
| `uikit/scroll.ts` | UIScrollView: deceleration rates, rubber band, bounce-back spring,
|
|
18
|
+
| `uikit/scroll.ts` | UIScrollView: deceleration rates, rubber band, bounce-back spring, `delaysContentTouches`, scroll to top. |
|
|
19
19
|
| `uikit/timing.ts` | Non-spring clocks and curves: press highlight, Core Animation's named curves, keyboard, Reduced Motion cross-fade. |
|
|
20
|
+
| `uikit/paging.ts` | A paging UIScrollView: the integrator that settles a release on its page, the page rule, the catch, `setContentOffset(animated:)`. |
|
|
20
21
|
| `uikit/navigation.ts` | UINavigationController: push velocity, parallax, dimming, transition clock, interactive pop finish rule. |
|
|
21
22
|
| `uikit/gestures.ts` | Gesture recognizer thresholds, the velocity filter and haptic stand-ins, as `{value, unit}` entries (sources in JSDoc). |
|
|
22
23
|
| `roles.ts` | The internal motion roles: the springs, durations and curves `spec:AST-094` FR4 names, registered by the shared tables, and a role's CSS form read back. |
|
|
@@ -28,8 +29,8 @@ so each keeps its measured value; no shipped component reads the roles yet.
|
|
|
28
29
|
| `coast.ts` | A release's momentum curves for `animator.decay`: the coast, the edge spring, a release past an edge. |
|
|
29
30
|
| `rubberBand.ts` | UIScrollView's resistance past an edge, and its inverse. |
|
|
30
31
|
| `projection.ts` | Release targets: the rest point nearest the projected coast. |
|
|
32
|
+
| `paging.ts` | Paging: UIKit's paging settle as a playable curve, its velocity floor, the animated page scroll. |
|
|
31
33
|
| `resolveSnapPoint.ts` | Release policy: the snap point a release lands on (flick, back-out cancel, step limit), on `selectSnapPoint`. Internal. |
|
|
32
|
-
| `selectPage.ts` | Paging: at most one page per release, on `resolveSnapPoint`. |
|
|
33
34
|
| `bezier.ts` | Cubic-bézier timing functions (Core Animation's named curves). |
|
|
34
35
|
| `animator.ts` | Compositor animator: springs, coasts and any closed-form curve compiled to Web Animations, read back analytically. |
|
|
35
36
|
| `presentation.ts` | Where each moving element is drawn relative to where the engine hit-tests it, reported by the animator while translate runs; read by the gesture host at touch-down. |
|