@vaadin/component-base 22.0.2 → 23.0.0-alpha4

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.
Files changed (42) hide show
  1. package/index.d.ts +1 -0
  2. package/index.js +1 -0
  3. package/package.json +3 -2
  4. package/src/a11y-announcer.d.ts +10 -0
  5. package/src/a11y-announcer.js +32 -0
  6. package/src/active-mixin.d.ts +1 -1
  7. package/src/active-mixin.js +5 -5
  8. package/src/async.js +8 -5
  9. package/src/browser-utils.js +1 -1
  10. package/src/controller-mixin.d.ts +1 -1
  11. package/src/controller-mixin.js +1 -1
  12. package/src/debounce.js +10 -4
  13. package/src/dir-helper.d.ts +1 -1
  14. package/src/dir-helper.js +3 -2
  15. package/src/dir-mixin.d.ts +1 -1
  16. package/src/dir-mixin.js +7 -7
  17. package/src/disabled-mixin.d.ts +1 -1
  18. package/src/disabled-mixin.js +1 -1
  19. package/src/element-mixin.d.ts +1 -1
  20. package/src/element-mixin.js +2 -2
  21. package/src/focus-mixin.d.ts +1 -1
  22. package/src/focus-mixin.js +1 -1
  23. package/src/focus-trap-controller.d.ts +39 -0
  24. package/src/focus-trap-controller.js +139 -0
  25. package/src/focus-utils.d.ts +45 -0
  26. package/src/focus-utils.js +228 -0
  27. package/src/gestures.d.ts +76 -0
  28. package/src/gestures.js +932 -0
  29. package/src/iron-list-core.js +40 -38
  30. package/src/keyboard-mixin.d.ts +1 -1
  31. package/src/keyboard-mixin.js +1 -1
  32. package/src/resize-mixin.d.ts +19 -0
  33. package/src/resize-mixin.js +56 -0
  34. package/src/slot-controller.d.ts +8 -2
  35. package/src/slot-controller.js +74 -27
  36. package/src/slot-mixin.d.ts +1 -1
  37. package/src/slot-mixin.js +11 -3
  38. package/src/tabindex-mixin.d.ts +1 -1
  39. package/src/tabindex-mixin.js +1 -1
  40. package/src/templates.js +1 -1
  41. package/src/virtualizer-iron-list-adapter.js +5 -6
  42. package/src/virtualizer.js +6 -6
@@ -0,0 +1,932 @@
1
+ /**
2
+ @license
3
+ Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
4
+ This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5
+ The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6
+ The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7
+ Code distributed by Google as part of the polymer project is also
8
+ subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
9
+ */
10
+
11
+ /**
12
+ * @fileoverview
13
+ *
14
+ * Module for adding listeners to a node for the following normalized
15
+ * cross-platform "gesture" events:
16
+ * - `down` - mouse or touch went down
17
+ * - `up` - mouse or touch went up
18
+ * - `tap` - mouse click or finger tap
19
+ * - `track` - mouse drag or touch move
20
+ *
21
+ * @summary Module for adding cross-platform gesture event listeners.
22
+ */
23
+
24
+ import { microTask } from './async.js';
25
+
26
+ const passiveTouchGestures = false;
27
+ const wrap = (node) => node;
28
+
29
+ // detect native touch action support
30
+ const HAS_NATIVE_TA = typeof document.head.style.touchAction === 'string';
31
+ const GESTURE_KEY = '__polymerGestures';
32
+ const HANDLED_OBJ = '__polymerGesturesHandled';
33
+ const TOUCH_ACTION = '__polymerGesturesTouchAction';
34
+ // radius for tap and track
35
+ const TAP_DISTANCE = 25;
36
+ const TRACK_DISTANCE = 5;
37
+ // number of last N track positions to keep
38
+ const TRACK_LENGTH = 2;
39
+
40
+ const MOUSE_EVENTS = ['mousedown', 'mousemove', 'mouseup', 'click'];
41
+ // an array of bitmask values for mapping MouseEvent.which to MouseEvent.buttons
42
+ const MOUSE_WHICH_TO_BUTTONS = [0, 1, 4, 2];
43
+ const MOUSE_HAS_BUTTONS = (function () {
44
+ try {
45
+ return new MouseEvent('test', { buttons: 1 }).buttons === 1;
46
+ } catch (e) {
47
+ return false;
48
+ }
49
+ })();
50
+
51
+ /**
52
+ * @param {string} name Possible mouse event name
53
+ * @return {boolean} true if mouse event, false if not
54
+ */
55
+ function isMouseEvent(name) {
56
+ return MOUSE_EVENTS.indexOf(name) > -1;
57
+ }
58
+
59
+ /* eslint no-empty: ["error", { "allowEmptyCatch": true }] */
60
+ // check for passive event listeners
61
+ let supportsPassive = false;
62
+ (function () {
63
+ try {
64
+ const opts = Object.defineProperty({}, 'passive', {
65
+ // eslint-disable-next-line getter-return
66
+ get() {
67
+ supportsPassive = true;
68
+ }
69
+ });
70
+ window.addEventListener('test', null, opts);
71
+ window.removeEventListener('test', null, opts);
72
+ } catch (e) {}
73
+ })();
74
+
75
+ /**
76
+ * Generate settings for event listeners, dependant on `passiveTouchGestures`
77
+ *
78
+ * @param {string} eventName Event name to determine if `{passive}` option is
79
+ * needed
80
+ * @return {{passive: boolean} | undefined} Options to use for addEventListener
81
+ * and removeEventListener
82
+ */
83
+ function PASSIVE_TOUCH(eventName) {
84
+ if (isMouseEvent(eventName) || eventName === 'touchend') {
85
+ return;
86
+ }
87
+ if (HAS_NATIVE_TA && supportsPassive && passiveTouchGestures) {
88
+ return { passive: true };
89
+ }
90
+ }
91
+
92
+ // Check for touch-only devices
93
+ const IS_TOUCH_ONLY = navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/);
94
+
95
+ // Defined at https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#enabling-and-disabling-form-controls:-the-disabled-attribute
96
+ /** @type {!Object<boolean>} */
97
+ const canBeDisabled = {
98
+ button: true,
99
+ command: true,
100
+ fieldset: true,
101
+ input: true,
102
+ keygen: true,
103
+ optgroup: true,
104
+ option: true,
105
+ select: true,
106
+ textarea: true
107
+ };
108
+
109
+ /**
110
+ * @param {MouseEvent} ev event to test for left mouse button down
111
+ * @return {boolean} has left mouse button down
112
+ */
113
+ function hasLeftMouseButton(ev) {
114
+ const type = ev.type;
115
+ // exit early if the event is not a mouse event
116
+ if (!isMouseEvent(type)) {
117
+ return false;
118
+ }
119
+ // ev.button is not reliable for mousemove (0 is overloaded as both left button and no buttons)
120
+ // instead we use ev.buttons (bitmask of buttons) or fall back to ev.which (deprecated, 0 for no buttons, 1 for left button)
121
+ if (type === 'mousemove') {
122
+ // allow undefined for testing events
123
+ let buttons = ev.buttons === undefined ? 1 : ev.buttons;
124
+ if (ev instanceof window.MouseEvent && !MOUSE_HAS_BUTTONS) {
125
+ buttons = MOUSE_WHICH_TO_BUTTONS[ev.which] || 0;
126
+ }
127
+ // buttons is a bitmask, check that the left button bit is set (1)
128
+ return Boolean(buttons & 1);
129
+ }
130
+ // allow undefined for testing events
131
+ const button = ev.button === undefined ? 0 : ev.button;
132
+ // ev.button is 0 in mousedown/mouseup/click for left button activation
133
+ return button === 0;
134
+ }
135
+
136
+ function isSyntheticClick(ev) {
137
+ if (ev.type === 'click') {
138
+ // ev.detail is 0 for HTMLElement.click in most browsers
139
+ if (ev.detail === 0) {
140
+ return true;
141
+ }
142
+ // in the worst case, check that the x/y position of the click is within
143
+ // the bounding box of the target of the event
144
+ // Thanks IE 10 >:(
145
+ const t = _findOriginalTarget(ev);
146
+ // make sure the target of the event is an element so we can use getBoundingClientRect,
147
+ // if not, just assume it is a synthetic click
148
+ if (!t.nodeType || /** @type {Element} */ (t).nodeType !== Node.ELEMENT_NODE) {
149
+ return true;
150
+ }
151
+ const bcr = /** @type {Element} */ (t).getBoundingClientRect();
152
+ // use page x/y to account for scrolling
153
+ const x = ev.pageX,
154
+ y = ev.pageY;
155
+ // ev is a synthetic click if the position is outside the bounding box of the target
156
+ return !(x >= bcr.left && x <= bcr.right && y >= bcr.top && y <= bcr.bottom);
157
+ }
158
+ return false;
159
+ }
160
+
161
+ const POINTERSTATE = {
162
+ mouse: {
163
+ target: null,
164
+ mouseIgnoreJob: null
165
+ },
166
+ touch: {
167
+ x: 0,
168
+ y: 0,
169
+ id: -1,
170
+ scrollDecided: false
171
+ }
172
+ };
173
+
174
+ function firstTouchAction(ev) {
175
+ let ta = 'auto';
176
+ const path = getComposedPath(ev);
177
+ for (let i = 0, n; i < path.length; i++) {
178
+ n = path[i];
179
+ if (n[TOUCH_ACTION]) {
180
+ ta = n[TOUCH_ACTION];
181
+ break;
182
+ }
183
+ }
184
+ return ta;
185
+ }
186
+
187
+ function trackDocument(stateObj, movefn, upfn) {
188
+ stateObj.movefn = movefn;
189
+ stateObj.upfn = upfn;
190
+ document.addEventListener('mousemove', movefn);
191
+ document.addEventListener('mouseup', upfn);
192
+ }
193
+
194
+ function untrackDocument(stateObj) {
195
+ document.removeEventListener('mousemove', stateObj.movefn);
196
+ document.removeEventListener('mouseup', stateObj.upfn);
197
+ stateObj.movefn = null;
198
+ stateObj.upfn = null;
199
+ }
200
+
201
+ /**
202
+ * Returns the composedPath for the given event.
203
+ * @param {Event} event to process
204
+ * @return {!Array<!EventTarget>} Path of the event
205
+ */
206
+ const getComposedPath =
207
+ window.ShadyDOM && window.ShadyDOM.noPatch
208
+ ? window.ShadyDOM.composedPath
209
+ : (event) => (event.composedPath && event.composedPath()) || [];
210
+
211
+ /** @type {!Object<string, !GestureRecognizer>} */
212
+ export const gestures = {};
213
+
214
+ /** @type {!Array<!GestureRecognizer>} */
215
+ export const recognizers = [];
216
+
217
+ /**
218
+ * Finds the element rendered on the screen at the provided coordinates.
219
+ *
220
+ * Similar to `document.elementFromPoint`, but pierces through
221
+ * shadow roots.
222
+ *
223
+ * @param {number} x Horizontal pixel coordinate
224
+ * @param {number} y Vertical pixel coordinate
225
+ * @return {Element} Returns the deepest shadowRoot inclusive element
226
+ * found at the screen position given.
227
+ */
228
+ export function deepTargetFind(x, y) {
229
+ let node = document.elementFromPoint(x, y);
230
+ let next = node;
231
+ // this code path is only taken when native ShadowDOM is used
232
+ // if there is a shadowroot, it may have a node at x/y
233
+ // if there is not a shadowroot, exit the loop
234
+ while (next && next.shadowRoot && !window.ShadyDOM) {
235
+ // if there is a node at x/y in the shadowroot, look deeper
236
+ const oldNext = next;
237
+ next = next.shadowRoot.elementFromPoint(x, y);
238
+ // on Safari, elementFromPoint may return the shadowRoot host
239
+ if (oldNext === next) {
240
+ break;
241
+ }
242
+ if (next) {
243
+ node = next;
244
+ }
245
+ }
246
+ return node;
247
+ }
248
+
249
+ /**
250
+ * a cheaper check than ev.composedPath()[0];
251
+ *
252
+ * @private
253
+ * @param {Event|Touch} ev Event.
254
+ * @return {EventTarget} Returns the event target.
255
+ */
256
+ function _findOriginalTarget(ev) {
257
+ const path = getComposedPath(/** @type {?Event} */ (ev));
258
+ // It shouldn't be, but sometimes path is empty (window on Safari).
259
+ return path.length > 0 ? path[0] : ev.target;
260
+ }
261
+
262
+ /**
263
+ * @private
264
+ * @param {Event} ev Event.
265
+ * @return {void}
266
+ */
267
+ function _handleNative(ev) {
268
+ const type = ev.type;
269
+ const node = ev.currentTarget;
270
+ const gobj = node[GESTURE_KEY];
271
+ if (!gobj) {
272
+ return;
273
+ }
274
+ const gs = gobj[type];
275
+ if (!gs) {
276
+ return;
277
+ }
278
+ if (!ev[HANDLED_OBJ]) {
279
+ ev[HANDLED_OBJ] = {};
280
+ if (type.slice(0, 5) === 'touch') {
281
+ // ev = /** @type {TouchEvent} */ (ev); // eslint-disable-line no-self-assign
282
+ const t = ev.changedTouches[0];
283
+ if (type === 'touchstart') {
284
+ // only handle the first finger
285
+ if (ev.touches.length === 1) {
286
+ POINTERSTATE.touch.id = t.identifier;
287
+ }
288
+ }
289
+ if (POINTERSTATE.touch.id !== t.identifier) {
290
+ return;
291
+ }
292
+ if (!HAS_NATIVE_TA) {
293
+ if (type === 'touchstart' || type === 'touchmove') {
294
+ _handleTouchAction(ev);
295
+ }
296
+ }
297
+ }
298
+ }
299
+ const handled = ev[HANDLED_OBJ];
300
+ // used to ignore synthetic mouse events
301
+ if (handled.skip) {
302
+ return;
303
+ }
304
+ // reset recognizer state
305
+ for (let i = 0, r; i < recognizers.length; i++) {
306
+ r = recognizers[i];
307
+ if (gs[r.name] && !handled[r.name]) {
308
+ if (r.flow && r.flow.start.indexOf(ev.type) > -1 && r.reset) {
309
+ r.reset();
310
+ }
311
+ }
312
+ }
313
+ // enforce gesture recognizer order
314
+ for (let i = 0, r; i < recognizers.length; i++) {
315
+ r = recognizers[i];
316
+ if (gs[r.name] && !handled[r.name]) {
317
+ handled[r.name] = true;
318
+ r[type](ev);
319
+ }
320
+ }
321
+ }
322
+
323
+ /**
324
+ * @private
325
+ * @param {TouchEvent} ev Event.
326
+ * @return {void}
327
+ */
328
+ function _handleTouchAction(ev) {
329
+ const t = ev.changedTouches[0];
330
+ const type = ev.type;
331
+ if (type === 'touchstart') {
332
+ POINTERSTATE.touch.x = t.clientX;
333
+ POINTERSTATE.touch.y = t.clientY;
334
+ POINTERSTATE.touch.scrollDecided = false;
335
+ } else if (type === 'touchmove') {
336
+ if (POINTERSTATE.touch.scrollDecided) {
337
+ return;
338
+ }
339
+ POINTERSTATE.touch.scrollDecided = true;
340
+ const ta = firstTouchAction(ev);
341
+ let shouldPrevent = false;
342
+ const dx = Math.abs(POINTERSTATE.touch.x - t.clientX);
343
+ const dy = Math.abs(POINTERSTATE.touch.y - t.clientY);
344
+ if (!ev.cancelable) {
345
+ // scrolling is happening
346
+ } else if (ta === 'none') {
347
+ shouldPrevent = true;
348
+ } else if (ta === 'pan-x') {
349
+ shouldPrevent = dy > dx;
350
+ } else if (ta === 'pan-y') {
351
+ shouldPrevent = dx > dy;
352
+ }
353
+ if (shouldPrevent) {
354
+ ev.preventDefault();
355
+ } else {
356
+ prevent('track');
357
+ }
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Adds an event listener to a node for the given gesture type.
363
+ *
364
+ * @param {!EventTarget} node Node to add listener on
365
+ * @param {string} evType Gesture type: `down`, `up`, `track`, or `tap`
366
+ * @param {!function(!Event):void} handler Event listener function to call
367
+ * @return {boolean} Returns true if a gesture event listener was added.
368
+ */
369
+ export function addListener(node, evType, handler) {
370
+ if (gestures[evType]) {
371
+ _add(node, evType, handler);
372
+ return true;
373
+ }
374
+ return false;
375
+ }
376
+
377
+ /**
378
+ * Removes an event listener from a node for the given gesture type.
379
+ *
380
+ * @param {!EventTarget} node Node to remove listener from
381
+ * @param {string} evType Gesture type: `down`, `up`, `track`, or `tap`
382
+ * @param {!function(!Event):void} handler Event listener function previously passed to
383
+ * `addListener`.
384
+ * @return {boolean} Returns true if a gesture event listener was removed.
385
+ */
386
+ export function removeListener(node, evType, handler) {
387
+ if (gestures[evType]) {
388
+ _remove(node, evType, handler);
389
+ return true;
390
+ }
391
+ return false;
392
+ }
393
+
394
+ /**
395
+ * automate the event listeners for the native events
396
+ *
397
+ * @private
398
+ * @param {!EventTarget} node Node on which to add the event.
399
+ * @param {string} evType Event type to add.
400
+ * @param {function(!Event)} handler Event handler function.
401
+ * @return {void}
402
+ */
403
+ function _add(node, evType, handler) {
404
+ const recognizer = gestures[evType];
405
+ const deps = recognizer.deps;
406
+ const name = recognizer.name;
407
+ let gobj = node[GESTURE_KEY];
408
+ if (!gobj) {
409
+ node[GESTURE_KEY] = gobj = {};
410
+ }
411
+ for (let i = 0, dep, gd; i < deps.length; i++) {
412
+ dep = deps[i];
413
+ // don't add mouse handlers on iOS because they cause gray selection overlays
414
+ if (IS_TOUCH_ONLY && isMouseEvent(dep) && dep !== 'click') {
415
+ continue;
416
+ }
417
+ gd = gobj[dep];
418
+ if (!gd) {
419
+ gobj[dep] = gd = { _count: 0 };
420
+ }
421
+ if (gd._count === 0) {
422
+ node.addEventListener(dep, _handleNative, PASSIVE_TOUCH(dep));
423
+ }
424
+ gd[name] = (gd[name] || 0) + 1;
425
+ gd._count = (gd._count || 0) + 1;
426
+ }
427
+ node.addEventListener(evType, handler);
428
+ if (recognizer.touchAction) {
429
+ setTouchAction(node, recognizer.touchAction);
430
+ }
431
+ }
432
+
433
+ /**
434
+ * automate event listener removal for native events
435
+ *
436
+ * @private
437
+ * @param {!EventTarget} node Node on which to remove the event.
438
+ * @param {string} evType Event type to remove.
439
+ * @param {function(!Event): void} handler Event handler function.
440
+ * @return {void}
441
+ */
442
+ function _remove(node, evType, handler) {
443
+ const recognizer = gestures[evType];
444
+ const deps = recognizer.deps;
445
+ const name = recognizer.name;
446
+ const gobj = node[GESTURE_KEY];
447
+ if (gobj) {
448
+ for (let i = 0, dep, gd; i < deps.length; i++) {
449
+ dep = deps[i];
450
+ gd = gobj[dep];
451
+ if (gd && gd[name]) {
452
+ gd[name] = (gd[name] || 1) - 1;
453
+ gd._count = (gd._count || 1) - 1;
454
+ if (gd._count === 0) {
455
+ node.removeEventListener(dep, _handleNative, PASSIVE_TOUCH(dep));
456
+ }
457
+ }
458
+ }
459
+ }
460
+ node.removeEventListener(evType, handler);
461
+ }
462
+
463
+ /**
464
+ * Registers a new gesture event recognizer for adding new custom
465
+ * gesture event types.
466
+ *
467
+ * @param {!GestureRecognizer} recog Gesture recognizer descriptor
468
+ * @return {void}
469
+ */
470
+ export function register(recog) {
471
+ recognizers.push(recog);
472
+ for (let i = 0; i < recog.emits.length; i++) {
473
+ gestures[recog.emits[i]] = recog;
474
+ }
475
+ }
476
+
477
+ /**
478
+ * @private
479
+ * @param {string} evName Event name.
480
+ * @return {Object} Returns the gesture for the given event name.
481
+ */
482
+ function _findRecognizerByEvent(evName) {
483
+ for (let i = 0, r; i < recognizers.length; i++) {
484
+ r = recognizers[i];
485
+ for (let j = 0, n; j < r.emits.length; j++) {
486
+ n = r.emits[j];
487
+ if (n === evName) {
488
+ return r;
489
+ }
490
+ }
491
+ }
492
+ return null;
493
+ }
494
+
495
+ /**
496
+ * Sets scrolling direction on node.
497
+ *
498
+ * This value is checked on first move, thus it should be called prior to
499
+ * adding event listeners.
500
+ *
501
+ * @param {!EventTarget} node Node to set touch action setting on
502
+ * @param {string} value Touch action value
503
+ * @return {void}
504
+ */
505
+ export function setTouchAction(node, value) {
506
+ if (HAS_NATIVE_TA && node instanceof HTMLElement) {
507
+ // NOTE: add touchAction async so that events can be added in
508
+ // custom element constructors. Otherwise we run afoul of custom
509
+ // elements restriction against settings attributes (style) in the
510
+ // constructor.
511
+ microTask.run(() => {
512
+ node.style.touchAction = value;
513
+ });
514
+ }
515
+ node[TOUCH_ACTION] = value;
516
+ }
517
+
518
+ /**
519
+ * Dispatches an event on the `target` element of `type` with the given
520
+ * `detail`.
521
+ * @private
522
+ * @param {!EventTarget} target The element on which to fire an event.
523
+ * @param {string} type The type of event to fire.
524
+ * @param {!Object=} detail The detail object to populate on the event.
525
+ * @return {void}
526
+ */
527
+ function _fire(target, type, detail) {
528
+ const ev = new Event(type, { bubbles: true, cancelable: true, composed: true });
529
+ ev.detail = detail;
530
+ wrap(/** @type {!Node} */ (target)).dispatchEvent(ev);
531
+ // forward `preventDefault` in a clean way
532
+ if (ev.defaultPrevented) {
533
+ const preventer = detail.preventer || detail.sourceEvent;
534
+ if (preventer && preventer.preventDefault) {
535
+ preventer.preventDefault();
536
+ }
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Prevents the dispatch and default action of the given event name.
542
+ *
543
+ * @param {string} evName Event name.
544
+ * @return {void}
545
+ */
546
+ export function prevent(evName) {
547
+ const recognizer = _findRecognizerByEvent(evName);
548
+ if (recognizer.info) {
549
+ recognizer.info.prevent = true;
550
+ }
551
+ }
552
+
553
+ register({
554
+ name: 'downup',
555
+ deps: ['mousedown', 'touchstart', 'touchend'],
556
+ flow: {
557
+ start: ['mousedown', 'touchstart'],
558
+ end: ['mouseup', 'touchend']
559
+ },
560
+ emits: ['down', 'up'],
561
+
562
+ info: {
563
+ movefn: null,
564
+ upfn: null
565
+ },
566
+
567
+ /**
568
+ * @this {GestureRecognizer}
569
+ * @return {void}
570
+ */
571
+ reset: function () {
572
+ untrackDocument(this.info);
573
+ },
574
+
575
+ /**
576
+ * @this {GestureRecognizer}
577
+ * @param {MouseEvent} e
578
+ * @return {void}
579
+ */
580
+ mousedown: function (e) {
581
+ if (!hasLeftMouseButton(e)) {
582
+ return;
583
+ }
584
+ const t = _findOriginalTarget(e);
585
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
586
+ const self = this;
587
+ const movefn = (e) => {
588
+ if (!hasLeftMouseButton(e)) {
589
+ downupFire('up', t, e);
590
+ untrackDocument(self.info);
591
+ }
592
+ };
593
+ const upfn = (e) => {
594
+ if (hasLeftMouseButton(e)) {
595
+ downupFire('up', t, e);
596
+ }
597
+ untrackDocument(self.info);
598
+ };
599
+ trackDocument(this.info, movefn, upfn);
600
+ downupFire('down', t, e);
601
+ },
602
+
603
+ /**
604
+ * @this {GestureRecognizer}
605
+ * @param {TouchEvent} e
606
+ * @return {void}
607
+ */
608
+ touchstart: function (e) {
609
+ downupFire('down', _findOriginalTarget(e), e.changedTouches[0], e);
610
+ },
611
+
612
+ /**
613
+ * @this {GestureRecognizer}
614
+ * @param {TouchEvent} e
615
+ * @return {void}
616
+ */
617
+ touchend: function (e) {
618
+ downupFire('up', _findOriginalTarget(e), e.changedTouches[0], e);
619
+ }
620
+ });
621
+
622
+ /**
623
+ * @param {string} type
624
+ * @param {EventTarget} target
625
+ * @param {Event|Touch} event
626
+ * @param {Event=} preventer
627
+ * @return {void}
628
+ */
629
+ function downupFire(type, target, event, preventer) {
630
+ if (!target) {
631
+ return;
632
+ }
633
+ _fire(target, type, {
634
+ x: event.clientX,
635
+ y: event.clientY,
636
+ sourceEvent: event,
637
+ preventer: preventer,
638
+ prevent: function (e) {
639
+ return prevent(e);
640
+ }
641
+ });
642
+ }
643
+
644
+ register({
645
+ name: 'track',
646
+ touchAction: 'none',
647
+ deps: ['mousedown', 'touchstart', 'touchmove', 'touchend'],
648
+ flow: {
649
+ start: ['mousedown', 'touchstart'],
650
+ end: ['mouseup', 'touchend']
651
+ },
652
+ emits: ['track'],
653
+
654
+ info: {
655
+ x: 0,
656
+ y: 0,
657
+ state: 'start',
658
+ started: false,
659
+ moves: [],
660
+ /** @this {GestureInfo} */
661
+ addMove: function (move) {
662
+ if (this.moves.length > TRACK_LENGTH) {
663
+ this.moves.shift();
664
+ }
665
+ this.moves.push(move);
666
+ },
667
+ movefn: null,
668
+ upfn: null,
669
+ prevent: false
670
+ },
671
+
672
+ /**
673
+ * @this {GestureRecognizer}
674
+ * @return {void}
675
+ */
676
+ reset: function () {
677
+ this.info.state = 'start';
678
+ this.info.started = false;
679
+ this.info.moves = [];
680
+ this.info.x = 0;
681
+ this.info.y = 0;
682
+ this.info.prevent = false;
683
+ untrackDocument(this.info);
684
+ },
685
+
686
+ /**
687
+ * @this {GestureRecognizer}
688
+ * @param {MouseEvent} e
689
+ * @return {void}
690
+ */
691
+ mousedown: function (e) {
692
+ if (!hasLeftMouseButton(e)) {
693
+ return;
694
+ }
695
+ const t = _findOriginalTarget(e);
696
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
697
+ const self = this;
698
+ const movefn = (e) => {
699
+ const x = e.clientX,
700
+ y = e.clientY;
701
+ if (trackHasMovedEnough(self.info, x, y)) {
702
+ // first move is 'start', subsequent moves are 'move', mouseup is 'end'
703
+ self.info.state = self.info.started ? (e.type === 'mouseup' ? 'end' : 'track') : 'start';
704
+ if (self.info.state === 'start') {
705
+ // if and only if tracking, always prevent tap
706
+ prevent('tap');
707
+ }
708
+ self.info.addMove({ x: x, y: y });
709
+ if (!hasLeftMouseButton(e)) {
710
+ // always fire "end"
711
+ self.info.state = 'end';
712
+ untrackDocument(self.info);
713
+ }
714
+ if (t) {
715
+ trackFire(self.info, t, e);
716
+ }
717
+ self.info.started = true;
718
+ }
719
+ };
720
+ const upfn = (e) => {
721
+ if (self.info.started) {
722
+ movefn(e);
723
+ }
724
+
725
+ // remove the temporary listeners
726
+ untrackDocument(self.info);
727
+ };
728
+ // add temporary document listeners as mouse retargets
729
+ trackDocument(this.info, movefn, upfn);
730
+ this.info.x = e.clientX;
731
+ this.info.y = e.clientY;
732
+ },
733
+
734
+ /**
735
+ * @this {GestureRecognizer}
736
+ * @param {TouchEvent} e
737
+ * @return {void}
738
+ */
739
+ touchstart: function (e) {
740
+ const ct = e.changedTouches[0];
741
+ this.info.x = ct.clientX;
742
+ this.info.y = ct.clientY;
743
+ },
744
+
745
+ /**
746
+ * @this {GestureRecognizer}
747
+ * @param {TouchEvent} e
748
+ * @return {void}
749
+ */
750
+ touchmove: function (e) {
751
+ const t = _findOriginalTarget(e);
752
+ const ct = e.changedTouches[0];
753
+ const x = ct.clientX,
754
+ y = ct.clientY;
755
+ if (trackHasMovedEnough(this.info, x, y)) {
756
+ if (this.info.state === 'start') {
757
+ // if and only if tracking, always prevent tap
758
+ prevent('tap');
759
+ }
760
+ this.info.addMove({ x: x, y: y });
761
+ trackFire(this.info, t, ct);
762
+ this.info.state = 'track';
763
+ this.info.started = true;
764
+ }
765
+ },
766
+
767
+ /**
768
+ * @this {GestureRecognizer}
769
+ * @param {TouchEvent} e
770
+ * @return {void}
771
+ */
772
+ touchend: function (e) {
773
+ const t = _findOriginalTarget(e);
774
+ const ct = e.changedTouches[0];
775
+ // only trackend if track was started and not aborted
776
+ if (this.info.started) {
777
+ // reset started state on up
778
+ this.info.state = 'end';
779
+ this.info.addMove({ x: ct.clientX, y: ct.clientY });
780
+ trackFire(this.info, t, ct);
781
+ }
782
+ }
783
+ });
784
+
785
+ /**
786
+ * @param {!GestureInfo} info
787
+ * @param {number} x
788
+ * @param {number} y
789
+ * @return {boolean}
790
+ */
791
+ function trackHasMovedEnough(info, x, y) {
792
+ if (info.prevent) {
793
+ return false;
794
+ }
795
+ if (info.started) {
796
+ return true;
797
+ }
798
+ const dx = Math.abs(info.x - x);
799
+ const dy = Math.abs(info.y - y);
800
+ return dx >= TRACK_DISTANCE || dy >= TRACK_DISTANCE;
801
+ }
802
+
803
+ /**
804
+ * @param {!GestureInfo} info
805
+ * @param {?EventTarget} target
806
+ * @param {Touch} touch
807
+ * @return {void}
808
+ */
809
+ function trackFire(info, target, touch) {
810
+ if (!target) {
811
+ return;
812
+ }
813
+ const secondlast = info.moves[info.moves.length - 2];
814
+ const lastmove = info.moves[info.moves.length - 1];
815
+ const dx = lastmove.x - info.x;
816
+ const dy = lastmove.y - info.y;
817
+ let ddx,
818
+ ddy = 0;
819
+ if (secondlast) {
820
+ ddx = lastmove.x - secondlast.x;
821
+ ddy = lastmove.y - secondlast.y;
822
+ }
823
+ _fire(target, 'track', {
824
+ state: info.state,
825
+ x: touch.clientX,
826
+ y: touch.clientY,
827
+ dx: dx,
828
+ dy: dy,
829
+ ddx: ddx,
830
+ ddy: ddy,
831
+ sourceEvent: touch,
832
+ hover: function () {
833
+ return deepTargetFind(touch.clientX, touch.clientY);
834
+ }
835
+ });
836
+ }
837
+
838
+ register({
839
+ name: 'tap',
840
+ deps: ['mousedown', 'click', 'touchstart', 'touchend'],
841
+ flow: {
842
+ start: ['mousedown', 'touchstart'],
843
+ end: ['click', 'touchend']
844
+ },
845
+ emits: ['tap'],
846
+ info: {
847
+ x: NaN,
848
+ y: NaN,
849
+ prevent: false
850
+ },
851
+
852
+ /**
853
+ * @this {GestureRecognizer}
854
+ * @return {void}
855
+ */
856
+ reset: function () {
857
+ this.info.x = NaN;
858
+ this.info.y = NaN;
859
+ this.info.prevent = false;
860
+ },
861
+
862
+ /**
863
+ * @this {GestureRecognizer}
864
+ * @param {MouseEvent} e
865
+ * @return {void}
866
+ */
867
+ mousedown: function (e) {
868
+ if (hasLeftMouseButton(e)) {
869
+ this.info.x = e.clientX;
870
+ this.info.y = e.clientY;
871
+ }
872
+ },
873
+
874
+ /**
875
+ * @this {GestureRecognizer}
876
+ * @param {MouseEvent} e
877
+ * @return {void}
878
+ */
879
+ click: function (e) {
880
+ if (hasLeftMouseButton(e)) {
881
+ trackForward(this.info, e);
882
+ }
883
+ },
884
+
885
+ /**
886
+ * @this {GestureRecognizer}
887
+ * @param {TouchEvent} e
888
+ * @return {void}
889
+ */
890
+ touchstart: function (e) {
891
+ const touch = e.changedTouches[0];
892
+ this.info.x = touch.clientX;
893
+ this.info.y = touch.clientY;
894
+ },
895
+
896
+ /**
897
+ * @this {GestureRecognizer}
898
+ * @param {TouchEvent} e
899
+ * @return {void}
900
+ */
901
+ touchend: function (e) {
902
+ trackForward(this.info, e.changedTouches[0], e);
903
+ }
904
+ });
905
+
906
+ /**
907
+ * @param {!GestureInfo} info
908
+ * @param {Event | Touch} e
909
+ * @param {Event=} preventer
910
+ * @return {void}
911
+ */
912
+ function trackForward(info, e, preventer) {
913
+ const dx = Math.abs(e.clientX - info.x);
914
+ const dy = Math.abs(e.clientY - info.y);
915
+ // find original target from `preventer` for TouchEvents, or `e` for MouseEvents
916
+ const t = _findOriginalTarget(preventer || e);
917
+ if (!t || (canBeDisabled[/** @type {!HTMLElement} */ (t).localName] && t.hasAttribute('disabled'))) {
918
+ return;
919
+ }
920
+ // dx,dy can be NaN if `click` has been simulated and there was no `down` for `start`
921
+ if (isNaN(dx) || isNaN(dy) || (dx <= TAP_DISTANCE && dy <= TAP_DISTANCE) || isSyntheticClick(e)) {
922
+ // prevent taps from being generated if an event has canceled them
923
+ if (!info.prevent) {
924
+ _fire(t, 'tap', {
925
+ x: e.clientX,
926
+ y: e.clientY,
927
+ sourceEvent: e,
928
+ preventer: preventer
929
+ });
930
+ }
931
+ }
932
+ }