@astryxdesign/core 0.7.6 → 0.7.7-canary.1d04d40
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/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 +335 -0
- package/dist/Layer/useTouchTrigger.d.ts +23 -1
- package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
- package/dist/Layer/useTouchTrigger.js +29 -2
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +1 -0
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +20 -9
- 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/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/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/astryx.css +40 -6
- 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 +791 -12
- 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 +5 -3
- package/dist/motion/uikit/index.d.ts.map +1 -1
- package/dist/motion/uikit/index.js +5 -3
- 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/progressBar.d.ts +59 -0
- package/dist/motion/uikit/progressBar.d.ts.map +1 -0
- package/dist/motion/uikit/progressBar.js +82 -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/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/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/Layer/layerClickBoundary.ts +2 -2
- package/src/Layer/usePressPin.ts +481 -0
- package/src/Layer/useTouchTrigger.ts +37 -1
- package/src/Link/Link.tsx +3 -0
- package/src/ProgressBar/ProgressBar.doc.mjs +3 -3
- package/src/ProgressBar/ProgressBar.tsx +82 -11
- package/src/ProgressBar/ProgressBarTouch.ts +109 -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/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/gestures/GestureArena.ts +45 -4
- package/src/gestures/GestureHost.ts +949 -12
- 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 +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 +12 -0
- package/src/gestures/wheelSegmenter.ts +114 -0
- package/src/motion/MEASUREMENTS.md +8 -1
- package/src/motion/README.md +6 -3
- package/src/motion/animator.ts +193 -25
- package/src/motion/paging.ts +138 -0
- package/src/motion/uikit/index.ts +5 -3
- package/src/motion/uikit/paging.ts +162 -0
- package/src/motion/uikit/progressBar.ts +82 -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/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
|
@@ -125,16 +125,17 @@ import { isRtlElement } from "../hooks/isRtlElement.js";
|
|
|
125
125
|
import { presentedElements, presentedOffsetOf, resolvePresented, watchPresentedElements } from "../motion/presentation.js";
|
|
126
126
|
import { UIKIT_PRESS, UIKIT_TAP } from "../motion/uikit/gestures.js";
|
|
127
127
|
import { browserClock, GestureArena } from "./GestureArena.js";
|
|
128
|
-
import { createNativeScrollProbe, parentAcrossShadow } from "./nativeScroll.js";
|
|
128
|
+
import { canElementScroll, createNativeScrollProbe, parentAcrossShadow } from "./nativeScroll.js";
|
|
129
129
|
import { containsAlongFlatTree } from "../utils/flatTree.js";
|
|
130
130
|
import { isIosWebKit } from "../utils/touchQueries.js";
|
|
131
|
+
import { WheelSegmenter } from "./wheelSegmenter.js";
|
|
131
132
|
import { gestureHostStyle, intersectTouchAction } from "./touchAction.js";
|
|
132
133
|
import { markTouchAction, TOUCH_ACTION_ATTRIBUTE, unmarkTouchAction } from "./touchActionRule.js";
|
|
133
134
|
import { internals } from "./recognizerInternals.js";
|
|
134
135
|
import { armClickGuard, disarmClickGuard, withoutClickGuard } from "../utils/clickGuard.js";
|
|
135
136
|
import { subscribeInput } from "../utils/inputStream.js";
|
|
136
137
|
import { isPrimaryClick } from "../utils/primaryClick.js";
|
|
137
|
-
import { locksSelectionFor } from "../utils/selectionLock.js";
|
|
138
|
+
import { lockDragSelection, locksSelectionFor, selectionRangesOf, startsOnSelectableText } from "../utils/selectionLock.js";
|
|
138
139
|
import { isCoarsePointer, watchCoarsePointer } from "../utils/coarsePointer.js";
|
|
139
140
|
import { gestureHosts } from "./hostRegistry.js";
|
|
140
141
|
const STYLE_PROPERTIES = {
|
|
@@ -182,6 +183,16 @@ function claimsWhileMoving(recognizer) {
|
|
|
182
183
|
return recognizer.claimsTouches && internals(recognizer).acceptsTouchInput && internals(recognizer).family !== 'tap';
|
|
183
184
|
}
|
|
184
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Whether a recognizer takes a contact that comes with Touch Events and a
|
|
188
|
+
* long press's contextmenu: a finger's or a pen's. A scroll-only recognizer
|
|
189
|
+
* (`allowedPointerTypes: []`) and a mouse-only one take neither.
|
|
190
|
+
*/
|
|
191
|
+
function takesTouchContacts(recognizer) {
|
|
192
|
+
const types = recognizer.allowedPointerTypes;
|
|
193
|
+
return types == null || types.has('touch') || types.has('pen');
|
|
194
|
+
}
|
|
195
|
+
|
|
185
196
|
/**
|
|
186
197
|
* A pan (or a pan subclass), told apart by its family rather than its class,
|
|
187
198
|
* so a page that constructs no pan never ships PanGestureRecognizer.
|
|
@@ -232,6 +243,138 @@ function landedOn(event, retargeted = event.target) {
|
|
|
232
243
|
return event.composedPath()[0] ?? retargeted;
|
|
233
244
|
}
|
|
234
245
|
|
|
246
|
+
/**
|
|
247
|
+
* How long after its last event a pinch or a mouse wheel's notches end, ms.
|
|
248
|
+
* The web reports no wheel phases, so a quiet wheel is a lifted one.
|
|
249
|
+
*/
|
|
250
|
+
const WHEEL_END_MS = 100;
|
|
251
|
+
/**
|
|
252
|
+
* How long after its last event a vertical trackpad scroll ends, ms (a
|
|
253
|
+
* sheet's pull), whoever keeps it. A slow drag's stream pauses longer than
|
|
254
|
+
* WHEEL_END_MS between events.
|
|
255
|
+
*/
|
|
256
|
+
const WHEEL_FOLLOW_END_V_MS = 180;
|
|
257
|
+
/**
|
|
258
|
+
* The same horizontally, ms (a pager's or a row's swipe), whose slow
|
|
259
|
+
* swipes pause longer still.
|
|
260
|
+
*/
|
|
261
|
+
const WHEEL_FOLLOW_END_MS = 220;
|
|
262
|
+
/** A line-mode wheel's line, px, as the platforms scroll it. */
|
|
263
|
+
const WHEEL_LINE_PX = 40;
|
|
264
|
+
/** The span a ctrl-wheel pinch's two contacts start at, px. */
|
|
265
|
+
const WHEEL_PINCH_SPAN = 100;
|
|
266
|
+
|
|
267
|
+
/** A wheel gesture: the contacts it drives in the arena, or none where the page keeps it. */
|
|
268
|
+
|
|
269
|
+
/** A trackpad scroll a pan follows: where its fingers lift, and what waits until then. */
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* How long after its last wheel a wheel gesture ends, ms. A trackpad's
|
|
273
|
+
* two-finger scroll waits out a slow drag's pauses for its axis, whoever
|
|
274
|
+
* keeps the stream. A pinch and a mouse wheel's notches end 100 ms after
|
|
275
|
+
* their last wheel.
|
|
276
|
+
*/
|
|
277
|
+
function wheelEndMs(kind, scrollType, isHorizontal) {
|
|
278
|
+
if (kind === 'pinch' || scrollType !== 'continuous') {
|
|
279
|
+
return WHEEL_END_MS;
|
|
280
|
+
}
|
|
281
|
+
return isHorizontal ? WHEEL_FOLLOW_END_MS : WHEEL_FOLLOW_END_V_MS;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* A provisional lead `debt` after a move of `moved`. The debt is what a
|
|
286
|
+
* followed pan shows past the fingers' travel, or short of it. Half the move
|
|
287
|
+
* is absorbed, so the pan keeps moving the way the fingers do.
|
|
288
|
+
*/
|
|
289
|
+
function absorbedDebt(debt, moved) {
|
|
290
|
+
return Math.sign(debt) * Math.max(0, Math.abs(debt) - Math.abs(moved) / 2);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* `target` where it is ahead of `shown` in the direction of `speed`, else
|
|
295
|
+
* `shown`: a reconcile that never steps back.
|
|
296
|
+
*/
|
|
297
|
+
function ahead(shown, target, speed) {
|
|
298
|
+
return speed === 0 || (target - shown) * Math.sign(speed) > 0 ? target : shown;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* A wheel's deltas in CSS px: a line is `WHEEL_LINE_PX`, a page the
|
|
303
|
+
* viewport.
|
|
304
|
+
*/
|
|
305
|
+
function wheelPixels(wheel, view) {
|
|
306
|
+
if (wheel.deltaMode === 1) {
|
|
307
|
+
return {
|
|
308
|
+
dx: wheel.deltaX * WHEEL_LINE_PX,
|
|
309
|
+
dy: wheel.deltaY * WHEEL_LINE_PX
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (wheel.deltaMode === 2) {
|
|
313
|
+
return {
|
|
314
|
+
dx: wheel.deltaX * (view?.innerWidth ?? 0),
|
|
315
|
+
dy: wheel.deltaY * (view?.innerHeight ?? 0)
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
dx: wheel.deltaX,
|
|
320
|
+
dy: wheel.deltaY
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* The web's best reading of what scrolled: a mouse wheel's notches
|
|
326
|
+
* (`discrete`) or a trackpad's scroll (`continuous`). Input it cannot tell
|
|
327
|
+
* apart counts as a mouse wheel's, which a pan that takes only a trackpad's
|
|
328
|
+
* scroll leaves to the page. These read as a mouse wheel's:
|
|
329
|
+
* - a line- or page-mode delta;
|
|
330
|
+
* - a scroll with Shift held (Shift+wheel);
|
|
331
|
+
* - a pixel delta a hair off a whole pixel (a mouse wheel on Windows);
|
|
332
|
+
* - a scroll with no legacy `wheelDelta` (an engine that sends none);
|
|
333
|
+
* - a delta that is not a third of its legacy `wheelDelta`;
|
|
334
|
+
* - a 120 tick over a 40 px delta.
|
|
335
|
+
* A trackpad's precise deltas, a third of their legacy `wheelDelta` in
|
|
336
|
+
* Chromium and WebKit, read as a trackpad's.
|
|
337
|
+
*/
|
|
338
|
+
function scrollTypeOf(wheel) {
|
|
339
|
+
if (wheel.deltaMode !== 0 || wheel.shiftKey) {
|
|
340
|
+
return 'discrete';
|
|
341
|
+
}
|
|
342
|
+
const remainder = value => {
|
|
343
|
+
const fraction = Math.abs(value - Math.round(value));
|
|
344
|
+
return fraction > 0 && fraction < 0.01;
|
|
345
|
+
};
|
|
346
|
+
if (remainder(wheel.deltaX) || remainder(wheel.deltaY)) {
|
|
347
|
+
return 'discrete';
|
|
348
|
+
}
|
|
349
|
+
const legacy = wheel;
|
|
350
|
+
if (typeof legacy.wheelDeltaY !== 'number' || typeof legacy.wheelDeltaX !== 'number') {
|
|
351
|
+
return 'discrete';
|
|
352
|
+
}
|
|
353
|
+
const precise = (delta, legacyDelta) => delta === 0 ? legacyDelta === 0 : Math.abs(Math.abs(legacyDelta) - 3 * Math.abs(delta)) <= Math.max(1.5, 0.05 * Math.abs(delta) * 3);
|
|
354
|
+
if (!precise(wheel.deltaY, legacy.wheelDeltaY) || !precise(wheel.deltaX, legacy.wheelDeltaX)) {
|
|
355
|
+
return 'discrete';
|
|
356
|
+
}
|
|
357
|
+
const tick = (delta, legacyDelta) => legacyDelta !== 0 && legacyDelta % 120 === 0 && delta % 40 === 0;
|
|
358
|
+
return tick(wheel.deltaY, legacy.wheelDeltaY) || tick(wheel.deltaX, legacy.wheelDeltaX) ? 'discrete' : 'continuous';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Read without the pan class, which a page with no pan never ships. */
|
|
362
|
+
function isScrollEdgePan(recognizer) {
|
|
363
|
+
return recognizer.scrollEdge === true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Whether a pan takes a contact moving by (cx, cy), by its own direction
|
|
368
|
+
* rule, `axisRatio` included. A move its rule leaves undecided stays the
|
|
369
|
+
* page's.
|
|
370
|
+
*/
|
|
371
|
+
function panTakesMove(recognizer, cx, cy) {
|
|
372
|
+
return (internals(recognizer).classify?.({
|
|
373
|
+
x: cx,
|
|
374
|
+
y: cy
|
|
375
|
+
}) ?? 'begin') === 'begin';
|
|
376
|
+
}
|
|
377
|
+
|
|
235
378
|
/**
|
|
236
379
|
* An event's time on the arena's clock (`performance.now()`). Some engines
|
|
237
380
|
* and DOM emulators stamp events in epoch milliseconds instead.
|
|
@@ -261,6 +404,23 @@ export class GestureHost {
|
|
|
261
404
|
cancelCallerRead = null;
|
|
262
405
|
/** The selection lock each contact holds, by host contact id. */
|
|
263
406
|
lockedBy = new Map();
|
|
407
|
+
/**
|
|
408
|
+
* The page's selection when each mouse or pen press began: a drag a
|
|
409
|
+
* recognizer claims keeps it.
|
|
410
|
+
*/
|
|
411
|
+
pressSelections = new Map();
|
|
412
|
+
/**
|
|
413
|
+
* Mouse and pen contacts that began on selectable text. Once one moves, the
|
|
414
|
+
* browser keeps the drag for a selection, and no recognizer claims it.
|
|
415
|
+
*/
|
|
416
|
+
selectingContacts = new Set();
|
|
417
|
+
/**
|
|
418
|
+
* Drag selection holds until their last task ends, a task after their lift
|
|
419
|
+
* (lockDragSelection); the last detach ends them at once.
|
|
420
|
+
*/
|
|
421
|
+
dragHolds = new Set();
|
|
422
|
+
/** Each claimed contact's drag hold, until its lift or a give-back. */
|
|
423
|
+
claimHolds = new Map();
|
|
264
424
|
/**
|
|
265
425
|
* Contacts sent to what is drawn instead of what the engine hit, by host
|
|
266
426
|
* contact id, with where they landed (for their click).
|
|
@@ -365,6 +525,16 @@ export class GestureHost {
|
|
|
365
525
|
*/
|
|
366
526
|
fingerTargets = [];
|
|
367
527
|
nextId = 1;
|
|
528
|
+
/** The wheel gesture in progress. */
|
|
529
|
+
wheel = null;
|
|
530
|
+
/** The wheel latch's input-stream subscription, while a gesture is latched. */
|
|
531
|
+
stopWheelLatch = null;
|
|
532
|
+
/** Splits the wheel stream into gestures and reads a trackpad's lift. */
|
|
533
|
+
segmenter = new WheelSegmenter();
|
|
534
|
+
/** The latest wheel's segment. */
|
|
535
|
+
wheelSegment = null;
|
|
536
|
+
/** Wheels already taken: each element on the path with a listener hears one. */
|
|
537
|
+
wheelsSeen = new WeakSet();
|
|
368
538
|
isInstalled = false;
|
|
369
539
|
/**
|
|
370
540
|
* Whether the primary pointer is coarse (`spec:AST-094` FR19), read when
|
|
@@ -428,7 +598,10 @@ export class GestureHost {
|
|
|
428
598
|
// A press whose decision waited on a timer (a tap waiting on a double
|
|
429
599
|
// tap) ends here: an armed press stops listening (FR24).
|
|
430
600
|
onIdle: () => this.releaseArmedPress(),
|
|
431
|
-
onTransition: options.onTransition
|
|
601
|
+
onTransition: options.onTransition,
|
|
602
|
+
onClaim: (ids, claimer) => this.lockClaimedDrags(ids, claimer),
|
|
603
|
+
onRelease: ids => this.releaseClaimedDrags(ids),
|
|
604
|
+
refusesClaim: touch => this.selectingContacts.has(touch.id) && (touch.x !== touch.startX || touch.y !== touch.startY)
|
|
432
605
|
});
|
|
433
606
|
const view = doc.defaultView;
|
|
434
607
|
// A test may pass its own read; otherwise the one coarse-pointer gate,
|
|
@@ -485,6 +658,12 @@ export class GestureHost {
|
|
|
485
658
|
if (recognizer.state !== 'possible' || recognizer.isInvolved()) {
|
|
486
659
|
recognizer.cancel();
|
|
487
660
|
}
|
|
661
|
+
const wheel = this.wheel;
|
|
662
|
+
if (wheel != null && wheel.takers.includes(recognizer) && !this.wheelHasTaker(wheel)) {
|
|
663
|
+
// At its last wheel: a later stamp would move the arena's clock past
|
|
664
|
+
// the next input's own time.
|
|
665
|
+
this.endWheel(wheel.last);
|
|
666
|
+
}
|
|
488
667
|
this.refresh(element, registration);
|
|
489
668
|
// Even when the element keeps other attachments: one left disabled
|
|
490
669
|
// keeps no listener or observer (FR24).
|
|
@@ -847,6 +1026,7 @@ export class GestureHost {
|
|
|
847
1026
|
consumer: null,
|
|
848
1027
|
hasTouchEndListener: false,
|
|
849
1028
|
hasTouchMoveListener: false,
|
|
1029
|
+
hasWheelListener: false,
|
|
850
1030
|
savedStyle: new Map(),
|
|
851
1031
|
authorTouchAction: null,
|
|
852
1032
|
manageStyle: true,
|
|
@@ -911,6 +1091,23 @@ export class GestureHost {
|
|
|
911
1091
|
element.removeEventListener('touchmove', this.onBlockingTouchMove);
|
|
912
1092
|
registration.hasTouchMoveListener = false;
|
|
913
1093
|
}
|
|
1094
|
+
// Wheel and trackpad input, only where an enabled recognizer takes it. A
|
|
1095
|
+
// non-passive wheel listener makes the browser's scrolling over the
|
|
1096
|
+
// element wait for the main thread.
|
|
1097
|
+
const needsWheel = registration.recognizers.some(recognizer => recognizer.isEnabled &&
|
|
1098
|
+
// A watcher never takes the wheel: the page keeps it.
|
|
1099
|
+
recognizer.claimsTouches && recognizer.allowedScrollTypes.size > 0 && (
|
|
1100
|
+
// A wheel drives a pan or a pinch only.
|
|
1101
|
+
internals(recognizer).family === 'pan' || internals(recognizer).family === 'pinch'));
|
|
1102
|
+
if (needsWheel && !registration.hasWheelListener) {
|
|
1103
|
+
element.addEventListener('wheel', this.onWheel, {
|
|
1104
|
+
passive: false
|
|
1105
|
+
});
|
|
1106
|
+
registration.hasWheelListener = true;
|
|
1107
|
+
} else if (!needsWheel && registration.hasWheelListener) {
|
|
1108
|
+
element.removeEventListener('wheel', this.onWheel);
|
|
1109
|
+
registration.hasWheelListener = false;
|
|
1110
|
+
}
|
|
914
1111
|
}
|
|
915
1112
|
applyStyle(element, registration) {
|
|
916
1113
|
// A disabled recognizer leaves the browser everything (FR24).
|
|
@@ -1050,6 +1247,9 @@ export class GestureHost {
|
|
|
1050
1247
|
if (registration.hasTouchMoveListener) {
|
|
1051
1248
|
element.removeEventListener('touchmove', this.onBlockingTouchMove);
|
|
1052
1249
|
}
|
|
1250
|
+
if (registration.hasWheelListener) {
|
|
1251
|
+
element.removeEventListener('wheel', this.onWheel);
|
|
1252
|
+
}
|
|
1053
1253
|
this.registry.delete(element);
|
|
1054
1254
|
this.uninstallIfIdle();
|
|
1055
1255
|
}
|
|
@@ -1138,7 +1338,9 @@ export class GestureHost {
|
|
|
1138
1338
|
if (!this.isCoarse) {
|
|
1139
1339
|
for (const [element, registration] of this.registry) {
|
|
1140
1340
|
const consumer = registration.consumer;
|
|
1141
|
-
if (registration.recognizers.some(recognizer => recognizer.isEnabled
|
|
1341
|
+
if (registration.recognizers.some(recognizer => recognizer.isEnabled && (
|
|
1342
|
+
// One that takes only scroll input has no press to arm.
|
|
1343
|
+
recognizer.allowedPointerTypes == null || recognizer.allowedPointerTypes.size > 0)) || consumer != null && consumer.isEnabled?.() !== false) {
|
|
1142
1344
|
wanted.add(element);
|
|
1143
1345
|
}
|
|
1144
1346
|
}
|
|
@@ -1240,6 +1442,15 @@ export class GestureHost {
|
|
|
1240
1442
|
}
|
|
1241
1443
|
this.ownScrollFrames.clear();
|
|
1242
1444
|
this.ownedScrolls.clear();
|
|
1445
|
+
// A drag's hold whose press lifted in this task ends now, not a task
|
|
1446
|
+
// later: nothing is attached to meet its mouseup or click.
|
|
1447
|
+
for (const release of [...this.dragHolds]) {
|
|
1448
|
+
release({
|
|
1449
|
+
isCancel: true
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
this.pressSelections.clear();
|
|
1453
|
+
this.selectingContacts.clear();
|
|
1243
1454
|
});
|
|
1244
1455
|
}
|
|
1245
1456
|
|
|
@@ -1273,13 +1484,14 @@ export class GestureHost {
|
|
|
1273
1484
|
|
|
1274
1485
|
/**
|
|
1275
1486
|
* Whether an enabled participant could claim a touch or cancel a lift: a
|
|
1276
|
-
* consumer, or a recognizer that claims touches
|
|
1277
|
-
*
|
|
1487
|
+
* consumer, or a recognizer that claims touches and takes a finger or a
|
|
1488
|
+
* pen. With only watchers, or only scroll-only recognizers
|
|
1489
|
+
* (`allowedPointerTypes: []`), the host's listeners are all passive.
|
|
1278
1490
|
*/
|
|
1279
1491
|
hasClaimer() {
|
|
1280
1492
|
for (const registration of this.registry.values()) {
|
|
1281
1493
|
const consumer = registration.consumer;
|
|
1282
|
-
if (consumer != null && consumer.isEnabled?.() !== false || registration.recognizers.some(recognizer => recognizer.isEnabled && recognizer.claimsTouches)) {
|
|
1494
|
+
if (consumer != null && consumer.isEnabled?.() !== false || registration.recognizers.some(recognizer => recognizer.isEnabled && recognizer.claimsTouches && takesTouchContacts(recognizer))) {
|
|
1283
1495
|
return true;
|
|
1284
1496
|
}
|
|
1285
1497
|
}
|
|
@@ -1498,10 +1710,564 @@ export class GestureHost {
|
|
|
1498
1710
|
}
|
|
1499
1711
|
}
|
|
1500
1712
|
|
|
1501
|
-
/**
|
|
1502
|
-
|
|
1503
|
-
|
|
1713
|
+
/**
|
|
1714
|
+
* Wheel and trackpad input. A wheel gesture drives the recognizers that
|
|
1715
|
+
* allow its scroll type, as a contact that moves with the content. A pan
|
|
1716
|
+
* takes it along its own axis, once no scroller between the pointer and
|
|
1717
|
+
* the pan's element can scroll that way. The gesture's first wheel decides
|
|
1718
|
+
* this. A pinch takes a ctrl-wheel, as a trackpad pinches, as two contacts
|
|
1719
|
+
* whose span follows exp(-deltaY / 100). A wheel the page keeps is never
|
|
1720
|
+
* prevented, and a wheel a recognizer takes always is. A gesture ends its
|
|
1721
|
+
* end window after its last wheel (wheelEndMs), at that wheel's time, so
|
|
1722
|
+
* its release keeps its velocity. A gesture a recognizer took is latched
|
|
1723
|
+
* to it, as a browser latches a scroller. While the gesture lasts, the
|
|
1724
|
+
* shared input stream hands this every wheel first, wherever it lands. So
|
|
1725
|
+
* a pan whose element moves from under a still pointer keeps the gesture.
|
|
1726
|
+
* The page listener exists only during a gesture.
|
|
1727
|
+
*/
|
|
1728
|
+
onWheel = event => {
|
|
1729
|
+
const wheel = event;
|
|
1730
|
+
if (this.wheelsSeen.has(wheel)) {
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
this.wheelsSeen.add(wheel);
|
|
1734
|
+
const time = eventTime(wheel);
|
|
1735
|
+
const kind = wheel.ctrlKey ? 'pinch' : 'pan';
|
|
1736
|
+
let gesture = this.wheel;
|
|
1737
|
+
if (gesture != null && (gesture.kind !== kind || time - gesture.last >= gesture.endMs)) {
|
|
1738
|
+
this.endWheel(time);
|
|
1739
|
+
gesture = null;
|
|
1740
|
+
}
|
|
1741
|
+
if (gesture != null && gesture.ids.length > 0 && gesture.follow?.lifted !== true && !this.wheelHasTaker(gesture)) {
|
|
1742
|
+
// Every recognizer that took it has left (detached, disabled or
|
|
1743
|
+
// cancelled), so the rest of the gesture is the page's. A followed
|
|
1744
|
+
// pan's own lift does not count. Its momentum stays latched and
|
|
1745
|
+
// prevented until the splitter's next gesture or the end timer, and
|
|
1746
|
+
// moves nothing. The release is stamped at its last wheel.
|
|
1747
|
+
this.endWheel(gesture.last);
|
|
1748
|
+
const left = wheelPixels(wheel, this.doc.defaultView);
|
|
1749
|
+
gesture = {
|
|
1750
|
+
kind,
|
|
1751
|
+
scrollType: scrollTypeOf(wheel),
|
|
1752
|
+
ids: [],
|
|
1753
|
+
x: wheel.clientX,
|
|
1754
|
+
y: wheel.clientY,
|
|
1755
|
+
span: WHEEL_PINCH_SPAN,
|
|
1756
|
+
last: time,
|
|
1757
|
+
endMs: wheelEndMs(kind, scrollTypeOf(wheel), Math.abs(left.dx) > Math.abs(left.dy)),
|
|
1758
|
+
timer: null,
|
|
1759
|
+
takers: []
|
|
1760
|
+
};
|
|
1761
|
+
this.wheel = gesture;
|
|
1762
|
+
}
|
|
1763
|
+
const {
|
|
1764
|
+
dx,
|
|
1765
|
+
dy
|
|
1766
|
+
} = wheelPixels(wheel, this.doc.defaultView);
|
|
1767
|
+
const horizontal = Math.abs(dx) > Math.abs(dy);
|
|
1768
|
+
// The splitter reads a pan's stream only. A ctrl-wheel pinch has no
|
|
1769
|
+
// fingers' phase or momentum to tell apart, and ends 100 ms after its
|
|
1770
|
+
// last wheel.
|
|
1771
|
+
let seg = null;
|
|
1772
|
+
if (kind === 'pan') {
|
|
1773
|
+
seg = this.segmenter.push(time, horizontal ? dx : dy, horizontal ? WHEEL_FOLLOW_END_MS : WHEEL_FOLLOW_END_V_MS);
|
|
1774
|
+
} else {
|
|
1775
|
+
this.segmenter.end();
|
|
1776
|
+
}
|
|
1777
|
+
this.wheelSegment = seg;
|
|
1778
|
+
if (seg?.boundary != null) {
|
|
1779
|
+
if (gesture != null) {
|
|
1780
|
+
this.endWheel(time);
|
|
1781
|
+
gesture = null;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
if (gesture == null) {
|
|
1785
|
+
gesture = this.beginWheel(wheel, kind, dx, dy, time);
|
|
1786
|
+
this.wheel = gesture;
|
|
1787
|
+
if (gesture.ids.length > 0) {
|
|
1788
|
+
this.latchWheel();
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
gesture.last = time;
|
|
1792
|
+
if (gesture.timer != null) {
|
|
1793
|
+
this.clock.clearTimeout(gesture.timer);
|
|
1794
|
+
}
|
|
1795
|
+
// A trackpad scroll waits out a slow drag's pauses, whoever
|
|
1796
|
+
// keeps it; a pinch or a mouse wheel's notches 100 ms.
|
|
1797
|
+
gesture.timer = this.clock.setTimeout(() => {
|
|
1798
|
+
this.segmenter.end();
|
|
1799
|
+
this.endWheel();
|
|
1800
|
+
}, gesture.endMs);
|
|
1801
|
+
if (gesture.ids.length === 0) {
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
wheel.preventDefault();
|
|
1805
|
+
if (gesture.kind === 'pan' && gesture.follow) {
|
|
1806
|
+
const f = gesture.follow;
|
|
1807
|
+
if (f.lifted) {
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
if (this.wheelSegment?.liftNow === true) {
|
|
1811
|
+
// Momentum began: the fingers lifted before the held deltas. The pan
|
|
1812
|
+
// keeps the travel it showed through the undecided stretch. It lets
|
|
1813
|
+
// go here, still moving at the fingers' speed, so its release carries
|
|
1814
|
+
// on from it. The rest of the momentum never moves the pan.
|
|
1815
|
+
this.followProvisionally(gesture, f, time);
|
|
1816
|
+
f.lifted = true;
|
|
1817
|
+
this.arena.ended([{
|
|
1818
|
+
id: gesture.ids[0],
|
|
1819
|
+
x: gesture.x,
|
|
1820
|
+
y: gesture.y,
|
|
1821
|
+
time
|
|
1822
|
+
}]);
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
if (this.wheelSegment?.decaying === true) {
|
|
1826
|
+
// Undecided: the real deltas wait for the splitter while the
|
|
1827
|
+
// fingers' last speed carries the pan on.
|
|
1828
|
+
f.heldX += dx;
|
|
1829
|
+
f.heldY += dy;
|
|
1830
|
+
this.followProvisionally(gesture, f, time);
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
// The fingers' own delta; the held ones were theirs too. Their moves
|
|
1834
|
+
// absorb what the pan shows ahead of their travel, or behind it, half a
|
|
1835
|
+
// move at a time. So the pan never steps back, and ends on their travel.
|
|
1836
|
+
const beforeX = f.trueX - f.heldX;
|
|
1837
|
+
const beforeY = f.trueY - f.heldY;
|
|
1838
|
+
const debtX = absorbedDebt(gesture.x - beforeX, dx);
|
|
1839
|
+
const debtY = absorbedDebt(gesture.y - beforeY, dy);
|
|
1840
|
+
const trueX = beforeX - dx;
|
|
1841
|
+
const trueY = beforeY - dy;
|
|
1842
|
+
const elapsed = time - f.lastMove;
|
|
1843
|
+
if (elapsed > 0) {
|
|
1844
|
+
f.speedX = (trueX - f.trueX) / elapsed;
|
|
1845
|
+
f.speedY = (trueY - f.trueY) / elapsed;
|
|
1846
|
+
}
|
|
1847
|
+
f.trueX = trueX;
|
|
1848
|
+
f.trueY = trueY;
|
|
1849
|
+
f.heldX = 0;
|
|
1850
|
+
f.heldY = 0;
|
|
1851
|
+
f.lastMove = time;
|
|
1852
|
+
gesture.x = trueX + debtX;
|
|
1853
|
+
gesture.y = trueY + debtY;
|
|
1854
|
+
f.baseX = gesture.x;
|
|
1855
|
+
f.baseY = gesture.y;
|
|
1856
|
+
this.arena.moved([{
|
|
1857
|
+
id: gesture.ids[0],
|
|
1858
|
+
x: gesture.x,
|
|
1859
|
+
y: gesture.y,
|
|
1860
|
+
time
|
|
1861
|
+
}]);
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
if (gesture.kind === 'pan') {
|
|
1865
|
+
gesture.x -= dx;
|
|
1866
|
+
gesture.y -= dy;
|
|
1867
|
+
this.arena.moved([{
|
|
1868
|
+
id: gesture.ids[0],
|
|
1869
|
+
x: gesture.x,
|
|
1870
|
+
y: gesture.y,
|
|
1871
|
+
time
|
|
1872
|
+
}]);
|
|
1873
|
+
} else {
|
|
1874
|
+
gesture.span *= Math.exp(-dy / 100);
|
|
1875
|
+
this.arena.moved(this.pinchContacts(gesture, time));
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
|
|
1879
|
+
/**
|
|
1880
|
+
* Move a followed pan through the splitter's undecided stretch. It moves on
|
|
1881
|
+
* from where it was shown at the fingers' last move, at their last speed.
|
|
1882
|
+
* It never passes what that speed projects for the time since.
|
|
1883
|
+
*/
|
|
1884
|
+
followProvisionally(gesture, f, time) {
|
|
1885
|
+
const elapsed = Math.max(0, time - f.lastMove);
|
|
1886
|
+
const x = f.baseX + f.speedX * elapsed;
|
|
1887
|
+
const y = f.baseY + f.speedY * elapsed;
|
|
1888
|
+
if (x === gesture.x && y === gesture.y) {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
gesture.x = x;
|
|
1892
|
+
gesture.y = y;
|
|
1893
|
+
this.arena.moved([{
|
|
1894
|
+
id: gesture.ids[0],
|
|
1895
|
+
x,
|
|
1896
|
+
y,
|
|
1897
|
+
time
|
|
1898
|
+
}]);
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
/** Start a wheel gesture: who takes it, decided once, or nobody. */
|
|
1902
|
+
beginWheel(wheel, kind, dx, dy, time) {
|
|
1903
|
+
const type = scrollTypeOf(wheel);
|
|
1904
|
+
const isHorizontal = Math.abs(dx) > Math.abs(dy);
|
|
1905
|
+
const kept = {
|
|
1906
|
+
kind,
|
|
1907
|
+
scrollType: type,
|
|
1908
|
+
ids: [],
|
|
1909
|
+
x: wheel.clientX,
|
|
1910
|
+
y: wheel.clientY,
|
|
1911
|
+
span: WHEEL_PINCH_SPAN,
|
|
1912
|
+
last: time,
|
|
1913
|
+
endMs: wheelEndMs(kind, type, isHorizontal),
|
|
1914
|
+
timer: null,
|
|
1915
|
+
takers: []
|
|
1916
|
+
};
|
|
1917
|
+
// A contact already down keeps its gesture; the wheel stays the page's.
|
|
1918
|
+
// So does a first wheel another handler already prevented, or one that
|
|
1919
|
+
// cannot be cancelled: no recognizer could take it from the page.
|
|
1920
|
+
if (this.arena.touches.length > 0 || dx === 0 && dy === 0 || wheel.defaultPrevented || !wheel.cancelable) {
|
|
1921
|
+
return kept;
|
|
1922
|
+
}
|
|
1923
|
+
// Every scroller under the pointer, the page included, as a finger's
|
|
1924
|
+
// probe reads them, without touch-action, which a wheel ignores.
|
|
1925
|
+
// What the wheel landed on: at a shadow host's listener the event's
|
|
1926
|
+
// target is the host, and a scroller inside its tree must count.
|
|
1927
|
+
const target = landedOn(wheel);
|
|
1928
|
+
const scroll = createNativeScrollProbe(target, {
|
|
1929
|
+
readsTouchAction: false
|
|
1930
|
+
});
|
|
1931
|
+
const takers = [];
|
|
1932
|
+
for (let node = target instanceof Node ? target : null; node != null; node = parentAcrossShadow(node)) {
|
|
1933
|
+
if (!(node instanceof Element)) {
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
for (const recognizer of this.registry.get(node)?.recognizers ?? []) {
|
|
1937
|
+
// A pan follows a trackpad, never a mouse wheel's notches, which
|
|
1938
|
+
// scroll what is under the pointer.
|
|
1939
|
+
if (!recognizer.isEnabled || !recognizer.claimsTouches || !recognizer.allowedScrollTypes.has(type) || internals(recognizer).family !== kind || kind === 'pan' && type !== 'continuous') {
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
if (kind === 'pan' && (!panTakesMove(recognizer, -dx, -dy) || this.scrollerTakes(target, node, dx, dy, isHorizontal) ||
|
|
1943
|
+
// A scroll-edge pan only past every scroller's edge.
|
|
1944
|
+
isScrollEdgePan(recognizer) && scroll.canScroll(-dx, -dy))) {
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
takers.push(recognizer);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
if (takers.length === 0) {
|
|
1951
|
+
return kept;
|
|
1952
|
+
}
|
|
1953
|
+
const base = {
|
|
1954
|
+
pointerType: 'mouse',
|
|
1955
|
+
target,
|
|
1956
|
+
scroll,
|
|
1957
|
+
scrollType: type,
|
|
1958
|
+
recognizers: takers,
|
|
1959
|
+
consumer: null
|
|
1960
|
+
};
|
|
1961
|
+
const ids = kind === 'pan' ? [this.nextId++] : [this.nextId++, this.nextId++];
|
|
1962
|
+
const gesture = {
|
|
1963
|
+
...kept,
|
|
1964
|
+
ids,
|
|
1965
|
+
takers
|
|
1966
|
+
};
|
|
1967
|
+
if (kind === 'pan' && type === 'continuous') {
|
|
1968
|
+
gesture.follow = {
|
|
1969
|
+
lifted: false,
|
|
1970
|
+
heldX: 0,
|
|
1971
|
+
heldY: 0,
|
|
1972
|
+
lastMove: time,
|
|
1973
|
+
trueX: kept.x,
|
|
1974
|
+
trueY: kept.y,
|
|
1975
|
+
baseX: kept.x,
|
|
1976
|
+
baseY: kept.y,
|
|
1977
|
+
speedX: 0,
|
|
1978
|
+
speedY: 0
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
this.arena.began(kind === 'pan' ? [{
|
|
1982
|
+
...base,
|
|
1983
|
+
id: ids[0],
|
|
1984
|
+
x: wheel.clientX,
|
|
1985
|
+
y: wheel.clientY,
|
|
1986
|
+
time
|
|
1987
|
+
}] : this.pinchContacts(gesture, time).map(input => ({
|
|
1988
|
+
...base,
|
|
1989
|
+
...input
|
|
1990
|
+
})));
|
|
1991
|
+
// Every recognizer it was offered to may decline it (its own
|
|
1992
|
+
// shouldReceiveTouch). Then nobody took the wheel, and it scrolls the
|
|
1993
|
+
// page from its first step, as with no gesture system.
|
|
1994
|
+
if (ids.every(id => this.arena.participantsOf(id).length === 0)) {
|
|
1995
|
+
this.arena.cancelled(ids, time);
|
|
1996
|
+
return kept;
|
|
1997
|
+
}
|
|
1998
|
+
return gesture;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
/**
|
|
2002
|
+
* Whether a scroller between `target` and `boundary` (both included)
|
|
2003
|
+
* scrolls this wheel now, along the wheel's major axis. It uses the edge
|
|
2004
|
+
* test a finger's probe uses (canElementScroll).
|
|
2005
|
+
*/
|
|
2006
|
+
scrollerTakes(target, boundary, dx, dy, isHorizontal) {
|
|
2007
|
+
for (let node = target instanceof Node ? target : null; node != null; node = parentAcrossShadow(node)) {
|
|
2008
|
+
if (node instanceof Element && canElementScroll(node, isHorizontal ? -dx : 0, isHorizontal ? 0 : -dy)) {
|
|
2009
|
+
return true;
|
|
2010
|
+
}
|
|
2011
|
+
if (node === boundary) {
|
|
2012
|
+
break;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
return false;
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
/** A ctrl-wheel pinch's two contacts, `span` apart around its center. */
|
|
2019
|
+
pinchContacts(gesture, time) {
|
|
2020
|
+
return [{
|
|
2021
|
+
id: gesture.ids[0],
|
|
2022
|
+
x: gesture.x - gesture.span / 2,
|
|
2023
|
+
y: gesture.y,
|
|
2024
|
+
time
|
|
2025
|
+
}, {
|
|
2026
|
+
id: gesture.ids[1],
|
|
2027
|
+
x: gesture.x + gesture.span / 2,
|
|
2028
|
+
y: gesture.y,
|
|
2029
|
+
time
|
|
2030
|
+
}];
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
/** Whether a recognizer that took this wheel gesture still takes part in it. */
|
|
2034
|
+
wheelHasTaker(gesture) {
|
|
2035
|
+
// A cancelled or failed recognizer keeps that state until its contacts
|
|
2036
|
+
// end, which a wheel gesture's do only here.
|
|
2037
|
+
return gesture.takers.some(recognizer => {
|
|
2038
|
+
const {
|
|
2039
|
+
state
|
|
2040
|
+
} = recognizer;
|
|
2041
|
+
return recognizer.isEnabled && (state === 'began' || state === 'changed' || state === 'possible' && recognizer.isInvolved());
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
/**
|
|
2046
|
+
* Route every wheel on the page to the live wheel gesture, from its claim.
|
|
2047
|
+
* The route is the shared input stream's capture listener on the window,
|
|
2048
|
+
* which runs before any document listener. It is non-passive only while it
|
|
2049
|
+
* is subscribed.
|
|
2050
|
+
*/
|
|
2051
|
+
latchWheel() {
|
|
2052
|
+
const view = this.doc.defaultView;
|
|
2053
|
+
if (this.stopWheelLatch == null && view != null) {
|
|
2054
|
+
const cancelWheel = () => {
|
|
2055
|
+
const ids = this.dropDrivenWheel();
|
|
2056
|
+
if (ids.length > 0) {
|
|
2057
|
+
this.arena.cancelled(ids, this.arena.now());
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
const stops = [subscribeInput(view, 'wheel', this.onWheel, {
|
|
2061
|
+
cancels: true
|
|
2062
|
+
}),
|
|
2063
|
+
// A page that loses focus or hides ends a trackpad pan, as it ends
|
|
2064
|
+
// every touch. An element's blur reaches the window's capture phase
|
|
2065
|
+
// too and means nothing here.
|
|
2066
|
+
subscribeInput(view, 'blur', event => {
|
|
2067
|
+
if (!(event.target instanceof view.Node)) {
|
|
2068
|
+
cancelWheel();
|
|
2069
|
+
}
|
|
2070
|
+
}), subscribeInput(view, 'visibilitychange', () => {
|
|
2071
|
+
if (this.doc.visibilityState === 'hidden') {
|
|
2072
|
+
cancelWheel();
|
|
2073
|
+
}
|
|
2074
|
+
})];
|
|
2075
|
+
this.stopWheelLatch = () => {
|
|
2076
|
+
for (const stop of stops) {
|
|
2077
|
+
stop();
|
|
2078
|
+
}
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
/**
|
|
2084
|
+
* Hold the page's selection for a mouse or pen contact that a recognizer
|
|
2085
|
+
* claims. Examples are a pan's drag and a press a group claims at its own
|
|
2086
|
+
* pointerdown. While the contact is down, the claiming recognizers'
|
|
2087
|
+
* elements refuse a new selection. The selection the page had at the press
|
|
2088
|
+
* is put back over anything that replaced it. That includes the press's own
|
|
2089
|
+
* collapse and a selection the drag made before its claim. The claiming
|
|
2090
|
+
* recognizer's element holds from its claim, in any state: a waiting
|
|
2091
|
+
* recognizer claims while possible, and a swipe claims as ended. The
|
|
2092
|
+
* participants that already recognize hold too. A claim given back to the
|
|
2093
|
+
* browser lets its hold go (releaseClaimedDrags). A drag that starts on
|
|
2094
|
+
* selectable text never gets here, because no recognizer claims it
|
|
2095
|
+
* (`selectingContacts`). A tap claims only at its lift, so a click keeps
|
|
2096
|
+
* the selection it made. A finger keeps the touch-down lock alone.
|
|
2097
|
+
*/
|
|
2098
|
+
lockClaimedDrags(ids, claimer) {
|
|
2099
|
+
for (const id of ids) {
|
|
2100
|
+
if (this.claimHolds.has(id)) {
|
|
2101
|
+
continue;
|
|
2102
|
+
}
|
|
2103
|
+
const touch = this.arena.touches.find(each => each.id === id);
|
|
2104
|
+
// A claim made at the contact's own lift (a tap's) holds nothing: the
|
|
2105
|
+
// drag is over, and a click keeps the selection it made. A wheel's
|
|
2106
|
+
// contact keeps the page's selection, as a scroll does.
|
|
2107
|
+
if (touch == null || touch.pointerType === 'touch' || touch.scrollType != null || touch.phase === 'ended' || touch.phase === 'cancelled') {
|
|
2108
|
+
continue;
|
|
2109
|
+
}
|
|
2110
|
+
const elements = new Set();
|
|
2111
|
+
for (const recognizer of this.arena.participantsOf(id)) {
|
|
2112
|
+
if ((recognizer.state === 'began' || recognizer.state === 'changed') && recognizer.view?.nodeType === 1) {
|
|
2113
|
+
elements.add(recognizer.view);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
if (claimer.view?.nodeType === 1) {
|
|
2117
|
+
elements.add(claimer.view);
|
|
2118
|
+
}
|
|
2119
|
+
if (elements.size === 0) {
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
2122
|
+
const release = lockDragSelection(elements, this.doc.defaultView, this.pressSelections.get(id) ?? [], () => {
|
|
2123
|
+
this.dragHolds.delete(release);
|
|
2124
|
+
});
|
|
2125
|
+
this.dragHolds.add(release);
|
|
2126
|
+
// A press lock the contact took at its press stays in `lockedBy`. The
|
|
2127
|
+
// hold sits beside it, and the contact's end releases both.
|
|
2128
|
+
if (!this.lockedBy.has(id)) {
|
|
2129
|
+
this.lockedBy.set(id, release);
|
|
2130
|
+
}
|
|
2131
|
+
this.claimHolds.set(id, release);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
/**
|
|
2136
|
+
* Claimed contacts the arena gave back. Their drag holds end at once, so
|
|
2137
|
+
* the browser's drag selects again. A contact's press lock (lockSelection)
|
|
2138
|
+
* is not a drag hold and stays.
|
|
2139
|
+
*/
|
|
2140
|
+
releaseClaimedDrags(ids) {
|
|
2141
|
+
for (const id of ids) {
|
|
2142
|
+
const release = this.claimHolds.get(id);
|
|
2143
|
+
if (release == null) {
|
|
2144
|
+
continue;
|
|
2145
|
+
}
|
|
2146
|
+
this.claimHolds.delete(id);
|
|
2147
|
+
if (this.lockedBy.get(id) === release) {
|
|
2148
|
+
this.lockedBy.delete(id);
|
|
2149
|
+
}
|
|
2150
|
+
release({
|
|
2151
|
+
isCancel: true
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
/**
|
|
2157
|
+
* End the trackpad pan a recognizer still drives, when the page loses focus
|
|
2158
|
+
* or hides, and return its contacts to cancel. The rest of its stream is
|
|
2159
|
+
* the page's until the stream ends, as a touch the browser takes is. A
|
|
2160
|
+
* ctrl-wheel pinch keeps its stream, so the page never zooms for it. A
|
|
2161
|
+
* gesture the page keeps stays as it is. So does a pan its lift released:
|
|
2162
|
+
* its momentum stays latched and prevented until the stream ends.
|
|
2163
|
+
*/
|
|
2164
|
+
dropDrivenWheel() {
|
|
2165
|
+
const gesture = this.wheel;
|
|
2166
|
+
if (gesture == null || gesture.kind !== 'pan' || gesture.ids.length === 0 || gesture.follow?.lifted === true) {
|
|
2167
|
+
return [];
|
|
2168
|
+
}
|
|
2169
|
+
const kept = {
|
|
2170
|
+
...gesture,
|
|
2171
|
+
ids: [],
|
|
2172
|
+
takers: []
|
|
2173
|
+
};
|
|
2174
|
+
delete kept.follow;
|
|
2175
|
+
this.wheel = kept;
|
|
2176
|
+
this.stopWheelLatch?.();
|
|
2177
|
+
this.stopWheelLatch = null;
|
|
2178
|
+
return gesture.ids;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
/**
|
|
2182
|
+
* End the wheel gesture in progress. On its own timer, it ends its end
|
|
2183
|
+
* window after its last wheel. A taker that leaves (a detach, a disable)
|
|
2184
|
+
* passes that last wheel's time. An input that ends it sooner (a press,
|
|
2185
|
+
* the next gesture's first wheel) passes its own time. Both times are on
|
|
2186
|
+
* the wheels' time base (eventTime), never the host's clock, which need
|
|
2187
|
+
* not share it.
|
|
2188
|
+
*/
|
|
2189
|
+
endWheel(endTime) {
|
|
2190
|
+
const gesture = this.wheel;
|
|
2191
|
+
this.stopWheelLatch?.();
|
|
2192
|
+
this.stopWheelLatch = null;
|
|
2193
|
+
if (gesture == null) {
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
this.wheel = null;
|
|
2197
|
+
if (gesture.timer != null) {
|
|
2198
|
+
this.clock.clearTimeout(gesture.timer);
|
|
2199
|
+
}
|
|
2200
|
+
if (gesture.ids.length === 0 || gesture.follow?.lifted) {
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
// Fingers that stop without a lift end on their travel, where it runs
|
|
2204
|
+
// ahead of what the pan shows. What the undecided stretch showed past it
|
|
2205
|
+
// stays, as a step back would show.
|
|
2206
|
+
if (gesture.follow != null) {
|
|
2207
|
+
const f = gesture.follow;
|
|
2208
|
+
const x = ahead(gesture.x, f.trueX - f.heldX, f.speedX);
|
|
2209
|
+
const y = ahead(gesture.y, f.trueY - f.heldY, f.speedY);
|
|
2210
|
+
if (x !== gesture.x || y !== gesture.y) {
|
|
2211
|
+
gesture.x = x;
|
|
2212
|
+
gesture.y = y;
|
|
2213
|
+
this.arena.moved([{
|
|
2214
|
+
id: gesture.ids[0],
|
|
2215
|
+
x,
|
|
2216
|
+
y,
|
|
2217
|
+
time: gesture.last
|
|
2218
|
+
}]);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
// A trackpad scroll whose fingers stop releases still, past the
|
|
2222
|
+
// velocity's stillness interval. A pinch releases 100 ms after its last
|
|
2223
|
+
// wheel, and a lift released the pan already. The release is never later
|
|
2224
|
+
// than the input that ends it (a press, the next gesture's first wheel).
|
|
2225
|
+
// The arena's clock only moves forward, so a later stamp would move that
|
|
2226
|
+
// input's times too.
|
|
2227
|
+
const end = gesture.last + gesture.endMs;
|
|
2228
|
+
const time = endTime == null ? end : Math.max(gesture.last, Math.min(end, endTime));
|
|
2229
|
+
this.arena.ended(gesture.kind === 'pan' ? [{
|
|
2230
|
+
id: gesture.ids[0],
|
|
2231
|
+
x: gesture.x,
|
|
2232
|
+
y: gesture.y,
|
|
2233
|
+
time
|
|
2234
|
+
}] : this.pinchContacts(gesture, time));
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
/**
|
|
2238
|
+
* At a mouse or pen press, mark one that lands on selectable text: once it
|
|
2239
|
+
* moves, it is the browser's drag, and selects text.
|
|
2240
|
+
* Text whose hold does something is locked at this press already
|
|
2241
|
+
* (lockSelection), so it is not selectable text.
|
|
2242
|
+
*/
|
|
2243
|
+
noteSelectingPress(id, x, y, target) {
|
|
2244
|
+
if (!this.lockedBy.has(id) && startsOnSelectableText(this.doc, x, y, target)) {
|
|
2245
|
+
this.selectingContacts.add(id);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
/**
|
|
2250
|
+
* Undo a contact's selection locks, its press lock and its drag hold, at
|
|
2251
|
+
* once for a cancel, and forget its press's selection; idempotent. A
|
|
2252
|
+
* drag's hold keeps its last task after a lift (lockDragSelection).
|
|
2253
|
+
*/
|
|
2254
|
+
unlockSelection(id, isCancel = false) {
|
|
2255
|
+
const lock = this.lockedBy.get(id);
|
|
2256
|
+
lock?.({
|
|
2257
|
+
isCancel
|
|
2258
|
+
});
|
|
2259
|
+
// A press lock keeps `lockedBy` when the contact also has a drag hold.
|
|
2260
|
+
// Release the hold too.
|
|
2261
|
+
const hold = this.claimHolds.get(id);
|
|
2262
|
+
if (hold != null && hold !== lock) {
|
|
2263
|
+
hold({
|
|
2264
|
+
isCancel
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
1504
2267
|
this.lockedBy.delete(id);
|
|
2268
|
+
this.claimHolds.delete(id);
|
|
2269
|
+
this.pressSelections.delete(id);
|
|
2270
|
+
this.selectingContacts.delete(id);
|
|
1505
2271
|
}
|
|
1506
2272
|
|
|
1507
2273
|
// --- Touch events ----------------------------------------------------------
|
|
@@ -1539,6 +2305,7 @@ export class GestureHost {
|
|
|
1539
2305
|
return true;
|
|
1540
2306
|
}
|
|
1541
2307
|
onTouchStart = event => {
|
|
2308
|
+
this.endWheel(eventTime(event));
|
|
1542
2309
|
this.noteFingersDown(event);
|
|
1543
2310
|
// Where the host reads a finger from its pointer events (no ontouchstart,
|
|
1544
2311
|
// as after touch emulation turns on), its Touch Events would track it twice.
|
|
@@ -2157,6 +2924,7 @@ export class GestureHost {
|
|
|
2157
2924
|
// --- Pointer events --------------------------------------------------------
|
|
2158
2925
|
|
|
2159
2926
|
onPointerDown = event => {
|
|
2927
|
+
this.endWheel(eventTime(event));
|
|
2160
2928
|
// A new press: whatever click is still owed to the last one is not this.
|
|
2161
2929
|
disarmClickGuard(this.doc);
|
|
2162
2930
|
if (!isPointerKind(event.pointerType)) {
|
|
@@ -2194,6 +2962,11 @@ export class GestureHost {
|
|
|
2194
2962
|
target
|
|
2195
2963
|
});
|
|
2196
2964
|
}
|
|
2965
|
+
// Before the arena admits it: a press a group claims at its own
|
|
2966
|
+
// pointerdown keeps the selection the page has now.
|
|
2967
|
+
if (event.pointerType !== 'touch') {
|
|
2968
|
+
this.pressSelections.set(id, selectionRangesOf(this.doc.defaultView));
|
|
2969
|
+
}
|
|
2197
2970
|
this.arena.began([{
|
|
2198
2971
|
id,
|
|
2199
2972
|
x: event.clientX,
|
|
@@ -2210,6 +2983,10 @@ export class GestureHost {
|
|
|
2210
2983
|
// hit test may still see the motion's start.
|
|
2211
2984
|
this.lockSelection(id, this.arena.participantsOf(id), target, this.arena.consumerOf(id) ?? null, consumerElement);
|
|
2212
2985
|
}
|
|
2986
|
+
if (event.pointerType !== 'touch') {
|
|
2987
|
+
// The engine's own hit: the text its caret query and a drag select.
|
|
2988
|
+
this.noteSelectingPress(id, event.clientX, event.clientY, landed);
|
|
2989
|
+
}
|
|
2213
2990
|
};
|
|
2214
2991
|
onPointerMove = event => {
|
|
2215
2992
|
if (event.pointerType === 'touch' && this.usesTouchEvents) {
|
|
@@ -2345,7 +3122,7 @@ export class GestureHost {
|
|
|
2345
3122
|
const id = this.pointerIds.get(event.pointerId);
|
|
2346
3123
|
if (id != null) {
|
|
2347
3124
|
this.pointerIds.delete(event.pointerId);
|
|
2348
|
-
this.unlockSelection(id);
|
|
3125
|
+
this.unlockSelection(id, true);
|
|
2349
3126
|
this.retargeted.delete(id);
|
|
2350
3127
|
this.arena.cancelled([id], eventTime(event));
|
|
2351
3128
|
}
|
|
@@ -2549,7 +3326,7 @@ export class GestureHost {
|
|
|
2549
3326
|
cancelEverything(time) {
|
|
2550
3327
|
const ids = [...this.pointerIds.values()];
|
|
2551
3328
|
for (const id of ids) {
|
|
2552
|
-
this.unlockSelection(id);
|
|
3329
|
+
this.unlockSelection(id, true);
|
|
2553
3330
|
}
|
|
2554
3331
|
for (const identifier of [...this.touchIds.keys()]) {
|
|
2555
3332
|
// The browser may keep the touch alive: its lift must not click.
|
|
@@ -2566,6 +3343,8 @@ export class GestureHost {
|
|
|
2566
3343
|
this.unpairedTouches.clear();
|
|
2567
3344
|
this.lastFed.clear();
|
|
2568
3345
|
this.pointerFed.clear();
|
|
3346
|
+
// A trackpad pan a recognizer still drives is cancelled with them.
|
|
3347
|
+
ids.push(...this.dropDrivenWheel());
|
|
2569
3348
|
if (ids.length > 0) {
|
|
2570
3349
|
this.arena.cancelled(ids, time);
|
|
2571
3350
|
}
|