@yuneta/gobj-ui 7.23.12 → 7.23.14
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/README.md +36 -3
- package/dist/gobj-ui.cjs.js +155 -39
- package/dist/gobj-ui.es.js +155 -39
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +25 -4
- package/src/c_yui_treedb_graph.js +64 -2
- package/src/g6_touch_gestures.js +148 -58
- package/src/lib_graph.css +34 -0
- package/src/press_arbiter.js +84 -0
- package/src/press_arbiter.test.js +63 -0
package/src/g6_touch_gestures.js
CHANGED
|
@@ -32,6 +32,21 @@
|
|
|
32
32
|
* forwarded event object -- which is why the plugin's
|
|
33
33
|
* `getItems(e)` receives exactly the shape it expects.
|
|
34
34
|
*
|
|
35
|
+
* ONE finger has to serve three commands -- move the
|
|
36
|
+
* node, act on it, open its menu -- so the press is
|
|
37
|
+
* arbitrated, and arbitrated at the RELEASE:
|
|
38
|
+
*
|
|
39
|
+
* moved -> drag
|
|
40
|
+
* still, let go fast -> the element's own action
|
|
41
|
+
* still, held >= 500ms -> the context menu
|
|
42
|
+
*
|
|
43
|
+
* A menu opened on a TIMER instead cannot be arbitrated
|
|
44
|
+
* at all: it opens while `drag-element` is already
|
|
45
|
+
* carrying the node, so the same press means both things
|
|
46
|
+
* and the user gets a menu over a card running away
|
|
47
|
+
* underneath it. `classify_press` is where the three
|
|
48
|
+
* live, and it is pure.
|
|
49
|
+
*
|
|
35
50
|
* Both are built on ONE per-graph record of the fingers
|
|
36
51
|
* on the glass, read from the NATIVE touch events -- see
|
|
37
52
|
* `touch_state_of` for why the pointer stream, which looks
|
|
@@ -50,19 +65,7 @@ import {
|
|
|
50
65
|
register,
|
|
51
66
|
} from '@antv/g6';
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
/***************************************************************
|
|
55
|
-
* Constants
|
|
56
|
-
***************************************************************/
|
|
57
|
-
|
|
58
|
-
/*
|
|
59
|
-
* How long a finger must stay put to mean "context menu", and how
|
|
60
|
-
* far it may wander first. 500ms is what Android and iOS both use
|
|
61
|
-
* for their own long press; a shorter one fires while the user is
|
|
62
|
-
* still deciding whether to pan.
|
|
63
|
-
*/
|
|
64
|
-
const LONG_PRESS_MS = 500;
|
|
65
|
-
const LONG_PRESS_SLOP = 10; /* CSS px */
|
|
68
|
+
import {press_moved, classify_press} from "./press_arbiter.js";
|
|
66
69
|
|
|
67
70
|
|
|
68
71
|
/***************************************************************
|
|
@@ -97,7 +100,11 @@ function touch_state_of(graph)
|
|
|
97
100
|
|
|
98
101
|
state = {
|
|
99
102
|
count: 0, /* fingers on the glass right now */
|
|
103
|
+
multi: false, /* two or more were down in THIS gesture */
|
|
100
104
|
container: null,
|
|
105
|
+
/* A long press fired, and the click that @antv/g makes out
|
|
106
|
+
* of the same pointerup has not been answered yet. */
|
|
107
|
+
long_press_click: false,
|
|
101
108
|
};
|
|
102
109
|
__touch_state__.set(graph, state);
|
|
103
110
|
|
|
@@ -109,6 +116,14 @@ function touch_state_of(graph)
|
|
|
109
116
|
|
|
110
117
|
let read = (event) => {
|
|
111
118
|
state.count = event.touches? event.touches.length: 0;
|
|
119
|
+
if(state.count >= 2) {
|
|
120
|
+
/* Sticky until the LAST finger goes: a pinch releases its
|
|
121
|
+
* two fingers one at a time, and the release of the first
|
|
122
|
+
* one must not be read as the end of a press. */
|
|
123
|
+
state.multi = true;
|
|
124
|
+
} else if(state.count === 0) {
|
|
125
|
+
state.multi = false;
|
|
126
|
+
}
|
|
112
127
|
};
|
|
113
128
|
|
|
114
129
|
/* Passive: we never preventDefault here -- the canvas already
|
|
@@ -119,6 +134,24 @@ function touch_state_of(graph)
|
|
|
119
134
|
container.addEventListener("touchend", read, {passive: true});
|
|
120
135
|
container.addEventListener("touchcancel", read, {passive: true});
|
|
121
136
|
|
|
137
|
+
/*
|
|
138
|
+
* The browser opens its OWN menu on a long press, and it opens it
|
|
139
|
+
* while the finger is still down -- on top of whatever the graph
|
|
140
|
+
* is about to do with the same press. Refuse it for as long as
|
|
141
|
+
* there is a finger on the glass.
|
|
142
|
+
*
|
|
143
|
+
* This costs the graph nothing: G6 reads no `contextmenu` from
|
|
144
|
+
* the DOM at all (it synthesises its own from `pointerdown` with
|
|
145
|
+
* `button === 2`), so a real right click keeps working with its
|
|
146
|
+
* DOM event refused -- and it is not refused here anyway, because
|
|
147
|
+
* a mouse leaves `count` at zero.
|
|
148
|
+
*/
|
|
149
|
+
container.addEventListener("contextmenu", (event) => {
|
|
150
|
+
if(state.count > 0) {
|
|
151
|
+
event.preventDefault();
|
|
152
|
+
}
|
|
153
|
+
}, {capture: true});
|
|
154
|
+
|
|
122
155
|
return state;
|
|
123
156
|
}
|
|
124
157
|
|
|
@@ -290,6 +323,30 @@ export function ensure_pinch_zoom_patch()
|
|
|
290
323
|
|
|
291
324
|
|
|
292
325
|
|
|
326
|
+
/************************************************************
|
|
327
|
+
* Is the click being dispatched right now the tail of a long
|
|
328
|
+
* press? Answers true ONCE, to the first asker.
|
|
329
|
+
*
|
|
330
|
+
* @antv/g does not take `click` from the DOM either: it makes
|
|
331
|
+
* one out of the pointerdown/pointerup pair itself, inside
|
|
332
|
+
* the same `onPointerUp` that just fed us the release. So
|
|
333
|
+
* the press that opened the menu goes on to click whatever it
|
|
334
|
+
* opened the menu ON -- selecting the node, or worse, dropping
|
|
335
|
+
* a selection of several -- unless the handler asks.
|
|
336
|
+
************************************************************/
|
|
337
|
+
export function consume_long_press_click(graph)
|
|
338
|
+
{
|
|
339
|
+
if(!graph) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
let state = __touch_state__.get(graph);
|
|
343
|
+
if(!state || !state.long_press_click) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
state.long_press_click = false;
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
|
|
293
350
|
/************************************************************
|
|
294
351
|
* Make a long press open the context menu.
|
|
295
352
|
*
|
|
@@ -300,33 +357,45 @@ export function ensure_pinch_zoom_patch()
|
|
|
300
357
|
* the name the plugin listens for, so `getItems(e)` and
|
|
301
358
|
* `enable(e)` see the same object a right-click would give
|
|
302
359
|
* them -- same `target`, same `targetType`, same `client`.
|
|
360
|
+
*
|
|
361
|
+
* `options.enable(event)` decides, at the DOWN, whether this
|
|
362
|
+
* press is even a candidate. Pass the plugin's own predicate:
|
|
363
|
+
* a press on the empty canvas would otherwise arm a menu the
|
|
364
|
+
* plugin then refuses to show, and swallow the click that
|
|
365
|
+
* would have cleared the selection.
|
|
303
366
|
************************************************************/
|
|
304
|
-
export function install_long_press_contextmenu(graph)
|
|
367
|
+
export function install_long_press_contextmenu(graph, options)
|
|
305
368
|
{
|
|
306
369
|
if(!graph) {
|
|
307
370
|
return () => {};
|
|
308
371
|
}
|
|
309
372
|
|
|
310
|
-
let
|
|
311
|
-
let
|
|
373
|
+
let enable = options?.enable;
|
|
374
|
+
let state = touch_state_of(graph);
|
|
375
|
+
let pending = null; /* {x, y, t0, event} */
|
|
312
376
|
|
|
313
|
-
let
|
|
314
|
-
|
|
315
|
-
clearTimeout(pending.timer);
|
|
316
|
-
pending = null;
|
|
317
|
-
}
|
|
377
|
+
let disarm = () => {
|
|
378
|
+
pending = null;
|
|
318
379
|
};
|
|
319
380
|
|
|
320
381
|
let fire = (event) => {
|
|
321
|
-
|
|
382
|
+
/*
|
|
383
|
+
* The click @antv/g is about to make out of this same
|
|
384
|
+
* pointerup belongs to the press, and the press has already
|
|
385
|
+
* been spent on the menu.
|
|
386
|
+
*/
|
|
387
|
+
state.long_press_click = true;
|
|
388
|
+
setTimeout(() => {
|
|
389
|
+
state.long_press_click = false;
|
|
390
|
+
}, 0);
|
|
322
391
|
|
|
323
392
|
/*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
393
|
+
* That click also reaches the plugin's own `document`
|
|
394
|
+
* listener, which hides the menu the press just opened -- a
|
|
395
|
+
* right click never produces a `click`, so the plugin has no
|
|
396
|
+
* reason to guard against it. Swallow that one click in the
|
|
397
|
+
* CAPTURE phase, where document sees it before anything else
|
|
398
|
+
* does.
|
|
330
399
|
*/
|
|
331
400
|
let swallow = (evt) => {
|
|
332
401
|
evt.stopPropagation();
|
|
@@ -337,30 +406,21 @@ export function install_long_press_contextmenu(graph)
|
|
|
337
406
|
document.removeEventListener("click", swallow, true);
|
|
338
407
|
}, 1000);
|
|
339
408
|
|
|
340
|
-
/*
|
|
341
|
-
* And the browser's own long-press menu, which would open on
|
|
342
|
-
* top of ours. One shot: a later right click must keep
|
|
343
|
-
* reaching the plugin.
|
|
344
|
-
*/
|
|
345
|
-
let container = graph.getCanvas?.()?.getContainer?.();
|
|
346
|
-
if(container) {
|
|
347
|
-
container.addEventListener("contextmenu", (evt) => {
|
|
348
|
-
evt.preventDefault();
|
|
349
|
-
}, {once: true, capture: true});
|
|
350
|
-
}
|
|
351
|
-
|
|
352
409
|
graph.emit(`${event.targetType}:${CommonEvent.CONTEXT_MENU}`, event);
|
|
353
410
|
graph.emit(CommonEvent.CONTEXT_MENU, event);
|
|
354
411
|
};
|
|
355
412
|
|
|
356
413
|
let on_down = (event) => {
|
|
357
|
-
|
|
414
|
+
disarm();
|
|
358
415
|
if(event.pointerType !== "touch") {
|
|
359
416
|
return; /* a mouse holding its button is not a menu */
|
|
360
417
|
}
|
|
361
|
-
if(state.count > 1) {
|
|
418
|
+
if(state.count > 1 || state.multi) {
|
|
362
419
|
return; /* two fingers: that is the pinch */
|
|
363
420
|
}
|
|
421
|
+
if(enable && !enable(event)) {
|
|
422
|
+
return; /* nothing here has a menu to open */
|
|
423
|
+
}
|
|
364
424
|
let x = event.client?.x;
|
|
365
425
|
let y = event.client?.y;
|
|
366
426
|
if(x === undefined || y === undefined) {
|
|
@@ -368,16 +428,15 @@ export function install_long_press_contextmenu(graph)
|
|
|
368
428
|
}
|
|
369
429
|
/*
|
|
370
430
|
* The event object is reused by @antv/g between dispatches,
|
|
371
|
-
* so what the
|
|
372
|
-
*
|
|
431
|
+
* so what the release must answer with is a COPY -- by then
|
|
432
|
+
* the live object describes the pointerup, and the menu is
|
|
433
|
+
* about what was PRESSED.
|
|
373
434
|
*/
|
|
374
|
-
let frozen = Object.assign({}, event);
|
|
375
435
|
pending = {
|
|
376
|
-
x:
|
|
377
|
-
y:
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}, LONG_PRESS_MS),
|
|
436
|
+
x: x,
|
|
437
|
+
y: y,
|
|
438
|
+
t0: Date.now(),
|
|
439
|
+
event: Object.assign({}, event),
|
|
381
440
|
};
|
|
382
441
|
};
|
|
383
442
|
|
|
@@ -385,8 +444,8 @@ export function install_long_press_contextmenu(graph)
|
|
|
385
444
|
if(!pending) {
|
|
386
445
|
return;
|
|
387
446
|
}
|
|
388
|
-
if(state.count > 1) {
|
|
389
|
-
|
|
447
|
+
if(state.count > 1 || state.multi) {
|
|
448
|
+
disarm();
|
|
390
449
|
return;
|
|
391
450
|
}
|
|
392
451
|
let x = event.client?.x;
|
|
@@ -394,20 +453,51 @@ export function install_long_press_contextmenu(graph)
|
|
|
394
453
|
if(x === undefined || y === undefined) {
|
|
395
454
|
return;
|
|
396
455
|
}
|
|
397
|
-
if(
|
|
398
|
-
|
|
399
|
-
|
|
456
|
+
if(press_moved(pending, x, y)) {
|
|
457
|
+
disarm(); /* the finger is carrying the node */
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
let on_up = (event) => {
|
|
462
|
+
let press = pending;
|
|
463
|
+
pending = null;
|
|
464
|
+
if(!press) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if(state.multi) {
|
|
468
|
+
return; /* a finger of a pinch, not a press */
|
|
469
|
+
}
|
|
470
|
+
let x = event.client?.x;
|
|
471
|
+
let y = event.client?.y;
|
|
472
|
+
if(x === undefined) {
|
|
473
|
+
x = press.x;
|
|
474
|
+
}
|
|
475
|
+
if(y === undefined) {
|
|
476
|
+
y = press.y;
|
|
400
477
|
}
|
|
478
|
+
if(classify_press(press, x, y, Date.now()) !== "long") {
|
|
479
|
+
return; /* a drag, or a tap: both are already served */
|
|
480
|
+
}
|
|
481
|
+
fire(press.event);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
/* The gesture can also end without a release: the browser takes
|
|
485
|
+
* the touch away (a system gesture, a call), and the press must
|
|
486
|
+
* not survive it into the next one. */
|
|
487
|
+
let on_touch_cancel = () => {
|
|
488
|
+
disarm();
|
|
401
489
|
};
|
|
490
|
+
state.container?.addEventListener("touchcancel", on_touch_cancel);
|
|
402
491
|
|
|
403
492
|
graph.on(CommonEvent.POINTER_DOWN, on_down);
|
|
404
493
|
graph.on(CommonEvent.POINTER_MOVE, on_move);
|
|
405
|
-
graph.on(CommonEvent.POINTER_UP,
|
|
494
|
+
graph.on(CommonEvent.POINTER_UP, on_up);
|
|
406
495
|
|
|
407
496
|
return () => {
|
|
408
|
-
|
|
497
|
+
disarm();
|
|
498
|
+
state.container?.removeEventListener("touchcancel", on_touch_cancel);
|
|
409
499
|
graph.off(CommonEvent.POINTER_DOWN, on_down);
|
|
410
500
|
graph.off(CommonEvent.POINTER_MOVE, on_move);
|
|
411
|
-
graph.off(CommonEvent.POINTER_UP,
|
|
501
|
+
graph.off(CommonEvent.POINTER_UP, on_up);
|
|
412
502
|
};
|
|
413
503
|
}
|
package/src/lib_graph.css
CHANGED
|
@@ -52,6 +52,27 @@
|
|
|
52
52
|
.graph-container {
|
|
53
53
|
-webkit-user-select: none;
|
|
54
54
|
user-select: none;
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* And the finger's half of the same problem: on a touch screen
|
|
58
|
+
* the browser owns the gesture until something says otherwise.
|
|
59
|
+
*
|
|
60
|
+
* G6 puts `touch-action: none` on its CANVAS, so dragging from
|
|
61
|
+
* the background pans the graph -- and it puts nothing on the
|
|
62
|
+
* HTML nodes, which are ordinary DIVs layered on top of it. So
|
|
63
|
+
* a finger that lands on a CARD was making a page scroll:
|
|
64
|
+
* Chrome let two `pointermove`s through, decided the gesture
|
|
65
|
+
* was a scroll, and killed the pointer stream with a
|
|
66
|
+
* `pointercancel`. The node followed the finger for about 20px
|
|
67
|
+
* and then stopped dead while the page slid -- which is what
|
|
68
|
+
* "I only want to move a node" looked like on a phone.
|
|
69
|
+
*
|
|
70
|
+
* The whole container, not the cards: every layer G6 stacks in
|
|
71
|
+
* here (nodes, plugin containers, the ones it adds next) is a
|
|
72
|
+
* DIV over a canvas that already refuses the browser's
|
|
73
|
+
* gestures, and they have to agree.
|
|
74
|
+
*/
|
|
75
|
+
touch-action: none;
|
|
55
76
|
}
|
|
56
77
|
.graph-container input,
|
|
57
78
|
.graph-container textarea,
|
|
@@ -66,6 +87,19 @@
|
|
|
66
87
|
user-select: text;
|
|
67
88
|
}
|
|
68
89
|
|
|
90
|
+
/*
|
|
91
|
+
* What a finger must still be able to SCROLL: the panels, and the
|
|
92
|
+
* context menu, which is as long as the impact of a delete.
|
|
93
|
+
*/
|
|
94
|
+
.graph-container .g6-confirm-popover,
|
|
95
|
+
.graph-container .g6-create-popover,
|
|
96
|
+
.graph-container .g6-node-popover,
|
|
97
|
+
.graph-container .g6-edge-popover,
|
|
98
|
+
.graph-container .g6-node-detail,
|
|
99
|
+
.graph-container .g6-contextmenu {
|
|
100
|
+
touch-action: auto;
|
|
101
|
+
}
|
|
102
|
+
|
|
69
103
|
.graph-container:fullscreen {
|
|
70
104
|
background-color: var(--bulma-background, #fff);
|
|
71
105
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* press_arbiter.js
|
|
3
|
+
*
|
|
4
|
+
* What ONE finger meant.
|
|
5
|
+
*
|
|
6
|
+
* A graph gives a single finger three commands -- move the
|
|
7
|
+
* node, act on it, open its menu -- and nothing in the press
|
|
8
|
+
* says which one until the finger either moves or lets go.
|
|
9
|
+
* So the press is arbitrated, and arbitrated at the RELEASE:
|
|
10
|
+
*
|
|
11
|
+
* moved -> "drag" (the node followed it)
|
|
12
|
+
* still, let go fast -> "tap" (the element's action)
|
|
13
|
+
* still, held >= 500ms -> "long" (the context menu)
|
|
14
|
+
*
|
|
15
|
+
* Deciding earlier is what breaks it. A menu opened on a
|
|
16
|
+
* TIMER opens while `drag-element` is already carrying the
|
|
17
|
+
* node: the same press then means two things at once, and
|
|
18
|
+
* the user gets a menu over a card running away underneath
|
|
19
|
+
* it. A timer cannot arbitrate, because at the moment it
|
|
20
|
+
* fires the gesture is not over.
|
|
21
|
+
*
|
|
22
|
+
* Pure, and separate from `g6_touch_gestures.js` for that
|
|
23
|
+
* reason: the rule can be read and tested without a graph,
|
|
24
|
+
* a canvas or a finger.
|
|
25
|
+
*
|
|
26
|
+
* Copyright (c) 2026, ArtGins.
|
|
27
|
+
* All Rights Reserved.
|
|
28
|
+
***********************************************************************/
|
|
29
|
+
|
|
30
|
+
/***************************************************************
|
|
31
|
+
* Constants
|
|
32
|
+
***************************************************************/
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* How long a finger must stay put to mean "context menu", and how
|
|
36
|
+
* far it may wander first. 500ms is what Android and iOS both use
|
|
37
|
+
* for their own long press.
|
|
38
|
+
*
|
|
39
|
+
* The 10px is not a taste: it is the `dragstartDistanceThreshold`
|
|
40
|
+
* G6 gives the dragndrop plugin of its main canvas. Keeping the
|
|
41
|
+
* two equal is what makes "still" mean the same thing to both --
|
|
42
|
+
* a press held inside the slop has NOT moved the node, so a menu
|
|
43
|
+
* it opens does not open over a card that quietly shifted.
|
|
44
|
+
*/
|
|
45
|
+
export const LONG_PRESS_MS = 500;
|
|
46
|
+
export const LONG_PRESS_SLOP = 10; /* CSS px */
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/***************************************************************
|
|
50
|
+
* Arbitration
|
|
51
|
+
***************************************************************/
|
|
52
|
+
|
|
53
|
+
/************************************************************
|
|
54
|
+
* Did the finger travel far enough to mean a DRAG?
|
|
55
|
+
*
|
|
56
|
+
* `press` is what the pointerdown recorded: {x, y, t0}.
|
|
57
|
+
************************************************************/
|
|
58
|
+
export function press_moved(press, x, y)
|
|
59
|
+
{
|
|
60
|
+
if(!press) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return Math.abs(x - press.x) > LONG_PRESS_SLOP ||
|
|
64
|
+
Math.abs(y - press.y) > LONG_PRESS_SLOP;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/************************************************************
|
|
68
|
+
* What the press meant, asked when the finger comes up.
|
|
69
|
+
*
|
|
70
|
+
* Answers "none" when there was no press to arbitrate.
|
|
71
|
+
************************************************************/
|
|
72
|
+
export function classify_press(press, x, y, t)
|
|
73
|
+
{
|
|
74
|
+
if(!press) {
|
|
75
|
+
return "none";
|
|
76
|
+
}
|
|
77
|
+
if(press_moved(press, x, y)) {
|
|
78
|
+
return "drag";
|
|
79
|
+
}
|
|
80
|
+
if(t - press.t0 >= LONG_PRESS_MS) {
|
|
81
|
+
return "long";
|
|
82
|
+
}
|
|
83
|
+
return "tap";
|
|
84
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* press_arbiter.test.js
|
|
3
|
+
***********************************************************************/
|
|
4
|
+
import {describe, it, expect} from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
LONG_PRESS_MS,
|
|
7
|
+
LONG_PRESS_SLOP,
|
|
8
|
+
press_moved,
|
|
9
|
+
classify_press,
|
|
10
|
+
} from "./press_arbiter.js";
|
|
11
|
+
|
|
12
|
+
const at = (x, y, t0) => ({x: x, y: y, t0: t0});
|
|
13
|
+
|
|
14
|
+
describe("press_moved", () => {
|
|
15
|
+
it("a finger that stays inside the slop has not moved", () => {
|
|
16
|
+
let p = at(100, 100, 0);
|
|
17
|
+
expect(press_moved(p, 100, 100)).toBe(false);
|
|
18
|
+
expect(press_moved(p, 100 + LONG_PRESS_SLOP, 100)).toBe(false);
|
|
19
|
+
expect(press_moved(p, 100, 100 - LONG_PRESS_SLOP)).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("one pixel past the slop, in either axis, is a move", () => {
|
|
23
|
+
let p = at(100, 100, 0);
|
|
24
|
+
expect(press_moved(p, 100 + LONG_PRESS_SLOP + 1, 100)).toBe(true);
|
|
25
|
+
expect(press_moved(p, 100, 100 + LONG_PRESS_SLOP + 1)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("no press, no move", () => {
|
|
29
|
+
expect(press_moved(null, 100, 100)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("classify_press", () => {
|
|
34
|
+
it("a press that moved is a drag, however long it was held", () => {
|
|
35
|
+
let p = at(100, 100, 0);
|
|
36
|
+
expect(classify_press(p, 400, 100, 10)).toBe("drag");
|
|
37
|
+
expect(classify_press(p, 400, 100, 10 * LONG_PRESS_MS)).toBe("drag");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("still and let go quickly is a tap", () => {
|
|
41
|
+
let p = at(100, 100, 1000);
|
|
42
|
+
expect(classify_press(p, 100, 100, 1000)).toBe("tap");
|
|
43
|
+
expect(classify_press(p, 105, 103, 1000 + LONG_PRESS_MS - 1)).toBe("tap");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("still and held to the threshold is the menu", () => {
|
|
47
|
+
let p = at(100, 100, 1000);
|
|
48
|
+
expect(classify_press(p, 100, 100, 1000 + LONG_PRESS_MS)).toBe("long");
|
|
49
|
+
expect(classify_press(p, 108, 100, 9000)).toBe("long");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("a long hold that ends somewhere else is still a drag", () => {
|
|
53
|
+
/* The finger that thought about the menu and then carried the
|
|
54
|
+
* node is carrying the node: the release is what decides, and
|
|
55
|
+
* the release is far away. */
|
|
56
|
+
let p = at(100, 100, 0);
|
|
57
|
+
expect(classify_press(p, 260, 340, 3000)).toBe("drag");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("no press to arbitrate", () => {
|
|
61
|
+
expect(classify_press(null, 0, 0, 0)).toBe("none");
|
|
62
|
+
});
|
|
63
|
+
});
|