@jsenv/dom 0.17.72 → 0.17.74
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/jsenv_dom.js +292 -166
- package/package.json +1 -1
package/dist/jsenv_dom.js
CHANGED
|
@@ -4549,6 +4549,39 @@ const clickIsSuppressed = (clickEvent) =>
|
|
|
4549
4549
|
it. */
|
|
4550
4550
|
const MENU_GRACE_AFTER_CANCEL = 300;
|
|
4551
4551
|
|
|
4552
|
+
/* The presses a gesture has taken, and the holds still counting on each. Keyed
|
|
4553
|
+
by the `pointerdown` itself: every listener of one press is handed that same
|
|
4554
|
+
object, so a hold asked for further along the dispatch that took the press is
|
|
4555
|
+
told as surely as one already counting. */
|
|
4556
|
+
const takenPressSet = new WeakSet();
|
|
4557
|
+
const holdGiveUpsByPress = new WeakMap();
|
|
4558
|
+
|
|
4559
|
+
/**
|
|
4560
|
+
* A gesture takes the press: the pointer is captured on `element`, and every
|
|
4561
|
+
* hold waiting on that press gives up — whatever the press turned out to be, it
|
|
4562
|
+
* is that gesture and not a hold.
|
|
4563
|
+
*
|
|
4564
|
+
* Said here rather than left to the capture, because the browser announces a
|
|
4565
|
+
* capture only when it MOVES. A finger lands already captured to what it touched
|
|
4566
|
+
* (see implicitCaptureHolder below): a gesture taking that same element changes
|
|
4567
|
+
* nothing the browser reports, and a hold listening for the capture alone
|
|
4568
|
+
* answers in the middle of the gesture that took its press.
|
|
4569
|
+
*
|
|
4570
|
+
* @param {PointerEvent} pressEvent The `pointerdown` of the press taken.
|
|
4571
|
+
* @param {Element} element What holds the pointer from here.
|
|
4572
|
+
*/
|
|
4573
|
+
const takePress = (pressEvent, element) => {
|
|
4574
|
+
element.setPointerCapture(pressEvent.pointerId);
|
|
4575
|
+
takenPressSet.add(pressEvent);
|
|
4576
|
+
const holdGiveUps = holdGiveUpsByPress.get(pressEvent);
|
|
4577
|
+
if (!holdGiveUps) {
|
|
4578
|
+
return;
|
|
4579
|
+
}
|
|
4580
|
+
for (const giveUp of [...holdGiveUps]) {
|
|
4581
|
+
giveUp();
|
|
4582
|
+
}
|
|
4583
|
+
};
|
|
4584
|
+
|
|
4552
4585
|
/**
|
|
4553
4586
|
* Waits for a press to be held still, then tells the caller.
|
|
4554
4587
|
*
|
|
@@ -4563,7 +4596,7 @@ const MENU_GRACE_AFTER_CANCEL = 300;
|
|
|
4563
4596
|
* @param {function} [options.onPressStart] The wait began (a cue that the press
|
|
4564
4597
|
* counts).
|
|
4565
4598
|
* @param {function} [options.onPressCancel] The pointer moved or lifted before
|
|
4566
|
-
* the wait was over.
|
|
4599
|
+
* the wait was over, or another gesture took the press (see takePress).
|
|
4567
4600
|
* @param {(pressEvent: PointerEvent, handle: {endPress: () => void}) => void} options.onPressHeld
|
|
4568
4601
|
* The wait completed. Whatever the press now means outlives this call — an
|
|
4569
4602
|
* object is being carried, a menu is open under the finger — so the caller
|
|
@@ -4703,22 +4736,28 @@ const waitForPressHeld = (
|
|
|
4703
4736
|
}
|
|
4704
4737
|
cancelPress(pointerEndEvent);
|
|
4705
4738
|
};
|
|
4706
|
-
// Somebody else settled what this press is
|
|
4707
|
-
//
|
|
4708
|
-
//
|
|
4709
|
-
//
|
|
4710
|
-
//
|
|
4711
|
-
//
|
|
4712
|
-
//
|
|
4713
|
-
|
|
4739
|
+
// Somebody else settled what this press is, and whatever it turned out to be,
|
|
4740
|
+
// it is not a hold. Two waits on one press is the ordinary case rather than an
|
|
4741
|
+
// odd one: an element that can be picked up AND held answers a finger with two
|
|
4742
|
+
// delays, the shorter one wins, and without this the longer one would answer a
|
|
4743
|
+
// hundred milliseconds into the carry. A gesture says it with takePress. Both
|
|
4744
|
+
// ways of hearing it go with the countdown, so the gesture THIS wait starts
|
|
4745
|
+
// (which takes the press from inside onPressHeld) never reaches them.
|
|
4746
|
+
let holdGiveUps = holdGiveUpsByPress.get(pressEvent);
|
|
4747
|
+
if (!holdGiveUps) {
|
|
4748
|
+
holdGiveUps = new Set();
|
|
4749
|
+
holdGiveUpsByPress.set(pressEvent, holdGiveUps);
|
|
4750
|
+
}
|
|
4751
|
+
holdGiveUps.add(cancelPress);
|
|
4752
|
+
// A gesture that captures the pointer without takePress: heard only when the
|
|
4753
|
+
// capture moves, the one it takes on the implicit holder being announced to
|
|
4754
|
+
// nobody (see takePress).
|
|
4714
4755
|
const onGotPointerCapture = (captureEvent) => {
|
|
4715
4756
|
if (captureEvent.pointerId !== pointerId) {
|
|
4716
4757
|
return;
|
|
4717
4758
|
}
|
|
4718
4759
|
if (captureEvent.target === implicitCaptureHolder) {
|
|
4719
4760
|
// The capture the press was born with, not one somebody took (see above).
|
|
4720
|
-
// A gesture taking that same element instead changes nothing for the
|
|
4721
|
-
// browser, so it announces nothing, and there is nothing to miss here.
|
|
4722
4761
|
return;
|
|
4723
4762
|
}
|
|
4724
4763
|
cancelPress(captureEvent);
|
|
@@ -4730,13 +4769,20 @@ const waitForPressHeld = (
|
|
|
4730
4769
|
window.addEventListener("pointercancel", onPointerEnd);
|
|
4731
4770
|
window.addEventListener("gotpointercapture", onGotPointerCapture, true);
|
|
4732
4771
|
countdownCleanupCallbacks.push(() => {
|
|
4772
|
+
holdGiveUps.delete(cancelPress);
|
|
4733
4773
|
window.removeEventListener("pointermove", onPointerMove);
|
|
4734
4774
|
window.removeEventListener("pointerup", onPointerEnd);
|
|
4735
4775
|
window.removeEventListener("pointercancel", onPointerEnd);
|
|
4736
4776
|
window.removeEventListener("gotpointercapture", onGotPointerCapture, true);
|
|
4737
4777
|
});
|
|
4738
4778
|
|
|
4739
|
-
|
|
4779
|
+
if (takenPressSet.has(pressEvent)) {
|
|
4780
|
+
// Taken before this wait was asked for, by a listener earlier in the same
|
|
4781
|
+
// dispatch: over before it began.
|
|
4782
|
+
cancelPress();
|
|
4783
|
+
} else {
|
|
4784
|
+
onPressStart?.(pressEvent);
|
|
4785
|
+
}
|
|
4740
4786
|
|
|
4741
4787
|
return {
|
|
4742
4788
|
cancel: () => {
|
|
@@ -8891,6 +8937,7 @@ const captureHolderByPointerId = new Map();
|
|
|
8891
8937
|
* `[data-droppable]` places) goes inert, a backdrop unifies the cursor and takes
|
|
8892
8938
|
* the pointer events, the focus moves to what is held and comes back at the
|
|
8893
8939
|
* release, and the selection is refused for the length of the gesture.
|
|
8940
|
+
* `documentInteractions` and `selection` each hand their part back to the caller.
|
|
8894
8941
|
*
|
|
8895
8942
|
* Whether the element may be grabbed AT ALL is decided before the finger lands
|
|
8896
8943
|
* and elsewhere: see `markDragSource` in drag_after_intent.js for the
|
|
@@ -8914,6 +8961,13 @@ const captureHolderByPointerId = new Map();
|
|
|
8914
8961
|
* @param {"auto"|"manual"} [options.documentInteractions="auto"]
|
|
8915
8962
|
* `"manual"` leaves the document alone — no inertness, no backdrop, no focus taken. For a
|
|
8916
8963
|
* gesture that only measures and hands over (see `dragAfterIntent`).
|
|
8964
|
+
* @param {"auto"|"manual"} [options.selection="auto"]
|
|
8965
|
+
* `"auto"` refuses the selection from the grab to the release, a press that may
|
|
8966
|
+
* become a drag not being a press that starts a selection. `"manual"` leaves it
|
|
8967
|
+
* alone — nothing refused, nothing collapsed, double and triple clicks the
|
|
8968
|
+
* browser's — for a gesture where a press on text is still a selection until it
|
|
8969
|
+
* has become something else, and says so itself once it has (a travel, see
|
|
8970
|
+
* drag_to_travel.js).
|
|
8917
8971
|
* @param {boolean} [options.backdrop=true]
|
|
8918
8972
|
* @param {number} [options.backdropZIndex=999999]
|
|
8919
8973
|
* @returns {{grab: function, grabViaPointer: function}}
|
|
@@ -8937,6 +8991,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8937
8991
|
threshold = 5,
|
|
8938
8992
|
direction: defaultDirection = { x: true, y: true },
|
|
8939
8993
|
documentInteractions = "auto",
|
|
8994
|
+
selection = "auto",
|
|
8940
8995
|
backdrop = true,
|
|
8941
8996
|
backdropZIndex = 999999,
|
|
8942
8997
|
} = options;
|
|
@@ -9459,96 +9514,101 @@ const createDragGestureController = (options = {}) => {
|
|
|
9459
9514
|
};
|
|
9460
9515
|
dragGesture.dragViaPointer = dragViaPointer;
|
|
9461
9516
|
dragGesture.releaseViaPointer = releaseViaPointer;
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
* own gesture — the words under the finger turn blue while the element
|
|
9466
|
-
* travels, and the selection outlives the release.
|
|
9467
|
-
*
|
|
9468
|
-
* Refused from the grab, before any threshold: whether the press becomes a
|
|
9469
|
-
* drag is decided a few pixels later, but the selection is decided at the
|
|
9470
|
-
* FIRST move, and by then it is too late to say no.
|
|
9471
|
-
*
|
|
9472
|
-
* `user-select: none` would say it in CSS, but it would say it to
|
|
9473
|
-
* everybody: the element would stop being selectable even when nobody is
|
|
9474
|
-
* dragging it. Here it is refused for the length of one gesture.
|
|
9475
|
-
*/
|
|
9476
|
-
const preventSelectStart = (selectStartEvent) => {
|
|
9477
|
-
selectStartEvent.preventDefault();
|
|
9478
|
-
};
|
|
9479
|
-
document.addEventListener("selectstart", preventSelectStart);
|
|
9480
|
-
// A press also puts an end to the selection the page was already holding,
|
|
9481
|
-
// the way the browser's own press does: refusing selectstart keeps a new
|
|
9482
|
-
// selection from being made, it says nothing about the one painted before
|
|
9483
|
-
// — which would otherwise sit there through a gesture that has nothing to
|
|
9484
|
-
// do with it.
|
|
9485
|
-
collapseSelection();
|
|
9486
|
-
dragGesture.addReleaseCallback(() => {
|
|
9487
|
-
document.removeEventListener("selectstart", preventSelectStart);
|
|
9488
|
-
});
|
|
9489
|
-
/*
|
|
9490
|
-
* Refusing every selection also refuses the ones a press is entitled to
|
|
9491
|
-
* make: a double click selects the word under it, a triple click the
|
|
9492
|
-
* paragraph around it, and both are over before the pointer has gone
|
|
9493
|
-
* anywhere. They are made here instead, spelled out (see
|
|
9494
|
-
* selectWordAtPoint, selectParagraphAtPoint) rather than left to a browser
|
|
9495
|
-
* heuristic that cannot tell a drag from a click.
|
|
9496
|
-
*
|
|
9497
|
-
* Read from `click` rather than `dblclick`, because a triple click has no
|
|
9498
|
-
* event of its own: what tells the clicks apart is `detail`, the count the
|
|
9499
|
-
* browser keeps of how many presses landed in the same place in a row — 2
|
|
9500
|
-
* for a word, 3 and beyond for a paragraph (a fourth click keeps the
|
|
9501
|
-
* paragraph, the way the browser does).
|
|
9502
|
-
*
|
|
9503
|
-
* On the document and outliving the gesture, because the gesture is
|
|
9504
|
-
* already over when the second click completes: click comes after mouseup,
|
|
9505
|
-
* the gesture ends at pointerup. Kept installed after it fires, since the
|
|
9506
|
-
* click that selects a word is also the one the next click turns into a
|
|
9507
|
-
* paragraph; it goes when the next press installs its own — a listener
|
|
9508
|
-
* waiting for a click that never comes costs nothing until then.
|
|
9509
|
-
*/
|
|
9510
|
-
removePendingMultiClickListener();
|
|
9511
|
-
const onClick = (clickEvent) => {
|
|
9512
|
-
const clickCount = clickEvent.detail;
|
|
9513
|
-
if (clickCount < 2) {
|
|
9514
|
-
return;
|
|
9517
|
+
refuse_selection: {
|
|
9518
|
+
if (selection === "manual") {
|
|
9519
|
+
break refuse_selection;
|
|
9515
9520
|
}
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
) {
|
|
9531
|
-
|
|
9532
|
-
}
|
|
9533
|
-
|
|
9534
|
-
//
|
|
9535
|
-
//
|
|
9536
|
-
//
|
|
9537
|
-
//
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9521
|
+
/*
|
|
9522
|
+
* A press that starts a drag is not a press that starts a selection: the
|
|
9523
|
+
* browser sees a pointer going down on text and moving, and that is its
|
|
9524
|
+
* own gesture — the words under the finger turn blue while the element
|
|
9525
|
+
* travels, and the selection outlives the release.
|
|
9526
|
+
*
|
|
9527
|
+
* Refused from the grab, before any threshold: whether the press becomes a
|
|
9528
|
+
* drag is decided a few pixels later, but the selection is decided at the
|
|
9529
|
+
* FIRST move, and by then it is too late to say no.
|
|
9530
|
+
*
|
|
9531
|
+
* `user-select: none` would say it in CSS, but it would say it to
|
|
9532
|
+
* everybody: the element would stop being selectable even when nobody is
|
|
9533
|
+
* dragging it. Here it is refused for the length of one gesture.
|
|
9534
|
+
*/
|
|
9535
|
+
const preventSelectStart = (selectStartEvent) => {
|
|
9536
|
+
selectStartEvent.preventDefault();
|
|
9537
|
+
};
|
|
9538
|
+
document.addEventListener("selectstart", preventSelectStart);
|
|
9539
|
+
// A press also puts an end to the selection the page was already holding,
|
|
9540
|
+
// the way the browser's own press does: refusing selectstart keeps a new
|
|
9541
|
+
// selection from being made, it says nothing about the one painted before
|
|
9542
|
+
// — which would otherwise sit there through a gesture that has nothing to
|
|
9543
|
+
// do with it.
|
|
9544
|
+
collapseSelection();
|
|
9545
|
+
dragGesture.addReleaseCallback(() => {
|
|
9546
|
+
document.removeEventListener("selectstart", preventSelectStart);
|
|
9547
|
+
});
|
|
9548
|
+
/*
|
|
9549
|
+
* Refusing every selection also refuses the ones a press is entitled to
|
|
9550
|
+
* make: a double click selects the word under it, a triple click the
|
|
9551
|
+
* paragraph around it, and both are over before the pointer has gone
|
|
9552
|
+
* anywhere. They are made here instead, spelled out (see
|
|
9553
|
+
* selectWordAtPoint, selectParagraphAtPoint) rather than left to a browser
|
|
9554
|
+
* heuristic that cannot tell a drag from a click.
|
|
9555
|
+
*
|
|
9556
|
+
* Read from `click` rather than `dblclick`, because a triple click has no
|
|
9557
|
+
* event of its own: what tells the clicks apart is `detail`, the count the
|
|
9558
|
+
* browser keeps of how many presses landed in the same place in a row — 2
|
|
9559
|
+
* for a word, 3 and beyond for a paragraph (a fourth click keeps the
|
|
9560
|
+
* paragraph, the way the browser does).
|
|
9561
|
+
*
|
|
9562
|
+
* On the document and outliving the gesture, because the gesture is
|
|
9563
|
+
* already over when the second click completes: click comes after mouseup,
|
|
9564
|
+
* the gesture ends at pointerup. Kept installed after it fires, since the
|
|
9565
|
+
* click that selects a word is also the one the next click turns into a
|
|
9566
|
+
* paragraph; it goes when the next press installs its own — a listener
|
|
9567
|
+
* waiting for a click that never comes costs nothing until then.
|
|
9568
|
+
*/
|
|
9569
|
+
removePendingMultiClickListener();
|
|
9570
|
+
const onClick = (clickEvent) => {
|
|
9571
|
+
const clickCount = clickEvent.detail;
|
|
9572
|
+
if (clickCount < 2) {
|
|
9573
|
+
return;
|
|
9574
|
+
}
|
|
9575
|
+
// A drag that happened is a gesture, not a click: the second press of a
|
|
9576
|
+
// double click can be the one that drags, and what it drags must not end
|
|
9577
|
+
// up selected too.
|
|
9578
|
+
if (dragGesture.gestureInfo.started) {
|
|
9579
|
+
return;
|
|
9580
|
+
}
|
|
9581
|
+
// Only the clicks that continue THIS press: the listener outlives the
|
|
9582
|
+
// gesture and the page keeps being clicked elsewhere, where the browser
|
|
9583
|
+
// is doing its own selecting — a second opinion there would only fight
|
|
9584
|
+
// it.
|
|
9585
|
+
const clickTarget = clickEvent.target;
|
|
9586
|
+
if (
|
|
9587
|
+
clickTarget !== grabEvent.target &&
|
|
9588
|
+
!clickTarget.contains(grabEvent.target)
|
|
9589
|
+
) {
|
|
9590
|
+
return;
|
|
9591
|
+
}
|
|
9592
|
+
// Text the page says is not selectable stays not selectable: a
|
|
9593
|
+
// programmatic selection goes through `user-select: none` in every
|
|
9594
|
+
// engine — it is a rule about what the USER may start, and the browser
|
|
9595
|
+
// does not read it back when asked directly. Read here so that doing the
|
|
9596
|
+
// browser's work does not also undo what the page asked of it.
|
|
9597
|
+
if (!isSelectable(clickEvent.target)) {
|
|
9598
|
+
return;
|
|
9599
|
+
}
|
|
9600
|
+
if (clickCount === 2) {
|
|
9601
|
+
selectWordAtPoint(clickEvent.clientX, clickEvent.clientY);
|
|
9602
|
+
return;
|
|
9603
|
+
}
|
|
9604
|
+
selectParagraphAtPoint(clickEvent.clientX, clickEvent.clientY);
|
|
9605
|
+
};
|
|
9606
|
+
document.addEventListener("click", onClick);
|
|
9607
|
+
removePendingMultiClickListener = () => {
|
|
9608
|
+
removePendingMultiClickListener = NOOP;
|
|
9609
|
+
document.removeEventListener("click", onClick);
|
|
9610
|
+
};
|
|
9611
|
+
}
|
|
9552
9612
|
const cleanup = initializer({
|
|
9553
9613
|
onMove: dragViaPointer,
|
|
9554
9614
|
onRelease: releaseViaPointer,
|
|
@@ -9601,7 +9661,10 @@ const createDragGestureController = (options = {}) => {
|
|
|
9601
9661
|
// last — which is what tells a hand-over from a capture the browser
|
|
9602
9662
|
// dropped on its own (see onCaptureLost).
|
|
9603
9663
|
captureHolderByPointerId.set(grabEvent.pointerId, dragGesture);
|
|
9604
|
-
|
|
9664
|
+
// The capture is often where the finger landed, which the browser
|
|
9665
|
+
// does not announce again: takePress is what tells a hold waiting
|
|
9666
|
+
// on this press that it is a drag.
|
|
9667
|
+
takePress(grabEvent, target);
|
|
9605
9668
|
};
|
|
9606
9669
|
if (!options?.pointerCaptureDeferred) {
|
|
9607
9670
|
dragGesture.capturePointer();
|
|
@@ -10110,6 +10173,13 @@ const dragSourceThatStoodDown = (pressEvent) => {
|
|
|
10110
10173
|
* The pointer moved or lifted before the wait was over.
|
|
10111
10174
|
* @param {function} [options.onPress]
|
|
10112
10175
|
* The wait completed and the object is now held (haptics, scale…).
|
|
10176
|
+
* @param {"auto"|"manual"} [options.selection="auto"]
|
|
10177
|
+
* Whether the selection is refused while the press travels towards the
|
|
10178
|
+
* distance: a press on a source belongs to the drag, so what its first pixels
|
|
10179
|
+
* would select is refused before the drag is sure (see `selection` in
|
|
10180
|
+
* drag_gesture.js). `"manual"` leaves the press to the browser until the
|
|
10181
|
+
* caller's own gesture says otherwise. A long press waits without refusing
|
|
10182
|
+
* anything either way (see the stylesheet above).
|
|
10113
10183
|
*/
|
|
10114
10184
|
const dragAfterIntent = (
|
|
10115
10185
|
grabEvent,
|
|
@@ -10122,6 +10192,7 @@ const dragAfterIntent = (
|
|
|
10122
10192
|
onPressStart,
|
|
10123
10193
|
onPressCancel,
|
|
10124
10194
|
onPress,
|
|
10195
|
+
selection = "auto",
|
|
10125
10196
|
} = {},
|
|
10126
10197
|
) => {
|
|
10127
10198
|
if (!isPrimaryButtonEvent(grabEvent)) {
|
|
@@ -10151,7 +10222,7 @@ const dragAfterIntent = (
|
|
|
10151
10222
|
});
|
|
10152
10223
|
return;
|
|
10153
10224
|
}
|
|
10154
|
-
dragAfterDistance(grabEvent, dragGestureInitializer, threshold);
|
|
10225
|
+
dragAfterDistance(grabEvent, dragGestureInitializer, threshold, selection);
|
|
10155
10226
|
};
|
|
10156
10227
|
|
|
10157
10228
|
const startDragGesture = (dragGestureInitializer, catchUpEvent) => {
|
|
@@ -10168,13 +10239,19 @@ const startDragGesture = (dragGestureInitializer, catchUpEvent) => {
|
|
|
10168
10239
|
return dragGesture;
|
|
10169
10240
|
};
|
|
10170
10241
|
|
|
10171
|
-
const dragAfterDistance = (
|
|
10242
|
+
const dragAfterDistance = (
|
|
10243
|
+
grabEvent,
|
|
10244
|
+
dragGestureInitializer,
|
|
10245
|
+
threshold,
|
|
10246
|
+
selection,
|
|
10247
|
+
) => {
|
|
10172
10248
|
const significantDragGestureController = createDragGestureController({
|
|
10173
10249
|
threshold,
|
|
10174
10250
|
// allow interaction for this intermediate gesture:
|
|
10175
10251
|
// user should still be able to scroll or interact with the document
|
|
10176
10252
|
// only once the gesture is significant we take control
|
|
10177
10253
|
documentInteractions: "manual",
|
|
10254
|
+
selection,
|
|
10178
10255
|
onDragStart: (gestureInfo) => {
|
|
10179
10256
|
significantDragGesture.release(); // kill that gesture
|
|
10180
10257
|
startDragGesture(dragGestureInitializer, gestureInfo.dragEvent);
|
|
@@ -12249,6 +12326,10 @@ import.meta.css = /* css */ [`:root[data-drag-travel-gesture] {
|
|
|
12249
12326
|
overscroll-behavior: none;
|
|
12250
12327
|
}
|
|
12251
12328
|
|
|
12329
|
+
:root[data-drag-travel-walking] {
|
|
12330
|
+
user-select: none;
|
|
12331
|
+
}
|
|
12332
|
+
|
|
12252
12333
|
[data-drag-travel*="x"] {
|
|
12253
12334
|
overscroll-behavior-x: contain !important;
|
|
12254
12335
|
}
|
|
@@ -12783,8 +12864,11 @@ const startDragToTravel = (
|
|
|
12783
12864
|
// never come.
|
|
12784
12865
|
// Nothing is being carried: the page keeps its focus, its scrolling and its
|
|
12785
12866
|
// cursor while a screen slides under the finger. That is the whole
|
|
12786
|
-
// difference with a drag that moves an object
|
|
12867
|
+
// difference with a drag that moves an object.
|
|
12787
12868
|
documentInteractions: "manual",
|
|
12869
|
+
// And a press on text is a selection until it has become a travel, which
|
|
12870
|
+
// is where the selection is refused (see where WALKING_ATTRIBUTE is set).
|
|
12871
|
+
selection: "manual",
|
|
12788
12872
|
onDragStart: () => {
|
|
12789
12873
|
document.documentElement.setAttribute(GESTURE_ATTRIBUTE, "");
|
|
12790
12874
|
},
|
|
@@ -12885,6 +12969,9 @@ const startDragToTravel = (
|
|
|
12885
12969
|
pulled: started.slack || 0,
|
|
12886
12970
|
};
|
|
12887
12971
|
document.documentElement.setAttribute(WALKING_ATTRIBUTE, axis);
|
|
12972
|
+
// What the first pixels may have started selecting is not a selection:
|
|
12973
|
+
// it is the beginning of this travel.
|
|
12974
|
+
collapseSelection();
|
|
12888
12975
|
// The travel exists: from here the pointer is this box's, and it is
|
|
12889
12976
|
// followed wherever it goes.
|
|
12890
12977
|
dragGesture.capturePointer();
|
|
@@ -13008,6 +13095,7 @@ const startDragToTravel = (
|
|
|
13008
13095
|
dragAfterIntent(pointerDownEvent, grab, {
|
|
13009
13096
|
longPress: false,
|
|
13010
13097
|
threshold: DRAG_START_THRESHOLD,
|
|
13098
|
+
selection: "manual",
|
|
13011
13099
|
});
|
|
13012
13100
|
}
|
|
13013
13101
|
window.addEventListener("pointerup", onPressOver);
|
|
@@ -13431,7 +13519,11 @@ const findPanZoomSurface = (element) => {
|
|
|
13431
13519
|
* @param {Element} element
|
|
13432
13520
|
* @param {object} options
|
|
13433
13521
|
* @param {(detail: {event: PointerEvent, x: number, y: number}) => void} [options.onPan]
|
|
13434
|
-
* The hand moved: `x`/`y` are how far since the last report, in px.
|
|
13522
|
+
* The hand moved: `x`/`y` are how far since the last report, in px. Left out,
|
|
13523
|
+
* one pointer is not the surface's: it is heard only for the pinch a second
|
|
13524
|
+
* one would make with it, and otherwise left to whatever else reads it — no
|
|
13525
|
+
* capture, no hold waited for, no click swallowed, no grab. `afterHold` then
|
|
13526
|
+
* leaves its scroll to the page, and `"kept"` has no pan to keep.
|
|
13435
13527
|
* @param {(detail: {event: PointerEvent|WheelEvent, factor: number, x: number, y: number}) => void} [options.onZoom]
|
|
13436
13528
|
* The zoom changed by `factor` (above 1 is in) around the point `x`/`y` of the
|
|
13437
13529
|
* surface, measured inside its border. Left out, a wheel over the surface is
|
|
@@ -13554,13 +13646,14 @@ const installPanZoom = (
|
|
|
13554
13646
|
|
|
13555
13647
|
const activate = (anchorWhere, event) => {
|
|
13556
13648
|
active = true;
|
|
13557
|
-
if (afterHold === "kept" && holdIsOwed) {
|
|
13649
|
+
if (afterHold === "kept" && holdIsOwed && onPan) {
|
|
13558
13650
|
// Asked for and given: whatever proved it — the hold, a second finger, a
|
|
13559
|
-
// mouse travelling — the surface is the hand's from here.
|
|
13651
|
+
// mouse travelling — the surface is the hand's from here. What it keeps
|
|
13652
|
+
// is the pan on contact, which a surface that does not pan has none of.
|
|
13560
13653
|
keepTheHand();
|
|
13561
13654
|
}
|
|
13562
|
-
for (const
|
|
13563
|
-
element
|
|
13655
|
+
for (const pointer of pointers.values()) {
|
|
13656
|
+
takePress(pointer.pressEvent, element);
|
|
13564
13657
|
}
|
|
13565
13658
|
anchor = readHand(anchorWhere);
|
|
13566
13659
|
// The click the release leaves behind is not for what is under the hand.
|
|
@@ -13631,6 +13724,7 @@ const installPanZoom = (
|
|
|
13631
13724
|
window.addEventListener("pointercancel", onPointerEnd, true);
|
|
13632
13725
|
}
|
|
13633
13726
|
const pointer = {
|
|
13727
|
+
pressEvent: event,
|
|
13634
13728
|
x: event.clientX,
|
|
13635
13729
|
y: event.clientY,
|
|
13636
13730
|
startX: event.clientX,
|
|
@@ -13642,7 +13736,7 @@ const installPanZoom = (
|
|
|
13642
13736
|
};
|
|
13643
13737
|
pointers.set(event.pointerId, pointer);
|
|
13644
13738
|
if (active) {
|
|
13645
|
-
|
|
13739
|
+
takePress(event, element);
|
|
13646
13740
|
anchor = readHand();
|
|
13647
13741
|
return;
|
|
13648
13742
|
}
|
|
@@ -13650,6 +13744,12 @@ const installPanZoom = (
|
|
|
13650
13744
|
activate("now", event);
|
|
13651
13745
|
return;
|
|
13652
13746
|
}
|
|
13747
|
+
if (!onPan) {
|
|
13748
|
+
// One pointer is the pan's to read. Without one it is only heard for the
|
|
13749
|
+
// pinch a second pointer would make with it: a tap, a hold or a double
|
|
13750
|
+
// click on it belongs to whatever else reads it.
|
|
13751
|
+
return;
|
|
13752
|
+
}
|
|
13653
13753
|
if (holdIsOwed && event.pointerType === "touch") {
|
|
13654
13754
|
pointer.waitsForHold = true;
|
|
13655
13755
|
pointer.holdWait = waitForPressHeld(event, {
|
|
@@ -13670,7 +13770,9 @@ const installPanZoom = (
|
|
|
13670
13770
|
pointer.x = event.clientX;
|
|
13671
13771
|
pointer.y = event.clientY;
|
|
13672
13772
|
if (!active) {
|
|
13673
|
-
|
|
13773
|
+
// No pan for the travel to start (see onPointerDown), or a finger whose
|
|
13774
|
+
// travel is the page scrolling.
|
|
13775
|
+
if (!onPan || pointer.waitsForHold) {
|
|
13674
13776
|
return;
|
|
13675
13777
|
}
|
|
13676
13778
|
const travelled = Math.hypot(
|
|
@@ -13716,13 +13818,13 @@ const installPanZoom = (
|
|
|
13716
13818
|
}
|
|
13717
13819
|
};
|
|
13718
13820
|
|
|
13719
|
-
//
|
|
13720
|
-
//
|
|
13721
|
-
//
|
|
13722
|
-
//
|
|
13723
|
-
// the
|
|
13724
|
-
//
|
|
13725
|
-
//
|
|
13821
|
+
// A touch drag left unrefused makes Chrome Android drop the click of the NEXT
|
|
13822
|
+
// tap, whatever `touch-action` says (see navi's
|
|
13823
|
+
// docs/mobile_tap_suppression_after_drag.md), so the surface refuses every
|
|
13824
|
+
// touchmove while it moves. Whether one can be refused at all is settled when
|
|
13825
|
+
// the touch begins, before the surface moves: the listener goes down with the
|
|
13826
|
+
// surface and refuses nothing until then — a tap, a hold, or under
|
|
13827
|
+
// `afterHold` the page scrolling, which is the whole point of the wait.
|
|
13726
13828
|
const preventTouchScroll = (touchMoveEvent) => {
|
|
13727
13829
|
if (active && touchMoveEvent.cancelable) {
|
|
13728
13830
|
touchMoveEvent.preventDefault();
|
|
@@ -13795,11 +13897,9 @@ const installPanZoom = (
|
|
|
13795
13897
|
|
|
13796
13898
|
element.addEventListener("pointerdown", onPointerDown);
|
|
13797
13899
|
element.addEventListener("lostpointercapture", onLostPointerCapture);
|
|
13798
|
-
|
|
13799
|
-
|
|
13800
|
-
|
|
13801
|
-
});
|
|
13802
|
-
}
|
|
13900
|
+
element.addEventListener("touchmove", preventTouchScroll, {
|
|
13901
|
+
passive: false,
|
|
13902
|
+
});
|
|
13803
13903
|
if (onZoom) {
|
|
13804
13904
|
element.addEventListener("wheel", onWheel, { passive: false });
|
|
13805
13905
|
}
|
|
@@ -14530,10 +14630,12 @@ const createDragToMoveGestureController = ({
|
|
|
14530
14630
|
*
|
|
14531
14631
|
* @param {PointerEvent} event The `pointerdown` that may become a drag.
|
|
14532
14632
|
* @param {("move"|"reorder"|"toss"|"land"|"leave")[]} effects
|
|
14533
|
-
* What letting go of this element can mean
|
|
14534
|
-
*
|
|
14535
|
-
*
|
|
14536
|
-
*
|
|
14633
|
+
* What letting go of this element can mean, and only what is listed is ever
|
|
14634
|
+
* answered. `reorder`, `toss` and `land` carry a copy; `move` carries the
|
|
14635
|
+
* element itself, and `leave` goes with either. Asking for `move` beside any of
|
|
14636
|
+
* the three that carry a copy is asking one release to mean two things, and so
|
|
14637
|
+
* is asking for `reorder` and `land`. A gesture taken away mid-air
|
|
14638
|
+
* (`gestureInfo.cancelled`) means none of them, and what it carried goes back.
|
|
14537
14639
|
* @param {object} [options]
|
|
14538
14640
|
* @param {Element} [options.draggedElement=event.currentTarget]
|
|
14539
14641
|
* @param {(detail: {gestureInfo: object, x: number, y: number}) => void} [options.onMoving]
|
|
@@ -14652,6 +14754,7 @@ const startDragTo = (
|
|
|
14652
14754
|
}
|
|
14653
14755
|
return startDragToMoveElement(event, {
|
|
14654
14756
|
draggedElement,
|
|
14757
|
+
canMove,
|
|
14655
14758
|
canLeave,
|
|
14656
14759
|
...options,
|
|
14657
14760
|
});
|
|
@@ -14728,12 +14831,13 @@ const refuseDragTo = (
|
|
|
14728
14831
|
onRefuse?.({ event });
|
|
14729
14832
|
return null;
|
|
14730
14833
|
}
|
|
14731
|
-
// Nothing is carried, and the
|
|
14834
|
+
// Nothing is carried, and the press is taken all the same: taking it is
|
|
14732
14835
|
// how a gesture says the press is settled, and another wait counting on the
|
|
14733
|
-
// same finger
|
|
14734
|
-
//
|
|
14836
|
+
// same finger gives up (see takePress in press_held.js), even when the
|
|
14837
|
+
// finger landed on this very element. A `longpress` declared beside the
|
|
14838
|
+
// drag is answered by the grab when there is one; there must be no
|
|
14735
14839
|
// difference when there is none.
|
|
14736
|
-
|
|
14840
|
+
takePress(event, draggedElement);
|
|
14737
14841
|
// And the click the browser fires afterwards belongs to what answered the
|
|
14738
14842
|
// press, a refusal included: something pulled and told to stay put must not
|
|
14739
14843
|
// also be clicked. Lifted at the release rather than with the click, which
|
|
@@ -14763,12 +14867,14 @@ const refuseDragTo = (
|
|
|
14763
14867
|
* go of away from it, when it can `leave`.
|
|
14764
14868
|
*
|
|
14765
14869
|
* No copy, unlike the others: what is being moved is the thing and not a
|
|
14766
|
-
* stand-in for it, so there is nothing to put back and nothing to reveal.
|
|
14870
|
+
* stand-in for it, so there is nothing to put back and nothing to reveal. What a
|
|
14871
|
+
* release means is read by resolveDropMeaning, as it is for a copy.
|
|
14767
14872
|
*/
|
|
14768
14873
|
const startDragToMoveElement = (
|
|
14769
14874
|
event,
|
|
14770
14875
|
{
|
|
14771
14876
|
draggedElement,
|
|
14877
|
+
canMove,
|
|
14772
14878
|
canLeave,
|
|
14773
14879
|
onMoving,
|
|
14774
14880
|
onMove,
|
|
@@ -14818,26 +14924,26 @@ const startDragToMoveElement = (
|
|
|
14818
14924
|
});
|
|
14819
14925
|
}
|
|
14820
14926
|
dragGesture.addReleaseCallback(async (gestureInfo) => {
|
|
14927
|
+
const dropMeans = resolveDropMeaning({
|
|
14928
|
+
gestureInfo,
|
|
14929
|
+
releasedOutside: canLeave && isOutsideOf(gestureInfo, outsideOf),
|
|
14930
|
+
canMove,
|
|
14931
|
+
canLeave,
|
|
14932
|
+
});
|
|
14821
14933
|
const { xDelta, yDelta } = gestureInfo.layout;
|
|
14822
|
-
if (!xDelta && !yDelta) {
|
|
14823
|
-
// Picked up and put back down: nothing moved, so no outcome is told.
|
|
14824
|
-
onRelease?.({ gestureInfo, x: xDelta, y: yDelta, outcome: null });
|
|
14825
|
-
gestureInfo.cancelPosition();
|
|
14826
|
-
return;
|
|
14827
|
-
}
|
|
14828
|
-
const leaving =
|
|
14829
|
-
canLeave &&
|
|
14830
|
-
!gestureInfo.cancelled &&
|
|
14831
|
-
isOutsideOf(gestureInfo, outsideOf);
|
|
14832
14934
|
onRelease?.({
|
|
14833
14935
|
gestureInfo,
|
|
14834
14936
|
x: xDelta,
|
|
14835
14937
|
y: yDelta,
|
|
14836
|
-
|
|
14837
|
-
// going: `onMoving` alone has already told all of it.
|
|
14838
|
-
outcome: leaving ? "leave" : onMove ? "move" : null,
|
|
14938
|
+
outcome: dropMeans === "cancel" ? null : dropMeans,
|
|
14839
14939
|
});
|
|
14840
|
-
if (
|
|
14940
|
+
if (dropMeans === "cancel") {
|
|
14941
|
+
// Nothing answers it: whatever the gesture translated goes home — and
|
|
14942
|
+
// there is nothing to take back when `onMoving` had the caller draw.
|
|
14943
|
+
gestureInfo.cancelPositionAnimated();
|
|
14944
|
+
return;
|
|
14945
|
+
}
|
|
14946
|
+
if (dropMeans === "leave") {
|
|
14841
14947
|
// Left where the hand let go of it while the answer is asked — a thing
|
|
14842
14948
|
// that snaps home with the request in flight says the gesture was not
|
|
14843
14949
|
// understood. The answer then says what becomes of that position: let
|
|
@@ -14931,10 +15037,14 @@ const readOwnLayout = (element) => ({
|
|
|
14931
15037
|
const TOSS_DISTANCE_TO_COMMIT = 110;
|
|
14932
15038
|
const TOSS_SPEED_TO_COMMIT = 0.45;
|
|
14933
15039
|
|
|
15040
|
+
// What a release means, whether it carried the element itself or a copy: one
|
|
15041
|
+
// reading, so a gesture taken away mid-air is nothing on both paths, and an
|
|
15042
|
+
// outcome is answered only when the caller listed it.
|
|
14934
15043
|
const resolveDropMeaning = ({
|
|
14935
15044
|
gestureInfo,
|
|
14936
15045
|
hasDropTarget,
|
|
14937
15046
|
releasedOutside,
|
|
15047
|
+
canMove,
|
|
14938
15048
|
canReorder,
|
|
14939
15049
|
canToss,
|
|
14940
15050
|
canLand,
|
|
@@ -14963,15 +15073,19 @@ const resolveDropMeaning = ({
|
|
|
14963
15073
|
return "reorder";
|
|
14964
15074
|
}
|
|
14965
15075
|
}
|
|
15076
|
+
// It has to have gone somewhere to be put anywhere, or away from anything:
|
|
15077
|
+
// picked up and put straight back down is a hand that changed its mind.
|
|
15078
|
+
const { xDelta, yDelta } = gestureInfo.layout;
|
|
15079
|
+
if (!xDelta && !yDelta) {
|
|
15080
|
+
return "cancel";
|
|
15081
|
+
}
|
|
14966
15082
|
if (canLeave && releasedOutside) {
|
|
14967
15083
|
// Let go of away from every place — off the plan and down, deliberate and
|
|
14968
|
-
// never a flick.
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
return "leave";
|
|
14974
|
-
}
|
|
15084
|
+
// never a flick.
|
|
15085
|
+
return "leave";
|
|
15086
|
+
}
|
|
15087
|
+
if (canMove) {
|
|
15088
|
+
return "move";
|
|
14975
15089
|
}
|
|
14976
15090
|
return "cancel";
|
|
14977
15091
|
};
|
|
@@ -15429,21 +15543,26 @@ const getRectInside = (element, containerElement) => {
|
|
|
15429
15543
|
};
|
|
15430
15544
|
};
|
|
15431
15545
|
|
|
15432
|
-
// Creates the two-layer clone structure
|
|
15546
|
+
// Creates the two-layer clone structure carried by reorder, land, toss and leave.
|
|
15433
15547
|
//
|
|
15434
15548
|
// Layer 1 — wrapper (navi-drag-clone-wrapper):
|
|
15435
|
-
// Positioned fixed via --clone
|
|
15436
|
-
//
|
|
15437
|
-
//
|
|
15549
|
+
// Positioned fixed via the --clone-* CSS vars, which also give it its size.
|
|
15550
|
+
// Moved every drag frame via dragStyleController.
|
|
15551
|
+
// It paints nothing, so it takes no view-transition-name: a name on it would
|
|
15552
|
+
// be a group with an empty image, and one more name to keep unique.
|
|
15438
15553
|
//
|
|
15439
15554
|
// Layer 2 — inner clone (navi-drag-clone):
|
|
15440
|
-
// A deep clone of the grabbed element
|
|
15441
|
-
//
|
|
15442
|
-
//
|
|
15443
|
-
//
|
|
15444
|
-
//
|
|
15555
|
+
// A deep clone of the grabbed element, and what the eye follows — so it is
|
|
15556
|
+
// the one named: a view-transition-name of its own (navi-drag-clone-<n>),
|
|
15557
|
+
// and the view-transition-class navi-drag-clone that every copy shares.
|
|
15558
|
+
// Casts the shadow and applies transform: scale(var(--drag-clone-scale, 1.03))
|
|
15559
|
+
// via the CSS rule for [navi-drag-clone], giving the "lifted" feel. The
|
|
15560
|
+
// transform-origin is set to the grab point so the element expands naturally
|
|
15561
|
+
// from where the user clicked.
|
|
15445
15562
|
// On release, the `navi-drag-clone` attribute is removed inside
|
|
15446
|
-
// startViewTransition to drop the scale back to 1 as the "new" state
|
|
15563
|
+
// startViewTransition to drop the scale back to 1 as the "new" state; the
|
|
15564
|
+
// name and the class stay, so the scale is morphed with the box rather than
|
|
15565
|
+
// cross-faded.
|
|
15447
15566
|
|
|
15448
15567
|
// The chevron is the one the table's column drop preview uses, rotated by the
|
|
15449
15568
|
// CSS above so each cap points into the line.
|
|
@@ -15688,6 +15807,7 @@ const liftDragClone = (cloneWrapper, pointerEvent) => {
|
|
|
15688
15807
|
cloneWrapper.firstElementChild.setAttribute("navi-drag-clone", "");
|
|
15689
15808
|
};
|
|
15690
15809
|
|
|
15810
|
+
let dragCloneCount = 0;
|
|
15691
15811
|
const createDragClone = (element, pointerEvent) => {
|
|
15692
15812
|
const rect = element.getBoundingClientRect();
|
|
15693
15813
|
|
|
@@ -15701,7 +15821,6 @@ const createDragClone = (element, pointerEvent) => {
|
|
|
15701
15821
|
// Manual: it is opened and closed with the drag, and must survive an Escape
|
|
15702
15822
|
// or a click elsewhere (light dismiss would take it away mid-gesture).
|
|
15703
15823
|
wrapper.setAttribute("popover", "manual");
|
|
15704
|
-
wrapper.viewTransitionName = "navi-drag-clone-wrapper";
|
|
15705
15824
|
setCloneViewportRect(wrapper, element);
|
|
15706
15825
|
// Grab point within the element — used as transform-origin so the
|
|
15707
15826
|
// scale expands from where the user clicked, not the element center.
|
|
@@ -15725,7 +15844,14 @@ const createDragClone = (element, pointerEvent) => {
|
|
|
15725
15844
|
// dresses `[data-grabbed]` on its own element once, and the copy is that element.
|
|
15726
15845
|
// (The original wears it too, but it is hidden — see navi-drag-clone-source.)
|
|
15727
15846
|
elementClone.setAttribute("data-grabbed", "");
|
|
15728
|
-
|
|
15847
|
+
// A name of its own: a copy outlives its gesture while its answer is asked,
|
|
15848
|
+
// so two can be on screen at once, and a name claimed twice aborts the
|
|
15849
|
+
// transition. The class is what every copy shares — inline like the name,
|
|
15850
|
+
// since a landing takes the [navi-drag-clone] attribute off before the
|
|
15851
|
+
// capture its group's class is read from.
|
|
15852
|
+
dragCloneCount++;
|
|
15853
|
+
elementClone.style.viewTransitionName = `navi-drag-clone-${dragCloneCount}`;
|
|
15854
|
+
elementClone.style.viewTransitionClass = "navi-drag-clone";
|
|
15729
15855
|
// The copy takes the wrapper's box, and nothing the page said about where the
|
|
15730
15856
|
// ORIGINAL stands may place it: a piece drawn at "left: 40px; top: 130px" on
|
|
15731
15857
|
// its board, a marker centred by a translate, a card pushed by a margin — the
|