@jsenv/dom 0.17.44 → 0.17.46
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 +1602 -1320
- package/package.json +1 -1
package/dist/jsenv_dom.js
CHANGED
|
@@ -4524,15 +4524,31 @@ const waitForPressHeld = (
|
|
|
4524
4524
|
}
|
|
4525
4525
|
cancelPress(pointerEndEvent);
|
|
4526
4526
|
};
|
|
4527
|
+
// Somebody else settled what this press is. Taking the pointer is how a gesture
|
|
4528
|
+
// says it — and it says it about the same finger this wait is counting on, so
|
|
4529
|
+
// whatever the press turned out to be, it is not a hold. Two waits on one press
|
|
4530
|
+
// is the ordinary case rather than an odd one: an element that can be picked up
|
|
4531
|
+
// AND held answers a finger with two delays, the shorter one wins, and without
|
|
4532
|
+
// this the longer one would answer a hundred milliseconds into the carry.
|
|
4533
|
+
// The listener goes with the countdown, so the gesture THIS wait starts (which
|
|
4534
|
+
// captures the pointer from inside onPressHeld) never reaches it.
|
|
4535
|
+
const onGotPointerCapture = (captureEvent) => {
|
|
4536
|
+
if (captureEvent.pointerId !== pointerId) {
|
|
4537
|
+
return;
|
|
4538
|
+
}
|
|
4539
|
+
cancelPress(captureEvent);
|
|
4540
|
+
};
|
|
4527
4541
|
// On window rather than on the element: the finger can leave it, and the
|
|
4528
4542
|
// element itself can be taken out of the document while the press is waiting.
|
|
4529
4543
|
window.addEventListener("pointermove", onPointerMove);
|
|
4530
4544
|
window.addEventListener("pointerup", onPointerEnd);
|
|
4531
4545
|
window.addEventListener("pointercancel", onPointerEnd);
|
|
4546
|
+
window.addEventListener("gotpointercapture", onGotPointerCapture, true);
|
|
4532
4547
|
countdownCleanupCallbacks.push(() => {
|
|
4533
4548
|
window.removeEventListener("pointermove", onPointerMove);
|
|
4534
4549
|
window.removeEventListener("pointerup", onPointerEnd);
|
|
4535
4550
|
window.removeEventListener("pointercancel", onPointerEnd);
|
|
4551
|
+
window.removeEventListener("gotpointercapture", onGotPointerCapture, true);
|
|
4536
4552
|
});
|
|
4537
4553
|
|
|
4538
4554
|
onPressStart?.(pressEvent);
|
|
@@ -7515,7 +7531,8 @@ const getScrollbarState = (
|
|
|
7515
7531
|
* scrollIntoViewScoped avoids this by targeting one container explicitly.
|
|
7516
7532
|
*
|
|
7517
7533
|
* Uses scrollTo() so CSS scroll-behavior:smooth on the container is respected.
|
|
7518
|
-
* Respects scroll-margin-* on the element
|
|
7534
|
+
* Respects scroll-margin-* on the element and scroll-padding-* on the
|
|
7535
|
+
* container.
|
|
7519
7536
|
*
|
|
7520
7537
|
* @param {Element} el - The element to scroll into view.
|
|
7521
7538
|
* @param {object} options
|
|
@@ -7621,6 +7638,31 @@ const getScrollIntoViewScopedOffsets = (
|
|
|
7621
7638
|
containerWidth = containerRect.width;
|
|
7622
7639
|
}
|
|
7623
7640
|
|
|
7641
|
+
// The band the container keeps free at each of its edges for whatever is
|
|
7642
|
+
// scrolled to — what a sticky header, a footer or a fixed bar reserves so an
|
|
7643
|
+
// element does not land underneath it (see navi's safe_area.js). The
|
|
7644
|
+
// scrollport shrinks by it on both axes, and every alignment below is against
|
|
7645
|
+
// that smaller rectangle.
|
|
7646
|
+
const containerStyle = getComputedStyle(container);
|
|
7647
|
+
const scrollPaddingTop = resolveScrollPadding(
|
|
7648
|
+
containerStyle.scrollPaddingTop,
|
|
7649
|
+
containerHeight,
|
|
7650
|
+
);
|
|
7651
|
+
const scrollPaddingBottom = resolveScrollPadding(
|
|
7652
|
+
containerStyle.scrollPaddingBottom,
|
|
7653
|
+
containerHeight,
|
|
7654
|
+
);
|
|
7655
|
+
const scrollPaddingLeft = resolveScrollPadding(
|
|
7656
|
+
containerStyle.scrollPaddingLeft,
|
|
7657
|
+
containerWidth,
|
|
7658
|
+
);
|
|
7659
|
+
const scrollPaddingRight = resolveScrollPadding(
|
|
7660
|
+
containerStyle.scrollPaddingRight,
|
|
7661
|
+
containerWidth,
|
|
7662
|
+
);
|
|
7663
|
+
const viewHeight = containerHeight - scrollPaddingTop - scrollPaddingBottom;
|
|
7664
|
+
const viewWidth = containerWidth - scrollPaddingLeft - scrollPaddingRight;
|
|
7665
|
+
|
|
7624
7666
|
// Element position relative to the container's scroll origin.
|
|
7625
7667
|
const elTop = elRect.top - containerTop + currentScrollTop - scrollMarginTop;
|
|
7626
7668
|
const elBottom = elTop + elRect.height + scrollMarginTop + scrollMarginBottom;
|
|
@@ -7628,55 +7670,67 @@ const getScrollIntoViewScopedOffsets = (
|
|
|
7628
7670
|
elRect.left - containerLeft + currentScrollLeft - scrollMarginLeft;
|
|
7629
7671
|
const elRight = elLeft + elRect.width + scrollMarginLeft + scrollMarginRight;
|
|
7630
7672
|
|
|
7673
|
+
// The two scroll positions that put the element against one edge of the view
|
|
7674
|
+
// or the other — every alignment below is one of them, or a point between.
|
|
7675
|
+
const scrollTopPuttingElAtViewStart = elTop - scrollPaddingTop;
|
|
7676
|
+
const scrollTopPuttingElAtViewEnd =
|
|
7677
|
+
elBottom - containerHeight + scrollPaddingBottom;
|
|
7678
|
+
const viewStart = currentScrollTop + scrollPaddingTop;
|
|
7679
|
+
const viewEnd = currentScrollTop + containerHeight - scrollPaddingBottom;
|
|
7680
|
+
|
|
7631
7681
|
let newScrollTop = currentScrollTop;
|
|
7632
7682
|
if (block === "start") {
|
|
7633
|
-
newScrollTop =
|
|
7683
|
+
newScrollTop = scrollTopPuttingElAtViewStart;
|
|
7634
7684
|
} else if (block === "end") {
|
|
7635
|
-
newScrollTop =
|
|
7685
|
+
newScrollTop = scrollTopPuttingElAtViewEnd;
|
|
7636
7686
|
} else if (block === "center") {
|
|
7637
|
-
newScrollTop = elTop + (elRect.height -
|
|
7687
|
+
newScrollTop = elTop + (elRect.height - viewHeight) / 2 - scrollPaddingTop;
|
|
7638
7688
|
} else {
|
|
7639
7689
|
// nearest: scroll only if partially or fully out of view.
|
|
7640
|
-
// When the element is taller than the
|
|
7690
|
+
// When the element is taller than the view, only scroll if it is
|
|
7641
7691
|
// completely out of view — otherwise it is already as visible as possible.
|
|
7642
|
-
const scrollBottom = currentScrollTop + containerHeight;
|
|
7643
7692
|
const elHeight = elBottom - elTop;
|
|
7644
|
-
if (elHeight <=
|
|
7645
|
-
if (elTop <
|
|
7646
|
-
newScrollTop =
|
|
7647
|
-
} else if (elBottom >
|
|
7648
|
-
newScrollTop =
|
|
7693
|
+
if (elHeight <= viewHeight) {
|
|
7694
|
+
if (elTop < viewStart) {
|
|
7695
|
+
newScrollTop = scrollTopPuttingElAtViewStart;
|
|
7696
|
+
} else if (elBottom > viewEnd) {
|
|
7697
|
+
newScrollTop = scrollTopPuttingElAtViewEnd;
|
|
7649
7698
|
}
|
|
7650
|
-
} else if (elBottom <
|
|
7651
|
-
newScrollTop =
|
|
7652
|
-
} else if (elTop >
|
|
7653
|
-
newScrollTop =
|
|
7699
|
+
} else if (elBottom < viewStart) {
|
|
7700
|
+
newScrollTop = scrollTopPuttingElAtViewEnd;
|
|
7701
|
+
} else if (elTop > viewEnd) {
|
|
7702
|
+
newScrollTop = scrollTopPuttingElAtViewStart;
|
|
7654
7703
|
}
|
|
7655
7704
|
}
|
|
7656
7705
|
|
|
7706
|
+
const scrollLeftPuttingElAtViewStart = elLeft - scrollPaddingLeft;
|
|
7707
|
+
const scrollLeftPuttingElAtViewEnd =
|
|
7708
|
+
elRight - containerWidth + scrollPaddingRight;
|
|
7709
|
+
const viewLeftEdge = currentScrollLeft + scrollPaddingLeft;
|
|
7710
|
+
const viewRightEdge = currentScrollLeft + containerWidth - scrollPaddingRight;
|
|
7711
|
+
|
|
7657
7712
|
let newScrollLeft = currentScrollLeft;
|
|
7658
7713
|
if (inline === "start") {
|
|
7659
|
-
newScrollLeft =
|
|
7714
|
+
newScrollLeft = scrollLeftPuttingElAtViewStart;
|
|
7660
7715
|
} else if (inline === "end") {
|
|
7661
|
-
newScrollLeft =
|
|
7716
|
+
newScrollLeft = scrollLeftPuttingElAtViewEnd;
|
|
7662
7717
|
} else if (inline === "center") {
|
|
7663
|
-
newScrollLeft = elLeft + (elRect.width -
|
|
7718
|
+
newScrollLeft = elLeft + (elRect.width - viewWidth) / 2 - scrollPaddingLeft;
|
|
7664
7719
|
} else {
|
|
7665
7720
|
// nearest: scroll only if partially or fully out of view.
|
|
7666
|
-
// When the element is wider than the
|
|
7721
|
+
// When the element is wider than the view, only scroll if it is
|
|
7667
7722
|
// completely out of view — otherwise it is already as visible as possible.
|
|
7668
|
-
const scrollRight = currentScrollLeft + containerWidth;
|
|
7669
7723
|
const elWidth = elRight - elLeft;
|
|
7670
|
-
if (elWidth <=
|
|
7671
|
-
if (elLeft <
|
|
7672
|
-
newScrollLeft =
|
|
7673
|
-
} else if (elRight >
|
|
7674
|
-
newScrollLeft =
|
|
7724
|
+
if (elWidth <= viewWidth) {
|
|
7725
|
+
if (elLeft < viewLeftEdge) {
|
|
7726
|
+
newScrollLeft = scrollLeftPuttingElAtViewStart;
|
|
7727
|
+
} else if (elRight > viewRightEdge) {
|
|
7728
|
+
newScrollLeft = scrollLeftPuttingElAtViewEnd;
|
|
7675
7729
|
}
|
|
7676
|
-
} else if (elRight <
|
|
7677
|
-
newScrollLeft =
|
|
7678
|
-
} else if (elLeft >
|
|
7679
|
-
newScrollLeft =
|
|
7730
|
+
} else if (elRight < viewLeftEdge) {
|
|
7731
|
+
newScrollLeft = scrollLeftPuttingElAtViewEnd;
|
|
7732
|
+
} else if (elLeft > viewRightEdge) {
|
|
7733
|
+
newScrollLeft = scrollLeftPuttingElAtViewStart;
|
|
7680
7734
|
}
|
|
7681
7735
|
}
|
|
7682
7736
|
|
|
@@ -7686,6 +7740,20 @@ const getScrollIntoViewScopedOffsets = (
|
|
|
7686
7740
|
};
|
|
7687
7741
|
};
|
|
7688
7742
|
|
|
7743
|
+
// "auto" is the one keyword scroll-padding takes, and it means "the browser
|
|
7744
|
+
// picks" — which is 0 everywhere in practice. Percentages resolve against the
|
|
7745
|
+
// scrollport, and getComputedStyle hands them back as written.
|
|
7746
|
+
const resolveScrollPadding = (value, scrollportSize) => {
|
|
7747
|
+
const number = parseFloat(value);
|
|
7748
|
+
if (Number.isNaN(number)) {
|
|
7749
|
+
return 0;
|
|
7750
|
+
}
|
|
7751
|
+
if (value.endsWith("%")) {
|
|
7752
|
+
return (number / 100) * scrollportSize;
|
|
7753
|
+
}
|
|
7754
|
+
return number;
|
|
7755
|
+
};
|
|
7756
|
+
|
|
7689
7757
|
/**
|
|
7690
7758
|
* DON'T USE THIS, use scroll-padding-top/bottom in CSS instead
|
|
7691
7759
|
* better in every aspect
|
|
@@ -8487,17 +8555,9 @@ const isolateInteractions = (elements) => {
|
|
|
8487
8555
|
};
|
|
8488
8556
|
};
|
|
8489
8557
|
|
|
8490
|
-
installImportMetaCssBuild(import.meta)
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
* TODO: rename moveX/moveY en juste x/y
|
|
8494
|
-
* puisque move c'est perturbant sachant que c'est drag + scroll
|
|
8495
|
-
* et que drag c'est juste la partie mouvement de la souris
|
|
8496
|
-
*
|
|
8497
|
-
* donc juste x/y ca seras surement mieux
|
|
8498
|
-
*
|
|
8499
|
-
*/
|
|
8500
|
-
const css$5 = /* css */`.navi_drag_gesture_backdrop {
|
|
8558
|
+
installImportMetaCssBuild(import.meta);
|
|
8559
|
+
|
|
8560
|
+
const css$5 = /* css */ `.navi_drag_gesture_backdrop {
|
|
8501
8561
|
touch-action: none;
|
|
8502
8562
|
user-select: none;
|
|
8503
8563
|
position: fixed;
|
|
@@ -8521,6 +8581,51 @@ import.meta.css = [css$5, "@jsenv/dom/src/interaction/drag/drag_gesture.js"];
|
|
|
8521
8581
|
* apart: `lostpointercapture` says the same thing either way.
|
|
8522
8582
|
*/
|
|
8523
8583
|
const captureHolderByPointerId = new Map();
|
|
8584
|
+
|
|
8585
|
+
/**
|
|
8586
|
+
* The drag loop: it takes the pointer, follows it, and says when it is let go of.
|
|
8587
|
+
* What the movement MEANS is the caller's — this one carries nothing, moves
|
|
8588
|
+
* nothing and commits nothing on its own, which is what makes it the primitive
|
|
8589
|
+
* for a gesture whose product is a value (an angle, a scale, an offset) rather
|
|
8590
|
+
* than an element that ends up somewhere.
|
|
8591
|
+
*
|
|
8592
|
+
* While it runs it owns the document: everything but the dragged element (and the
|
|
8593
|
+
* `[data-droppable]` places) goes inert, a backdrop unifies the cursor and takes
|
|
8594
|
+
* the pointer events, the focus moves to what is held and comes back at the
|
|
8595
|
+
* release, and the selection is refused for the length of the gesture.
|
|
8596
|
+
*
|
|
8597
|
+
* Whether the element may be grabbed AT ALL is decided before the finger lands
|
|
8598
|
+
* and elsewhere: see `markDragSource` in drag_after_intent.js for the
|
|
8599
|
+
* touch-action half, and `dragAfterIntent` for when a press becomes a drag.
|
|
8600
|
+
*
|
|
8601
|
+
* @param {object} [options]
|
|
8602
|
+
* @param {string} [options.name] Carried on `gestureInfo`, for whoever reads a gesture they did not start.
|
|
8603
|
+
* @param {(gestureInfo: object) => void} [options.onGrab]
|
|
8604
|
+
* The pointer went down: there is a gesture, which may still turn out to be a press.
|
|
8605
|
+
* @param {(gestureInfo: object) => void} [options.onDragStart] The threshold was crossed — this one IS a drag.
|
|
8606
|
+
* @param {(gestureInfo: object, someLayoutChange: boolean) => void} [options.onDrag]
|
|
8607
|
+
* Every move, and every scroll of the container: `someLayoutChange` is false when
|
|
8608
|
+
* nothing about the element changed, which still matters to whatever sticks to the scroll.
|
|
8609
|
+
* @param {(gestureInfo: object) => void} [options.onRelease]
|
|
8610
|
+
* The hand let go — or did not: `gestureInfo.cancelled` says the gesture was taken away
|
|
8611
|
+
* (the browser took the touch to scroll with, another gesture took the pointer), and a
|
|
8612
|
+
* release nobody asked for says nothing about where things belong, so nothing read from
|
|
8613
|
+
* it may be committed.
|
|
8614
|
+
* @param {number} [options.threshold=5] How far (px) the pointer travels before the drag starts. `0` starts it at the grab.
|
|
8615
|
+
* @param {{x: boolean, y: boolean}} [options.direction] Which axes the gesture reads.
|
|
8616
|
+
* @param {"auto"|"manual"} [options.documentInteractions="auto"]
|
|
8617
|
+
* `"manual"` leaves the document alone — no inertness, no backdrop, no focus taken. For a
|
|
8618
|
+
* gesture that only measures and hands over (see `dragAfterIntent`).
|
|
8619
|
+
* @param {boolean} [options.backdrop=true]
|
|
8620
|
+
* @param {number} [options.backdropZIndex=999999]
|
|
8621
|
+
* @returns {{grab: function, grabViaPointer: function}}
|
|
8622
|
+
* `grabViaPointer(pointerdownEvent, { element, direction, cursor, scrollContainer })` for a
|
|
8623
|
+
* gesture a hand starts, `grab({ element, grabX, grabY })` for one nothing pressed. Both give
|
|
8624
|
+
* back the gesture — `gestureInfo` (its `layout` with `xDelta`/`yDelta`, its velocity, the way
|
|
8625
|
+
* it is going), `drag`, `release`, and `addDragCallback`/`addReleaseCallback` for whoever joins
|
|
8626
|
+
* after it started. `grabViaPointer` gives back `null` when the press is not one it can read: a
|
|
8627
|
+
* secondary button, a text node.
|
|
8628
|
+
*/
|
|
8524
8629
|
const createDragGestureController = (options = {}) => {
|
|
8525
8630
|
const {
|
|
8526
8631
|
name,
|
|
@@ -8529,18 +8634,17 @@ const createDragGestureController = (options = {}) => {
|
|
|
8529
8634
|
onDrag,
|
|
8530
8635
|
onRelease,
|
|
8531
8636
|
threshold = 5,
|
|
8532
|
-
direction: defaultDirection = {
|
|
8533
|
-
x: true,
|
|
8534
|
-
y: true
|
|
8535
|
-
},
|
|
8637
|
+
direction: defaultDirection = { x: true, y: true },
|
|
8536
8638
|
documentInteractions = "auto",
|
|
8537
8639
|
backdrop = true,
|
|
8538
|
-
backdropZIndex = 999999
|
|
8640
|
+
backdropZIndex = 999999,
|
|
8539
8641
|
} = options;
|
|
8642
|
+
|
|
8540
8643
|
const dragGestureController = {
|
|
8541
8644
|
grab: null,
|
|
8542
|
-
gravViaPointer: null
|
|
8645
|
+
gravViaPointer: null,
|
|
8543
8646
|
};
|
|
8647
|
+
|
|
8544
8648
|
const grab = ({
|
|
8545
8649
|
element,
|
|
8546
8650
|
direction = defaultDirection,
|
|
@@ -8550,7 +8654,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8550
8654
|
cursor = "grabbing",
|
|
8551
8655
|
scrollContainer = document.documentElement,
|
|
8552
8656
|
layoutScrollableLeft: scrollableLeftAtGrab = 0,
|
|
8553
|
-
layoutScrollableTop: scrollableTopAtGrab = 0
|
|
8657
|
+
layoutScrollableTop: scrollableTopAtGrab = 0,
|
|
8554
8658
|
} = {}) => {
|
|
8555
8659
|
if (!element) {
|
|
8556
8660
|
throw new Error("element is required");
|
|
@@ -8558,6 +8662,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8558
8662
|
if (!direction.x && !direction.y) {
|
|
8559
8663
|
return null;
|
|
8560
8664
|
}
|
|
8665
|
+
|
|
8561
8666
|
const [publishBeforeDrag, addBeforeDragCallback] = createPubSub();
|
|
8562
8667
|
const [publishDrag, addDragCallback] = createPubSub();
|
|
8563
8668
|
const [publishRelease, addReleaseCallback] = createPubSub();
|
|
@@ -8567,15 +8672,13 @@ const createDragGestureController = (options = {}) => {
|
|
|
8567
8672
|
if (onRelease) {
|
|
8568
8673
|
addReleaseCallback(onRelease);
|
|
8569
8674
|
}
|
|
8675
|
+
|
|
8570
8676
|
const scrollLeftAtGrab = scrollContainer.scrollLeft;
|
|
8571
8677
|
const scrollTopAtGrab = scrollContainer.scrollTop;
|
|
8572
8678
|
const leftAtGrab = scrollLeftAtGrab + scrollableLeftAtGrab;
|
|
8573
8679
|
const topAtGrab = scrollTopAtGrab + scrollableTopAtGrab;
|
|
8574
8680
|
const createLayout = (x, y) => {
|
|
8575
|
-
const {
|
|
8576
|
-
scrollLeft,
|
|
8577
|
-
scrollTop
|
|
8578
|
-
} = scrollContainer;
|
|
8681
|
+
const { scrollLeft, scrollTop } = scrollContainer;
|
|
8579
8682
|
const left = scrollableLeftAtGrab + x;
|
|
8580
8683
|
const top = scrollableTopAtGrab + y;
|
|
8581
8684
|
const scrollableLeft = left - scrollLeft;
|
|
@@ -8595,30 +8698,33 @@ const createDragGestureController = (options = {}) => {
|
|
|
8595
8698
|
top,
|
|
8596
8699
|
// Delta since grab (number representing how much we dragged)
|
|
8597
8700
|
xDelta: left - leftAtGrab,
|
|
8598
|
-
yDelta: top - topAtGrab
|
|
8701
|
+
yDelta: top - topAtGrab,
|
|
8599
8702
|
};
|
|
8600
8703
|
return layoutProps;
|
|
8601
8704
|
};
|
|
8602
|
-
|
|
8705
|
+
|
|
8706
|
+
const grabLayout = createLayout(
|
|
8707
|
+
grabX + scrollContainer.scrollLeft,
|
|
8708
|
+
grabY + scrollContainer.scrollTop,
|
|
8709
|
+
);
|
|
8603
8710
|
const gestureInfo = {
|
|
8604
8711
|
name,
|
|
8605
8712
|
direction,
|
|
8606
8713
|
started: !threshold,
|
|
8607
8714
|
status: "grabbed",
|
|
8715
|
+
|
|
8608
8716
|
element,
|
|
8609
8717
|
scrollContainer,
|
|
8610
|
-
grabX,
|
|
8611
|
-
//
|
|
8612
|
-
grabY,
|
|
8613
|
-
// y grab coordinate (excluding scroll)
|
|
8718
|
+
grabX, // x grab coordinate (excluding scroll)
|
|
8719
|
+
grabY, // y grab coordinate (excluding scroll)
|
|
8614
8720
|
grabLayout,
|
|
8615
8721
|
leftAtGrab,
|
|
8616
8722
|
topAtGrab,
|
|
8617
|
-
|
|
8618
|
-
// coordinate of the last drag (excluding scroll of the scrollContainer)
|
|
8619
|
-
dragY: grabY,
|
|
8620
|
-
// coordinate of the last drag (excluding scroll of the scrollContainer)
|
|
8723
|
+
|
|
8724
|
+
dragX: grabX, // coordinate of the last drag (excluding scroll of the scrollContainer)
|
|
8725
|
+
dragY: grabY, // coordinate of the last drag (excluding scroll of the scrollContainer)
|
|
8621
8726
|
layout: grabLayout,
|
|
8727
|
+
|
|
8622
8728
|
isGoingUp: undefined,
|
|
8623
8729
|
isGoingDown: undefined,
|
|
8624
8730
|
isGoingLeft: undefined,
|
|
@@ -8627,11 +8733,13 @@ const createDragGestureController = (options = {}) => {
|
|
|
8627
8733
|
intentGoingDown: false,
|
|
8628
8734
|
intentGoingLeft: false,
|
|
8629
8735
|
intentGoingRight: false,
|
|
8736
|
+
|
|
8630
8737
|
// How fast the pointer is going, in px/ms, signed per axis
|
|
8631
8738
|
// (see measureVelocity)
|
|
8632
8739
|
velocityX: 0,
|
|
8633
8740
|
velocityY: 0,
|
|
8634
8741
|
velocity: 0,
|
|
8742
|
+
|
|
8635
8743
|
// metadata about interaction sources
|
|
8636
8744
|
grabEvent: event,
|
|
8637
8745
|
dragEvent: null,
|
|
@@ -8640,7 +8748,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8640
8748
|
// the touch to scroll with, another gesture took the pointer. Whoever
|
|
8641
8749
|
// reads it must not commit anything — a release that was not asked for
|
|
8642
8750
|
// says nothing about where things belong.
|
|
8643
|
-
cancelled: false
|
|
8751
|
+
cancelled: false,
|
|
8644
8752
|
};
|
|
8645
8753
|
definePropertyAsReadOnly(gestureInfo, "name");
|
|
8646
8754
|
definePropertyAsReadOnly(gestureInfo, "direction");
|
|
@@ -8656,6 +8764,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8656
8764
|
// matter of speed, and the gesture is the only place that sees the timing of
|
|
8657
8765
|
// the events it receives.
|
|
8658
8766
|
const measureVelocity = createVelocityMeter(grabX, grabY);
|
|
8767
|
+
|
|
8659
8768
|
document_interactions: {
|
|
8660
8769
|
if (documentInteractions === "manual") {
|
|
8661
8770
|
break document_interactions;
|
|
@@ -8668,18 +8777,23 @@ const createDragGestureController = (options = {}) => {
|
|
|
8668
8777
|
2. Break the visual feedback (inconsistent cursors, hover states)
|
|
8669
8778
|
3. Cause unwanted scrolling (keyboard shortcuts, wheel events in restricted directions)
|
|
8670
8779
|
4. Create accessibility issues (focus jumping, screen reader confusion)
|
|
8671
|
-
|
|
8780
|
+
|
|
8781
|
+
STRATEGY: Create a controlled interaction environment by:
|
|
8672
8782
|
1. VISUAL CONTROL: Use a backdrop to unify cursor appearance and block pointer events
|
|
8673
8783
|
2. INTERACTION ISOLATION: Make non-dragged elements inert to prevent interference
|
|
8674
8784
|
3. FOCUS MANAGEMENT: Control focus location and prevent focus changes during drag
|
|
8675
8785
|
4. SELECTIVE SCROLLING: Allow scrolling only in directions supported by the drag gesture
|
|
8676
|
-
|
|
8786
|
+
|
|
8787
|
+
IMPLEMENTATION:
|
|
8677
8788
|
*/
|
|
8678
8789
|
|
|
8679
8790
|
// 1. INTERACTION ISOLATION: Make everything except the dragged element inert
|
|
8680
8791
|
// This prevents keyboard events, pointer interactions, and screen reader navigation
|
|
8681
8792
|
// on non-relevant elements during the drag operation
|
|
8682
|
-
const cleanupInert = isolateInteractions([
|
|
8793
|
+
const cleanupInert = isolateInteractions([
|
|
8794
|
+
element,
|
|
8795
|
+
...Array.from(document.querySelectorAll("[data-droppable]")),
|
|
8796
|
+
]);
|
|
8683
8797
|
addReleaseCallback(() => {
|
|
8684
8798
|
cleanupInert();
|
|
8685
8799
|
});
|
|
@@ -8696,14 +8810,14 @@ const createDragGestureController = (options = {}) => {
|
|
|
8696
8810
|
// Handle wheel events on backdrop for directionally-constrained drag gestures
|
|
8697
8811
|
// (e.g., table column resize should only allow horizontal scrolling)
|
|
8698
8812
|
if (!direction.x || !direction.y) {
|
|
8699
|
-
backdropElement.onwheel = e => {
|
|
8813
|
+
backdropElement.onwheel = (e) => {
|
|
8700
8814
|
e.preventDefault();
|
|
8701
8815
|
const scrollX = direction.x ? e.deltaX : 0;
|
|
8702
8816
|
const scrollY = direction.y ? e.deltaY : 0;
|
|
8703
8817
|
scrollContainer.scrollBy({
|
|
8704
8818
|
left: scrollX,
|
|
8705
8819
|
top: scrollY,
|
|
8706
|
-
behavior: "auto"
|
|
8820
|
+
behavior: "auto",
|
|
8707
8821
|
});
|
|
8708
8822
|
};
|
|
8709
8823
|
}
|
|
@@ -8714,9 +8828,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8714
8828
|
}
|
|
8715
8829
|
|
|
8716
8830
|
// 3. FOCUS MANAGEMENT: Control and stabilize focus during drag
|
|
8717
|
-
const {
|
|
8718
|
-
activeElement
|
|
8719
|
-
} = document;
|
|
8831
|
+
const { activeElement } = document;
|
|
8720
8832
|
const focusableElement = findFocusable(element);
|
|
8721
8833
|
// Focus the dragged element (or document.body as fallback) to establish clear focus context
|
|
8722
8834
|
// This also ensure any keydown event listened by the currently focused element
|
|
@@ -8724,17 +8836,17 @@ const createDragGestureController = (options = {}) => {
|
|
|
8724
8836
|
const elementToFocus = focusableElement || document.body;
|
|
8725
8837
|
elementToFocus.setAttribute("data-drag-focus", "");
|
|
8726
8838
|
elementToFocus.focus({
|
|
8727
|
-
preventScroll: true
|
|
8839
|
+
preventScroll: true,
|
|
8728
8840
|
});
|
|
8729
8841
|
addReleaseCallback(() => {
|
|
8730
8842
|
elementToFocus.removeAttribute("data-drag-focus");
|
|
8731
8843
|
// Restore original focus on release
|
|
8732
8844
|
activeElement.focus({
|
|
8733
|
-
preventScroll: true
|
|
8845
|
+
preventScroll: true,
|
|
8734
8846
|
});
|
|
8735
8847
|
});
|
|
8736
8848
|
// Prevent Tab navigation entirely (focus should stay stable)
|
|
8737
|
-
const onkeydown = e => {
|
|
8849
|
+
const onkeydown = (e) => {
|
|
8738
8850
|
if (e.key === "Tab") {
|
|
8739
8851
|
e.preventDefault();
|
|
8740
8852
|
return;
|
|
@@ -8747,16 +8859,27 @@ const createDragGestureController = (options = {}) => {
|
|
|
8747
8859
|
|
|
8748
8860
|
// 4. SELECTIVE SCROLLING: Allow keyboard scrolling only in supported directions
|
|
8749
8861
|
{
|
|
8750
|
-
const onDocumentKeydown = keyboardEvent => {
|
|
8862
|
+
const onDocumentKeydown = (keyboardEvent) => {
|
|
8751
8863
|
// Vertical scrolling keys - prevent if vertical movement not supported
|
|
8752
|
-
if (
|
|
8864
|
+
if (
|
|
8865
|
+
keyboardEvent.key === "ArrowUp" ||
|
|
8866
|
+
keyboardEvent.key === "ArrowDown" ||
|
|
8867
|
+
keyboardEvent.key === " " ||
|
|
8868
|
+
keyboardEvent.key === "PageUp" ||
|
|
8869
|
+
keyboardEvent.key === "PageDown" ||
|
|
8870
|
+
keyboardEvent.key === "Home" ||
|
|
8871
|
+
keyboardEvent.key === "End"
|
|
8872
|
+
) {
|
|
8753
8873
|
if (!direction.y) {
|
|
8754
8874
|
keyboardEvent.preventDefault();
|
|
8755
8875
|
}
|
|
8756
8876
|
return;
|
|
8757
8877
|
}
|
|
8758
8878
|
// Horizontal scrolling keys - prevent if horizontal movement not supported
|
|
8759
|
-
if (
|
|
8879
|
+
if (
|
|
8880
|
+
keyboardEvent.key === "ArrowLeft" ||
|
|
8881
|
+
keyboardEvent.key === "ArrowRight"
|
|
8882
|
+
) {
|
|
8760
8883
|
if (!direction.x) {
|
|
8761
8884
|
keyboardEvent.preventDefault();
|
|
8762
8885
|
}
|
|
@@ -8773,38 +8896,36 @@ const createDragGestureController = (options = {}) => {
|
|
|
8773
8896
|
// Set up scroll event handling to adjust drag position when scrolling occurs
|
|
8774
8897
|
{
|
|
8775
8898
|
let isHandlingScroll = false;
|
|
8776
|
-
const handleScroll = scrollEvent => {
|
|
8899
|
+
const handleScroll = (scrollEvent) => {
|
|
8777
8900
|
if (isHandlingScroll) {
|
|
8778
8901
|
return;
|
|
8779
8902
|
}
|
|
8780
8903
|
isHandlingScroll = true;
|
|
8781
|
-
drag(gestureInfo.dragX, gestureInfo.dragY, {
|
|
8782
|
-
event: scrollEvent
|
|
8783
|
-
});
|
|
8904
|
+
drag(gestureInfo.dragX, gestureInfo.dragY, { event: scrollEvent });
|
|
8784
8905
|
isHandlingScroll = false;
|
|
8785
8906
|
};
|
|
8786
|
-
const scrollEventReceiver =
|
|
8907
|
+
const scrollEventReceiver =
|
|
8908
|
+
scrollContainer === document.documentElement
|
|
8909
|
+
? document
|
|
8910
|
+
: scrollContainer;
|
|
8787
8911
|
scrollEventReceiver.addEventListener("scroll", handleScroll, {
|
|
8788
|
-
passive: true
|
|
8912
|
+
passive: true,
|
|
8789
8913
|
});
|
|
8790
8914
|
addReleaseCallback(() => {
|
|
8791
8915
|
scrollEventReceiver.removeEventListener("scroll", handleScroll, {
|
|
8792
|
-
passive: true
|
|
8916
|
+
passive: true,
|
|
8793
8917
|
});
|
|
8794
8918
|
});
|
|
8795
8919
|
}
|
|
8920
|
+
|
|
8796
8921
|
const determineDragData = ({
|
|
8797
8922
|
dragX,
|
|
8798
8923
|
dragY,
|
|
8799
8924
|
dragEvent,
|
|
8800
|
-
isRelease = false
|
|
8925
|
+
isRelease = false,
|
|
8801
8926
|
}) => {
|
|
8802
8927
|
// === ÉTAT INITIAL (au moment du grab) ===
|
|
8803
|
-
const {
|
|
8804
|
-
grabX,
|
|
8805
|
-
grabY,
|
|
8806
|
-
grabLayout
|
|
8807
|
-
} = gestureInfo;
|
|
8928
|
+
const { grabX, grabY, grabLayout } = gestureInfo;
|
|
8808
8929
|
// === CE QUI EST DEMANDÉ (où on veut aller) ===
|
|
8809
8930
|
// Calcul de la direction basé sur le mouvement précédent
|
|
8810
8931
|
// (ne tient pas compte du mouvement final une fois les contraintes appliquées)
|
|
@@ -8816,38 +8937,56 @@ const createDragGestureController = (options = {}) => {
|
|
|
8816
8937
|
const isGoingRight = dragX > currentDragX;
|
|
8817
8938
|
const isGoingUp = dragY < currentDragY;
|
|
8818
8939
|
const isGoingDown = dragY > currentDragY;
|
|
8819
|
-
|
|
8820
|
-
const
|
|
8940
|
+
|
|
8941
|
+
const layoutXRequested = direction.x
|
|
8942
|
+
? scrollContainer.scrollLeft + (dragX - grabX)
|
|
8943
|
+
: grabLayout.scrollLeft;
|
|
8944
|
+
const layoutYRequested = direction.y
|
|
8945
|
+
? scrollContainer.scrollTop + (dragY - grabY)
|
|
8946
|
+
: grabLayout.scrollTop;
|
|
8821
8947
|
const layoutRequested = createLayout(layoutXRequested, layoutYRequested);
|
|
8822
8948
|
const currentLayout = gestureInfo.layout;
|
|
8823
8949
|
let layout;
|
|
8824
|
-
if (
|
|
8950
|
+
if (
|
|
8951
|
+
layoutRequested.x === currentLayout.x &&
|
|
8952
|
+
layoutRequested.y === currentLayout.y
|
|
8953
|
+
) {
|
|
8825
8954
|
layout = currentLayout;
|
|
8826
8955
|
} else {
|
|
8827
8956
|
// === APPLICATION DES CONTRAINTES ===
|
|
8828
8957
|
let layoutConstrained = layoutRequested;
|
|
8829
8958
|
const limitLayout = (left, top) => {
|
|
8830
|
-
layoutConstrained = createLayout(
|
|
8959
|
+
layoutConstrained = createLayout(
|
|
8960
|
+
left === undefined
|
|
8961
|
+
? layoutConstrained.x
|
|
8962
|
+
: left - scrollableLeftAtGrab,
|
|
8963
|
+
top === undefined ? layoutConstrained.y : top - scrollableTopAtGrab,
|
|
8964
|
+
);
|
|
8831
8965
|
};
|
|
8966
|
+
|
|
8832
8967
|
publishBeforeDrag(layoutRequested, currentLayout, limitLayout, {
|
|
8833
8968
|
dragEvent,
|
|
8834
|
-
isRelease
|
|
8969
|
+
isRelease,
|
|
8835
8970
|
});
|
|
8836
8971
|
// === ÉTAT FINAL ===
|
|
8837
8972
|
layout = layoutConstrained;
|
|
8838
8973
|
}
|
|
8974
|
+
|
|
8839
8975
|
const dragData = {
|
|
8840
8976
|
dragX,
|
|
8841
8977
|
dragY,
|
|
8842
8978
|
layout,
|
|
8979
|
+
|
|
8843
8980
|
isGoingLeft,
|
|
8844
8981
|
isGoingRight,
|
|
8845
8982
|
isGoingUp,
|
|
8846
8983
|
isGoingDown,
|
|
8984
|
+
|
|
8847
8985
|
status: isRelease ? "released" : "dragging",
|
|
8848
8986
|
dragEvent: isRelease ? gestureInfo.dragEvent : dragEvent,
|
|
8849
|
-
releaseEvent: isRelease ? dragEvent : null
|
|
8987
|
+
releaseEvent: isRelease ? dragEvent : null,
|
|
8850
8988
|
};
|
|
8989
|
+
|
|
8851
8990
|
if (isRelease) {
|
|
8852
8991
|
return dragData;
|
|
8853
8992
|
}
|
|
@@ -8872,6 +9011,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8872
9011
|
}
|
|
8873
9012
|
return dragData;
|
|
8874
9013
|
};
|
|
9014
|
+
|
|
8875
9015
|
const markAsStarted = () => {
|
|
8876
9016
|
const clickSuppressionIsOver = suppressClickAfterGesture();
|
|
8877
9017
|
addReleaseCallback(clickSuppressionIsOver);
|
|
@@ -8884,7 +9024,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8884
9024
|
// would then outlive it: what one sees is a click swallowed long after
|
|
8885
9025
|
// the drag it belonged to.
|
|
8886
9026
|
dispatchPublicCustomEvent(element, "navi_drag_start", {
|
|
8887
|
-
gestureInfo
|
|
9027
|
+
gestureInfo,
|
|
8888
9028
|
});
|
|
8889
9029
|
onDragStart?.(gestureInfo);
|
|
8890
9030
|
};
|
|
@@ -8899,19 +9039,18 @@ const createDragGestureController = (options = {}) => {
|
|
|
8899
9039
|
gestureInfo.started = true;
|
|
8900
9040
|
markAsStarted();
|
|
8901
9041
|
};
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
} = {}) => {
|
|
9042
|
+
|
|
9043
|
+
const drag = (
|
|
9044
|
+
dragX = gestureInfo.dragX, // Scroll container relative X coordinate
|
|
9045
|
+
dragY = gestureInfo.dragY, // Scroll container relative Y coordinate
|
|
9046
|
+
{ event = new CustomEvent("programmatic"), isRelease = false } = {},
|
|
9047
|
+
) => {
|
|
9048
|
+
|
|
8910
9049
|
const dragData = determineDragData({
|
|
8911
9050
|
dragX,
|
|
8912
9051
|
dragY,
|
|
8913
9052
|
dragEvent: event,
|
|
8914
|
-
isRelease
|
|
9053
|
+
isRelease,
|
|
8915
9054
|
});
|
|
8916
9055
|
const [velocityX, velocityY] = measureVelocity(dragX, dragY);
|
|
8917
9056
|
const startedPrevious = gestureInfo.started;
|
|
@@ -8941,36 +9080,33 @@ const createDragGestureController = (options = {}) => {
|
|
|
8941
9080
|
const someLayoutChange = gestureInfo.layout !== layoutPrevious;
|
|
8942
9081
|
dispatchPublicCustomEvent(element, "navi_drag", {
|
|
8943
9082
|
gestureInfo,
|
|
8944
|
-
someLayoutChange
|
|
9083
|
+
someLayoutChange,
|
|
8945
9084
|
});
|
|
8946
|
-
publishDrag(
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
9085
|
+
publishDrag(
|
|
9086
|
+
gestureInfo,
|
|
9087
|
+
// we still publish drag event even when unchanged
|
|
9088
|
+
// because UI might need to adjust when document scrolls
|
|
9089
|
+
// even if nothing truly changes visually the element
|
|
9090
|
+
// can decide to stick to the scroll for example
|
|
9091
|
+
someLayoutChange,
|
|
9092
|
+
);
|
|
8952
9093
|
};
|
|
9094
|
+
|
|
8953
9095
|
const release = ({
|
|
8954
9096
|
event = new CustomEvent("programmatic"),
|
|
8955
9097
|
cancelled = event.type === "pointercancel",
|
|
8956
9098
|
releaseX = gestureInfo.dragX,
|
|
8957
|
-
releaseY = gestureInfo.dragY
|
|
9099
|
+
releaseY = gestureInfo.dragY,
|
|
8958
9100
|
} = {}) => {
|
|
8959
9101
|
// Written before the last drag is reported, so the callbacks reading the
|
|
8960
9102
|
// end of the gesture all see the same one.
|
|
8961
9103
|
gestureInfo.cancelled = cancelled;
|
|
8962
|
-
drag(releaseX, releaseY, {
|
|
8963
|
-
|
|
8964
|
-
isRelease: true
|
|
8965
|
-
});
|
|
8966
|
-
dispatchPublicCustomEvent(element, "navi_drag_release", {
|
|
8967
|
-
gestureInfo
|
|
8968
|
-
});
|
|
9104
|
+
drag(releaseX, releaseY, { event, isRelease: true });
|
|
9105
|
+
dispatchPublicCustomEvent(element, "navi_drag_release", { gestureInfo });
|
|
8969
9106
|
publishRelease(gestureInfo);
|
|
8970
9107
|
};
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
});
|
|
9108
|
+
|
|
9109
|
+
dispatchPublicCustomEvent(element, "navi_drag_grab", { gestureInfo });
|
|
8974
9110
|
onGrab?.(gestureInfo);
|
|
8975
9111
|
const dragGesture = {
|
|
8976
9112
|
gestureInfo,
|
|
@@ -8979,11 +9115,12 @@ const createDragGestureController = (options = {}) => {
|
|
|
8979
9115
|
addReleaseCallback,
|
|
8980
9116
|
start,
|
|
8981
9117
|
drag,
|
|
8982
|
-
release
|
|
9118
|
+
release,
|
|
8983
9119
|
};
|
|
8984
9120
|
return dragGesture;
|
|
8985
9121
|
};
|
|
8986
9122
|
dragGestureController.grab = grab;
|
|
9123
|
+
|
|
8987
9124
|
const initDragByPointer = (grabEvent, dragOptions, initializer) => {
|
|
8988
9125
|
if (!isPrimaryButtonEvent(grabEvent)) {
|
|
8989
9126
|
return null;
|
|
@@ -8993,11 +9130,8 @@ const createDragGestureController = (options = {}) => {
|
|
|
8993
9130
|
// target is a text node
|
|
8994
9131
|
return null;
|
|
8995
9132
|
}
|
|
8996
|
-
const mouseEventCoords = mouseEvent => {
|
|
8997
|
-
const {
|
|
8998
|
-
clientX,
|
|
8999
|
-
clientY
|
|
9000
|
-
} = mouseEvent;
|
|
9133
|
+
const mouseEventCoords = (mouseEvent) => {
|
|
9134
|
+
const { clientX, clientY } = mouseEvent;
|
|
9001
9135
|
return [clientX, clientY];
|
|
9002
9136
|
};
|
|
9003
9137
|
const [grabX, grabY] = mouseEventCoords(grabEvent);
|
|
@@ -9005,23 +9139,21 @@ const createDragGestureController = (options = {}) => {
|
|
|
9005
9139
|
grabX,
|
|
9006
9140
|
grabY,
|
|
9007
9141
|
event: grabEvent,
|
|
9008
|
-
...dragOptions
|
|
9142
|
+
...dragOptions,
|
|
9009
9143
|
});
|
|
9010
|
-
const dragViaPointer = dragEvent => {
|
|
9144
|
+
const dragViaPointer = (dragEvent) => {
|
|
9011
9145
|
const [mouseDragX, mouseDragY] = mouseEventCoords(dragEvent);
|
|
9012
9146
|
dragGesture.drag(mouseDragX, mouseDragY, {
|
|
9013
|
-
event: dragEvent
|
|
9147
|
+
event: dragEvent,
|
|
9014
9148
|
});
|
|
9015
9149
|
};
|
|
9016
|
-
const releaseViaPointer = (pointerEvent, {
|
|
9017
|
-
cancelled
|
|
9018
|
-
} = {}) => {
|
|
9150
|
+
const releaseViaPointer = (pointerEvent, { cancelled } = {}) => {
|
|
9019
9151
|
const [mouseReleaseX, mouseReleaseY] = mouseEventCoords(pointerEvent);
|
|
9020
9152
|
dragGesture.release({
|
|
9021
9153
|
event: pointerEvent,
|
|
9022
9154
|
cancelled,
|
|
9023
9155
|
releaseX: mouseReleaseX,
|
|
9024
|
-
releaseY: mouseReleaseY
|
|
9156
|
+
releaseY: mouseReleaseY,
|
|
9025
9157
|
});
|
|
9026
9158
|
};
|
|
9027
9159
|
dragGesture.dragViaPointer = dragViaPointer;
|
|
@@ -9040,7 +9172,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
9040
9172
|
* everybody: the element would stop being selectable even when nobody is
|
|
9041
9173
|
* dragging it. Here it is refused for the length of one gesture.
|
|
9042
9174
|
*/
|
|
9043
|
-
const preventSelectStart = selectStartEvent => {
|
|
9175
|
+
const preventSelectStart = (selectStartEvent) => {
|
|
9044
9176
|
selectStartEvent.preventDefault();
|
|
9045
9177
|
};
|
|
9046
9178
|
document.addEventListener("selectstart", preventSelectStart);
|
|
@@ -9075,7 +9207,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
9075
9207
|
* waiting for a click that never comes costs nothing until then.
|
|
9076
9208
|
*/
|
|
9077
9209
|
removePendingMultiClickListener();
|
|
9078
|
-
const onClick = clickEvent => {
|
|
9210
|
+
const onClick = (clickEvent) => {
|
|
9079
9211
|
const clickCount = clickEvent.detail;
|
|
9080
9212
|
if (clickCount < 2) {
|
|
9081
9213
|
return;
|
|
@@ -9091,7 +9223,10 @@ const createDragGestureController = (options = {}) => {
|
|
|
9091
9223
|
// is doing its own selecting — a second opinion there would only fight
|
|
9092
9224
|
// it.
|
|
9093
9225
|
const clickTarget = clickEvent.target;
|
|
9094
|
-
if (
|
|
9226
|
+
if (
|
|
9227
|
+
clickTarget !== grabEvent.target &&
|
|
9228
|
+
!clickTarget.contains(grabEvent.target)
|
|
9229
|
+
) {
|
|
9095
9230
|
return;
|
|
9096
9231
|
}
|
|
9097
9232
|
// Text the page says is not selectable stays not selectable: a
|
|
@@ -9117,204 +9252,204 @@ const createDragGestureController = (options = {}) => {
|
|
|
9117
9252
|
onMove: dragViaPointer,
|
|
9118
9253
|
onRelease: releaseViaPointer,
|
|
9119
9254
|
gestureInfo: dragGesture.gestureInfo,
|
|
9120
|
-
dragGesture
|
|
9255
|
+
dragGesture,
|
|
9121
9256
|
});
|
|
9122
9257
|
dragGesture.addReleaseCallback(() => {
|
|
9123
9258
|
cleanup();
|
|
9124
9259
|
});
|
|
9125
9260
|
return dragGesture;
|
|
9126
9261
|
};
|
|
9262
|
+
|
|
9127
9263
|
const grabViaPointer = (grabEvent, options) => {
|
|
9128
9264
|
if (grabEvent.type === "pointerdown") {
|
|
9129
|
-
return initDragByPointer(
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
gestureInfo,
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
if (!options?.pointerCaptureDeferred) {
|
|
9159
|
-
dragGesture.capturePointer();
|
|
9160
|
-
}
|
|
9161
|
-
/*
|
|
9162
|
-
* A touchmove left alone is the browser deciding the touch belongs to
|
|
9163
|
-
* it: it takes it to scroll with, and a touch it has taken is a pointer
|
|
9164
|
-
* stream it CANCELS — the gesture dies mid-move, the finger is still
|
|
9165
|
-
* down, and nothing reads it anymore.
|
|
9166
|
-
*
|
|
9167
|
-
* Refused only once the gesture is established (a `touch-action: none`
|
|
9168
|
-
* would take the touch from everyone who merely brushes past the
|
|
9169
|
-
* element), but LISTENED FOR from the grab: whether a touchmove can be
|
|
9170
|
-
* refused at all is decided when the touch begins, from the listeners
|
|
9171
|
-
* present at that moment. Registered later, the listener is handed
|
|
9172
|
-
* events that are already `cancelable: false` — refusing them does
|
|
9173
|
-
* nothing, and the reason is invisible in the code that refuses.
|
|
9174
|
-
*
|
|
9175
|
-
* On the window in capture AND on the grabbed element: a touch keeps
|
|
9176
|
-
* being dispatched at the node it started on, and a gesture may take
|
|
9177
|
-
* that node out of the document (a page that travels navigates) — from
|
|
9178
|
-
* then on the event never passes through the window on its way
|
|
9179
|
-
* anywhere.
|
|
9180
|
-
*/
|
|
9181
|
-
const preventTouchScroll = touchMoveEvent => {
|
|
9182
|
-
if (gestureInfo.started && touchMoveEvent.cancelable) {
|
|
9183
|
-
touchMoveEvent.preventDefault();
|
|
9184
|
-
}
|
|
9185
|
-
};
|
|
9186
|
-
const grabTarget = grabEvent.target;
|
|
9187
|
-
window.addEventListener("touchmove", preventTouchScroll, {
|
|
9188
|
-
passive: false,
|
|
9189
|
-
capture: true
|
|
9190
|
-
});
|
|
9191
|
-
grabTarget.addEventListener("touchmove", preventTouchScroll, {
|
|
9192
|
-
passive: false
|
|
9193
|
-
});
|
|
9194
|
-
// Only OUR capture ending is this gesture's business:
|
|
9195
|
-
// lostpointercapture bubbles, so a descendant giving up its own capture
|
|
9196
|
-
// walks straight into this listener. That is not a rare shape — it is
|
|
9197
|
-
// exactly what happens when a gesture hands over to another one (a
|
|
9198
|
-
// press that becomes a drag releases its intermediate gesture, held on
|
|
9199
|
-
// the pressed element, while the real one is being held on a container
|
|
9200
|
-
// above it), and taken as our own it kills the new gesture one
|
|
9201
|
-
// millisecond after it started.
|
|
9202
|
-
//
|
|
9203
|
-
// And when it IS ours, it is a loss and never an end: the ends a
|
|
9204
|
-
// gesture has are the pointer going up and the pointer being
|
|
9205
|
-
// cancelled, both listened for below. What a loss MEANS is the
|
|
9206
|
-
// question, and the event does not answer it — two very different
|
|
9207
|
-
// things arrive as the same one:
|
|
9208
|
-
//
|
|
9209
|
-
// - it was HANDED OVER: another gesture took the pointer, or the
|
|
9210
|
-
// element it was held on left the document. There is nothing to go
|
|
9211
|
-
// on with, and what was being carried must go back rather than land
|
|
9212
|
-
// wherever the hand happened to be.
|
|
9213
|
-
// - it was simply LET GO OF by the browser, with the hand still down
|
|
9214
|
-
// and still moving. It happens, and not rarely: the capture is a
|
|
9215
|
-
// guarantee that events keep coming to one element, and the browser
|
|
9216
|
-
// drops it for reasons of its own that no code here can see. Killing
|
|
9217
|
-
// the gesture for that is dropping an object mid-air — the copy
|
|
9218
|
-
// vanishes, the place the hint had lit up is thrown away, and the
|
|
9219
|
-
// hand is left having done nothing.
|
|
9220
|
-
//
|
|
9221
|
-
// They are told apart by who asked (see captureHolderByPointerId): a
|
|
9222
|
-
// capture nobody here took, on an element still in the document, was
|
|
9223
|
-
// let go of. The gesture does not need it — every move and the release
|
|
9224
|
-
// are read at the WINDOW, not at the element — so it goes on.
|
|
9225
|
-
const onCaptureLost = pointerEvent => {
|
|
9226
|
-
if (!captured || pointerEvent.target !== target) {
|
|
9227
|
-
return;
|
|
9228
|
-
}
|
|
9229
|
-
const handedOver = captureHolderByPointerId.get(grabEvent.pointerId) !== dragGesture;
|
|
9230
|
-
if (!handedOver && target.isConnected) {
|
|
9231
|
-
// Nobody took it and the element it was held on is still there:
|
|
9232
|
-
// the browser let the capture go by itself, which it does — a
|
|
9233
|
-
// node moved by a re-render and put straight back, a decision of
|
|
9234
|
-
// its own we are not told the reason for. The hand has not let go
|
|
9235
|
-
// of anything, so neither does the gesture: it is a guarantee that
|
|
9236
|
-
// was lost, not the gesture. Every move and the release are read
|
|
9237
|
-
// at the window (see below), so it goes on without it rather than
|
|
9238
|
-
// dropping what is still being carried — and the drop the hand was
|
|
9239
|
-
// aiming at, which the hint had already lit up, still happens.
|
|
9240
|
-
captured = false;
|
|
9241
|
-
return;
|
|
9265
|
+
return initDragByPointer(
|
|
9266
|
+
grabEvent,
|
|
9267
|
+
options,
|
|
9268
|
+
({ onMove, onRelease, gestureInfo, dragGesture }) => {
|
|
9269
|
+
// Captured on something that will still be there at the end of the
|
|
9270
|
+
// gesture: the browser releases the capture when its element leaves the
|
|
9271
|
+
// document, and a gesture whose own effect replaces the DOM under the
|
|
9272
|
+
// finger would lose the pointer at its first move. Callers whose target
|
|
9273
|
+
// is stable have nothing to say and keep it.
|
|
9274
|
+
const target = options?.pointerCaptureElement || grabEvent.target;
|
|
9275
|
+
// The capture is ONE per pointer for the whole document: taking it is
|
|
9276
|
+
// taking it away from whoever had it, who is then told the exact same
|
|
9277
|
+
// thing it is told when its own gesture ends. So it is taken when this
|
|
9278
|
+
// gesture is established and not a moment earlier — for most callers
|
|
9279
|
+
// that is the grab itself (the intent was settled before, by a handle
|
|
9280
|
+
// or a long press), and for a caller that is still deciding what the
|
|
9281
|
+
// press means, it is whenever it says so (see capturePointer).
|
|
9282
|
+
let captured = false;
|
|
9283
|
+
dragGesture.capturePointer = () => {
|
|
9284
|
+
captured = true;
|
|
9285
|
+
// Written down before it is taken: this is the only place a capture
|
|
9286
|
+
// is ever taken from, so what this map says is who asked for it
|
|
9287
|
+
// last — which is what tells a hand-over from a capture the browser
|
|
9288
|
+
// dropped on its own (see onCaptureLost).
|
|
9289
|
+
captureHolderByPointerId.set(grabEvent.pointerId, dragGesture);
|
|
9290
|
+
target.setPointerCapture(grabEvent.pointerId);
|
|
9291
|
+
};
|
|
9292
|
+
if (!options?.pointerCaptureDeferred) {
|
|
9293
|
+
dragGesture.capturePointer();
|
|
9242
9294
|
}
|
|
9243
|
-
|
|
9244
|
-
|
|
9295
|
+
/*
|
|
9296
|
+
* A touchmove left alone is the browser deciding the touch belongs to
|
|
9297
|
+
* it: it takes it to scroll with, and a touch it has taken is a pointer
|
|
9298
|
+
* stream it CANCELS — the gesture dies mid-move, the finger is still
|
|
9299
|
+
* down, and nothing reads it anymore.
|
|
9300
|
+
*
|
|
9301
|
+
* Refused only once the gesture is established (a `touch-action: none`
|
|
9302
|
+
* would take the touch from everyone who merely brushes past the
|
|
9303
|
+
* element), but LISTENED FOR from the grab: whether a touchmove can be
|
|
9304
|
+
* refused at all is decided when the touch begins, from the listeners
|
|
9305
|
+
* present at that moment. Registered later, the listener is handed
|
|
9306
|
+
* events that are already `cancelable: false` — refusing them does
|
|
9307
|
+
* nothing, and the reason is invisible in the code that refuses.
|
|
9308
|
+
*
|
|
9309
|
+
* On the window in capture AND on the grabbed element: a touch keeps
|
|
9310
|
+
* being dispatched at the node it started on, and a gesture may take
|
|
9311
|
+
* that node out of the document (a page that travels navigates) — from
|
|
9312
|
+
* then on the event never passes through the window on its way
|
|
9313
|
+
* anywhere.
|
|
9314
|
+
*/
|
|
9315
|
+
const preventTouchScroll = (touchMoveEvent) => {
|
|
9316
|
+
if (gestureInfo.started && touchMoveEvent.cancelable) {
|
|
9317
|
+
touchMoveEvent.preventDefault();
|
|
9318
|
+
}
|
|
9319
|
+
};
|
|
9320
|
+
const grabTarget = grabEvent.target;
|
|
9321
|
+
window.addEventListener("touchmove", preventTouchScroll, {
|
|
9322
|
+
passive: false,
|
|
9323
|
+
capture: true,
|
|
9245
9324
|
});
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
target.addEventListener("pointercancel", onRelease);
|
|
9249
|
-
target.addEventListener("pointerup", onRelease);
|
|
9250
|
-
// Read from the window rather than from the element while the pointer
|
|
9251
|
-
// is not this gesture's: without a capture a move is delivered to
|
|
9252
|
-
// whatever is under the pointer, and a hand that has left the element
|
|
9253
|
-
// is exactly the hand this gesture is trying to make sense of. The
|
|
9254
|
-
// capture phase reaches the window before anything else, captured or
|
|
9255
|
-
// not, so there is one place to read whichever way the gesture ends up.
|
|
9256
|
-
const onPointerMove = pointerEvent => {
|
|
9257
|
-
if (pointerEvent.pointerId !== grabEvent.pointerId) {
|
|
9258
|
-
return;
|
|
9259
|
-
}
|
|
9260
|
-
onMove(pointerEvent);
|
|
9261
|
-
};
|
|
9262
|
-
window.addEventListener("pointermove", onPointerMove, true);
|
|
9263
|
-
// The end of the pointer is also listened for on the window, because
|
|
9264
|
-
// the end is the one event a gesture cannot afford to miss and the
|
|
9265
|
-
// element it is captured on is not always on its way: a pointer can
|
|
9266
|
-
// be delivered somewhere else entirely (a browser view transition
|
|
9267
|
-
// sends presses to the document root), and a cancel dispatched there
|
|
9268
|
-
// never passes through this element. Missed, the gesture never ends —
|
|
9269
|
-
// whatever it was holding stays held.
|
|
9270
|
-
let released = false;
|
|
9271
|
-
const onPointerEnd = pointerEvent => {
|
|
9272
|
-
if (pointerEvent.pointerId !== grabEvent.pointerId || released) {
|
|
9273
|
-
return;
|
|
9274
|
-
}
|
|
9275
|
-
released = true;
|
|
9276
|
-
onRelease(pointerEvent);
|
|
9277
|
-
};
|
|
9278
|
-
window.addEventListener("pointerup", onPointerEnd, true);
|
|
9279
|
-
window.addEventListener("pointercancel", onPointerEnd, true);
|
|
9280
|
-
return () => {
|
|
9281
|
-
// Listeners first, capture last: giving the pointer back is the
|
|
9282
|
-
// one thing here that can throw, and a gesture that fails to clean
|
|
9283
|
-
// up half way is worse than one that never cleaned up at all — its
|
|
9284
|
-
// listeners stay on the element and answer the NEXT gesture, from
|
|
9285
|
-
// a gesture whose pointer is long gone.
|
|
9286
|
-
window.removeEventListener("touchmove", preventTouchScroll, {
|
|
9287
|
-
capture: true
|
|
9325
|
+
grabTarget.addEventListener("touchmove", preventTouchScroll, {
|
|
9326
|
+
passive: false,
|
|
9288
9327
|
});
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
//
|
|
9297
|
-
//
|
|
9298
|
-
//
|
|
9299
|
-
//
|
|
9300
|
-
//
|
|
9301
|
-
//
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9328
|
+
// Only OUR capture ending is this gesture's business:
|
|
9329
|
+
// lostpointercapture bubbles, so a descendant giving up its own capture
|
|
9330
|
+
// walks straight into this listener. That is not a rare shape — it is
|
|
9331
|
+
// exactly what happens when a gesture hands over to another one (a
|
|
9332
|
+
// press that becomes a drag releases its intermediate gesture, held on
|
|
9333
|
+
// the pressed element, while the real one is being held on a container
|
|
9334
|
+
// above it), and taken as our own it kills the new gesture one
|
|
9335
|
+
// millisecond after it started.
|
|
9336
|
+
//
|
|
9337
|
+
// And when it IS ours, it is a loss and never an end: the ends a
|
|
9338
|
+
// gesture has are the pointer going up and the pointer being
|
|
9339
|
+
// cancelled, both listened for below. What a loss MEANS is the
|
|
9340
|
+
// question, and the event does not answer it — two very different
|
|
9341
|
+
// things arrive as the same one:
|
|
9342
|
+
//
|
|
9343
|
+
// - it was HANDED OVER: another gesture took the pointer, or the
|
|
9344
|
+
// element it was held on left the document. There is nothing to go
|
|
9345
|
+
// on with, and what was being carried must go back rather than land
|
|
9346
|
+
// wherever the hand happened to be.
|
|
9347
|
+
// - it was simply LET GO OF by the browser, with the hand still down
|
|
9348
|
+
// and still moving. It happens, and not rarely: the capture is a
|
|
9349
|
+
// guarantee that events keep coming to one element, and the browser
|
|
9350
|
+
// drops it for reasons of its own that no code here can see. Killing
|
|
9351
|
+
// the gesture for that is dropping an object mid-air — the copy
|
|
9352
|
+
// vanishes, the place the hint had lit up is thrown away, and the
|
|
9353
|
+
// hand is left having done nothing.
|
|
9354
|
+
//
|
|
9355
|
+
// They are told apart by who asked (see captureHolderByPointerId): a
|
|
9356
|
+
// capture nobody here took, on an element still in the document, was
|
|
9357
|
+
// let go of. The gesture does not need it — every move and the release
|
|
9358
|
+
// are read at the WINDOW, not at the element — so it goes on.
|
|
9359
|
+
const onCaptureLost = (pointerEvent) => {
|
|
9360
|
+
if (!captured || pointerEvent.target !== target) {
|
|
9361
|
+
return;
|
|
9362
|
+
}
|
|
9363
|
+
const handedOver =
|
|
9364
|
+
captureHolderByPointerId.get(grabEvent.pointerId) !== dragGesture;
|
|
9365
|
+
if (!handedOver && target.isConnected) {
|
|
9366
|
+
// Nobody took it and the element it was held on is still there:
|
|
9367
|
+
// the browser let the capture go by itself, which it does — a
|
|
9368
|
+
// node moved by a re-render and put straight back, a decision of
|
|
9369
|
+
// its own we are not told the reason for. The hand has not let go
|
|
9370
|
+
// of anything, so neither does the gesture: it is a guarantee that
|
|
9371
|
+
// was lost, not the gesture. Every move and the release are read
|
|
9372
|
+
// at the window (see below), so it goes on without it rather than
|
|
9373
|
+
// dropping what is still being carried — and the drop the hand was
|
|
9374
|
+
// aiming at, which the hint had already lit up, still happens.
|
|
9375
|
+
captured = false;
|
|
9376
|
+
return;
|
|
9377
|
+
}
|
|
9378
|
+
onRelease(pointerEvent, { cancelled: true });
|
|
9379
|
+
};
|
|
9380
|
+
target.addEventListener("lostpointercapture", onCaptureLost);
|
|
9381
|
+
target.addEventListener("pointercancel", onRelease);
|
|
9382
|
+
target.addEventListener("pointerup", onRelease);
|
|
9383
|
+
// Read from the window rather than from the element while the pointer
|
|
9384
|
+
// is not this gesture's: without a capture a move is delivered to
|
|
9385
|
+
// whatever is under the pointer, and a hand that has left the element
|
|
9386
|
+
// is exactly the hand this gesture is trying to make sense of. The
|
|
9387
|
+
// capture phase reaches the window before anything else, captured or
|
|
9388
|
+
// not, so there is one place to read whichever way the gesture ends up.
|
|
9389
|
+
const onPointerMove = (pointerEvent) => {
|
|
9390
|
+
if (pointerEvent.pointerId !== grabEvent.pointerId) {
|
|
9391
|
+
return;
|
|
9392
|
+
}
|
|
9393
|
+
onMove(pointerEvent);
|
|
9394
|
+
};
|
|
9395
|
+
window.addEventListener("pointermove", onPointerMove, true);
|
|
9396
|
+
// The end of the pointer is also listened for on the window, because
|
|
9397
|
+
// the end is the one event a gesture cannot afford to miss and the
|
|
9398
|
+
// element it is captured on is not always on its way: a pointer can
|
|
9399
|
+
// be delivered somewhere else entirely (a browser view transition
|
|
9400
|
+
// sends presses to the document root), and a cancel dispatched there
|
|
9401
|
+
// never passes through this element. Missed, the gesture never ends —
|
|
9402
|
+
// whatever it was holding stays held.
|
|
9403
|
+
let released = false;
|
|
9404
|
+
const onPointerEnd = (pointerEvent) => {
|
|
9405
|
+
if (pointerEvent.pointerId !== grabEvent.pointerId || released) {
|
|
9406
|
+
return;
|
|
9407
|
+
}
|
|
9408
|
+
released = true;
|
|
9409
|
+
onRelease(pointerEvent);
|
|
9410
|
+
};
|
|
9411
|
+
window.addEventListener("pointerup", onPointerEnd, true);
|
|
9412
|
+
window.addEventListener("pointercancel", onPointerEnd, true);
|
|
9413
|
+
return () => {
|
|
9414
|
+
// Listeners first, capture last: giving the pointer back is the
|
|
9415
|
+
// one thing here that can throw, and a gesture that fails to clean
|
|
9416
|
+
// up half way is worse than one that never cleaned up at all — its
|
|
9417
|
+
// listeners stay on the element and answer the NEXT gesture, from
|
|
9418
|
+
// a gesture whose pointer is long gone.
|
|
9419
|
+
window.removeEventListener("touchmove", preventTouchScroll, {
|
|
9420
|
+
capture: true,
|
|
9421
|
+
});
|
|
9422
|
+
grabTarget.removeEventListener("touchmove", preventTouchScroll);
|
|
9423
|
+
target.removeEventListener("lostpointercapture", onCaptureLost);
|
|
9424
|
+
target.removeEventListener("pointercancel", onRelease);
|
|
9425
|
+
window.removeEventListener("pointermove", onPointerMove, true);
|
|
9426
|
+
target.removeEventListener("pointerup", onRelease);
|
|
9427
|
+
window.removeEventListener("pointerup", onPointerEnd, true);
|
|
9428
|
+
window.removeEventListener("pointercancel", onPointerEnd, true);
|
|
9429
|
+
// Only what this gesture took, and only while there is something to
|
|
9430
|
+
// give back: a capture on that element may be someone else's (two
|
|
9431
|
+
// gestures reading the same press hold the same node), and a pointer
|
|
9432
|
+
// that is up no longer exists — the browser has already dropped the
|
|
9433
|
+
// capture with it, and asking again throws ("No active pointer with
|
|
9434
|
+
// the given id is found") on the most ordinary release there is.
|
|
9435
|
+
if (
|
|
9436
|
+
captureHolderByPointerId.get(grabEvent.pointerId) === dragGesture
|
|
9437
|
+
) {
|
|
9438
|
+
captureHolderByPointerId.delete(grabEvent.pointerId);
|
|
9439
|
+
}
|
|
9440
|
+
if (captured && target.hasPointerCapture(grabEvent.pointerId)) {
|
|
9441
|
+
target.releasePointerCapture(grabEvent.pointerId);
|
|
9442
|
+
}
|
|
9443
|
+
};
|
|
9444
|
+
},
|
|
9445
|
+
);
|
|
9310
9446
|
}
|
|
9311
9447
|
if (grabEvent.type === "mousedown") {
|
|
9312
|
-
console.warn(
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
const onPointerUp = pointerEvent => {
|
|
9448
|
+
console.warn(
|
|
9449
|
+
`Received "mousedown" event, "pointerdown" events are recommended to perform drag gestures.`,
|
|
9450
|
+
);
|
|
9451
|
+
return initDragByPointer(grabEvent, options, ({ onMove, onRelease }) => {
|
|
9452
|
+
const onPointerUp = (pointerEvent) => {
|
|
9318
9453
|
// <button disabled> for example does not emit mouseup if we release mouse over it
|
|
9319
9454
|
// -> we add "pointerup" to catch mouseup occuring on disabled element
|
|
9320
9455
|
if (pointerEvent.pointerType === "mouse") {
|
|
@@ -9331,15 +9466,19 @@ const createDragGestureController = (options = {}) => {
|
|
|
9331
9466
|
};
|
|
9332
9467
|
});
|
|
9333
9468
|
}
|
|
9334
|
-
throw new Error(
|
|
9469
|
+
throw new Error(
|
|
9470
|
+
`Unsupported "${grabEvent.type}" evenet passed to grabViaPointer. "pointerdown" was expected.`,
|
|
9471
|
+
);
|
|
9335
9472
|
};
|
|
9336
9473
|
dragGestureController.grabViaPointer = grabViaPointer;
|
|
9474
|
+
|
|
9337
9475
|
return dragGestureController;
|
|
9338
9476
|
};
|
|
9339
9477
|
|
|
9340
9478
|
// Only the primary button drags: a right click (or any secondary button) opens
|
|
9341
9479
|
// a context menu, it never grabs anything.
|
|
9342
|
-
const isPrimaryButtonEvent = event =>
|
|
9480
|
+
const isPrimaryButtonEvent = (event) =>
|
|
9481
|
+
event.button === undefined || event.button === 0;
|
|
9343
9482
|
|
|
9344
9483
|
/*
|
|
9345
9484
|
* Speed over the last VELOCITY_WINDOW_MS rather than between the last two
|
|
@@ -9352,18 +9491,11 @@ const isPrimaryButtonEvent = event => event.button === undefined || event.button
|
|
|
9352
9491
|
*/
|
|
9353
9492
|
const VELOCITY_WINDOW_MS = 100;
|
|
9354
9493
|
const createVelocityMeter = (grabX, grabY) => {
|
|
9355
|
-
const samples = [{
|
|
9356
|
-
|
|
9357
|
-
x: grabX,
|
|
9358
|
-
y: grabY
|
|
9359
|
-
}];
|
|
9494
|
+
const samples = [{ time: performance.now(), x: grabX, y: grabY }];
|
|
9495
|
+
|
|
9360
9496
|
const measureVelocity = (x, y) => {
|
|
9361
9497
|
const time = performance.now();
|
|
9362
|
-
samples.push({
|
|
9363
|
-
time,
|
|
9364
|
-
x,
|
|
9365
|
-
y
|
|
9366
|
-
});
|
|
9498
|
+
samples.push({ time, x, y });
|
|
9367
9499
|
while (samples.length > 2 && time - samples[1].time > VELOCITY_WINDOW_MS) {
|
|
9368
9500
|
samples.shift();
|
|
9369
9501
|
}
|
|
@@ -9374,14 +9506,17 @@ const createVelocityMeter = (grabX, grabY) => {
|
|
|
9374
9506
|
}
|
|
9375
9507
|
return [(x - oldestSample.x) / elapsed, (y - oldestSample.y) / elapsed];
|
|
9376
9508
|
};
|
|
9509
|
+
|
|
9377
9510
|
return measureVelocity;
|
|
9378
9511
|
};
|
|
9512
|
+
|
|
9379
9513
|
const definePropertyAsReadOnly = (object, propertyName) => {
|
|
9380
9514
|
Object.defineProperty(object, propertyName, {
|
|
9381
9515
|
writable: false,
|
|
9382
|
-
value: object[propertyName]
|
|
9516
|
+
value: object[propertyName],
|
|
9383
9517
|
});
|
|
9384
9518
|
};
|
|
9519
|
+
|
|
9385
9520
|
const NOOP = () => {};
|
|
9386
9521
|
let removePendingMultiClickListener = NOOP;
|
|
9387
9522
|
|
|
@@ -9427,12 +9562,11 @@ const selectParagraphAtPoint = (x, y) => {
|
|
|
9427
9562
|
paragraphRange.selectNodeContents(block);
|
|
9428
9563
|
selection.addRange(paragraphRange);
|
|
9429
9564
|
};
|
|
9430
|
-
|
|
9565
|
+
|
|
9566
|
+
const getClosestBlock = (node) => {
|
|
9431
9567
|
let element = node.nodeType === 1 ? node : node.parentElement;
|
|
9432
9568
|
while (element) {
|
|
9433
|
-
const {
|
|
9434
|
-
display
|
|
9435
|
-
} = window.getComputedStyle(element);
|
|
9569
|
+
const { display } = window.getComputedStyle(element);
|
|
9436
9570
|
// Everything that is not laid out as a line inside another line: the first
|
|
9437
9571
|
// ancestor that breaks the flow is the one whose text reads as a paragraph
|
|
9438
9572
|
// of its own.
|
|
@@ -9443,6 +9577,7 @@ const getClosestBlock = node => {
|
|
|
9443
9577
|
}
|
|
9444
9578
|
return null;
|
|
9445
9579
|
};
|
|
9580
|
+
|
|
9446
9581
|
const createCaretRange = (x, y) => {
|
|
9447
9582
|
if (document.caretPositionFromPoint) {
|
|
9448
9583
|
const caretPosition = document.caretPositionFromPoint(x, y);
|
|
@@ -9459,7 +9594,8 @@ const createCaretRange = (x, y) => {
|
|
|
9459
9594
|
}
|
|
9460
9595
|
return null;
|
|
9461
9596
|
};
|
|
9462
|
-
|
|
9597
|
+
|
|
9598
|
+
const isSelectable = (element) => {
|
|
9463
9599
|
if (!element || element.nodeType !== 1) {
|
|
9464
9600
|
return true;
|
|
9465
9601
|
}
|
|
@@ -9467,6 +9603,7 @@ const isSelectable = element => {
|
|
|
9467
9603
|
const userSelect = computedStyle.userSelect || computedStyle.webkitUserSelect;
|
|
9468
9604
|
return userSelect !== "none";
|
|
9469
9605
|
};
|
|
9606
|
+
|
|
9470
9607
|
const collapseSelection = () => {
|
|
9471
9608
|
const selection = window.getSelection();
|
|
9472
9609
|
if (selection && !selection.isCollapsed) {
|
|
@@ -9474,53 +9611,7 @@ const collapseSelection = () => {
|
|
|
9474
9611
|
}
|
|
9475
9612
|
};
|
|
9476
9613
|
|
|
9477
|
-
installImportMetaCssBuild(import.meta)
|
|
9478
|
-
* When a press becomes a drag.
|
|
9479
|
-
*
|
|
9480
|
-
* A pointer going down on a draggable element is ambiguous — it may be a click,
|
|
9481
|
-
* a text selection, a scroll, or a drag — and starting the gesture right away
|
|
9482
|
-
* would steal all the others. This module picks which signal resolves the
|
|
9483
|
-
* ambiguity for the pointer at hand, and only then hands over to the real
|
|
9484
|
-
* gesture.
|
|
9485
|
-
*
|
|
9486
|
-
* There is one gesture, with a trigger per pointer:
|
|
9487
|
-
* - a dedicated handle ([data-drag-handle]) says it outright: drag on contact
|
|
9488
|
-
* - a mouse resolves it by distance — a mouse scrolls with its wheel, so travel
|
|
9489
|
-
* can only mean drag
|
|
9490
|
-
* - a finger resolves it by time — travel is exactly what a scroll looks like,
|
|
9491
|
-
* so the only unambiguous signal left is a finger that does NOT move
|
|
9492
|
-
*
|
|
9493
|
-
* Whichever trigger fired, it has established the intent: the gesture then
|
|
9494
|
-
* starts at the first pixel, without a second threshold to cross.
|
|
9495
|
-
*
|
|
9496
|
-
* WHEN A FINGER MAY TRAVEL TOO: [data-drag-on-contact].
|
|
9497
|
-
*
|
|
9498
|
-
* The wait a finger is asked for is not a rule about fingers, it is the answer to
|
|
9499
|
-
* an ambiguity — travel means scroll as much as it means drag, so the two have to
|
|
9500
|
-
* be told apart. Where nothing scrolls the ambiguity does not exist, and the wait
|
|
9501
|
-
* is asking the hand to prove something nothing else could have meant: inside a
|
|
9502
|
-
* dialog that holds the page still, a finger travelling on a piece can only be
|
|
9503
|
-
* carrying it.
|
|
9504
|
-
*
|
|
9505
|
-
* So the attribute says that place, not that element — put on the dialog, every
|
|
9506
|
-
* source inside it reads by distance like a mouse does, at the same few pixels.
|
|
9507
|
-
* A tap is left alone by that: a press that goes nowhere is still a press, which
|
|
9508
|
-
* is what a piece that is also a link or a card needs.
|
|
9509
|
-
*
|
|
9510
|
-
* A HOLD is not, and nothing in such a place is text to select. Taking the wait
|
|
9511
|
-
* away takes away the only thing that answered a finger held still, so the
|
|
9512
|
-
* browser answers it alone: its own long press selects the word under the thumb,
|
|
9513
|
-
* and the card the hand meant to carry is left blue and handled. Said to every
|
|
9514
|
-
* pointer — a mouse there drags from the first few pixels, so a selection begun
|
|
9515
|
-
* there is one that could never be finished either. What says its press is its
|
|
9516
|
-
* own keeps its text (see the stylesheet below).
|
|
9517
|
-
*
|
|
9518
|
-
* It is opt-in and cannot be anything else. Nothing here can see whether the
|
|
9519
|
-
* surroundings scroll — a page scrolls by default, an overflow is one CSS
|
|
9520
|
-
* property away, and getting it wrong the wrong way means the list runs away
|
|
9521
|
-
* under the finger that meant to reorder it. Only the application knows it has
|
|
9522
|
-
* taken the scroll away.
|
|
9523
|
-
*/
|
|
9614
|
+
installImportMetaCssBuild(import.meta);
|
|
9524
9615
|
|
|
9525
9616
|
/* At module scope, and on the markers rather than on the pressed element: both
|
|
9526
9617
|
rules below have to be true BEFORE the finger lands — a stylesheet, never a
|
|
@@ -9541,7 +9632,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
9541
9632
|
lets the page scroll and still makes the refusal effective — provided the
|
|
9542
9633
|
listener that will refuse is already known too, which is markDragSource's
|
|
9543
9634
|
half of the same rule. */
|
|
9544
|
-
const css$4 = /* css
|
|
9635
|
+
const css$4 = /* css */ `[data-drag-handle], [data-drag-source] {
|
|
9545
9636
|
-webkit-touch-callout: none;
|
|
9546
9637
|
}
|
|
9547
9638
|
|
|
@@ -9632,9 +9723,7 @@ const keepTouchRefusable = () => {
|
|
|
9632
9723
|
*/
|
|
9633
9724
|
const markDragSource = (element, axes = "xy") => {
|
|
9634
9725
|
element.setAttribute("data-drag-source", axes);
|
|
9635
|
-
element.addEventListener("touchmove", keepTouchRefusable, {
|
|
9636
|
-
passive: false
|
|
9637
|
-
});
|
|
9726
|
+
element.addEventListener("touchmove", keepTouchRefusable, { passive: false });
|
|
9638
9727
|
return () => {
|
|
9639
9728
|
element.removeAttribute("data-drag-source");
|
|
9640
9729
|
element.removeEventListener("touchmove", keepTouchRefusable);
|
|
@@ -9671,40 +9760,49 @@ const markDragSource = (element, axes = "xy") => {
|
|
|
9671
9760
|
* @param {function} [options.onPress]
|
|
9672
9761
|
* The wait completed and the object is now held (haptics, scale…).
|
|
9673
9762
|
*/
|
|
9674
|
-
const dragAfterIntent = (
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9763
|
+
const dragAfterIntent = (
|
|
9764
|
+
grabEvent,
|
|
9765
|
+
dragGestureInitializer,
|
|
9766
|
+
{
|
|
9767
|
+
threshold = 5,
|
|
9768
|
+
longPress = "if-touch",
|
|
9769
|
+
longPressDelay = 400,
|
|
9770
|
+
longPressSlop = 8,
|
|
9771
|
+
onPressStart,
|
|
9772
|
+
onPressCancel,
|
|
9773
|
+
onPress,
|
|
9774
|
+
} = {},
|
|
9775
|
+
) => {
|
|
9683
9776
|
if (!isPrimaryButtonEvent(grabEvent)) {
|
|
9684
9777
|
return;
|
|
9685
9778
|
}
|
|
9686
9779
|
const target = grabEvent.target;
|
|
9687
|
-
const isDedicatedHandle =
|
|
9780
|
+
const isDedicatedHandle =
|
|
9781
|
+
target.closest && target.closest("[data-drag-handle]");
|
|
9688
9782
|
if (isDedicatedHandle) {
|
|
9689
9783
|
startDragGesture(dragGestureInitializer);
|
|
9690
9784
|
return;
|
|
9691
9785
|
}
|
|
9692
|
-
const startsOnLongPress =
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9786
|
+
const startsOnLongPress =
|
|
9787
|
+
longPress === true ||
|
|
9788
|
+
(longPress === "if-touch" &&
|
|
9789
|
+
grabEvent.pointerType === "touch" &&
|
|
9790
|
+
// The wait tells a scroll from a drag, and here there is no scroll to tell
|
|
9791
|
+
// it from — see [data-drag-on-contact] at the top of this file.
|
|
9792
|
+
!(target.closest && target.closest("[data-drag-on-contact]")));
|
|
9696
9793
|
if (startsOnLongPress) {
|
|
9697
9794
|
dragAfterLongPress(grabEvent, dragGestureInitializer, {
|
|
9698
9795
|
longPressDelay,
|
|
9699
9796
|
longPressSlop,
|
|
9700
9797
|
onPressStart,
|
|
9701
9798
|
onPressCancel,
|
|
9702
|
-
onPress
|
|
9799
|
+
onPress,
|
|
9703
9800
|
});
|
|
9704
9801
|
return;
|
|
9705
9802
|
}
|
|
9706
9803
|
dragAfterDistance(grabEvent, dragGestureInitializer, threshold);
|
|
9707
9804
|
};
|
|
9805
|
+
|
|
9708
9806
|
const startDragGesture = (dragGestureInitializer, catchUpEvent) => {
|
|
9709
9807
|
const dragGesture = dragGestureInitializer();
|
|
9710
9808
|
if (!dragGesture) {
|
|
@@ -9718,6 +9816,7 @@ const startDragGesture = (dragGestureInitializer, catchUpEvent) => {
|
|
|
9718
9816
|
}
|
|
9719
9817
|
return dragGesture;
|
|
9720
9818
|
};
|
|
9819
|
+
|
|
9721
9820
|
const dragAfterDistance = (grabEvent, dragGestureInitializer, threshold) => {
|
|
9722
9821
|
const significantDragGestureController = createDragGestureController({
|
|
9723
9822
|
threshold,
|
|
@@ -9725,28 +9824,28 @@ const dragAfterDistance = (grabEvent, dragGestureInitializer, threshold) => {
|
|
|
9725
9824
|
// user should still be able to scroll or interact with the document
|
|
9726
9825
|
// only once the gesture is significant we take control
|
|
9727
9826
|
documentInteractions: "manual",
|
|
9728
|
-
onDragStart: gestureInfo => {
|
|
9827
|
+
onDragStart: (gestureInfo) => {
|
|
9729
9828
|
significantDragGesture.release(); // kill that gesture
|
|
9730
9829
|
startDragGesture(dragGestureInitializer, gestureInfo.dragEvent);
|
|
9731
|
-
}
|
|
9732
|
-
});
|
|
9733
|
-
const significantDragGesture = significantDragGestureController.grabViaPointer(grabEvent, {
|
|
9734
|
-
element: grabEvent.target,
|
|
9735
|
-
// This one owns nothing: it measures a distance to find out whether there
|
|
9736
|
-
// is a gesture at all, and it is over the moment there is. Taking the
|
|
9737
|
-
// pointer for that would take it from whoever is already holding this same
|
|
9738
|
-
// element for a gesture of their own — and giving it back at the threshold
|
|
9739
|
-
// would tell them theirs is over.
|
|
9740
|
-
pointerCaptureDeferred: true
|
|
9830
|
+
},
|
|
9741
9831
|
});
|
|
9832
|
+
const significantDragGesture =
|
|
9833
|
+
significantDragGestureController.grabViaPointer(grabEvent, {
|
|
9834
|
+
element: grabEvent.target,
|
|
9835
|
+
// This one owns nothing: it measures a distance to find out whether there
|
|
9836
|
+
// is a gesture at all, and it is over the moment there is. Taking the
|
|
9837
|
+
// pointer for that would take it from whoever is already holding this same
|
|
9838
|
+
// element for a gesture of their own — and giving it back at the threshold
|
|
9839
|
+
// would tell them theirs is over.
|
|
9840
|
+
pointerCaptureDeferred: true,
|
|
9841
|
+
});
|
|
9742
9842
|
};
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
onPressCancel,
|
|
9748
|
-
|
|
9749
|
-
}) => {
|
|
9843
|
+
|
|
9844
|
+
const dragAfterLongPress = (
|
|
9845
|
+
grabEvent,
|
|
9846
|
+
dragGestureInitializer,
|
|
9847
|
+
{ longPressDelay, longPressSlop, onPressStart, onPressCancel, onPress },
|
|
9848
|
+
) => {
|
|
9750
9849
|
/*
|
|
9751
9850
|
* Nothing is done here to keep the touch refusable: whether it can be refused
|
|
9752
9851
|
* at all was settled when the finger landed, from what the element already
|
|
@@ -9760,9 +9859,7 @@ const dragAfterLongPress = (grabEvent, dragGestureInitializer, {
|
|
|
9760
9859
|
slop: longPressSlop,
|
|
9761
9860
|
onPressStart,
|
|
9762
9861
|
onPressCancel,
|
|
9763
|
-
onPressHeld: (pressEvent, {
|
|
9764
|
-
endPress
|
|
9765
|
-
}) => {
|
|
9862
|
+
onPressHeld: (pressEvent, { endPress }) => {
|
|
9766
9863
|
onPress?.(pressEvent);
|
|
9767
9864
|
const dragGesture = startDragGesture(dragGestureInitializer);
|
|
9768
9865
|
if (!dragGesture) {
|
|
@@ -9772,7 +9869,7 @@ const dragAfterLongPress = (grabEvent, dragGestureInitializer, {
|
|
|
9772
9869
|
dragGesture.addReleaseCallback(() => {
|
|
9773
9870
|
endPress();
|
|
9774
9871
|
});
|
|
9775
|
-
}
|
|
9872
|
+
},
|
|
9776
9873
|
});
|
|
9777
9874
|
};
|
|
9778
9875
|
|
|
@@ -9949,6 +10046,13 @@ const createDragElementPositioner = (
|
|
|
9949
10046
|
// returns null (document.documentElement instead — see its own doc).
|
|
9950
10047
|
const positionedParent = getPositionedParent(elementToMove || element);
|
|
9951
10048
|
const scrollContainer = getScrollContainer(element);
|
|
10049
|
+
// Whether what MOVES is pinned to the viewport — a copy carried above the page
|
|
10050
|
+
// in the top layer, a fixed panel dragged by its handle. Such a thing does not
|
|
10051
|
+
// travel with any scroll, so the scroll that happens while it is carried is
|
|
10052
|
+
// none of its business (see getScrollOffsets).
|
|
10053
|
+
const elementMovedIsPinnedToViewport = Boolean(
|
|
10054
|
+
findSelfOrAncestorFixedPosition(elementToMove || element),
|
|
10055
|
+
);
|
|
9952
10056
|
const [getPositionOffsets, getScrollOffsets] = createGetOffsets({
|
|
9953
10057
|
positionedParent,
|
|
9954
10058
|
referencePositionedParent: referenceElement
|
|
@@ -9958,6 +10062,7 @@ const createDragElementPositioner = (
|
|
|
9958
10062
|
referenceScrollContainer: referenceElement
|
|
9959
10063
|
? getScrollContainer(referenceElement)
|
|
9960
10064
|
: scrollContainer,
|
|
10065
|
+
elementMovedIsPinnedToViewport,
|
|
9961
10066
|
});
|
|
9962
10067
|
|
|
9963
10068
|
{
|
|
@@ -10010,6 +10115,7 @@ const createGetOffsets = ({
|
|
|
10010
10115
|
referencePositionedParent,
|
|
10011
10116
|
scrollContainer,
|
|
10012
10117
|
referenceScrollContainer,
|
|
10118
|
+
elementMovedIsPinnedToViewport,
|
|
10013
10119
|
}) => {
|
|
10014
10120
|
const samePositionedParent = positionedParent === referencePositionedParent;
|
|
10015
10121
|
const getScrollOffsets = createGetScrollOffsets(
|
|
@@ -10017,6 +10123,7 @@ const createGetOffsets = ({
|
|
|
10017
10123
|
referenceScrollContainer,
|
|
10018
10124
|
positionedParent,
|
|
10019
10125
|
samePositionedParent,
|
|
10126
|
+
elementMovedIsPinnedToViewport,
|
|
10020
10127
|
);
|
|
10021
10128
|
|
|
10022
10129
|
if (samePositionedParent) {
|
|
@@ -10221,6 +10328,7 @@ const createGetScrollOffsets = (
|
|
|
10221
10328
|
referenceScrollContainer,
|
|
10222
10329
|
positionedParent,
|
|
10223
10330
|
samePositionedParent,
|
|
10331
|
+
elementMovedIsPinnedToViewport,
|
|
10224
10332
|
) => {
|
|
10225
10333
|
const getGetScrollOffsetsSameContainer = () => {
|
|
10226
10334
|
const scrollContainerIsDocument = scrollContainer === documentElement;
|
|
@@ -10242,9 +10350,14 @@ const createGetScrollOffsets = (
|
|
|
10242
10350
|
return getScrollOffsetsFixed;
|
|
10243
10351
|
}
|
|
10244
10352
|
}
|
|
10353
|
+
// A thing pinned to the viewport is inside nothing that scrolls: the page
|
|
10354
|
+
// can run under it and it stays where it is. So it takes the frozen offset
|
|
10355
|
+
// below whatever containment says — and the document, as a scroll container,
|
|
10356
|
+
// answers "yes, it is inside me" to everything.
|
|
10245
10357
|
const positionedParentIsInsideScrollContainer =
|
|
10246
|
-
|
|
10247
|
-
referenceScrollContainer
|
|
10358
|
+
!elementMovedIsPinnedToViewport &&
|
|
10359
|
+
(referenceScrollContainer === documentElement ||
|
|
10360
|
+
referenceScrollContainer.contains(positionedParent));
|
|
10248
10361
|
if (!positionedParentIsInsideScrollContainer) {
|
|
10249
10362
|
// positionedParent is outside the scroll container (e.g. clone in document.body
|
|
10250
10363
|
// while tracking an element inside a custom scroll container).
|
|
@@ -10292,7 +10405,9 @@ const getDragCoordinates = (
|
|
|
10292
10405
|
return [leftRelativeToScrollContainer, topRelativeToScrollContainer];
|
|
10293
10406
|
};
|
|
10294
10407
|
|
|
10295
|
-
installImportMetaCssBuild(import.meta);
|
|
10408
|
+
installImportMetaCssBuild(import.meta);
|
|
10409
|
+
|
|
10410
|
+
const css$3 = /* css */ `.navi_constraint_feedback_line {
|
|
10296
10411
|
z-index: 9998;
|
|
10297
10412
|
visibility: hidden;
|
|
10298
10413
|
transform-origin: 0;
|
|
@@ -10306,6 +10421,7 @@ installImportMetaCssBuild(import.meta);const css$3 = /* css */`.navi_constraint_
|
|
|
10306
10421
|
visibility: visible;
|
|
10307
10422
|
}
|
|
10308
10423
|
`;
|
|
10424
|
+
|
|
10309
10425
|
const setupConstraintFeedbackLine = () => {
|
|
10310
10426
|
import.meta.css = [css$3, "@jsenv/dom/src/interaction/drag/constraint_feedback_line.js"];
|
|
10311
10427
|
const constraintFeedbackLine = createConstraintFeedbackLine();
|
|
@@ -10315,17 +10431,18 @@ const setupConstraintFeedbackLine = () => {
|
|
|
10315
10431
|
let lastMouseY = null;
|
|
10316
10432
|
|
|
10317
10433
|
// Internal function to update constraint feedback line
|
|
10318
|
-
const onDrag = gestureInfo => {
|
|
10319
|
-
const {
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10434
|
+
const onDrag = (gestureInfo) => {
|
|
10435
|
+
const { grabEvent, dragEvent } = gestureInfo;
|
|
10436
|
+
if (
|
|
10437
|
+
grabEvent.type === "programmatic" ||
|
|
10438
|
+
// dragEvent can be null when only mousedown without yet any mousemove
|
|
10439
|
+
!dragEvent ||
|
|
10440
|
+
dragEvent.type === "programmatic"
|
|
10441
|
+
) {
|
|
10326
10442
|
// programmatic drag
|
|
10327
10443
|
return;
|
|
10328
10444
|
}
|
|
10445
|
+
|
|
10329
10446
|
const mouseX = dragEvent.clientX;
|
|
10330
10447
|
const mouseY = dragEvent.clientY;
|
|
10331
10448
|
// Use last known position if current position not available (e.g., during scroll)
|
|
@@ -10338,6 +10455,7 @@ const setupConstraintFeedbackLine = () => {
|
|
|
10338
10455
|
// Store current mouse position for potential use during scroll
|
|
10339
10456
|
lastMouseX = mouseX;
|
|
10340
10457
|
lastMouseY = mouseY;
|
|
10458
|
+
|
|
10341
10459
|
const grabClientX = grabEvent.clientX;
|
|
10342
10460
|
const grabClientY = grabEvent.clientY;
|
|
10343
10461
|
|
|
@@ -10366,28 +10484,33 @@ const setupConstraintFeedbackLine = () => {
|
|
|
10366
10484
|
constraintFeedbackLine.style.opacity = `${maxOpacity * opacityFactor}`;
|
|
10367
10485
|
constraintFeedbackLine.setAttribute("data-visible", "");
|
|
10368
10486
|
};
|
|
10487
|
+
|
|
10369
10488
|
return {
|
|
10370
10489
|
onDrag,
|
|
10371
10490
|
onRelease: () => {
|
|
10372
10491
|
constraintFeedbackLine.remove();
|
|
10373
|
-
}
|
|
10492
|
+
},
|
|
10374
10493
|
};
|
|
10375
10494
|
};
|
|
10495
|
+
|
|
10376
10496
|
const createConstraintFeedbackLine = () => {
|
|
10377
10497
|
const line = document.createElement("div");
|
|
10378
10498
|
line.className = "navi_constraint_feedback_line";
|
|
10379
|
-
line.title =
|
|
10499
|
+
line.title =
|
|
10500
|
+
"Constraint feedback - shows distance between mouse and moving grab point";
|
|
10380
10501
|
document.body.appendChild(line);
|
|
10381
10502
|
return line;
|
|
10382
10503
|
};
|
|
10383
10504
|
|
|
10384
|
-
installImportMetaCssBuild(import.meta)
|
|
10505
|
+
installImportMetaCssBuild(import.meta);
|
|
10385
10506
|
const MARKER_SIZE = 12;
|
|
10507
|
+
|
|
10386
10508
|
let currentDebugMarkers = [];
|
|
10387
10509
|
let currentConstraintMarkers = [];
|
|
10388
10510
|
let currentReferenceElementMarker = null;
|
|
10389
10511
|
let currentElementMarker = null;
|
|
10390
|
-
|
|
10512
|
+
|
|
10513
|
+
const css$2 = /* css */ `.navi_debug_markers_container {
|
|
10391
10514
|
z-index: 999998;
|
|
10392
10515
|
pointer-events: none;
|
|
10393
10516
|
--marker-size: ${MARKER_SIZE}px;
|
|
@@ -10554,9 +10677,8 @@ const css$2 = /* css */`.navi_debug_markers_container {
|
|
|
10554
10677
|
left: 0;
|
|
10555
10678
|
}
|
|
10556
10679
|
`;
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
}) => {
|
|
10680
|
+
|
|
10681
|
+
const setupDragDebugMarkers = (dragGesture, { referenceElement }) => {
|
|
10560
10682
|
import.meta.css = [css$2, "@jsenv/dom/src/interaction/drag/drag_debug_markers.js"];
|
|
10561
10683
|
|
|
10562
10684
|
// Clean up any existing persistent markers from previous drag gestures
|
|
@@ -10567,27 +10689,29 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10567
10689
|
container.innerHTML = ""; // Clear all markers efficiently
|
|
10568
10690
|
}
|
|
10569
10691
|
}
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
} = dragGesture.gestureInfo;
|
|
10692
|
+
|
|
10693
|
+
const { direction, scrollContainer } = dragGesture.gestureInfo;
|
|
10694
|
+
|
|
10574
10695
|
return {
|
|
10575
|
-
onConstraints: (
|
|
10576
|
-
|
|
10577
|
-
top,
|
|
10578
|
-
|
|
10579
|
-
bottom,
|
|
10580
|
-
autoScrollArea
|
|
10581
|
-
}) => {
|
|
10696
|
+
onConstraints: (
|
|
10697
|
+
constraints,
|
|
10698
|
+
{ left, top, right, bottom, autoScrollArea },
|
|
10699
|
+
) => {
|
|
10582
10700
|
// Schedule removal of previous markers if they exist
|
|
10583
10701
|
const previousDebugMarkers = [...currentDebugMarkers];
|
|
10584
10702
|
const previousConstraintMarkers = [...currentConstraintMarkers];
|
|
10585
10703
|
const previousReferenceElementMarker = currentReferenceElementMarker;
|
|
10586
10704
|
const previousElementMarker = currentElementMarker;
|
|
10587
|
-
|
|
10705
|
+
|
|
10706
|
+
if (
|
|
10707
|
+
previousDebugMarkers.length > 0 ||
|
|
10708
|
+
previousConstraintMarkers.length > 0 ||
|
|
10709
|
+
previousReferenceElementMarker ||
|
|
10710
|
+
previousElementMarker
|
|
10711
|
+
) {
|
|
10588
10712
|
setTimeout(() => {
|
|
10589
|
-
previousDebugMarkers.forEach(marker => marker.remove());
|
|
10590
|
-
previousConstraintMarkers.forEach(marker => marker.remove());
|
|
10713
|
+
previousDebugMarkers.forEach((marker) => marker.remove());
|
|
10714
|
+
previousConstraintMarkers.forEach((marker) => marker.remove());
|
|
10591
10715
|
if (previousReferenceElementMarker) {
|
|
10592
10716
|
previousReferenceElementMarker.remove();
|
|
10593
10717
|
}
|
|
@@ -10616,7 +10740,7 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10616
10740
|
bottom,
|
|
10617
10741
|
scrollContainer,
|
|
10618
10742
|
label: elementLabel,
|
|
10619
|
-
color: elementColor
|
|
10743
|
+
color: elementColor,
|
|
10620
10744
|
});
|
|
10621
10745
|
|
|
10622
10746
|
// Create reference element marker if reference element exists
|
|
@@ -10626,47 +10750,52 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10626
10750
|
top,
|
|
10627
10751
|
right,
|
|
10628
10752
|
bottom,
|
|
10629
|
-
scrollContainer
|
|
10753
|
+
scrollContainer,
|
|
10630
10754
|
});
|
|
10631
10755
|
}
|
|
10632
10756
|
|
|
10633
10757
|
// Collect all markers to be created, then merge duplicates
|
|
10634
10758
|
const markersToCreate = [];
|
|
10759
|
+
|
|
10635
10760
|
{
|
|
10636
10761
|
if (direction.x) {
|
|
10637
10762
|
markersToCreate.push({
|
|
10638
|
-
name: autoScrollArea.paddingLeft
|
|
10763
|
+
name: autoScrollArea.paddingLeft
|
|
10764
|
+
? `autoscroll.left + padding(${autoScrollArea.paddingLeft})`
|
|
10765
|
+
: "autoscroll.left",
|
|
10639
10766
|
x: autoScrollArea.left,
|
|
10640
10767
|
y: 0,
|
|
10641
|
-
color: "0 128 0",
|
|
10642
|
-
|
|
10643
|
-
side: "left"
|
|
10768
|
+
color: "0 128 0", // green
|
|
10769
|
+
side: "left",
|
|
10644
10770
|
});
|
|
10645
10771
|
markersToCreate.push({
|
|
10646
|
-
name: autoScrollArea.paddingRight
|
|
10772
|
+
name: autoScrollArea.paddingRight
|
|
10773
|
+
? `autoscroll.right + padding(${autoScrollArea.paddingRight})`
|
|
10774
|
+
: "autoscroll.right",
|
|
10647
10775
|
x: autoScrollArea.right,
|
|
10648
10776
|
y: 0,
|
|
10649
|
-
color: "0 128 0",
|
|
10650
|
-
|
|
10651
|
-
side: "right"
|
|
10777
|
+
color: "0 128 0", // green
|
|
10778
|
+
side: "right",
|
|
10652
10779
|
});
|
|
10653
10780
|
}
|
|
10654
10781
|
if (direction.y) {
|
|
10655
10782
|
markersToCreate.push({
|
|
10656
|
-
name: autoScrollArea.paddingTop
|
|
10783
|
+
name: autoScrollArea.paddingTop
|
|
10784
|
+
? `autoscroll.top + padding(${autoScrollArea.paddingTop})`
|
|
10785
|
+
: "autoscroll.top",
|
|
10657
10786
|
x: 0,
|
|
10658
10787
|
y: autoScrollArea.top,
|
|
10659
|
-
color: "255 0 0",
|
|
10660
|
-
|
|
10661
|
-
side: "top"
|
|
10788
|
+
color: "255 0 0", // red
|
|
10789
|
+
side: "top",
|
|
10662
10790
|
});
|
|
10663
10791
|
markersToCreate.push({
|
|
10664
|
-
name: autoScrollArea.paddingBottom
|
|
10792
|
+
name: autoScrollArea.paddingBottom
|
|
10793
|
+
? `autoscroll.bottom + padding(${autoScrollArea.paddingBottom})`
|
|
10794
|
+
: "autoscroll.bottom",
|
|
10665
10795
|
x: 0,
|
|
10666
10796
|
y: autoScrollArea.bottom,
|
|
10667
|
-
color: "255 165 0",
|
|
10668
|
-
|
|
10669
|
-
side: "bottom"
|
|
10797
|
+
color: "255 165 0", // orange
|
|
10798
|
+
side: "bottom",
|
|
10670
10799
|
});
|
|
10671
10800
|
}
|
|
10672
10801
|
}
|
|
@@ -10674,9 +10803,7 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10674
10803
|
// Process each constraint individually to preserve names
|
|
10675
10804
|
for (const constraint of constraints) {
|
|
10676
10805
|
if (constraint.type === "bounds") {
|
|
10677
|
-
const {
|
|
10678
|
-
bounds
|
|
10679
|
-
} = constraint;
|
|
10806
|
+
const { bounds } = constraint;
|
|
10680
10807
|
|
|
10681
10808
|
// Create individual markers for each bound with constraint name
|
|
10682
10809
|
if (direction.x) {
|
|
@@ -10685,9 +10812,8 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10685
10812
|
name: `${constraint.name}.left`,
|
|
10686
10813
|
x: bounds.left,
|
|
10687
10814
|
y: 0,
|
|
10688
|
-
color: "128 0 128",
|
|
10689
|
-
|
|
10690
|
-
side: "left"
|
|
10815
|
+
color: "128 0 128", // purple
|
|
10816
|
+
side: "left",
|
|
10691
10817
|
});
|
|
10692
10818
|
}
|
|
10693
10819
|
if (bounds.right !== undefined) {
|
|
@@ -10697,9 +10823,8 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10697
10823
|
name: `${constraint.name}.right`,
|
|
10698
10824
|
x: bounds.right,
|
|
10699
10825
|
y: 0,
|
|
10700
|
-
color: "128 0 128",
|
|
10701
|
-
|
|
10702
|
-
side: "right"
|
|
10826
|
+
color: "128 0 128", // purple
|
|
10827
|
+
side: "right",
|
|
10703
10828
|
});
|
|
10704
10829
|
}
|
|
10705
10830
|
}
|
|
@@ -10709,9 +10834,8 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10709
10834
|
name: `${constraint.name}.top`,
|
|
10710
10835
|
x: 0,
|
|
10711
10836
|
y: bounds.top,
|
|
10712
|
-
color: "128 0 128",
|
|
10713
|
-
|
|
10714
|
-
side: "top"
|
|
10837
|
+
color: "128 0 128", // purple
|
|
10838
|
+
side: "top",
|
|
10715
10839
|
});
|
|
10716
10840
|
}
|
|
10717
10841
|
if (bounds.bottom !== undefined) {
|
|
@@ -10721,28 +10845,37 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10721
10845
|
name: `${constraint.name}.bottom`,
|
|
10722
10846
|
x: 0,
|
|
10723
10847
|
y: bounds.bottom,
|
|
10724
|
-
color: "128 0 128",
|
|
10725
|
-
|
|
10726
|
-
side: "bottom"
|
|
10848
|
+
color: "128 0 128", // purple
|
|
10849
|
+
side: "bottom",
|
|
10727
10850
|
});
|
|
10728
10851
|
}
|
|
10729
10852
|
}
|
|
10730
10853
|
} else if (constraint.type === "obstacle") {
|
|
10731
|
-
const obstacleMarker = createObstacleMarker(
|
|
10854
|
+
const obstacleMarker = createObstacleMarker(
|
|
10855
|
+
constraint,
|
|
10856
|
+
scrollContainer,
|
|
10857
|
+
);
|
|
10732
10858
|
currentConstraintMarkers.push(obstacleMarker);
|
|
10733
10859
|
}
|
|
10734
10860
|
}
|
|
10735
10861
|
|
|
10736
10862
|
// Create markers with merging for overlapping positions
|
|
10737
|
-
const createdMarkers = createMergedMarkers(
|
|
10738
|
-
|
|
10739
|
-
|
|
10863
|
+
const createdMarkers = createMergedMarkers(
|
|
10864
|
+
markersToCreate,
|
|
10865
|
+
scrollContainer,
|
|
10866
|
+
);
|
|
10867
|
+
currentDebugMarkers.push(
|
|
10868
|
+
...createdMarkers.filter((m) => m.type !== "constraint"),
|
|
10869
|
+
);
|
|
10870
|
+
currentConstraintMarkers.push(
|
|
10871
|
+
...createdMarkers.filter((m) => m.type === "constraint"),
|
|
10872
|
+
);
|
|
10740
10873
|
},
|
|
10741
10874
|
onRelease: () => {
|
|
10742
10875
|
{
|
|
10743
10876
|
return;
|
|
10744
10877
|
}
|
|
10745
|
-
}
|
|
10878
|
+
},
|
|
10746
10879
|
};
|
|
10747
10880
|
};
|
|
10748
10881
|
|
|
@@ -10761,9 +10894,8 @@ const getMarkersContainer = () => {
|
|
|
10761
10894
|
// Convert document-relative coordinates to viewport coordinates for marker positioning
|
|
10762
10895
|
// Takes the scroll container into account for proper positioning relative to the container
|
|
10763
10896
|
const getDebugMarkerPos = (x, y, scrollContainer, side = null) => {
|
|
10764
|
-
const {
|
|
10765
|
-
|
|
10766
|
-
} = document;
|
|
10897
|
+
const { documentElement } = document;
|
|
10898
|
+
|
|
10767
10899
|
const leftWithoutScroll = x - scrollContainer.scrollLeft;
|
|
10768
10900
|
const topWithoutScroll = y - scrollContainer.scrollTop;
|
|
10769
10901
|
let baseX;
|
|
@@ -10795,6 +10927,7 @@ const getDebugMarkerPos = (x, y, scrollContainer, side = null) => {
|
|
|
10795
10927
|
// For obstacles and other markers: use converted coordinates directly
|
|
10796
10928
|
return [baseX, baseY];
|
|
10797
10929
|
};
|
|
10930
|
+
|
|
10798
10931
|
const createMergedMarkers = (markersToCreate, scrollContainer) => {
|
|
10799
10932
|
const mergedMarkers = [];
|
|
10800
10933
|
const positionMap = new Map();
|
|
@@ -10802,6 +10935,7 @@ const createMergedMarkers = (markersToCreate, scrollContainer) => {
|
|
|
10802
10935
|
// Group markers by position and side
|
|
10803
10936
|
for (const marker of markersToCreate) {
|
|
10804
10937
|
const key = `${marker.x},${marker.y},${marker.side}`;
|
|
10938
|
+
|
|
10805
10939
|
if (!positionMap.has(key)) {
|
|
10806
10940
|
positionMap.set(key, []);
|
|
10807
10941
|
}
|
|
@@ -10819,36 +10953,43 @@ const createMergedMarkers = (markersToCreate, scrollContainer) => {
|
|
|
10819
10953
|
} else {
|
|
10820
10954
|
// Multiple markers at same position - merge labels
|
|
10821
10955
|
const firstMarker = markers[0];
|
|
10822
|
-
const combinedName = markers.map(m => m.name).join(" + ");
|
|
10956
|
+
const combinedName = markers.map((m) => m.name).join(" + ");
|
|
10823
10957
|
|
|
10824
10958
|
// Use the first marker's color, or mix colors if needed
|
|
10825
|
-
const domMarker = createDebugMarker(
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10959
|
+
const domMarker = createDebugMarker(
|
|
10960
|
+
{
|
|
10961
|
+
...firstMarker,
|
|
10962
|
+
name: combinedName,
|
|
10963
|
+
},
|
|
10964
|
+
scrollContainer,
|
|
10965
|
+
);
|
|
10966
|
+
domMarker.type = markers.some((m) => m.name.includes("Bound"))
|
|
10967
|
+
? "constraint"
|
|
10968
|
+
: "visible";
|
|
10830
10969
|
mergedMarkers.push(domMarker);
|
|
10831
10970
|
}
|
|
10832
10971
|
}
|
|
10972
|
+
|
|
10833
10973
|
return mergedMarkers;
|
|
10834
10974
|
};
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
x,
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
side
|
|
10841
|
-
}, scrollContainer) => {
|
|
10975
|
+
|
|
10976
|
+
const createDebugMarker = (
|
|
10977
|
+
{ name, x, y, color = "255 0 0", side },
|
|
10978
|
+
scrollContainer,
|
|
10979
|
+
) => {
|
|
10842
10980
|
// Convert coordinates from document-relative to viewport
|
|
10843
10981
|
const [viewportX, viewportY] = getDebugMarkerPos(x, y, scrollContainer, side);
|
|
10982
|
+
|
|
10844
10983
|
const marker = document.createElement("div");
|
|
10845
10984
|
marker.className = `navi_debug_marker`;
|
|
10846
10985
|
marker.setAttribute(`data-${side}`, "");
|
|
10847
10986
|
// Set the color as a CSS custom property
|
|
10848
10987
|
marker.style.setProperty("--marker-color", `rgb(${color})`);
|
|
10849
10988
|
// Position markers exactly at the boundary coordinates
|
|
10850
|
-
marker.style.left =
|
|
10851
|
-
|
|
10989
|
+
marker.style.left =
|
|
10990
|
+
side === "right" ? `${viewportX - MARKER_SIZE}px` : `${viewportX}px`;
|
|
10991
|
+
marker.style.top =
|
|
10992
|
+
side === "bottom" ? `${viewportY - MARKER_SIZE}px` : `${viewportY}px`;
|
|
10852
10993
|
marker.title = name;
|
|
10853
10994
|
|
|
10854
10995
|
// Add label
|
|
@@ -10856,6 +10997,7 @@ const createDebugMarker = ({
|
|
|
10856
10997
|
label.className = `navi_debug_marker_label`;
|
|
10857
10998
|
label.textContent = name;
|
|
10858
10999
|
marker.appendChild(label);
|
|
11000
|
+
|
|
10859
11001
|
const container = getMarkersContainer();
|
|
10860
11002
|
container.appendChild(marker);
|
|
10861
11003
|
return marker;
|
|
@@ -10865,7 +11007,13 @@ const createObstacleMarker = (obstacleObj, scrollContainer) => {
|
|
|
10865
11007
|
const height = obstacleObj.bounds.bottom - obstacleObj.bounds.top;
|
|
10866
11008
|
|
|
10867
11009
|
// Convert document-relative coordinates to viewport coordinates
|
|
10868
|
-
const [x, y] = getDebugMarkerPos(
|
|
11010
|
+
const [x, y] = getDebugMarkerPos(
|
|
11011
|
+
obstacleObj.bounds.left,
|
|
11012
|
+
obstacleObj.bounds.top,
|
|
11013
|
+
scrollContainer,
|
|
11014
|
+
"obstacle",
|
|
11015
|
+
);
|
|
11016
|
+
|
|
10869
11017
|
const marker = document.createElement("div");
|
|
10870
11018
|
marker.className = "navi_obstacle_marker";
|
|
10871
11019
|
marker.style.left = `${x}px`;
|
|
@@ -10879,10 +11027,12 @@ const createObstacleMarker = (obstacleObj, scrollContainer) => {
|
|
|
10879
11027
|
label.className = "navi_obstacle_marker_label";
|
|
10880
11028
|
label.textContent = obstacleObj.name;
|
|
10881
11029
|
marker.appendChild(label);
|
|
11030
|
+
|
|
10882
11031
|
const container = getMarkersContainer();
|
|
10883
11032
|
container.appendChild(marker);
|
|
10884
11033
|
return marker;
|
|
10885
11034
|
};
|
|
11035
|
+
|
|
10886
11036
|
const createElementMarker = ({
|
|
10887
11037
|
left,
|
|
10888
11038
|
top,
|
|
@@ -10890,12 +11040,13 @@ const createElementMarker = ({
|
|
|
10890
11040
|
bottom,
|
|
10891
11041
|
scrollContainer,
|
|
10892
11042
|
label = "Element",
|
|
10893
|
-
color = "0, 200, 0" // Default green color
|
|
11043
|
+
color = "0, 200, 0", // Default green color
|
|
10894
11044
|
}) => {
|
|
10895
11045
|
const width = right - left;
|
|
10896
11046
|
const height = bottom - top;
|
|
10897
11047
|
// Convert document-relative coordinates to viewport coordinates
|
|
10898
11048
|
const [x, y] = getDebugMarkerPos(left, top, scrollContainer, "element");
|
|
11049
|
+
|
|
10899
11050
|
const marker = document.createElement("div");
|
|
10900
11051
|
marker.className = "navi_element_marker";
|
|
10901
11052
|
marker.style.left = `${x}px`;
|
|
@@ -10913,21 +11064,24 @@ const createElementMarker = ({
|
|
|
10913
11064
|
labelEl.className = "navi_element_marker_label";
|
|
10914
11065
|
labelEl.textContent = label;
|
|
10915
11066
|
marker.appendChild(labelEl);
|
|
11067
|
+
|
|
10916
11068
|
const container = getMarkersContainer();
|
|
10917
11069
|
container.appendChild(marker);
|
|
10918
11070
|
return marker;
|
|
10919
11071
|
};
|
|
11072
|
+
|
|
10920
11073
|
const createReferenceElementMarker = ({
|
|
10921
11074
|
left,
|
|
10922
11075
|
top,
|
|
10923
11076
|
right,
|
|
10924
11077
|
bottom,
|
|
10925
|
-
scrollContainer
|
|
11078
|
+
scrollContainer,
|
|
10926
11079
|
}) => {
|
|
10927
11080
|
const width = right - left;
|
|
10928
11081
|
const height = bottom - top;
|
|
10929
11082
|
// Convert document-relative coordinates to viewport coordinates
|
|
10930
11083
|
const [x, y] = getDebugMarkerPos(left, top, scrollContainer, "reference");
|
|
11084
|
+
|
|
10931
11085
|
const marker = document.createElement("div");
|
|
10932
11086
|
marker.className = "navi_reference_element_marker";
|
|
10933
11087
|
marker.style.left = `${x}px`;
|
|
@@ -10941,6 +11095,7 @@ const createReferenceElementMarker = ({
|
|
|
10941
11095
|
label.className = "navi_reference_element_marker_label";
|
|
10942
11096
|
label.textContent = "Reference Element";
|
|
10943
11097
|
marker.appendChild(label);
|
|
11098
|
+
|
|
10944
11099
|
const container = getMarkersContainer();
|
|
10945
11100
|
container.appendChild(marker);
|
|
10946
11101
|
return marker;
|
|
@@ -11847,39 +12002,8 @@ const createStickyFrontierOnAxis = (
|
|
|
11847
12002
|
return matchingStickyFrontiers;
|
|
11848
12003
|
};
|
|
11849
12004
|
|
|
11850
|
-
installImportMetaCssBuild(import.meta)
|
|
11851
|
-
|
|
11852
|
-
*
|
|
11853
|
-
* What a hand does is always the same — pick the thing up, carry it, let go — so
|
|
11854
|
-
* the gesture is not what distinguishes these. What distinguishes them is the
|
|
11855
|
-
* outcome the caller asked for, and that is what `startDragTo` takes:
|
|
11856
|
-
*
|
|
11857
|
-
* - **move**: it stays where it was put. The element ITSELF travels and keeps the
|
|
11858
|
-
* place the hand gave it.
|
|
11859
|
-
* - **reorder**: it takes a place in a list. A COPY travels while the original
|
|
11860
|
-
* keeps its place in the layout, which is what makes the gesture possible at
|
|
11861
|
-
* all — nothing else moves while the hand looks for a place, so there is a
|
|
11862
|
-
* stable row of items to look between.
|
|
11863
|
-
* - **toss**: it is gotten rid of. The same copy, for the opposite reason: the
|
|
11864
|
-
* original stays until the answer says it is really gone.
|
|
11865
|
-
* - **land**: it comes down ON something. Also a copy, and the closest to
|
|
11866
|
-
* `reorder` — the difference is what a target IS: a row of a list is a place
|
|
11867
|
-
* BETWEEN two others, whereas a square of a board is a place of its own, which
|
|
11868
|
-
* may already be taken. So nothing is inserted and nothing is a no-op: the
|
|
11869
|
-
* answer is "this one came down on that one", and what that means (take the
|
|
11870
|
-
* place, swap the two, refuse) is the caller's.
|
|
11871
|
-
*
|
|
11872
|
-
* The caller lists which outcomes ITS element can answer, and only the machinery
|
|
11873
|
-
* those need runs: no copy for a move, no drop hint for something that can only be
|
|
11874
|
-
* thrown away, no landing looked for where nothing lands. `reorder` and `toss`
|
|
11875
|
-
* combine (dropped on a row, or thrown off the screen); `move` and `reorder` cannot
|
|
11876
|
-
* both be true of one release, and the caller is the one who must not ask for both.
|
|
11877
|
-
*
|
|
11878
|
-
* `createDragToMoveGestureController` below is the layer under all of that — the
|
|
11879
|
-
* translation, the auto-scroll, the constraints — and stays usable on its own for
|
|
11880
|
-
* anything that is none of the three (a table column being dragged, a sticky
|
|
11881
|
-
* frontier being moved).
|
|
11882
|
-
*/
|
|
12005
|
+
installImportMetaCssBuild(import.meta);
|
|
12006
|
+
|
|
11883
12007
|
const dragStyleController = createStyleController("drag_to_move");
|
|
11884
12008
|
|
|
11885
12009
|
// How long the copy takes to leave the screen, and to come back. Written into the
|
|
@@ -11888,7 +12012,8 @@ const dragStyleController = createStyleController("drag_to_move");
|
|
|
11888
12012
|
const TOSS_DURATION_MS = 320;
|
|
11889
12013
|
// Far enough to be off any screen, in the direction the hand was going.
|
|
11890
12014
|
const TOSS_DISTANCE = 900;
|
|
11891
|
-
|
|
12015
|
+
|
|
12016
|
+
const css$1 = /* css */ `.navi_drop_hint {
|
|
11892
12017
|
inset: auto;
|
|
11893
12018
|
top: var(--drop-hint-y);
|
|
11894
12019
|
left: calc(var(--drop-target-left) + var(--drop-hint-margin-x, 0px));
|
|
@@ -12025,6 +12150,7 @@ const css$1 = /* css */`.navi_drop_hint {
|
|
|
12025
12150
|
transform: scale(var(--drag-clone-scale, 1.03));
|
|
12026
12151
|
transform-origin: var(--drag-origin);
|
|
12027
12152
|
transition: transform .15s cubic-bezier(.34, 1.56, .64, 1), box-shadow .15s;
|
|
12153
|
+
translate: none !important;
|
|
12028
12154
|
}
|
|
12029
12155
|
|
|
12030
12156
|
@starting-style {
|
|
@@ -12052,99 +12178,16 @@ import.meta.css = [css$1, "@jsenv/dom/src/interaction/drag/drag_to.js"];
|
|
|
12052
12178
|
// stays a descendant of whatever it is anchored in (a callout next to a button,
|
|
12053
12179
|
// in the card that carries both), and the press bubbles through the surface as
|
|
12054
12180
|
// if it had landed on it.
|
|
12055
|
-
const DRAG_IGNORED_SELECTOR =
|
|
12181
|
+
const DRAG_IGNORED_SELECTOR =
|
|
12182
|
+
'[data-drag-ignore],[data-self-interactions~="drag"],[data-self-interactions~="*"],[popover],dialog';
|
|
12056
12183
|
|
|
12057
12184
|
/**
|
|
12058
|
-
*
|
|
12059
|
-
*
|
|
12060
|
-
* Handles the full reorder UX:
|
|
12061
|
-
* - Activates only once the intent is established — a short movement with a mouse, a long
|
|
12062
|
-
* press with a finger (see `dragAfterIntent`), so that neither a click nor a scroll
|
|
12063
|
-
* reorders anything by accident.
|
|
12064
|
-
* - Clones the grabbed element and moves the clone while the original stays hidden in place
|
|
12065
|
-
* (keeps the layout intact so other items don't shift during the drag).
|
|
12066
|
-
* - The clone and the drop-hint live in the dragged element's own parent, so the CSS vars
|
|
12067
|
-
* that dress them (`--drag-clone-shadow`, `--drop-hint-size`, …) reach them by plain
|
|
12068
|
-
* inheritance, and so do the rules the list writes for its items.
|
|
12069
|
-
* - Shows a drop-hint line indicating where the item will land.
|
|
12070
|
-
* - Drop-target detection is intersection-based: the clone's bounding rect is compared
|
|
12071
|
-
* against every item that matches `itemSelector` in the scroll container.
|
|
12072
|
-
* - No-ops are filtered: releasing on the grabbed element itself, or in a position that
|
|
12073
|
-
* would leave it at exactly the same index, never triggers `onReorder`.
|
|
12074
|
-
* - On a valid drop, the clone animates to the drop position via the View Transitions API,
|
|
12075
|
-
* `onReorder` is called inside the transition callback so the DOM update and the animation
|
|
12076
|
-
* are captured together, then the clone is removed.
|
|
12077
|
-
* - On a cancelled drop (pointer released with no valid target), the clone is removed
|
|
12078
|
-
* immediately without calling `onReorder`.
|
|
12079
|
-
*
|
|
12080
|
-
* IDs are used as the bridge between DOM elements and JS state because:
|
|
12081
|
-
* - Not all DOM elements matching `itemSelector` may be valid drop targets
|
|
12082
|
-
* (holes in the structure), so DOM indices don't reliably map to state indices.
|
|
12083
|
-
* - Virtual lists render fewer DOM nodes than the total item count, so
|
|
12084
|
-
* DOM-index-based counting would be wrong.
|
|
12185
|
+
* Creates a gesture controller that moves elements via drag.
|
|
12085
12186
|
*
|
|
12086
|
-
*
|
|
12087
|
-
* (
|
|
12088
|
-
*
|
|
12089
|
-
*
|
|
12090
|
-
*
|
|
12091
|
-
* @param {PointerEvent} event
|
|
12092
|
-
* The `pointerdown` event that may become a reorder.
|
|
12093
|
-
* @param {object} options
|
|
12094
|
-
* @param {Element} [options.draggedElement=event.currentTarget]
|
|
12095
|
-
* The list item to drag.
|
|
12096
|
-
* @param {Element} [options.containerElement=draggedElement.parentElement]
|
|
12097
|
-
* Element searched with `itemSelector` to find the items to drop between.
|
|
12098
|
-
* @param {string} [options.itemSelector]
|
|
12099
|
-
* CSS selector that matches all list items inside `containerElement`.
|
|
12100
|
-
* Used for drop-target detection and no-op filtering. Left out, nothing is a
|
|
12101
|
-
* drop target: no hint is drawn and no reorder can be answered — which is what
|
|
12102
|
-
* a drag that only ever throws the thing away asks for.
|
|
12103
|
-
* @param {function} options.getItemId
|
|
12104
|
-
* Returns the stable ID for a given DOM element.
|
|
12105
|
-
* Signature: `getItemId(element) → id`.
|
|
12106
|
-
* @param {function} options.onReorder
|
|
12107
|
-
* Called when the user drops the item in a new position.
|
|
12108
|
-
* Signature: `onReorder(fromId, toId, syncCloneWithDropTarget)`.
|
|
12109
|
-
* - `fromId`: stable ID of the dragged item.
|
|
12110
|
-
* - `toId`: stable ID of the item to insert before, or `null` to append at the end.
|
|
12111
|
-
* - `syncCloneWithDropTarget`: call it synchronously inside a
|
|
12112
|
-
* `document.startViewTransition` callback, next to the DOM mutation, so the
|
|
12113
|
-
* clone is captured at its landing position.
|
|
12114
|
-
* @param {(detail: {gestureInfo: object, dropTarget: Element|null}) => "reorder"|"toss"|"cancel"} [options.resolveDrop]
|
|
12115
|
-
* What THIS release means, when the answer is not simply "a target was found or
|
|
12116
|
-
* not": the same grab can be meant to reorder or to get rid of the thing, and
|
|
12117
|
-
* only the caller knows which — far and fast is a throw, over a row is a move.
|
|
12118
|
-
* Left out, a drop target reorders and anything else is cancelled.
|
|
12119
|
-
* @param {(detail: {gestureInfo: object}) => Promise|void} [options.onToss]
|
|
12120
|
-
* The release was a throw. The clone leaves the screen the way it was thrown
|
|
12121
|
-
* while this runs; it comes back if the promise rejects, because the thing still
|
|
12122
|
-
* exists and the screen has to say so.
|
|
12123
|
-
* @param {object} [options.direction={ x: false, y: true }]
|
|
12124
|
-
* Axes along which dragging is allowed. Passed to `createDragToMoveGestureController`.
|
|
12125
|
-
* @param {number} [options.threshold=5]
|
|
12126
|
-
* Distance (px) a mouse must travel before the press becomes a drag.
|
|
12127
|
-
* @param {boolean|"if-touch"} [options.longPress="if-touch"]
|
|
12128
|
-
* Which pointers start the drag by holding still instead of by travelling.
|
|
12129
|
-
* @param {number} [options.longPressDelay=400]
|
|
12130
|
-
* How long (ms) such a pointer must stay down.
|
|
12131
|
-
* @param {number} [options.longPressSlop=8]
|
|
12132
|
-
* How far (px) it may drift during that wait before the press is abandoned.
|
|
12133
|
-
* @param {function} [options.onPressStart]
|
|
12134
|
-
* The pointer went down and the wait began (a cue that the press counts).
|
|
12135
|
-
* @param {function} [options.onPressCancel]
|
|
12136
|
-
* The pointer moved or lifted before the wait was over.
|
|
12137
|
-
* @param {function} [options.onPress]
|
|
12138
|
-
* The wait completed and the item is now held (haptics, scale…).
|
|
12139
|
-
*/
|
|
12140
|
-
|
|
12141
|
-
/**
|
|
12142
|
-
* Creates a gesture controller that moves elements via drag.
|
|
12143
|
-
*
|
|
12144
|
-
* Wraps `createDragGestureController` and adds:
|
|
12145
|
-
* - Element translation via CSS transform (translate only; other existing transforms are preserved)
|
|
12146
|
-
* - Auto-scroll while dragging near scroll-container edges
|
|
12147
|
-
* - Constraints (area boundaries, obstacle elements)
|
|
12187
|
+
* Wraps `createDragGestureController` and adds:
|
|
12188
|
+
* - Element translation via CSS transform (translate only; other existing transforms are preserved)
|
|
12189
|
+
* - Auto-scroll while dragging near scroll-container edges
|
|
12190
|
+
* - Constraints (area boundaries, obstacle elements)
|
|
12148
12191
|
*
|
|
12149
12192
|
* The returned controller exposes a `grab(options)` / `grabViaPointer(event, options)` method.
|
|
12150
12193
|
* Key grab options:
|
|
@@ -12213,13 +12256,12 @@ const createDragToMoveGestureController = ({
|
|
|
12213
12256
|
cancelAnimationEasing = "ease-out",
|
|
12214
12257
|
...options
|
|
12215
12258
|
} = {}) => {
|
|
12216
|
-
const initGrabToMoveElement = (
|
|
12217
|
-
|
|
12218
|
-
referenceElement,
|
|
12219
|
-
|
|
12220
|
-
convertScrollablePosition
|
|
12221
|
-
}) => {
|
|
12259
|
+
const initGrabToMoveElement = (
|
|
12260
|
+
dragGesture,
|
|
12261
|
+
{ element, referenceElement, elementToMove, convertScrollablePosition },
|
|
12262
|
+
) => {
|
|
12222
12263
|
const scrollContainer = dragGesture.gestureInfo.scrollContainer;
|
|
12264
|
+
|
|
12223
12265
|
const direction = dragGesture.gestureInfo.direction;
|
|
12224
12266
|
// elementImpacted is either an externally provided elementToMove (e.g. a drag clone)
|
|
12225
12267
|
const elementImpacted = elementToMove || element;
|
|
@@ -12227,9 +12269,13 @@ const createDragToMoveGestureController = ({
|
|
|
12227
12269
|
// (e.g. a drag clone passed by the caller) or the element itself.
|
|
12228
12270
|
// Capture any pre-existing translate so we can accumulate on top of it
|
|
12229
12271
|
// rather than resetting it to zero on the first drag event.
|
|
12230
|
-
const transformAtGrab = dragStyleController.getUnderlyingValue(
|
|
12272
|
+
const transformAtGrab = dragStyleController.getUnderlyingValue(
|
|
12273
|
+
elementImpacted,
|
|
12274
|
+
"transform",
|
|
12275
|
+
);
|
|
12231
12276
|
const translateXAtGrab = transformAtGrab.translateX;
|
|
12232
12277
|
const translateYAtGrab = transformAtGrab.translateY;
|
|
12278
|
+
|
|
12233
12279
|
const cancelPosition = () => {
|
|
12234
12280
|
dragStyleController.clear(elementImpacted);
|
|
12235
12281
|
};
|
|
@@ -12239,7 +12285,7 @@ const createDragToMoveGestureController = ({
|
|
|
12239
12285
|
// animation has only to bridge the two.
|
|
12240
12286
|
const cancelPositionAnimated = ({
|
|
12241
12287
|
duration = cancelAnimationDuration,
|
|
12242
|
-
easing = cancelAnimationEasing
|
|
12288
|
+
easing = cancelAnimationEasing,
|
|
12243
12289
|
} = {}) => {
|
|
12244
12290
|
const transformWhileHeld = getComputedStyle(elementImpacted).transform;
|
|
12245
12291
|
cancelPosition();
|
|
@@ -12249,14 +12295,10 @@ const createDragToMoveGestureController = ({
|
|
|
12249
12295
|
}
|
|
12250
12296
|
// No fill: the element already sits at its resting transform, the
|
|
12251
12297
|
// animation only replays the way back to it.
|
|
12252
|
-
return elementImpacted.animate(
|
|
12253
|
-
transform: transformWhileHeld
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
}], {
|
|
12257
|
-
duration,
|
|
12258
|
-
easing
|
|
12259
|
-
});
|
|
12298
|
+
return elementImpacted.animate(
|
|
12299
|
+
[{ transform: transformWhileHeld }, { transform: transformAtRest }],
|
|
12300
|
+
{ duration, easing },
|
|
12301
|
+
);
|
|
12260
12302
|
};
|
|
12261
12303
|
const commitPosition = () => {
|
|
12262
12304
|
dragStyleController.commit(elementImpacted);
|
|
@@ -12264,6 +12306,7 @@ const createDragToMoveGestureController = ({
|
|
|
12264
12306
|
dragGesture.gestureInfo.cancelPosition = cancelPosition;
|
|
12265
12307
|
dragGesture.gestureInfo.cancelPositionAnimated = cancelPositionAnimated;
|
|
12266
12308
|
dragGesture.gestureInfo.commitPosition = commitPosition;
|
|
12309
|
+
|
|
12267
12310
|
dragGesture.addReleaseCallback(() => {
|
|
12268
12311
|
if (releasePositionEffect === "cancel") {
|
|
12269
12312
|
cancelPosition();
|
|
@@ -12274,6 +12317,7 @@ const createDragToMoveGestureController = ({
|
|
|
12274
12317
|
}
|
|
12275
12318
|
// "manual": caller handles cleanup, do nothing.
|
|
12276
12319
|
});
|
|
12320
|
+
|
|
12277
12321
|
let elementWidth;
|
|
12278
12322
|
let elementHeight;
|
|
12279
12323
|
{
|
|
@@ -12285,6 +12329,7 @@ const createDragToMoveGestureController = ({
|
|
|
12285
12329
|
updateElementDimension();
|
|
12286
12330
|
dragGesture.addBeforeDragCallback(updateElementDimension);
|
|
12287
12331
|
}
|
|
12332
|
+
|
|
12288
12333
|
let scrollArea;
|
|
12289
12334
|
{
|
|
12290
12335
|
// Snapshot at grab time so that DOM mutations during dragging
|
|
@@ -12293,9 +12338,10 @@ const createDragToMoveGestureController = ({
|
|
|
12293
12338
|
left: 0,
|
|
12294
12339
|
top: 0,
|
|
12295
12340
|
right: scrollContainer.scrollWidth,
|
|
12296
|
-
bottom: scrollContainer.scrollHeight
|
|
12341
|
+
bottom: scrollContainer.scrollHeight,
|
|
12297
12342
|
};
|
|
12298
12343
|
}
|
|
12344
|
+
|
|
12299
12345
|
let scrollport;
|
|
12300
12346
|
let autoScrollArea;
|
|
12301
12347
|
{
|
|
@@ -12306,11 +12352,14 @@ const createDragToMoveGestureController = ({
|
|
|
12306
12352
|
scrollport = getScrollport(scrollBox, scrollContainer);
|
|
12307
12353
|
autoScrollArea = scrollport;
|
|
12308
12354
|
if (stickyFrontiers) {
|
|
12309
|
-
autoScrollArea = applyStickyFrontiersToAutoScrollArea(
|
|
12310
|
-
|
|
12311
|
-
|
|
12312
|
-
|
|
12313
|
-
|
|
12355
|
+
autoScrollArea = applyStickyFrontiersToAutoScrollArea(
|
|
12356
|
+
autoScrollArea,
|
|
12357
|
+
{
|
|
12358
|
+
scrollContainer,
|
|
12359
|
+
direction,
|
|
12360
|
+
// dragGestureName,
|
|
12361
|
+
},
|
|
12362
|
+
);
|
|
12314
12363
|
}
|
|
12315
12364
|
if (autoScrollAreaPadding > 0) {
|
|
12316
12365
|
autoScrollArea = {
|
|
@@ -12321,7 +12370,7 @@ const createDragToMoveGestureController = ({
|
|
|
12321
12370
|
left: autoScrollArea.left + autoScrollAreaPadding,
|
|
12322
12371
|
top: autoScrollArea.top + autoScrollAreaPadding,
|
|
12323
12372
|
right: autoScrollArea.right - autoScrollAreaPadding,
|
|
12324
|
-
bottom: autoScrollArea.bottom - autoScrollAreaPadding
|
|
12373
|
+
bottom: autoScrollArea.bottom - autoScrollAreaPadding,
|
|
12325
12374
|
};
|
|
12326
12375
|
}
|
|
12327
12376
|
};
|
|
@@ -12344,77 +12393,97 @@ const createDragToMoveGestureController = ({
|
|
|
12344
12393
|
obstacleAttributeName,
|
|
12345
12394
|
showConstraintFeedbackLine,
|
|
12346
12395
|
showDebugMarkers,
|
|
12347
|
-
referenceElement
|
|
12348
|
-
});
|
|
12349
|
-
dragGesture.addBeforeDragCallback((layoutRequested, currentLayout, limitLayout, {
|
|
12350
|
-
dragEvent
|
|
12351
|
-
}) => {
|
|
12352
|
-
dragConstraints.applyConstraints(layoutRequested, currentLayout, limitLayout, {
|
|
12353
|
-
elementWidth,
|
|
12354
|
-
elementHeight,
|
|
12355
|
-
scrollArea,
|
|
12356
|
-
scrollport,
|
|
12357
|
-
hasCrossedScrollportLeftOnce,
|
|
12358
|
-
hasCrossedScrollportTopOnce,
|
|
12359
|
-
autoScrollArea,
|
|
12360
|
-
dragEvent
|
|
12361
|
-
});
|
|
12396
|
+
referenceElement,
|
|
12362
12397
|
});
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12398
|
+
dragGesture.addBeforeDragCallback(
|
|
12399
|
+
(layoutRequested, currentLayout, limitLayout, { dragEvent }) => {
|
|
12400
|
+
dragConstraints.applyConstraints(
|
|
12401
|
+
layoutRequested,
|
|
12402
|
+
currentLayout,
|
|
12403
|
+
limitLayout,
|
|
12404
|
+
{
|
|
12405
|
+
elementWidth,
|
|
12406
|
+
elementHeight,
|
|
12407
|
+
scrollArea,
|
|
12408
|
+
scrollport,
|
|
12409
|
+
hasCrossedScrollportLeftOnce,
|
|
12410
|
+
hasCrossedScrollportTopOnce,
|
|
12411
|
+
autoScrollArea,
|
|
12412
|
+
dragEvent,
|
|
12413
|
+
},
|
|
12414
|
+
);
|
|
12415
|
+
},
|
|
12416
|
+
);
|
|
12417
|
+
|
|
12418
|
+
const dragToMove = (gestureInfo) => {
|
|
12419
|
+
const { isGoingDown, isGoingUp, isGoingLeft, isGoingRight, layout } =
|
|
12420
|
+
gestureInfo;
|
|
12371
12421
|
const left = layout.left;
|
|
12372
12422
|
const top = layout.top;
|
|
12373
12423
|
const right = left + elementWidth;
|
|
12374
12424
|
const bottom = top + elementHeight;
|
|
12425
|
+
|
|
12375
12426
|
{
|
|
12376
|
-
hasCrossedScrollportLeftOnce =
|
|
12377
|
-
|
|
12378
|
-
|
|
12427
|
+
hasCrossedScrollportLeftOnce =
|
|
12428
|
+
hasCrossedScrollportLeftOnce || left < scrollport.left;
|
|
12429
|
+
hasCrossedScrollportTopOnce =
|
|
12430
|
+
hasCrossedScrollportTopOnce || top < scrollport.top;
|
|
12431
|
+
|
|
12432
|
+
const getScrollMove = (axis) => {
|
|
12379
12433
|
const isGoingPositive = axis === "x" ? isGoingRight : isGoingDown;
|
|
12380
12434
|
if (isGoingPositive) {
|
|
12381
12435
|
const elementEnd = axis === "x" ? right : bottom;
|
|
12382
|
-
const autoScrollAreaEnd =
|
|
12436
|
+
const autoScrollAreaEnd =
|
|
12437
|
+
axis === "x" ? autoScrollArea.right : autoScrollArea.bottom;
|
|
12438
|
+
|
|
12383
12439
|
if (elementEnd <= autoScrollAreaEnd) {
|
|
12384
12440
|
return 0;
|
|
12385
12441
|
}
|
|
12386
12442
|
const scrollAmountNeeded = elementEnd - autoScrollAreaEnd;
|
|
12387
12443
|
return scrollAmountNeeded;
|
|
12388
12444
|
}
|
|
12445
|
+
|
|
12389
12446
|
const isGoingNegative = axis === "x" ? isGoingLeft : isGoingUp;
|
|
12390
12447
|
if (!isGoingNegative) {
|
|
12391
12448
|
return 0;
|
|
12392
12449
|
}
|
|
12450
|
+
|
|
12393
12451
|
const referenceOrEl = referenceElement || element;
|
|
12394
|
-
const canAutoScrollNegative =
|
|
12452
|
+
const canAutoScrollNegative =
|
|
12453
|
+
axis === "x"
|
|
12454
|
+
? !referenceOrEl.hasAttribute("data-sticky-left") ||
|
|
12455
|
+
hasCrossedScrollportLeftOnce
|
|
12456
|
+
: !referenceOrEl.hasAttribute("data-sticky-top") ||
|
|
12457
|
+
hasCrossedScrollportTopOnce;
|
|
12395
12458
|
if (!canAutoScrollNegative) {
|
|
12396
12459
|
return 0;
|
|
12397
12460
|
}
|
|
12461
|
+
|
|
12398
12462
|
const elementStart = axis === "x" ? left : top;
|
|
12399
|
-
const autoScrollAreaStart =
|
|
12463
|
+
const autoScrollAreaStart =
|
|
12464
|
+
axis === "x" ? autoScrollArea.left : autoScrollArea.top;
|
|
12400
12465
|
if (elementStart >= autoScrollAreaStart) {
|
|
12401
12466
|
return 0;
|
|
12402
12467
|
}
|
|
12468
|
+
|
|
12403
12469
|
const scrollAmountNeeded = autoScrollAreaStart - elementStart;
|
|
12404
12470
|
return -scrollAmountNeeded;
|
|
12405
12471
|
};
|
|
12472
|
+
|
|
12406
12473
|
let scrollLeftTarget;
|
|
12407
12474
|
let scrollTopTarget;
|
|
12408
12475
|
if (direction.x) {
|
|
12409
12476
|
const containerScrollLeftMove = getScrollMove("x");
|
|
12410
12477
|
if (containerScrollLeftMove) {
|
|
12411
|
-
scrollLeftTarget =
|
|
12478
|
+
scrollLeftTarget =
|
|
12479
|
+
scrollContainer.scrollLeft + containerScrollLeftMove;
|
|
12412
12480
|
}
|
|
12413
12481
|
}
|
|
12414
12482
|
if (direction.y) {
|
|
12415
12483
|
const containerScrollTopMove = getScrollMove("y");
|
|
12416
12484
|
if (containerScrollTopMove) {
|
|
12417
|
-
scrollTopTarget =
|
|
12485
|
+
scrollTopTarget =
|
|
12486
|
+
scrollContainer.scrollTop + containerScrollTopMove;
|
|
12418
12487
|
}
|
|
12419
12488
|
}
|
|
12420
12489
|
// now we know what to do, do it
|
|
@@ -12425,12 +12494,13 @@ const createDragToMoveGestureController = ({
|
|
|
12425
12494
|
scrollContainer.scrollTop = scrollTopTarget;
|
|
12426
12495
|
}
|
|
12427
12496
|
}
|
|
12497
|
+
|
|
12428
12498
|
{
|
|
12429
|
-
const {
|
|
12499
|
+
const { scrollableLeft, scrollableTop } = layout;
|
|
12500
|
+
const [positionedLeft, positionedTop] = convertScrollablePosition(
|
|
12430
12501
|
scrollableLeft,
|
|
12431
|
-
scrollableTop
|
|
12432
|
-
|
|
12433
|
-
const [positionedLeft, positionedTop] = convertScrollablePosition(scrollableLeft, scrollableTop);
|
|
12502
|
+
scrollableTop,
|
|
12503
|
+
);
|
|
12434
12504
|
// Build the transform to apply, preserving any transforms that were
|
|
12435
12505
|
// already on the element before the grab (e.g. rotate from another
|
|
12436
12506
|
// controller), and accumulating from the pre-grab translate baseline.
|
|
@@ -12442,32 +12512,33 @@ const createDragToMoveGestureController = ({
|
|
|
12442
12512
|
// the distance covered. Dragging moves things on screen, so its translate
|
|
12443
12513
|
// has to come first, whatever else the element carries. The spread still
|
|
12444
12514
|
// wins on the value when the element already had a translate of its own.
|
|
12445
|
-
const transform = {
|
|
12446
|
-
translateX: 0,
|
|
12447
|
-
translateY: 0,
|
|
12448
|
-
...transformAtGrab
|
|
12449
|
-
};
|
|
12515
|
+
const transform = { translateX: 0, translateY: 0, ...transformAtGrab };
|
|
12450
12516
|
if (direction.x) {
|
|
12451
12517
|
const leftTarget = positionedLeft;
|
|
12452
12518
|
const leftAtGrab = dragGesture.gestureInfo.leftAtGrab;
|
|
12453
12519
|
const leftDelta = leftTarget - leftAtGrab;
|
|
12454
|
-
const translateX = translateXAtGrab
|
|
12520
|
+
const translateX = translateXAtGrab
|
|
12521
|
+
? translateXAtGrab + leftDelta
|
|
12522
|
+
: leftDelta;
|
|
12455
12523
|
transform.translateX = translateX;
|
|
12456
12524
|
}
|
|
12457
12525
|
if (direction.y) {
|
|
12458
12526
|
const topTarget = positionedTop;
|
|
12459
12527
|
const topAtGrab = dragGesture.gestureInfo.topAtGrab;
|
|
12460
12528
|
const topDelta = topTarget - topAtGrab;
|
|
12461
|
-
const translateY = translateYAtGrab
|
|
12529
|
+
const translateY = translateYAtGrab
|
|
12530
|
+
? translateYAtGrab + topDelta
|
|
12531
|
+
: topDelta;
|
|
12462
12532
|
transform.translateY = translateY;
|
|
12463
12533
|
}
|
|
12464
12534
|
dragStyleController.set(elementImpacted, {
|
|
12465
|
-
transform
|
|
12535
|
+
transform,
|
|
12466
12536
|
});
|
|
12467
12537
|
}
|
|
12468
12538
|
};
|
|
12469
12539
|
dragGesture.addDragCallback(dragToMove);
|
|
12470
12540
|
};
|
|
12541
|
+
|
|
12471
12542
|
const dragGestureController = createDragGestureController(options);
|
|
12472
12543
|
const grab = dragGestureController.grab;
|
|
12473
12544
|
dragGestureController.grab = ({
|
|
@@ -12478,23 +12549,28 @@ const createDragToMoveGestureController = ({
|
|
|
12478
12549
|
...rest
|
|
12479
12550
|
} = {}) => {
|
|
12480
12551
|
const scrollContainer = getScrollContainer(referenceElement || element);
|
|
12481
|
-
const [
|
|
12552
|
+
const [
|
|
12553
|
+
elementScrollableLeft,
|
|
12554
|
+
elementScrollableTop,
|
|
12555
|
+
convertScrollablePosition,
|
|
12556
|
+
] = createDragElementPositioner(element, referenceElement, elementToMove);
|
|
12482
12557
|
const dragGesture = grab({
|
|
12483
12558
|
element,
|
|
12484
12559
|
scrollContainer,
|
|
12485
12560
|
layoutScrollableLeft: elementScrollableLeft,
|
|
12486
12561
|
layoutScrollableTop: elementScrollableTop,
|
|
12487
12562
|
event,
|
|
12488
|
-
...rest
|
|
12563
|
+
...rest,
|
|
12489
12564
|
});
|
|
12490
12565
|
initGrabToMoveElement(dragGesture, {
|
|
12491
12566
|
element,
|
|
12492
12567
|
referenceElement,
|
|
12493
12568
|
elementToMove,
|
|
12494
|
-
convertScrollablePosition
|
|
12569
|
+
convertScrollablePosition,
|
|
12495
12570
|
});
|
|
12496
12571
|
return dragGesture;
|
|
12497
12572
|
};
|
|
12573
|
+
|
|
12498
12574
|
return dragGestureController;
|
|
12499
12575
|
};
|
|
12500
12576
|
|
|
@@ -12513,25 +12589,43 @@ const createDragToMoveGestureController = ({
|
|
|
12513
12589
|
* It was put somewhere. The position is already committed when this runs — the
|
|
12514
12590
|
* hand let go of it there — and travels back if the promise rejects.
|
|
12515
12591
|
* @param {Element} [options.containerElement=draggedElement.parentElement]
|
|
12516
|
-
*
|
|
12592
|
+
* Where the places are looked for with `itemSelector`, and where the drop hint
|
|
12593
|
+
* is drawn. The parent covers a list and a board, whose items are siblings of
|
|
12594
|
+
* what is carried; anything else has to be said — a palette beside the surface
|
|
12595
|
+
* it fills, a piece drawn INSIDE the place it can be put back on.
|
|
12517
12596
|
* @param {string} [options.itemSelector] What matches the items of the list.
|
|
12518
|
-
*
|
|
12597
|
+
* Left out, nothing is a place: no hint is drawn and no landing can be
|
|
12598
|
+
* answered, which is what a drag that only ever throws the thing away asks for.
|
|
12599
|
+
* @param {function} [options.getItemId] `getItemId(element) → id`. An id rather
|
|
12600
|
+
* than a DOM index, because the two do not match: a list draws fewer rows than
|
|
12601
|
+
* it has, a search reorders them, and the structure may have holes.
|
|
12519
12602
|
* @param {function} [options.onReorder]
|
|
12520
12603
|
* `onReorder(fromId, toId, syncCloneWithDropTarget)` — see its own note below.
|
|
12521
12604
|
* @param {(detail: {gestureInfo: object}) => Promise|void} [options.onToss]
|
|
12522
12605
|
* It was thrown away. The copy leaves the screen while this runs and comes back
|
|
12523
12606
|
* if the promise rejects, because the thing still exists and the screen has to
|
|
12524
12607
|
* say so.
|
|
12525
|
-
* @param {
|
|
12526
|
-
*
|
|
12527
|
-
* is
|
|
12528
|
-
*
|
|
12529
|
-
*
|
|
12530
|
-
*
|
|
12608
|
+
* @param {(detail: {fromId: string, toId: string, x: number, y: number, width: number, height: number, syncCloneWithDropTarget: Function}) => Promise|void} [options.onLand]
|
|
12609
|
+
* It came down on `toId`, which is an element and never null: nothing under the
|
|
12610
|
+
* copy is a cancelled release. `x`/`y`/`width`/`height` say WHERE on it, which
|
|
12611
|
+
* is the whole answer when the place is a surface — a plan, a map — with no
|
|
12612
|
+
* element under the copy to name. The copy is held until what comes back
|
|
12613
|
+
* settles, exactly like `onReorder`. `syncCloneWithDropTarget` takes an element
|
|
12614
|
+
* when the place is not the shape of what stands on it: the copy then takes
|
|
12615
|
+
* THAT box instead of the target's.
|
|
12531
12616
|
* @param {number} [options.tossDistance=110] How far a throw goes, in px.
|
|
12532
12617
|
* @param {number} [options.tossSpeed=0.45] And how fast, in px/ms. BOTH are asked
|
|
12533
12618
|
* for: one without the other is moving the thing while hesitating, and nothing is
|
|
12534
12619
|
* thrown away on a hesitation.
|
|
12620
|
+
* @param {("throw"|"release-outside")[]} [options.tossBy=["throw"]] How a toss
|
|
12621
|
+
* is made here. A THROW is far and fast — the flick that gets rid of something,
|
|
12622
|
+
* a list's gesture, judged before any landing. A RELEASE OUTSIDE has no place
|
|
12623
|
+
* under it — dragging something off the surface it belongs to and letting go,
|
|
12624
|
+
* deliberate and never a flick, a surface's gesture. Each is asked for on its
|
|
12625
|
+
* own: a surface wants only the second (a fast drag across it that ends ON it
|
|
12626
|
+
* has not asked for the thing to go), a list only the first (a row let go of
|
|
12627
|
+
* beside its list is a row put back). With no place to be outside of (no
|
|
12628
|
+
* `itemSelector`), every release is outside.
|
|
12535
12629
|
*
|
|
12536
12630
|
* Everything else is forwarded to `createDragToMoveGestureController`
|
|
12537
12631
|
* (`areaConstraint`, `autoScrollAreaPadding`, `direction`…) and to `dragAfterIntent`
|
|
@@ -12547,10 +12641,11 @@ const createDragToMoveGestureController = ({
|
|
|
12547
12641
|
* The gesture holds its copy until what `onReorder` returns settles, so returning
|
|
12548
12642
|
* the transition is what makes the landing continuous.
|
|
12549
12643
|
*/
|
|
12550
|
-
const startDragTo = (
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12644
|
+
const startDragTo = (
|
|
12645
|
+
event,
|
|
12646
|
+
effects,
|
|
12647
|
+
{ draggedElement = event.currentTarget, ...options } = {},
|
|
12648
|
+
) => {
|
|
12554
12649
|
// An area that opted out of dragging (a text one wants to select, a control that
|
|
12555
12650
|
// owns the gesture, a callout): the press there is none of our business.
|
|
12556
12651
|
if (isPressIgnored(event.target, draggedElement)) {
|
|
@@ -12569,13 +12664,10 @@ const startDragTo = (event, effects, {
|
|
|
12569
12664
|
canReorder,
|
|
12570
12665
|
canToss,
|
|
12571
12666
|
canLand,
|
|
12572
|
-
...options
|
|
12667
|
+
...options,
|
|
12573
12668
|
});
|
|
12574
12669
|
}
|
|
12575
|
-
return startDragToMoveElement(event, {
|
|
12576
|
-
draggedElement,
|
|
12577
|
-
...options
|
|
12578
|
-
});
|
|
12670
|
+
return startDragToMoveElement(event, { draggedElement, ...options });
|
|
12579
12671
|
};
|
|
12580
12672
|
|
|
12581
12673
|
/**
|
|
@@ -12584,70 +12676,73 @@ const startDragTo = (event, effects, {
|
|
|
12584
12676
|
* No copy, unlike the two others: what is being moved is the thing and not a
|
|
12585
12677
|
* stand-in for it, so there is nothing to put back and nothing to reveal.
|
|
12586
12678
|
*/
|
|
12587
|
-
const startDragToMoveElement = (
|
|
12588
|
-
|
|
12589
|
-
|
|
12590
|
-
|
|
12591
|
-
|
|
12592
|
-
longPressDelay,
|
|
12593
|
-
longPressSlop,
|
|
12594
|
-
onPressStart,
|
|
12595
|
-
onPressCancel,
|
|
12596
|
-
onPress,
|
|
12597
|
-
...options
|
|
12598
|
-
}) => {
|
|
12599
|
-
event.preventDefault();
|
|
12600
|
-
return dragAfterIntent(event, () => {
|
|
12601
|
-
const gestureController = createDragToMoveGestureController({
|
|
12602
|
-
releasePositionEffect: "manual",
|
|
12603
|
-
...options
|
|
12604
|
-
});
|
|
12605
|
-
const dragGesture = gestureController.grabViaPointer(event, {
|
|
12606
|
-
element: draggedElement
|
|
12607
|
-
});
|
|
12608
|
-
if (!dragGesture) {
|
|
12609
|
-
return null;
|
|
12610
|
-
}
|
|
12611
|
-
dragGesture.addReleaseCallback(async gestureInfo => {
|
|
12612
|
-
const {
|
|
12613
|
-
xDelta,
|
|
12614
|
-
yDelta
|
|
12615
|
-
} = gestureInfo.layout;
|
|
12616
|
-
if (!xDelta && !yDelta) {
|
|
12617
|
-
// Picked up and put back down: nothing moved, so nobody is told.
|
|
12618
|
-
gestureInfo.cancelPosition();
|
|
12619
|
-
return;
|
|
12620
|
-
}
|
|
12621
|
-
// Committed before the answer rather than after: the hand let go of it
|
|
12622
|
-
// there, and a thing that snaps home while a request is in flight says the
|
|
12623
|
-
// gesture was not understood.
|
|
12624
|
-
gestureInfo.commitPosition();
|
|
12625
|
-
try {
|
|
12626
|
-
await onMove?.({
|
|
12627
|
-
gestureInfo,
|
|
12628
|
-
x: xDelta,
|
|
12629
|
-
y: yDelta
|
|
12630
|
-
});
|
|
12631
|
-
} catch {
|
|
12632
|
-
gestureInfo.cancelPositionAnimated();
|
|
12633
|
-
}
|
|
12634
|
-
});
|
|
12635
|
-
return dragGesture;
|
|
12636
|
-
}, {
|
|
12679
|
+
const startDragToMoveElement = (
|
|
12680
|
+
event,
|
|
12681
|
+
{
|
|
12682
|
+
draggedElement,
|
|
12683
|
+
onMove,
|
|
12637
12684
|
threshold,
|
|
12638
12685
|
longPress,
|
|
12639
12686
|
longPressDelay,
|
|
12640
12687
|
longPressSlop,
|
|
12641
12688
|
onPressStart,
|
|
12642
12689
|
onPressCancel,
|
|
12643
|
-
onPress
|
|
12644
|
-
|
|
12690
|
+
onPress,
|
|
12691
|
+
...options
|
|
12692
|
+
},
|
|
12693
|
+
) => {
|
|
12694
|
+
event.preventDefault();
|
|
12695
|
+
return dragAfterIntent(
|
|
12696
|
+
event,
|
|
12697
|
+
() => {
|
|
12698
|
+
const gestureController = createDragToMoveGestureController({
|
|
12699
|
+
releasePositionEffect: "manual",
|
|
12700
|
+
...options,
|
|
12701
|
+
});
|
|
12702
|
+
const dragGesture = gestureController.grabViaPointer(event, {
|
|
12703
|
+
element: draggedElement,
|
|
12704
|
+
});
|
|
12705
|
+
if (!dragGesture) {
|
|
12706
|
+
return null;
|
|
12707
|
+
}
|
|
12708
|
+
dragGesture.addReleaseCallback(async (gestureInfo) => {
|
|
12709
|
+
const { xDelta, yDelta } = gestureInfo.layout;
|
|
12710
|
+
if (!xDelta && !yDelta) {
|
|
12711
|
+
// Picked up and put back down: nothing moved, so nobody is told.
|
|
12712
|
+
gestureInfo.cancelPosition();
|
|
12713
|
+
return;
|
|
12714
|
+
}
|
|
12715
|
+
// Committed before the answer rather than after: the hand let go of it
|
|
12716
|
+
// there, and a thing that snaps home while a request is in flight says the
|
|
12717
|
+
// gesture was not understood.
|
|
12718
|
+
gestureInfo.commitPosition();
|
|
12719
|
+
try {
|
|
12720
|
+
await onMove?.({ gestureInfo, x: xDelta, y: yDelta });
|
|
12721
|
+
} catch {
|
|
12722
|
+
gestureInfo.cancelPositionAnimated();
|
|
12723
|
+
}
|
|
12724
|
+
});
|
|
12725
|
+
return dragGesture;
|
|
12726
|
+
},
|
|
12727
|
+
{
|
|
12728
|
+
threshold,
|
|
12729
|
+
longPress,
|
|
12730
|
+
longPressDelay,
|
|
12731
|
+
longPressSlop,
|
|
12732
|
+
onPressStart,
|
|
12733
|
+
onPressCancel,
|
|
12734
|
+
onPress,
|
|
12735
|
+
},
|
|
12736
|
+
);
|
|
12645
12737
|
};
|
|
12646
12738
|
|
|
12647
12739
|
// Far and fast, both at once: one without the other is moving the thing while
|
|
12648
12740
|
// hesitating, and nothing is thrown away on a hesitation — it comes back.
|
|
12649
12741
|
const TOSS_DISTANCE_TO_COMMIT = 110;
|
|
12650
12742
|
const TOSS_SPEED_TO_COMMIT = 0.45;
|
|
12743
|
+
// The flick is how a list gets rid of a row; a surface says otherwise.
|
|
12744
|
+
const TOSS_BY_DEFAULT = ["throw"];
|
|
12745
|
+
|
|
12651
12746
|
const resolveDropMeaning = ({
|
|
12652
12747
|
gestureInfo,
|
|
12653
12748
|
hasDropTarget,
|
|
@@ -12655,7 +12750,8 @@ const resolveDropMeaning = ({
|
|
|
12655
12750
|
canToss,
|
|
12656
12751
|
canLand,
|
|
12657
12752
|
tossDistance = TOSS_DISTANCE_TO_COMMIT,
|
|
12658
|
-
tossSpeed = TOSS_SPEED_TO_COMMIT
|
|
12753
|
+
tossSpeed = TOSS_SPEED_TO_COMMIT,
|
|
12754
|
+
tossBy = TOSS_BY_DEFAULT,
|
|
12659
12755
|
}) => {
|
|
12660
12756
|
if (gestureInfo.cancelled) {
|
|
12661
12757
|
// Nobody let go of anything: the gesture was taken away mid-air (the
|
|
@@ -12663,11 +12759,8 @@ const resolveDropMeaning = ({
|
|
|
12663
12759
|
// to be at that moment is not a place it was put.
|
|
12664
12760
|
return "cancel";
|
|
12665
12761
|
}
|
|
12666
|
-
if (canToss) {
|
|
12667
|
-
const {
|
|
12668
|
-
xDelta,
|
|
12669
|
-
yDelta
|
|
12670
|
-
} = gestureInfo.layout;
|
|
12762
|
+
if (canToss && tossBy.includes("throw")) {
|
|
12763
|
+
const { xDelta, yDelta } = gestureInfo.layout;
|
|
12671
12764
|
const distance = Math.hypot(xDelta, yDelta);
|
|
12672
12765
|
if (distance > tossDistance && gestureInfo.velocity > tossSpeed) {
|
|
12673
12766
|
return "toss";
|
|
@@ -12681,6 +12774,21 @@ const resolveDropMeaning = ({
|
|
|
12681
12774
|
return "reorder";
|
|
12682
12775
|
}
|
|
12683
12776
|
}
|
|
12777
|
+
if (canToss && tossBy.includes("release-outside")) {
|
|
12778
|
+
// Let go of away from every place: the other way of meaning "get rid of
|
|
12779
|
+
// this", and the one a surface asks for — off the plan and down is
|
|
12780
|
+
// deliberate, never a flick.
|
|
12781
|
+
//
|
|
12782
|
+
// "Away from every place" is the last frame's answer, not the one above: a
|
|
12783
|
+
// row let go of where it already was has no place to INSERT it at and still
|
|
12784
|
+
// has a place under it. And it has to have gone somewhere to be away from
|
|
12785
|
+
// anything — picked up and put straight back down is a hand that changed its
|
|
12786
|
+
// mind, not a thing dropped over nothing.
|
|
12787
|
+
const { xDelta, yDelta } = gestureInfo.layout;
|
|
12788
|
+
if ((xDelta || yDelta) && !gestureInfo.dropTargetInfo) {
|
|
12789
|
+
return "toss";
|
|
12790
|
+
}
|
|
12791
|
+
}
|
|
12684
12792
|
return "cancel";
|
|
12685
12793
|
};
|
|
12686
12794
|
|
|
@@ -12692,47 +12800,45 @@ const resolveDropMeaning = ({
|
|
|
12692
12800
|
* for the opposite reason: the original stays until the answer says it is really
|
|
12693
12801
|
* gone.
|
|
12694
12802
|
*/
|
|
12695
|
-
const startDragToCarryCopy = (
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
|
|
12803
|
+
const startDragToCarryCopy = (
|
|
12804
|
+
event,
|
|
12805
|
+
{
|
|
12806
|
+
draggedElement,
|
|
12807
|
+
canReorder,
|
|
12808
|
+
canToss,
|
|
12809
|
+
canLand,
|
|
12810
|
+
// Something that can be thrown away has to be able to LEAVE. The default of
|
|
12811
|
+
// the layer below keeps what is dragged inside its scroll area, which is right
|
|
12812
|
+
// for a reorder (a row belongs to its list) and makes a throw impossible — the
|
|
12813
|
+
// copy hits the edge of the list and no distance is ever covered, so no throw
|
|
12814
|
+
// ever happens and no sideways movement is even visible.
|
|
12815
|
+
// Destructured with the default here rather than written at the call below: a
|
|
12816
|
+
// caller passing `areaConstraint: undefined` (which is what saying nothing
|
|
12817
|
+
// through an options object looks like) would otherwise put the layer below
|
|
12818
|
+
// back on its own default and undo this.
|
|
12819
|
+
areaConstraint = canToss ? "none" : undefined,
|
|
12820
|
+
containerElement = draggedElement.parentElement,
|
|
12821
|
+
itemSelector,
|
|
12822
|
+
getItemId,
|
|
12823
|
+
onReorder,
|
|
12824
|
+
onLand,
|
|
12825
|
+
onToss,
|
|
12826
|
+
tossDistance,
|
|
12827
|
+
tossSpeed,
|
|
12828
|
+
tossBy,
|
|
12829
|
+
// A list runs one way and reordering walks it; a board has places all around,
|
|
12830
|
+
// so something landing on one of them goes wherever the hand takes it.
|
|
12831
|
+
direction = canLand ? { x: true, y: true } : { x: false, y: true },
|
|
12832
|
+
threshold,
|
|
12833
|
+
longPress,
|
|
12834
|
+
longPressDelay,
|
|
12835
|
+
longPressSlop,
|
|
12836
|
+
onPressStart,
|
|
12837
|
+
onPressCancel,
|
|
12838
|
+
onPress,
|
|
12839
|
+
...options
|
|
12726
12840
|
},
|
|
12727
|
-
|
|
12728
|
-
longPress,
|
|
12729
|
-
longPressDelay,
|
|
12730
|
-
longPressSlop,
|
|
12731
|
-
onPressStart,
|
|
12732
|
-
onPressCancel,
|
|
12733
|
-
onPress,
|
|
12734
|
-
...options
|
|
12735
|
-
}) => {
|
|
12841
|
+
) => {
|
|
12736
12842
|
// An area that opted out of dragging (a text one wants to select, a control
|
|
12737
12843
|
// that owns the gesture, a callout): the press there is none of our business.
|
|
12738
12844
|
if (isPressIgnored(event.target, draggedElement)) {
|
|
@@ -12748,243 +12854,318 @@ const startDragToCarryCopy = (event, {
|
|
|
12748
12854
|
// had got to.
|
|
12749
12855
|
const startCarry = (pointerEvent, cloneWrapperCaught, onCarryStart) => {
|
|
12750
12856
|
pointerEvent.preventDefault();
|
|
12751
|
-
return dragAfterIntent(
|
|
12752
|
-
|
|
12753
|
-
|
|
12754
|
-
|
|
12755
|
-
|
|
12756
|
-
|
|
12757
|
-
|
|
12758
|
-
|
|
12759
|
-
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
direction,
|
|
12763
|
-
releasePositionEffect: "manual",
|
|
12764
|
-
areaConstraint,
|
|
12765
|
-
...options
|
|
12766
|
-
});
|
|
12767
|
-
const dragGesture = gestureController.grabViaPointer(pointerEvent, {
|
|
12768
|
-
element: draggedElement,
|
|
12769
|
-
elementToMove: cloneWrapper
|
|
12770
|
-
});
|
|
12771
|
-
// getDropTargetInfo uses gestureInfo.elementImpacted to compute the dragged rect.
|
|
12772
|
-
// Point it at the clone so drop detection tracks the clone's current position.
|
|
12773
|
-
dragGesture.gestureInfo.elementImpacted = cloneWrapper;
|
|
12774
|
-
|
|
12775
|
-
// No place to land, no hint: an element that can only be thrown away has
|
|
12776
|
-
// nowhere to be put. What the hint LOOKS like follows what a place is here —
|
|
12777
|
-
// a line in the gap between two items, or the place itself lit up.
|
|
12778
|
-
const dropHintEl = canLand ? createDropSurface() : canReorder ? createDropHint() : null;
|
|
12779
|
-
if (dropHintEl) {
|
|
12780
|
-
// In the container it draws into, which is where its own vars are set: the
|
|
12781
|
-
// shape of a drop hint is a property of the list or board it belongs to,
|
|
12782
|
-
// and reading it from there is inheritance rather than a hand-off.
|
|
12783
|
-
draggedElement.parentElement.appendChild(dropHintEl);
|
|
12784
|
-
}
|
|
12785
|
-
// The hint first, the clone second: that order is what stacks them in the
|
|
12786
|
-
// top layer. A copy taken back in hand is already up there, and the hint
|
|
12787
|
-
// just went above it — shown again it returns to the top, where what the
|
|
12788
|
-
// hand carries belongs.
|
|
12789
|
-
dropHintEl?.showPopover();
|
|
12790
|
-
if (cloneWrapperCaught) {
|
|
12791
|
-
cloneWrapper.hidePopover();
|
|
12792
|
-
}
|
|
12793
|
-
cloneWrapper.showPopover();
|
|
12794
|
-
|
|
12795
|
-
// currentBeforeElement: element before which the grabbed item will be inserted (null = end)
|
|
12796
|
-
// currentReleaseElement: the actual hovered drop target — used to snap the clone on release
|
|
12797
|
-
let currentBeforeElement;
|
|
12798
|
-
let currentReleaseElement;
|
|
12799
|
-
const clearDropHintDOM = () => {
|
|
12800
|
-
if (!dropHintEl) {
|
|
12801
|
-
return;
|
|
12857
|
+
return dragAfterIntent(
|
|
12858
|
+
pointerEvent,
|
|
12859
|
+
() => {
|
|
12860
|
+
// Here and nowhere else is where a press has turned into a carry — the
|
|
12861
|
+
// one moment both ways in (a finger held still, a mouse travelled)
|
|
12862
|
+
// agree on.
|
|
12863
|
+
onCarryStart?.();
|
|
12864
|
+
const cloneWrapper =
|
|
12865
|
+
cloneWrapperCaught || createDragClone(draggedElement, pointerEvent);
|
|
12866
|
+
if (cloneWrapperCaught) {
|
|
12867
|
+
liftDragClone(cloneWrapperCaught, pointerEvent);
|
|
12802
12868
|
}
|
|
12803
|
-
|
|
12804
|
-
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12869
|
+
draggedElement.setAttribute("navi-drag-clone-source", "");
|
|
12870
|
+
|
|
12871
|
+
const gestureController = createDragToMoveGestureController({
|
|
12872
|
+
direction,
|
|
12873
|
+
releasePositionEffect: "manual",
|
|
12874
|
+
areaConstraint,
|
|
12875
|
+
...options,
|
|
12876
|
+
});
|
|
12877
|
+
const dragGesture = gestureController.grabViaPointer(pointerEvent, {
|
|
12878
|
+
element: draggedElement,
|
|
12879
|
+
elementToMove: cloneWrapper,
|
|
12880
|
+
});
|
|
12881
|
+
// getDropTargetInfo uses gestureInfo.elementImpacted to compute the dragged rect.
|
|
12882
|
+
// Point it at the clone so drop detection tracks the clone's current position.
|
|
12883
|
+
dragGesture.gestureInfo.elementImpacted = cloneWrapper;
|
|
12884
|
+
|
|
12885
|
+
// No place to land, no hint: an element that can only be thrown away has
|
|
12886
|
+
// nowhere to be put. What the hint LOOKS like follows what a place is here —
|
|
12887
|
+
// a line in the gap between two items, or the place itself lit up.
|
|
12888
|
+
const dropHintEl = canLand
|
|
12889
|
+
? createDropSurface()
|
|
12890
|
+
: canReorder
|
|
12891
|
+
? createDropHint()
|
|
12892
|
+
: null;
|
|
12893
|
+
if (dropHintEl) {
|
|
12894
|
+
// Among the places it lights up, which is where its own vars are set: the
|
|
12895
|
+
// shape of a drop hint is a property of the list or board it belongs to,
|
|
12896
|
+
// and reading it from there is inheritance rather than a hand-off. The
|
|
12897
|
+
// copy goes the other way (see createDragClone) — it is dressed by where
|
|
12898
|
+
// the thing it copies stands, which may be a palette far from here.
|
|
12899
|
+
containerElement.appendChild(dropHintEl);
|
|
12819
12900
|
}
|
|
12820
|
-
|
|
12821
|
-
|
|
12822
|
-
|
|
12823
|
-
|
|
12824
|
-
|
|
12825
|
-
|
|
12826
|
-
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
12901
|
+
// The hint first, the clone second: that order is what stacks them in the
|
|
12902
|
+
// top layer. A copy taken back in hand is already up there, and the hint
|
|
12903
|
+
// just went above it — shown again it returns to the top, where what the
|
|
12904
|
+
// hand carries belongs.
|
|
12905
|
+
dropHintEl?.showPopover();
|
|
12906
|
+
if (cloneWrapperCaught) {
|
|
12907
|
+
cloneWrapper.hidePopover();
|
|
12908
|
+
}
|
|
12909
|
+
cloneWrapper.showPopover();
|
|
12910
|
+
|
|
12911
|
+
// currentBeforeElement: element before which the grabbed item will be inserted (null = end)
|
|
12912
|
+
// currentReleaseElement: the actual hovered drop target — used to snap the clone on release
|
|
12913
|
+
let currentBeforeElement;
|
|
12914
|
+
let currentReleaseElement;
|
|
12915
|
+
|
|
12916
|
+
const clearDropHintDOM = () => {
|
|
12917
|
+
if (!dropHintEl) {
|
|
12918
|
+
return;
|
|
12830
12919
|
}
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12920
|
+
dropHintEl.removeAttribute("data-drop-edge");
|
|
12921
|
+
dropHintEl.removeAttribute("data-drop-over");
|
|
12922
|
+
dropHintEl.style.removeProperty("--drop-target-top");
|
|
12923
|
+
dropHintEl.style.removeProperty("--drop-target-bottom");
|
|
12924
|
+
dropHintEl.style.removeProperty("--drop-target-left");
|
|
12925
|
+
dropHintEl.style.removeProperty("--drop-target-width");
|
|
12926
|
+
dropHintEl.style.removeProperty("--drop-target-height");
|
|
12927
|
+
};
|
|
12928
|
+
|
|
12929
|
+
const clearDropHint = () => {
|
|
12930
|
+
currentBeforeElement = undefined;
|
|
12931
|
+
currentReleaseElement = undefined;
|
|
12932
|
+
clearDropHintDOM();
|
|
12933
|
+
};
|
|
12934
|
+
|
|
12935
|
+
dragGesture.addDragCallback((gestureInfo) => {
|
|
12936
|
+
if (!dropHintEl) {
|
|
12937
|
+
return;
|
|
12834
12938
|
}
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
12848
|
-
|
|
12849
|
-
|
|
12850
|
-
|
|
12851
|
-
|
|
12939
|
+
const allItems = [];
|
|
12940
|
+
const items = [];
|
|
12941
|
+
for (const el of containerElement.querySelectorAll(itemSelector)) {
|
|
12942
|
+
// The copy in hand is a clone of an item, attributes included, and it
|
|
12943
|
+
// lives in this same parent: it matches the selector without being
|
|
12944
|
+
// an item. Excluded here rather than by stripping attributes off the
|
|
12945
|
+
// clone, because a custom itemSelector (a class, a tag) would still
|
|
12946
|
+
// match it.
|
|
12947
|
+
if (cloneWrapper.contains(el)) {
|
|
12948
|
+
continue;
|
|
12949
|
+
}
|
|
12950
|
+
allItems.push(el);
|
|
12951
|
+
if (el !== draggedElement) {
|
|
12952
|
+
items.push(el);
|
|
12953
|
+
}
|
|
12954
|
+
}
|
|
12955
|
+
|
|
12956
|
+
const dropTargetInfo = getDropTargetInfo(gestureInfo, items, {
|
|
12957
|
+
// The edges of a LIST: above the first row means the top of it, below
|
|
12958
|
+
// the last one means the end of it. A board has no such reading — away
|
|
12959
|
+
// from every place is away from every place.
|
|
12960
|
+
fallbackToEdge: !canLand,
|
|
12961
|
+
});
|
|
12962
|
+
gestureInfo.dropTargetInfo = dropTargetInfo || null;
|
|
12963
|
+
if (!dropTargetInfo) {
|
|
12964
|
+
clearDropHint();
|
|
12852
12965
|
return;
|
|
12853
12966
|
}
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
12876
|
-
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
12902
|
-
|
|
12903
|
-
|
|
12904
|
-
|
|
12905
|
-
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12915
|
-
|
|
12916
|
-
|
|
12967
|
+
if (canLand) {
|
|
12968
|
+
// The whole element is the target, so which of its edges the copy came
|
|
12969
|
+
// in by says nothing: there is no gap to be on one side of.
|
|
12970
|
+
const dropElement = dropTargetInfo.element;
|
|
12971
|
+
if (dropElement === currentReleaseElement) {
|
|
12972
|
+
return;
|
|
12973
|
+
}
|
|
12974
|
+
currentReleaseElement = dropElement;
|
|
12975
|
+
const dropRect = dropElement.getBoundingClientRect();
|
|
12976
|
+
dropHintEl.setAttribute("data-drop-over", "");
|
|
12977
|
+
dropHintEl.style.setProperty(
|
|
12978
|
+
"--drop-target-top",
|
|
12979
|
+
`${dropRect.top}px`,
|
|
12980
|
+
);
|
|
12981
|
+
dropHintEl.style.setProperty(
|
|
12982
|
+
"--drop-target-left",
|
|
12983
|
+
`${dropRect.left}px`,
|
|
12984
|
+
);
|
|
12985
|
+
dropHintEl.style.setProperty(
|
|
12986
|
+
"--drop-target-width",
|
|
12987
|
+
`${dropRect.width}px`,
|
|
12988
|
+
);
|
|
12989
|
+
dropHintEl.style.setProperty(
|
|
12990
|
+
"--drop-target-height",
|
|
12991
|
+
`${dropRect.height}px`,
|
|
12992
|
+
);
|
|
12993
|
+
return;
|
|
12994
|
+
}
|
|
12995
|
+
// Convert {element, edge} to a beforeElement using the items array
|
|
12996
|
+
// (not nextElementSibling, which breaks if non-item elements exist between items).
|
|
12997
|
+
// edge "start" → insert before the hovered element
|
|
12998
|
+
// edge "end" → insert before the next item (null = append at end)
|
|
12999
|
+
const edge = dropTargetInfo.elementSide.y;
|
|
13000
|
+
const hoveredIndex = items.indexOf(dropTargetInfo.element);
|
|
13001
|
+
const beforeElement =
|
|
13002
|
+
edge === "start"
|
|
13003
|
+
? dropTargetInfo.element
|
|
13004
|
+
: (items[hoveredIndex + 1] ?? null);
|
|
13005
|
+
// Detect no-op: result would leave the grabbed element in the same position.
|
|
13006
|
+
const elementIndex = allItems.indexOf(draggedElement);
|
|
13007
|
+
const elementNextItem = allItems[elementIndex + 1] ?? null;
|
|
13008
|
+
const isNoop = beforeElement === elementNextItem;
|
|
13009
|
+
if (isNoop) {
|
|
13010
|
+
clearDropHint();
|
|
13011
|
+
return;
|
|
13012
|
+
}
|
|
13013
|
+
// Early return if nothing changed.
|
|
13014
|
+
const releaseElement = dropTargetInfo.element;
|
|
13015
|
+
if (
|
|
13016
|
+
beforeElement === currentBeforeElement &&
|
|
13017
|
+
releaseElement === currentReleaseElement
|
|
13018
|
+
) {
|
|
13019
|
+
return;
|
|
13020
|
+
}
|
|
13021
|
+
currentBeforeElement = beforeElement;
|
|
13022
|
+
currentReleaseElement = releaseElement;
|
|
13023
|
+
// Update drop hint CSS vars.
|
|
13024
|
+
// beforeElement = null → insert at end (hint after last item)
|
|
13025
|
+
// beforeElement = X → insert before X (hint at top edge of X)
|
|
13026
|
+
const anchorEl = beforeElement || items[items.length - 1];
|
|
13027
|
+
const anchorEdge = beforeElement !== null ? "top" : "bottom";
|
|
13028
|
+
// Viewport coordinates, straight from the anchor row: the hint is fixed
|
|
13029
|
+
// in the page (see its CSS), so there is no container box to be relative
|
|
13030
|
+
// to and no scroll offset to add back.
|
|
13031
|
+
const anchorRect = anchorEl.getBoundingClientRect();
|
|
13032
|
+
dropHintEl.setAttribute("data-drop-edge", anchorEdge);
|
|
13033
|
+
dropHintEl.style.setProperty(
|
|
13034
|
+
"--drop-target-top",
|
|
13035
|
+
`${anchorRect.top}px`,
|
|
13036
|
+
);
|
|
13037
|
+
dropHintEl.style.setProperty(
|
|
13038
|
+
"--drop-target-bottom",
|
|
13039
|
+
`${anchorRect.bottom}px`,
|
|
13040
|
+
);
|
|
13041
|
+
dropHintEl.style.setProperty(
|
|
13042
|
+
"--drop-target-left",
|
|
13043
|
+
`${anchorRect.left}px`,
|
|
13044
|
+
);
|
|
13045
|
+
dropHintEl.style.setProperty(
|
|
13046
|
+
"--drop-target-width",
|
|
13047
|
+
`${anchorRect.width}px`,
|
|
13048
|
+
);
|
|
12917
13049
|
});
|
|
12918
13050
|
|
|
12919
|
-
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
const
|
|
12928
|
-
|
|
12929
|
-
|
|
12930
|
-
|
|
12931
|
-
|
|
12932
|
-
|
|
12933
|
-
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
|
|
12941
|
-
|
|
13051
|
+
dragGesture.addReleaseCallback(async (gestureInfo) => {
|
|
13052
|
+
clearDropHintDOM();
|
|
13053
|
+
dropHintEl?.remove();
|
|
13054
|
+
|
|
13055
|
+
// What THIS release means, from what the element said it can answer. A
|
|
13056
|
+
// throw is asked about first: it is the more insistent of the two, and a
|
|
13057
|
+
// hand that sent the thing across the screen has not asked for it to swap
|
|
13058
|
+
// places with whatever it happened to fly over.
|
|
13059
|
+
const hasDropTarget = canLand
|
|
13060
|
+
? currentReleaseElement !== undefined
|
|
13061
|
+
: currentBeforeElement !== undefined;
|
|
13062
|
+
const dropMeans = resolveDropMeaning({
|
|
13063
|
+
gestureInfo,
|
|
13064
|
+
hasDropTarget,
|
|
13065
|
+
canReorder,
|
|
13066
|
+
canToss,
|
|
13067
|
+
canLand,
|
|
13068
|
+
tossDistance,
|
|
13069
|
+
tossSpeed,
|
|
13070
|
+
tossBy,
|
|
13071
|
+
});
|
|
13072
|
+
|
|
13073
|
+
// Let go of and still on the screen: from here until it is taken away
|
|
13074
|
+
// the copy can be taken back in hand (see letCopyBeCaught).
|
|
13075
|
+
const copyLetGoOf = letCopyBeCaught(
|
|
13076
|
+
cloneWrapper,
|
|
13077
|
+
(pointerDownEvent, whenCarried) =>
|
|
13078
|
+
startCarry(pointerDownEvent, cloneWrapper, whenCarried),
|
|
13079
|
+
);
|
|
13080
|
+
|
|
13081
|
+
// The copy stops where the hand left it, and the answer is given a way to
|
|
13082
|
+
// take it the rest of the way — synchronously, inside a view transition, so
|
|
13083
|
+
// it is captured where it lands rather than where it was let go of.
|
|
13084
|
+
const landCopyOn = async (targetElement, answer) => {
|
|
13085
|
+
const clone = cloneWrapper.firstElementChild;
|
|
13086
|
+
// Bake the current visual position (transform included) into the CSS vars
|
|
13087
|
+
// so the copy stays where the user released it when the transform goes.
|
|
13088
|
+
setCloneViewportRect(cloneWrapper, cloneWrapper);
|
|
13089
|
+
gestureInfo.cancelPosition();
|
|
13090
|
+
// Where the copy comes down is not always the thing it came down ON: a
|
|
13091
|
+
// place of a board can be larger than what stands on it, and the copy
|
|
13092
|
+
// has to keep its own size and land where the item will be. Said with
|
|
13093
|
+
// an element, because the caller has one — the piece already standing
|
|
13094
|
+
// there, the empty slot waiting.
|
|
13095
|
+
const syncCloneWithDropTarget = (
|
|
13096
|
+
landingElement = targetElement,
|
|
13097
|
+
) => {
|
|
13098
|
+
setCloneViewportRect(cloneWrapper, landingElement);
|
|
13099
|
+
// Removing this attr drops the CSS scale, so the browser captures the
|
|
13100
|
+
// copy at scale 1 as the "new" state.
|
|
13101
|
+
clone.removeAttribute("navi-drag-clone");
|
|
13102
|
+
};
|
|
13103
|
+
await answer(syncCloneWithDropTarget);
|
|
12942
13104
|
};
|
|
12943
|
-
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
13105
|
+
|
|
13106
|
+
if (dropMeans === "toss") {
|
|
13107
|
+
// Bake the position the hand left it at, so the flight starts from
|
|
13108
|
+
// there rather than from where the clone was declared.
|
|
13109
|
+
setCloneViewportRect(cloneWrapper, cloneWrapper);
|
|
13110
|
+
gestureInfo.cancelPosition();
|
|
13111
|
+
const gone = await tossCloneAway(cloneWrapper, gestureInfo, onToss);
|
|
13112
|
+
if (!gone) {
|
|
13113
|
+
// It still exists, so the screen has to say so: the copy comes back
|
|
13114
|
+
// over the original, and taking it away then reveals the row in
|
|
13115
|
+
// place.
|
|
13116
|
+
await settleCloneBack(cloneWrapper, draggedElement);
|
|
13117
|
+
}
|
|
13118
|
+
} else if (dropMeans === "land") {
|
|
13119
|
+
// Read before the copy is put down: landing bakes its position and
|
|
13120
|
+
// drops the transform, and where it came down is where the hand let
|
|
13121
|
+
// go of it.
|
|
13122
|
+
const landedAt = getRectInside(cloneWrapper, currentReleaseElement);
|
|
13123
|
+
await landCopyOn(currentReleaseElement, (syncCloneWithDropTarget) =>
|
|
13124
|
+
onLand({
|
|
13125
|
+
fromId: getItemId(draggedElement),
|
|
13126
|
+
toId: getItemId(currentReleaseElement),
|
|
13127
|
+
...landedAt,
|
|
13128
|
+
syncCloneWithDropTarget,
|
|
13129
|
+
}),
|
|
13130
|
+
);
|
|
13131
|
+
} else if (dropMeans === "reorder") {
|
|
13132
|
+
await landCopyOn(currentReleaseElement, (syncCloneWithDropTarget) =>
|
|
13133
|
+
onReorder(
|
|
13134
|
+
getItemId(draggedElement),
|
|
13135
|
+
currentBeforeElement ? getItemId(currentBeforeElement) : null,
|
|
13136
|
+
syncCloneWithDropTarget,
|
|
13137
|
+
),
|
|
13138
|
+
);
|
|
12956
13139
|
}
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
|
|
12963
|
-
|
|
12964
|
-
|
|
12965
|
-
|
|
12966
|
-
|
|
12967
|
-
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
|
|
12973
|
-
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
12977
|
-
|
|
12978
|
-
|
|
12979
|
-
|
|
12980
|
-
|
|
12981
|
-
|
|
12982
|
-
|
|
12983
|
-
|
|
12984
|
-
|
|
12985
|
-
|
|
12986
|
-
onPress
|
|
12987
|
-
});
|
|
13140
|
+
if (await copyLetGoOf.settled()) {
|
|
13141
|
+
// In a hand again: the copy, and the place kept for it, are the new
|
|
13142
|
+
// gesture's — this one has nothing left to take away.
|
|
13143
|
+
return;
|
|
13144
|
+
}
|
|
13145
|
+
draggedElement.removeAttribute("navi-drag-clone-source");
|
|
13146
|
+
cloneWrapper.remove();
|
|
13147
|
+
});
|
|
13148
|
+
|
|
13149
|
+
return dragGesture;
|
|
13150
|
+
},
|
|
13151
|
+
{
|
|
13152
|
+
threshold,
|
|
13153
|
+
// A copy caught on its way home is not an ambiguous press: the hand
|
|
13154
|
+
// reached for something moving, and the press was already matched
|
|
13155
|
+
// against the copy's own box before it got here. The wait a finger is
|
|
13156
|
+
// asked for elsewhere tells a scroll from a drag, and there is no scroll
|
|
13157
|
+
// to tell it from — the copy covers that spot from the top layer. Asked
|
|
13158
|
+
// for anyway it cannot even be answered: the wait is about as long as the
|
|
13159
|
+
// journey, so the thing is home before the proof is done, while a mouse
|
|
13160
|
+
// takes it in five pixels.
|
|
13161
|
+
longPress: cloneWrapperCaught ? false : longPress,
|
|
13162
|
+
longPressDelay,
|
|
13163
|
+
longPressSlop,
|
|
13164
|
+
onPressStart,
|
|
13165
|
+
onPressCancel,
|
|
13166
|
+
onPress,
|
|
13167
|
+
},
|
|
13168
|
+
);
|
|
12988
13169
|
};
|
|
12989
13170
|
return startCarry(event);
|
|
12990
13171
|
};
|
|
@@ -13000,6 +13181,28 @@ const setCloneViewportRect = (cloneWrapper, el) => {
|
|
|
13000
13181
|
cloneWrapper.style.setProperty("--clone-height", `${rect.height}px`);
|
|
13001
13182
|
};
|
|
13002
13183
|
|
|
13184
|
+
// Where one box sits inside another, in that other's own content: its border and
|
|
13185
|
+
// its scroll are taken out, so the numbers say where IN the thing rather than
|
|
13186
|
+
// where in what is visible of it at that moment.
|
|
13187
|
+
const getRectInside = (element, containerElement) => {
|
|
13188
|
+
const rect = element.getBoundingClientRect();
|
|
13189
|
+
const containerRect = containerElement.getBoundingClientRect();
|
|
13190
|
+
return {
|
|
13191
|
+
x:
|
|
13192
|
+
rect.left -
|
|
13193
|
+
containerRect.left -
|
|
13194
|
+
containerElement.clientLeft +
|
|
13195
|
+
containerElement.scrollLeft,
|
|
13196
|
+
y:
|
|
13197
|
+
rect.top -
|
|
13198
|
+
containerRect.top -
|
|
13199
|
+
containerElement.clientTop +
|
|
13200
|
+
containerElement.scrollTop,
|
|
13201
|
+
width: rect.width,
|
|
13202
|
+
height: rect.height,
|
|
13203
|
+
};
|
|
13204
|
+
};
|
|
13205
|
+
|
|
13003
13206
|
// Creates the two-layer clone structure used for drag-to-reorder.
|
|
13004
13207
|
//
|
|
13005
13208
|
// Layer 1 — wrapper (navi-drag-clone-wrapper):
|
|
@@ -13017,7 +13220,7 @@ const setCloneViewportRect = (cloneWrapper, el) => {
|
|
|
13017
13220
|
|
|
13018
13221
|
// The chevron is the one the table's column drop preview uses, rotated by the
|
|
13019
13222
|
// CSS above so each cap points into the line.
|
|
13020
|
-
const dropHintTemplate = /* html
|
|
13223
|
+
const dropHintTemplate = /* html */ `
|
|
13021
13224
|
<div
|
|
13022
13225
|
class="navi_drop_hint"
|
|
13023
13226
|
popover="manual"
|
|
@@ -13043,6 +13246,7 @@ const createDropHint = () => {
|
|
|
13043
13246
|
div.innerHTML = dropHintTemplate.trim();
|
|
13044
13247
|
return div.firstElementChild;
|
|
13045
13248
|
};
|
|
13249
|
+
|
|
13046
13250
|
const createDropSurface = () => {
|
|
13047
13251
|
const div = document.createElement("div");
|
|
13048
13252
|
div.className = "navi_drop_surface";
|
|
@@ -13061,17 +13265,14 @@ const createDropSurface = () => {
|
|
|
13061
13265
|
* that means "get rid of this" — nobody waits to watch it go.
|
|
13062
13266
|
*/
|
|
13063
13267
|
const tossCloneAway = async (cloneWrapper, gestureInfo, onToss) => {
|
|
13064
|
-
const {
|
|
13065
|
-
xDelta,
|
|
13066
|
-
yDelta
|
|
13067
|
-
} = gestureInfo.layout;
|
|
13268
|
+
const { xDelta, yDelta } = gestureInfo.layout;
|
|
13068
13269
|
const distance = Math.hypot(xDelta, yDelta) || 1;
|
|
13069
13270
|
cloneWrapper.dataset.tossed = "away";
|
|
13070
|
-
cloneWrapper.style.translate = `${xDelta / distance * TOSS_DISTANCE}px ${
|
|
13271
|
+
cloneWrapper.style.translate = `${(xDelta / distance) * TOSS_DISTANCE}px ${
|
|
13272
|
+
(yDelta / distance) * TOSS_DISTANCE
|
|
13273
|
+
}px`;
|
|
13071
13274
|
try {
|
|
13072
|
-
await onToss?.({
|
|
13073
|
-
gestureInfo
|
|
13074
|
-
});
|
|
13275
|
+
await onToss?.({ gestureInfo });
|
|
13075
13276
|
return true;
|
|
13076
13277
|
} catch {
|
|
13077
13278
|
return false;
|
|
@@ -13088,11 +13289,17 @@ const tossCloneAway = async (cloneWrapper, gestureInfo, onToss) => {
|
|
|
13088
13289
|
*/
|
|
13089
13290
|
const settleCloneBack = (cloneWrapper, sourceElement) => {
|
|
13090
13291
|
const sourceRect = sourceElement.getBoundingClientRect();
|
|
13091
|
-
const releaseLeft = parseFloat(
|
|
13092
|
-
|
|
13292
|
+
const releaseLeft = parseFloat(
|
|
13293
|
+
cloneWrapper.style.getPropertyValue("--clone-left"),
|
|
13294
|
+
);
|
|
13295
|
+
const releaseTop = parseFloat(
|
|
13296
|
+
cloneWrapper.style.getPropertyValue("--clone-top"),
|
|
13297
|
+
);
|
|
13093
13298
|
cloneWrapper.dataset.tossed = "back";
|
|
13094
|
-
cloneWrapper.style.translate = `${sourceRect.left - releaseLeft}px ${
|
|
13095
|
-
|
|
13299
|
+
cloneWrapper.style.translate = `${sourceRect.left - releaseLeft}px ${
|
|
13300
|
+
sourceRect.top - releaseTop
|
|
13301
|
+
}px`;
|
|
13302
|
+
return new Promise((resolve) => {
|
|
13096
13303
|
setTimeout(resolve, TOSS_DURATION_MS);
|
|
13097
13304
|
});
|
|
13098
13305
|
};
|
|
@@ -13132,23 +13339,20 @@ const settleCloneBack = (cloneWrapper, sourceElement) => {
|
|
|
13132
13339
|
const letCopyBeCaught = (cloneWrapper, carryAgain) => {
|
|
13133
13340
|
let caught = false;
|
|
13134
13341
|
let pressIsOver = Promise.resolve();
|
|
13135
|
-
const onPointerDown = pointerDownEvent => {
|
|
13136
|
-
const {
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
clientY
|
|
13145
|
-
} = pointerDownEvent;
|
|
13146
|
-
if (clientX < left || clientX > right || clientY < top || clientY > bottom) {
|
|
13342
|
+
const onPointerDown = (pointerDownEvent) => {
|
|
13343
|
+
const { left, right, top, bottom } = cloneWrapper.getBoundingClientRect();
|
|
13344
|
+
const { clientX, clientY } = pointerDownEvent;
|
|
13345
|
+
if (
|
|
13346
|
+
clientX < left ||
|
|
13347
|
+
clientX > right ||
|
|
13348
|
+
clientY < top ||
|
|
13349
|
+
clientY > bottom
|
|
13350
|
+
) {
|
|
13147
13351
|
// Somewhere else on the page: this press is not about the copy.
|
|
13148
13352
|
return;
|
|
13149
13353
|
}
|
|
13150
13354
|
let pressIsOverResolve;
|
|
13151
|
-
pressIsOver = new Promise(resolve => {
|
|
13355
|
+
pressIsOver = new Promise((resolve) => {
|
|
13152
13356
|
pressIsOverResolve = resolve;
|
|
13153
13357
|
});
|
|
13154
13358
|
const onPointerEnd = () => {
|
|
@@ -13176,9 +13380,8 @@ const letCopyBeCaught = (cloneWrapper, carryAgain) => {
|
|
|
13176
13380
|
// caught with a finger and can with a mouse.
|
|
13177
13381
|
const root = document.documentElement;
|
|
13178
13382
|
root.setAttribute("data-drag-catchable", "");
|
|
13179
|
-
root.addEventListener("touchmove", keepTouchRefusable, {
|
|
13180
|
-
|
|
13181
|
-
});
|
|
13383
|
+
root.addEventListener("touchmove", keepTouchRefusable, { passive: false });
|
|
13384
|
+
|
|
13182
13385
|
return {
|
|
13183
13386
|
settled: async () => {
|
|
13184
13387
|
// A hand that lets go and presses again while the copy is still there is
|
|
@@ -13193,7 +13396,7 @@ const letCopyBeCaught = (cloneWrapper, carryAgain) => {
|
|
|
13193
13396
|
root.removeAttribute("data-drag-catchable");
|
|
13194
13397
|
root.removeEventListener("touchmove", keepTouchRefusable);
|
|
13195
13398
|
return caught;
|
|
13196
|
-
}
|
|
13399
|
+
},
|
|
13197
13400
|
};
|
|
13198
13401
|
};
|
|
13199
13402
|
|
|
@@ -13203,11 +13406,16 @@ const letCopyBeCaught = (cloneWrapper, carryAgain) => {
|
|
|
13203
13406
|
// again is somewhere else on it — so taking one back hands it both again.
|
|
13204
13407
|
const liftDragClone = (cloneWrapper, pointerEvent) => {
|
|
13205
13408
|
const rect = cloneWrapper.getBoundingClientRect();
|
|
13206
|
-
cloneWrapper.style.setProperty(
|
|
13409
|
+
cloneWrapper.style.setProperty(
|
|
13410
|
+
"--drag-origin",
|
|
13411
|
+
`${pointerEvent.clientX - rect.left}px ${pointerEvent.clientY - rect.top}px`,
|
|
13412
|
+
);
|
|
13207
13413
|
cloneWrapper.firstElementChild.setAttribute("navi-drag-clone", "");
|
|
13208
13414
|
};
|
|
13415
|
+
|
|
13209
13416
|
const createDragClone = (element, pointerEvent) => {
|
|
13210
13417
|
const rect = element.getBoundingClientRect();
|
|
13418
|
+
|
|
13211
13419
|
const wrapper = document.createElement("div");
|
|
13212
13420
|
wrapper.setAttribute("navi-drag-clone-wrapper", "");
|
|
13213
13421
|
// A copy can be caught on its way home (see settleCloneBack), which is a press
|
|
@@ -13223,7 +13431,10 @@ const createDragClone = (element, pointerEvent) => {
|
|
|
13223
13431
|
// Grab point within the element — used as transform-origin so the
|
|
13224
13432
|
// scale(1.15) expands from where the user clicked, not the element center.
|
|
13225
13433
|
// These offsets are element-relative so viewport coords are correct here.
|
|
13226
|
-
wrapper.style.setProperty(
|
|
13434
|
+
wrapper.style.setProperty(
|
|
13435
|
+
"--drag-origin",
|
|
13436
|
+
`${pointerEvent.clientX - rect.left}px ${pointerEvent.clientY - rect.top}px`,
|
|
13437
|
+
);
|
|
13227
13438
|
const elementClone = element.cloneNode(true);
|
|
13228
13439
|
// A deep copy copies the ids too, and two elements answering to one id is a
|
|
13229
13440
|
// document that lies: getElementById picks whichever comes first, an anchor
|
|
@@ -13240,6 +13451,18 @@ const createDragClone = (element, pointerEvent) => {
|
|
|
13240
13451
|
// (The original wears it too, but it is hidden — see navi-drag-clone-source.)
|
|
13241
13452
|
elementClone.setAttribute("data-grabbed", "");
|
|
13242
13453
|
elementClone.style.viewTransitionName = "navi-drag-clone";
|
|
13454
|
+
// The copy takes the wrapper's box, and nothing the page said about where the
|
|
13455
|
+
// ORIGINAL stands may place it: a piece drawn at "left: 40px; top: 130px" on
|
|
13456
|
+
// its board, a marker centred by a translate, a card pushed by a margin — the
|
|
13457
|
+
// deep copy carries all of that, inline styles included, and would sit that far
|
|
13458
|
+
// from the wrapper's corner, so from the hand. Written inline on the copy
|
|
13459
|
+
// because that is the one place that outranks both the inline values it copied
|
|
13460
|
+
// and any rule of the page's, without a stylesheet having to shout !important.
|
|
13461
|
+
elementClone.style.position = "absolute";
|
|
13462
|
+
elementClone.style.inset = "0";
|
|
13463
|
+
elementClone.style.margin = "0";
|
|
13464
|
+
elementClone.style.translate = "none";
|
|
13465
|
+
|
|
13243
13466
|
wrapper.appendChild(elementClone);
|
|
13244
13467
|
// Beside the thing it copies, so it stands where that thing stands: every
|
|
13245
13468
|
// inherited value and every custom property the original reads, the copy reads
|
|
@@ -13248,6 +13471,7 @@ const createDragClone = (element, pointerEvent) => {
|
|
|
13248
13471
|
// whatever its depth in the tree, and being fixed keeps it out of the
|
|
13249
13472
|
// scrollable overflow of the list it sits in.
|
|
13250
13473
|
element.parentElement.appendChild(wrapper);
|
|
13474
|
+
|
|
13251
13475
|
return wrapper;
|
|
13252
13476
|
};
|
|
13253
13477
|
|
|
@@ -13329,44 +13553,7 @@ const getResizeDirection = (element) => {
|
|
|
13329
13553
|
return { x, y };
|
|
13330
13554
|
};
|
|
13331
13555
|
|
|
13332
|
-
installImportMetaCssBuild(import.meta)
|
|
13333
|
-
* What a drag means when it TRAVELS: a whole screen pushed aside to bring in the
|
|
13334
|
-
* next one — slides inside one box, pages that are URLs.
|
|
13335
|
-
*
|
|
13336
|
-
* The pointer itself is not read here: reading a press, waiting for it to become
|
|
13337
|
-
* a gesture, capturing it, measuring how fast it goes, swallowing the click it
|
|
13338
|
-
* would have made is one gesture system for the whole codebase
|
|
13339
|
-
* (@jsenv/dom's drag_gesture + drag_after_intent), and this asks it for the
|
|
13340
|
-
* plain version — nothing carried, so no backdrop over the page, nothing made
|
|
13341
|
-
* inert, no focus taken: a screen slides and the page keeps its scrolling and
|
|
13342
|
-
* its keyboard.
|
|
13343
|
-
*
|
|
13344
|
-
* What IS here is everything that makes a travel a travel rather than a
|
|
13345
|
-
* carry — and it is policy, not plumbing:
|
|
13346
|
-
* - the axis is LOCKED by the first movement, instead of being constrained
|
|
13347
|
-
* ahead of time;
|
|
13348
|
-
* - a press becomes a gesture by distance for every pointer, finger included:
|
|
13349
|
-
* a swipe is a travel, and asking a finger to hold still first (the rule for
|
|
13350
|
-
* picking an object up) would mean waiting before being allowed to swipe;
|
|
13351
|
-
* - what travels walks ONE BOX, resists past its ends, and is measured from
|
|
13352
|
-
* where the finger is once it gets there — unless the caller has another box
|
|
13353
|
-
* to offer at that edge, and then the gesture walks on into it;
|
|
13354
|
-
* - letting go is a question with an answer: a third of a box, or a flick.
|
|
13355
|
-
*
|
|
13356
|
-
* What is NOT here is geometry: how big a box is, what lies one step that way,
|
|
13357
|
-
* what to paint while the finger moves. The caller knows those and nothing else
|
|
13358
|
-
* does — this reads the gesture and calls back.
|
|
13359
|
-
*
|
|
13360
|
-
* Who owns a gesture is decided in three places, and all three are read here:
|
|
13361
|
-
* - what says so itself, with [data-no-drag-travel] or by being a field — a
|
|
13362
|
-
* component that reads the pointer marks itself, because the container it
|
|
13363
|
-
* ends up in cannot know what it is;
|
|
13364
|
-
* - a scroller between the pointer and the box with room left that way, which
|
|
13365
|
-
* keeps the gesture until it has none;
|
|
13366
|
-
* - another box that travels, between the pointer and this one: the innermost
|
|
13367
|
-
* one walks the axis it walks, and leaves the others whatever axis it does
|
|
13368
|
-
* not (see axesLeftBy).
|
|
13369
|
-
*/
|
|
13556
|
+
installImportMetaCssBuild(import.meta);
|
|
13370
13557
|
|
|
13371
13558
|
// While a pointer is on something that travels: said on the document, because
|
|
13372
13559
|
// what has to be told is the document.
|
|
@@ -13375,7 +13562,8 @@ const GESTURE_ATTRIBUTE = "data-drag-travel-gesture";
|
|
|
13375
13562
|
// …and while one is actually travelling something, which is a later moment and
|
|
13376
13563
|
// takes more away (see the CSS).
|
|
13377
13564
|
const WALKING_ATTRIBUTE = "data-drag-travel-walking";
|
|
13378
|
-
|
|
13565
|
+
|
|
13566
|
+
import.meta.css = /* css */ [`
|
|
13379
13567
|
:root[${GESTURE_ATTRIBUTE}] {
|
|
13380
13568
|
/* The bounce the browser plays when a gesture reaches the end of a page —
|
|
13381
13569
|
and the swipe that goes back in history with it. Both are the browser
|
|
@@ -13535,24 +13723,42 @@ const DRAG_RESISTANCE = 0.3;
|
|
|
13535
13723
|
// press, and nothing hands that back mid-gesture — the travel would drag a
|
|
13536
13724
|
// selection along with it. With a finger the hold is the selection UI itself, so
|
|
13537
13725
|
// there is nothing to take there either.
|
|
13538
|
-
const PRESSED_INPUT_TYPES = [
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13726
|
+
const PRESSED_INPUT_TYPES = [
|
|
13727
|
+
'[type="checkbox"]',
|
|
13728
|
+
'[type="radio"]',
|
|
13729
|
+
'[type="button"]',
|
|
13730
|
+
'[type="submit"]',
|
|
13731
|
+
'[type="reset"]',
|
|
13732
|
+
'[type="image"]',
|
|
13733
|
+
'[type="color"]',
|
|
13734
|
+
'[type="file"]',
|
|
13735
|
+
].join(",");
|
|
13736
|
+
const NATIVE_POINTER_GESTURES = [
|
|
13737
|
+
{
|
|
13738
|
+
// The value follows the pointer, pixel by pixel: a thumb along its rail, a
|
|
13739
|
+
// spinner clicked up and down. There is no moment in that which could be
|
|
13740
|
+
// given away — the gesture IS the pointer.
|
|
13741
|
+
selector: 'input[type="range"],input[type="number"]',
|
|
13742
|
+
},
|
|
13743
|
+
{
|
|
13744
|
+
// The text answers the pointer, and differently per hand: a mouse drags a
|
|
13745
|
+
// caret through it (a selection), a finger drags the field's own content
|
|
13746
|
+
// sideways when it overflows, and a finger held still opens the selection
|
|
13747
|
+
// UI. Three things a field is for, against one travel that has a whole box
|
|
13748
|
+
// to start from.
|
|
13749
|
+
selector: [
|
|
13750
|
+
`input:not(${PRESSED_INPUT_TYPES},[type="range"],[type="number"])`,
|
|
13751
|
+
"textarea",
|
|
13752
|
+
'[contenteditable=""]',
|
|
13753
|
+
'[contenteditable="true"]',
|
|
13754
|
+
].join(","),
|
|
13755
|
+
},
|
|
13756
|
+
{
|
|
13757
|
+
// A menu opens under the pointer and the browser keeps the rest of the
|
|
13758
|
+
// gesture: dragged over, it picks an option.
|
|
13759
|
+
selector: "select",
|
|
13760
|
+
},
|
|
13761
|
+
];
|
|
13556
13762
|
|
|
13557
13763
|
// …and what an element says against its own tag. Every line above is about what
|
|
13558
13764
|
// an element DOES, and a tag only says what it IS: a picker's façade is an
|
|
@@ -13569,9 +13775,9 @@ const NATIVE_POINTER_GESTURES = [{
|
|
|
13569
13775
|
// that can really be typed in) has not made a travel possible, it has taken the
|
|
13570
13776
|
// gesture the user was making away from them.
|
|
13571
13777
|
const PRESS_ONLY_ATTRIBUTE = "data-press-only";
|
|
13572
|
-
const NATIVE_POINTER_GESTURE_SELECTOR = NATIVE_POINTER_GESTURES.map(
|
|
13573
|
-
selector
|
|
13574
|
-
|
|
13778
|
+
const NATIVE_POINTER_GESTURE_SELECTOR = NATIVE_POINTER_GESTURES.map(
|
|
13779
|
+
({ selector }) => `:is(${selector}):not([${PRESS_ONLY_ATTRIBUTE}])`,
|
|
13780
|
+
).join(",");
|
|
13575
13781
|
|
|
13576
13782
|
// What a drag must not start on: what answers the pointer natively (above),
|
|
13577
13783
|
// plus what has said the gesture is its own. A drag SOURCE is not in the list:
|
|
@@ -13583,7 +13789,15 @@ const NATIVE_POINTER_GESTURE_SELECTOR = NATIVE_POINTER_GESTURES.map(({
|
|
|
13583
13789
|
// through (see DRAG_IGNORED_SELECTOR in drag_to.js). And so is a popover or a
|
|
13584
13790
|
// dialog: a layer OVER the box, whose press only bubbles through the box because
|
|
13585
13791
|
// the layer is anchored in it.
|
|
13586
|
-
const DRAG_EXCLUDED_SELECTOR = [
|
|
13792
|
+
const DRAG_EXCLUDED_SELECTOR = [
|
|
13793
|
+
NATIVE_POINTER_GESTURE_SELECTOR,
|
|
13794
|
+
"[data-drag-handle]",
|
|
13795
|
+
"[data-no-drag-travel]",
|
|
13796
|
+
'[data-self-interactions~="drag"]',
|
|
13797
|
+
'[data-self-interactions~="*"]',
|
|
13798
|
+
"[popover]",
|
|
13799
|
+
"dialog",
|
|
13800
|
+
].join(",");
|
|
13587
13801
|
|
|
13588
13802
|
// Which axes a box travels on, one attribute per gesture, said in the DOM by
|
|
13589
13803
|
// whoever owns the box: it is what a box ABOVE another reads to know the
|
|
@@ -13603,8 +13817,12 @@ const DRAG_SOURCE_AXES_ATTRIBUTE = "data-drag-source";
|
|
|
13603
13817
|
// covers everything. So a gesture that happened on it is not the gesture of any
|
|
13604
13818
|
// box it merely sits on top of, and every walk up the tree from the pointer
|
|
13605
13819
|
// ends here: the boxes above are behind, and behind is not under the finger.
|
|
13606
|
-
const TOP_LAYER_SELECTOR = [
|
|
13607
|
-
|
|
13820
|
+
const TOP_LAYER_SELECTOR = [
|
|
13821
|
+
":popover-open",
|
|
13822
|
+
"dialog:modal",
|
|
13823
|
+
":fullscreen",
|
|
13824
|
+
].join(",");
|
|
13825
|
+
const isTopLayer = (element) => {
|
|
13608
13826
|
return element.matches(TOP_LAYER_SELECTOR);
|
|
13609
13827
|
};
|
|
13610
13828
|
|
|
@@ -13675,12 +13893,10 @@ const scrollRoomTowards = (fromElement, stopElement, axis, sign) => {
|
|
|
13675
13893
|
return false;
|
|
13676
13894
|
}
|
|
13677
13895
|
const size = axis === "x" ? element.clientWidth : element.clientHeight;
|
|
13678
|
-
const scrollSize =
|
|
13896
|
+
const scrollSize =
|
|
13897
|
+
axis === "x" ? element.scrollWidth : element.scrollHeight;
|
|
13679
13898
|
if (scrollSize > size + 1) {
|
|
13680
|
-
const {
|
|
13681
|
-
overflowX,
|
|
13682
|
-
overflowY
|
|
13683
|
-
} = getComputedStyle(element);
|
|
13899
|
+
const { overflowX, overflowY } = getComputedStyle(element);
|
|
13684
13900
|
const overflow = axis === "x" ? overflowX : overflowY;
|
|
13685
13901
|
if (overflow === "auto" || overflow === "scroll") {
|
|
13686
13902
|
const position = axis === "x" ? element.scrollLeft : element.scrollTop;
|
|
@@ -13706,7 +13922,7 @@ const travelsAfter = ({
|
|
|
13706
13922
|
size,
|
|
13707
13923
|
velocity,
|
|
13708
13924
|
towardsSomething,
|
|
13709
|
-
commitRatio
|
|
13925
|
+
commitRatio,
|
|
13710
13926
|
}) => {
|
|
13711
13927
|
if (!towardsSomething) {
|
|
13712
13928
|
return false;
|
|
@@ -13727,11 +13943,15 @@ const travelsAfter = ({
|
|
|
13727
13943
|
// other way. Without this the picture alone decides, and a screen caught at
|
|
13728
13944
|
// two thirds and thrown back still arrives: the gesture was read as the
|
|
13729
13945
|
// place it was let go of rather than as a movement.
|
|
13730
|
-
if (
|
|
13946
|
+
if (
|
|
13947
|
+
Math.abs(velocity) > DRAG_THROW_BACK_VELOCITY &&
|
|
13948
|
+
Math.sign(velocity) !== sign
|
|
13949
|
+
) {
|
|
13731
13950
|
return false;
|
|
13732
13951
|
}
|
|
13733
13952
|
// …and going towards it travels, however slowly: see DRAG_DRIFT_VELOCITY.
|
|
13734
|
-
const drifting =
|
|
13953
|
+
const drifting =
|
|
13954
|
+
Math.abs(velocity) > DRAG_DRIFT_VELOCITY && Math.sign(velocity) === sign;
|
|
13735
13955
|
if (drifting && Math.abs(pulled) > DRAG_DRIFT_DISTANCE) {
|
|
13736
13956
|
return true;
|
|
13737
13957
|
}
|
|
@@ -13799,17 +14019,20 @@ const travelsAfter = ({
|
|
|
13799
14019
|
* refused it. Nothing was painted and nothing has to be put back — this is
|
|
13800
14020
|
* only so the caller can forget the gesture it is holding.
|
|
13801
14021
|
*/
|
|
13802
|
-
const startDragToTravel = (
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
14022
|
+
const startDragToTravel = (
|
|
14023
|
+
pointerDownEvent,
|
|
14024
|
+
{
|
|
14025
|
+
element,
|
|
14026
|
+
axes = "xy",
|
|
14027
|
+
immediate = false,
|
|
14028
|
+
commitRatio = DRAG_COMMIT_RATIO,
|
|
14029
|
+
onStart,
|
|
14030
|
+
onPull,
|
|
14031
|
+
onEnd,
|
|
14032
|
+
onEdge = () => false,
|
|
14033
|
+
onGiveUp = () => {},
|
|
14034
|
+
},
|
|
14035
|
+
) => {
|
|
13813
14036
|
const target = pointerDownEvent.target;
|
|
13814
14037
|
if (!target.closest || isPressExcluded(target, element)) {
|
|
13815
14038
|
return null;
|
|
@@ -13818,8 +14041,15 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13818
14041
|
// anything between them that is picked up and carried the same way: the
|
|
13819
14042
|
// gesture is theirs, and this one is left with the axes none of them walks —
|
|
13820
14043
|
// none at all, most of the time, and then there is no gesture here to read.
|
|
13821
|
-
const axesLeftByTravels = axesLeftBy(
|
|
13822
|
-
|
|
14044
|
+
const axesLeftByTravels = axesLeftBy(
|
|
14045
|
+
axes,
|
|
14046
|
+
target,
|
|
14047
|
+
element,
|
|
14048
|
+
DRAG_AXES_ATTRIBUTE,
|
|
14049
|
+
);
|
|
14050
|
+
const axesLeft =
|
|
14051
|
+
axesLeftByTravels &&
|
|
14052
|
+
axesLeftBy(axesLeftByTravels, target, element, DRAG_SOURCE_AXES_ATTRIBUTE);
|
|
13823
14053
|
if (!axesLeft) {
|
|
13824
14054
|
return null;
|
|
13825
14055
|
}
|
|
@@ -13835,6 +14065,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13835
14065
|
let travel = null;
|
|
13836
14066
|
let dragGesture = null;
|
|
13837
14067
|
let over = false;
|
|
14068
|
+
|
|
13838
14069
|
const finish = () => {
|
|
13839
14070
|
if (over) {
|
|
13840
14071
|
return;
|
|
@@ -13848,7 +14079,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13848
14079
|
// A press that never became a travel: the intent never resolved, or the axis
|
|
13849
14080
|
// it leaned on is not one this box walks. Nothing was painted and nothing has
|
|
13850
14081
|
// to be put back — the caller is only told so it can forget the gesture.
|
|
13851
|
-
const onPressOver = pointerEvent => {
|
|
14082
|
+
const onPressOver = (pointerEvent) => {
|
|
13852
14083
|
if (pointerEvent.pointerId !== pointerDownEvent.pointerId || travel) {
|
|
13853
14084
|
return;
|
|
13854
14085
|
}
|
|
@@ -13869,8 +14100,11 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13869
14100
|
// layout the gesture computes for something being carried: nothing is being
|
|
13870
14101
|
// carried here, and a scroll happening meanwhile must not read as a finger
|
|
13871
14102
|
// that moved.
|
|
13872
|
-
const coveredOn = (axis, gestureInfo) =>
|
|
13873
|
-
|
|
14103
|
+
const coveredOn = (axis, gestureInfo) =>
|
|
14104
|
+
axis === "x"
|
|
14105
|
+
? gestureInfo.dragX - gestureInfo.grabX
|
|
14106
|
+
: gestureInfo.dragY - gestureInfo.grabY;
|
|
14107
|
+
const pullOf = (gestureInfo) => {
|
|
13874
14108
|
const covered = coveredOn(travel.axis, gestureInfo);
|
|
13875
14109
|
return travel.slack + (covered - travel.origin);
|
|
13876
14110
|
};
|
|
@@ -13884,7 +14118,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13884
14118
|
const next = onEdge({
|
|
13885
14119
|
axis: travel.axis,
|
|
13886
14120
|
sign,
|
|
13887
|
-
event: gestureInfo.dragEvent
|
|
14121
|
+
event: gestureInfo.dragEvent,
|
|
13888
14122
|
});
|
|
13889
14123
|
if (!next || !next.size) {
|
|
13890
14124
|
return null;
|
|
@@ -13902,6 +14136,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13902
14136
|
travel.origin = coveredOn(travel.axis, gestureInfo) - pulled;
|
|
13903
14137
|
return pulled;
|
|
13904
14138
|
};
|
|
14139
|
+
|
|
13905
14140
|
const controller = createDragGestureController({
|
|
13906
14141
|
// The threshold is left at its default and never crossed: what says this
|
|
13907
14142
|
// press has become a gesture is the intent module below, which calls
|
|
@@ -13916,7 +14151,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13916
14151
|
onDragStart: () => {
|
|
13917
14152
|
document.documentElement.setAttribute(GESTURE_ATTRIBUTE, "");
|
|
13918
14153
|
},
|
|
13919
|
-
onDrag: gestureInfo => {
|
|
14154
|
+
onDrag: (gestureInfo) => {
|
|
13920
14155
|
// Releasing a gesture reports one last move, so giving one up would come
|
|
13921
14156
|
// back through here and give it up again, forever.
|
|
13922
14157
|
if (over) {
|
|
@@ -13982,7 +14217,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13982
14217
|
axis,
|
|
13983
14218
|
sign,
|
|
13984
14219
|
target,
|
|
13985
|
-
event: gestureInfo.dragEvent
|
|
14220
|
+
event: gestureInfo.dragEvent,
|
|
13986
14221
|
});
|
|
13987
14222
|
if (!started || !started.size) {
|
|
13988
14223
|
giveUp();
|
|
@@ -14003,17 +14238,21 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
14003
14238
|
// established before the press (see immediate): there was no
|
|
14004
14239
|
// threshold to cross, so every pixel since the grab is the hand's
|
|
14005
14240
|
// and is owed to it.
|
|
14006
|
-
origin: immediate
|
|
14007
|
-
|
|
14241
|
+
origin: immediate
|
|
14242
|
+
? 0
|
|
14243
|
+
: covered > DRAG_START_THRESHOLD
|
|
14244
|
+
? DRAG_START_THRESHOLD
|
|
14245
|
+
: covered < -DRAG_START_THRESHOLD
|
|
14246
|
+
? -DRAG_START_THRESHOLD
|
|
14247
|
+
: covered,
|
|
14248
|
+
pulled: started.slack || 0,
|
|
14008
14249
|
};
|
|
14009
14250
|
document.documentElement.setAttribute(WALKING_ATTRIBUTE, axis);
|
|
14010
14251
|
// The travel exists: from here the pointer is this box's, and it is
|
|
14011
14252
|
// followed wherever it goes.
|
|
14012
14253
|
dragGesture.capturePointer();
|
|
14013
14254
|
}
|
|
14014
|
-
const {
|
|
14015
|
-
axis
|
|
14016
|
-
} = travel;
|
|
14255
|
+
const { axis } = travel;
|
|
14017
14256
|
let pulled = pullOf(gestureInfo);
|
|
14018
14257
|
// Which side is being pulled in: dragging to the right brings in what is
|
|
14019
14258
|
// on the left, which is what comes BEFORE.
|
|
@@ -14051,7 +14290,8 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
14051
14290
|
// from where the finger IS once it has reached the end, so turning
|
|
14052
14291
|
// around moves the picture at once instead of first walking back
|
|
14053
14292
|
// over the distance the hand went too far.
|
|
14054
|
-
travel.origin =
|
|
14293
|
+
travel.origin =
|
|
14294
|
+
coveredOn(axis, gestureInfo) - (pulled - travel.slack);
|
|
14055
14295
|
} else {
|
|
14056
14296
|
pulled = relayed;
|
|
14057
14297
|
size = travel.size;
|
|
@@ -14064,46 +14304,42 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
14064
14304
|
pulled,
|
|
14065
14305
|
size,
|
|
14066
14306
|
progress: pulled / size,
|
|
14067
|
-
event: gestureInfo.dragEvent
|
|
14307
|
+
event: gestureInfo.dragEvent,
|
|
14068
14308
|
});
|
|
14069
14309
|
},
|
|
14070
|
-
onRelease: gestureInfo => {
|
|
14310
|
+
onRelease: (gestureInfo) => {
|
|
14071
14311
|
if (over || !travel) {
|
|
14072
14312
|
return;
|
|
14073
14313
|
}
|
|
14074
14314
|
finish();
|
|
14075
|
-
const {
|
|
14076
|
-
axis,
|
|
14077
|
-
size,
|
|
14078
|
-
pulled,
|
|
14079
|
-
slack
|
|
14080
|
-
} = travel;
|
|
14315
|
+
const { axis, size, pulled, slack } = travel;
|
|
14081
14316
|
const towardsSomething = pulled > 0 ? travel.travelBack : travel.travelOn;
|
|
14082
|
-
const velocity =
|
|
14317
|
+
const velocity =
|
|
14318
|
+
axis === "x" ? gestureInfo.velocityX : gestureInfo.velocityY;
|
|
14083
14319
|
// A gesture taken away rather than let go of (the browser scrolling
|
|
14084
14320
|
// something else, a call coming in, another gesture taking the pointer)
|
|
14085
14321
|
// said nothing: things go back.
|
|
14086
14322
|
const releaseEvent = gestureInfo.releaseEvent || gestureInfo.dragEvent;
|
|
14087
|
-
const {
|
|
14088
|
-
cancelled
|
|
14089
|
-
} = gestureInfo;
|
|
14323
|
+
const { cancelled } = gestureInfo;
|
|
14090
14324
|
onEnd({
|
|
14091
14325
|
axis,
|
|
14092
14326
|
pulled,
|
|
14093
14327
|
size,
|
|
14094
14328
|
sign: pulled > 0 ? 1 : -1,
|
|
14095
|
-
travels:
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
|
|
14102
|
-
|
|
14329
|
+
travels:
|
|
14330
|
+
!cancelled &&
|
|
14331
|
+
travelsAfter({
|
|
14332
|
+
pulled,
|
|
14333
|
+
slack,
|
|
14334
|
+
size,
|
|
14335
|
+
velocity,
|
|
14336
|
+
towardsSomething,
|
|
14337
|
+
commitRatio,
|
|
14338
|
+
}),
|
|
14103
14339
|
cancelled,
|
|
14104
|
-
event: releaseEvent
|
|
14340
|
+
event: releaseEvent,
|
|
14105
14341
|
});
|
|
14106
|
-
}
|
|
14342
|
+
},
|
|
14107
14343
|
});
|
|
14108
14344
|
|
|
14109
14345
|
// When a press becomes a gesture, and by which rule. A travel is a swipe, so
|
|
@@ -14125,7 +14361,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
14125
14361
|
// would be taken away from whoever else is reading the same press for
|
|
14126
14362
|
// gestures that give themselves up one event later. It is claimed once
|
|
14127
14363
|
// the travel exists, in onDrag below.
|
|
14128
|
-
pointerCaptureDeferred: true
|
|
14364
|
+
pointerCaptureDeferred: true,
|
|
14129
14365
|
});
|
|
14130
14366
|
return dragGesture;
|
|
14131
14367
|
};
|
|
@@ -14138,16 +14374,17 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
14138
14374
|
} else {
|
|
14139
14375
|
dragAfterIntent(pointerDownEvent, grab, {
|
|
14140
14376
|
longPress: false,
|
|
14141
|
-
threshold: DRAG_START_THRESHOLD
|
|
14377
|
+
threshold: DRAG_START_THRESHOLD,
|
|
14142
14378
|
});
|
|
14143
14379
|
}
|
|
14144
14380
|
window.addEventListener("pointerup", onPressOver);
|
|
14145
14381
|
window.addEventListener("pointercancel", onPressOver);
|
|
14382
|
+
|
|
14146
14383
|
return {
|
|
14147
14384
|
stop: () => {
|
|
14148
14385
|
finish();
|
|
14149
14386
|
dragGesture?.release();
|
|
14150
|
-
}
|
|
14387
|
+
},
|
|
14151
14388
|
};
|
|
14152
14389
|
};
|
|
14153
14390
|
|
|
@@ -14219,11 +14456,9 @@ const WHEEL_REGROW_RUN = 2;
|
|
|
14219
14456
|
* scrolls, and pushing content to the right reveals its left.
|
|
14220
14457
|
* @returns {() => void} stop listening.
|
|
14221
14458
|
*/
|
|
14222
|
-
const watchWheelTravel = (element, {
|
|
14223
|
-
axes = "xy",
|
|
14224
|
-
onStep
|
|
14225
|
-
}) => {
|
|
14459
|
+
const watchWheelTravel = (element, { axes = "xy", onStep }) => {
|
|
14226
14460
|
let gesture = null;
|
|
14461
|
+
|
|
14227
14462
|
const forgetGesture = () => {
|
|
14228
14463
|
gesture = null;
|
|
14229
14464
|
document.documentElement.removeAttribute(GESTURE_ATTRIBUTE);
|
|
@@ -14236,10 +14471,8 @@ const watchWheelTravel = (element, {
|
|
|
14236
14471
|
// pseudo-elements are told about pointer-events. Heard on the box alone, a
|
|
14237
14472
|
// gesture that sets a travel off loses every event after the first — and the
|
|
14238
14473
|
// page scrolls behind the travel with everything that was not taken.
|
|
14239
|
-
const isOverElement = wheelEvent => {
|
|
14240
|
-
const {
|
|
14241
|
-
target
|
|
14242
|
-
} = wheelEvent;
|
|
14474
|
+
const isOverElement = (wheelEvent) => {
|
|
14475
|
+
const { target } = wheelEvent;
|
|
14243
14476
|
if (element.contains(target)) {
|
|
14244
14477
|
return true;
|
|
14245
14478
|
}
|
|
@@ -14253,26 +14486,22 @@ const watchWheelTravel = (element, {
|
|
|
14253
14486
|
if (!target.contains(element)) {
|
|
14254
14487
|
return false;
|
|
14255
14488
|
}
|
|
14256
|
-
const {
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
top
|
|
14260
|
-
|
|
14261
|
-
} = element.getBoundingClientRect();
|
|
14262
|
-
const {
|
|
14263
|
-
clientX,
|
|
14264
|
-
clientY
|
|
14265
|
-
} = wheelEvent;
|
|
14266
|
-
return clientX >= left && clientX <= right && clientY >= top && clientY <= bottom;
|
|
14489
|
+
const { left, right, top, bottom } = element.getBoundingClientRect();
|
|
14490
|
+
const { clientX, clientY } = wheelEvent;
|
|
14491
|
+
return (
|
|
14492
|
+
clientX >= left && clientX <= right && clientY >= top && clientY <= bottom
|
|
14493
|
+
);
|
|
14267
14494
|
};
|
|
14268
|
-
|
|
14495
|
+
|
|
14496
|
+
const onWheel = (wheelEvent) => {
|
|
14269
14497
|
// The burst is already somebody else's — the box inside this one, a wheel
|
|
14270
14498
|
// picker, whoever answered its first event. It is theirs to the end of it,
|
|
14271
14499
|
// wherever the pointer has drifted since (see wheel_gesture.js).
|
|
14272
14500
|
if (wheelGestureIsTakenFrom(element)) {
|
|
14273
14501
|
return;
|
|
14274
14502
|
}
|
|
14275
|
-
const axis =
|
|
14503
|
+
const axis =
|
|
14504
|
+
Math.abs(wheelEvent.deltaX) > Math.abs(wheelEvent.deltaY) ? "x" : "y";
|
|
14276
14505
|
const delta = axis === "x" ? wheelEvent.deltaX : wheelEvent.deltaY;
|
|
14277
14506
|
if (!delta) {
|
|
14278
14507
|
return;
|
|
@@ -14297,15 +14526,16 @@ const watchWheelTravel = (element, {
|
|
|
14297
14526
|
// it — the same claims a press is read against (see the top of this
|
|
14298
14527
|
// file), and all of them are answered by giving the gesture up whole:
|
|
14299
14528
|
// nothing is prevented and the browser scrolls as it would have.
|
|
14300
|
-
const {
|
|
14301
|
-
|
|
14302
|
-
|
|
14303
|
-
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14529
|
+
const { target } = wheelEvent;
|
|
14530
|
+
if (
|
|
14531
|
+
(target.closest && isPressExcluded(target, element)) ||
|
|
14532
|
+
scrollRoomTowards(target, element, axis, sign) ||
|
|
14533
|
+
// …plus the third: a box below this one that travels on this axis. Its
|
|
14534
|
+
// watcher hears the same wheel event this one does — they all listen at
|
|
14535
|
+
// the document — so without this both step, and one push moves two
|
|
14536
|
+
// things.
|
|
14537
|
+
!axesLeftBy(axis, target, element, WHEEL_AXES_ATTRIBUTE)
|
|
14538
|
+
) {
|
|
14309
14539
|
return;
|
|
14310
14540
|
}
|
|
14311
14541
|
gesture = {
|
|
@@ -14316,7 +14546,7 @@ const watchWheelTravel = (element, {
|
|
|
14316
14546
|
fadeRun: 0,
|
|
14317
14547
|
growRun: 0,
|
|
14318
14548
|
faded: false,
|
|
14319
|
-
stepped: false
|
|
14549
|
+
stepped: false,
|
|
14320
14550
|
};
|
|
14321
14551
|
document.documentElement.setAttribute(GESTURE_ATTRIBUTE, "");
|
|
14322
14552
|
document.documentElement.setAttribute(WALKING_ATTRIBUTE, axis);
|
|
@@ -14345,9 +14575,7 @@ const watchWheelTravel = (element, {
|
|
|
14345
14575
|
}
|
|
14346
14576
|
// …and said on every event on our axis, because a claim nobody renews is a
|
|
14347
14577
|
// gesture that is over.
|
|
14348
|
-
claimWheelGesture(element, {
|
|
14349
|
-
onEnd: forgetGesture
|
|
14350
|
-
});
|
|
14578
|
+
claimWheelGesture(element, { onEnd: forgetGesture });
|
|
14351
14579
|
if (sign !== gesture.sign) {
|
|
14352
14580
|
// Turned around: what was adding up was going the other way.
|
|
14353
14581
|
gesture.sign = sign;
|
|
@@ -14364,11 +14592,7 @@ const watchWheelTravel = (element, {
|
|
|
14364
14592
|
// happen pushes harder rather than waiting. Everything a threshold could
|
|
14365
14593
|
// buy here is bought by what a screen AFTER this one costs.
|
|
14366
14594
|
gesture.stepped = true;
|
|
14367
|
-
onStep({
|
|
14368
|
-
axis: gesture.axis,
|
|
14369
|
-
sign: gesture.sign,
|
|
14370
|
-
event: wheelEvent
|
|
14371
|
-
});
|
|
14595
|
+
onStep({ axis: gesture.axis, sign: gesture.sign, event: wheelEvent });
|
|
14372
14596
|
return;
|
|
14373
14597
|
}
|
|
14374
14598
|
const magnitude = Math.abs(delta);
|
|
@@ -14393,11 +14617,7 @@ const watchWheelTravel = (element, {
|
|
|
14393
14617
|
gesture.faded = false;
|
|
14394
14618
|
gesture.growRun = 0;
|
|
14395
14619
|
gesture.pushed = 0;
|
|
14396
|
-
onStep({
|
|
14397
|
-
axis: gesture.axis,
|
|
14398
|
-
sign: gesture.sign,
|
|
14399
|
-
event: wheelEvent
|
|
14400
|
-
});
|
|
14620
|
+
onStep({ axis: gesture.axis, sign: gesture.sign, event: wheelEvent });
|
|
14401
14621
|
return;
|
|
14402
14622
|
}
|
|
14403
14623
|
if (gesture.fadeRun >= WHEEL_FADE_RUN) {
|
|
@@ -14408,20 +14628,15 @@ const watchWheelTravel = (element, {
|
|
|
14408
14628
|
return;
|
|
14409
14629
|
}
|
|
14410
14630
|
gesture.pushed = 0;
|
|
14411
|
-
onStep({
|
|
14412
|
-
axis: gesture.axis,
|
|
14413
|
-
sign: gesture.sign,
|
|
14414
|
-
event: wheelEvent
|
|
14415
|
-
});
|
|
14631
|
+
onStep({ axis: gesture.axis, sign: gesture.sign, event: wheelEvent });
|
|
14416
14632
|
};
|
|
14633
|
+
|
|
14417
14634
|
document.addEventListener("wheel", onWheel, {
|
|
14418
14635
|
passive: false,
|
|
14419
|
-
capture: true
|
|
14636
|
+
capture: true,
|
|
14420
14637
|
});
|
|
14421
14638
|
return () => {
|
|
14422
|
-
document.removeEventListener("wheel", onWheel, {
|
|
14423
|
-
capture: true
|
|
14424
|
-
});
|
|
14639
|
+
document.removeEventListener("wheel", onWheel, { capture: true });
|
|
14425
14640
|
// Handed back rather than left to lapse: a box that is gone must not hold a
|
|
14426
14641
|
// gesture the boxes still there are asking about.
|
|
14427
14642
|
releaseWheelGesture(element);
|
|
@@ -14462,11 +14677,13 @@ const isPressExcluded = (target, element) => {
|
|
|
14462
14677
|
* @param {Element} element Where the press landed.
|
|
14463
14678
|
* @returns {boolean}
|
|
14464
14679
|
*/
|
|
14465
|
-
const isPressDisputedByDrag = element => {
|
|
14680
|
+
const isPressDisputedByDrag = (element) => {
|
|
14466
14681
|
if (!element || typeof element.closest !== "function") {
|
|
14467
14682
|
return false;
|
|
14468
14683
|
}
|
|
14469
|
-
const dragged = element.closest(
|
|
14684
|
+
const dragged = element.closest(
|
|
14685
|
+
`[${DRAG_AXES_ATTRIBUTE}],[${DRAG_SOURCE_AXES_ATTRIBUTE}]`,
|
|
14686
|
+
);
|
|
14470
14687
|
if (!dragged) {
|
|
14471
14688
|
return false;
|
|
14472
14689
|
}
|
|
@@ -14534,6 +14751,12 @@ const isAncestorOpen = (ancestor) => {
|
|
|
14534
14751
|
* own closed-state CSS ([navi-hidden], :not([popover])) for the custom
|
|
14535
14752
|
* renderers — so nothing inside one answers true here, while everything a
|
|
14536
14753
|
* trigger keeps on screen does.
|
|
14754
|
+
*
|
|
14755
|
+
* Ask it about an ancestor that is closed AND settled — on mount, or when
|
|
14756
|
+
* setting up a long-lived observer. A surface animating its way out still has a
|
|
14757
|
+
* box for the length of the animation (transition-behavior: allow-discrete on
|
|
14758
|
+
* display/overlay), so asked at the instant one closes this says "on screen"
|
|
14759
|
+
* about something on its way off it.
|
|
14537
14760
|
*/
|
|
14538
14761
|
const isDisplayedDespiteClosedAncestor = (element) => {
|
|
14539
14762
|
if (typeof element.checkVisibility !== "function") {
|
|
@@ -14681,36 +14904,17 @@ const getWidth = (element) => {
|
|
|
14681
14904
|
return width;
|
|
14682
14905
|
};
|
|
14683
14906
|
|
|
14684
|
-
installImportMetaCssBuild(import.meta)
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
* This module provides a workaround for position:sticky limitations when used with
|
|
14688
|
-
* overflow:auto/hidden parent elements (see https://github.com/w3c/csswg-drafts/issues/865).
|
|
14689
|
-
*
|
|
14690
|
-
* How it works:
|
|
14691
|
-
* 1. Creates a placeholder clone of the sticky element to maintain document flow
|
|
14692
|
-
* 2. Positions the real element using fixed positioning relative to viewport
|
|
14693
|
-
* 3. Adjusts position on scroll to emulate position:sticky behavior
|
|
14694
|
-
* 4. Handles parent boundary detection to keep element within its container
|
|
14695
|
-
* 5. Updates dimensions on resize and DOM changes
|
|
14696
|
-
*
|
|
14697
|
-
* Usage:
|
|
14698
|
-
* ```
|
|
14699
|
-
* const cleanup = initPositionSticky(element);
|
|
14700
|
-
* // Later when no longer needed
|
|
14701
|
-
* cleanup();
|
|
14702
|
-
* ```
|
|
14703
|
-
*
|
|
14704
|
-
* The element should have a CSS "top" value specified (e.g., top: 10px).
|
|
14705
|
-
*/
|
|
14706
|
-
const css = /* css */`[data-position-sticky-placeholder] {
|
|
14907
|
+
installImportMetaCssBuild(import.meta);
|
|
14908
|
+
|
|
14909
|
+
const css = /* css */ `[data-position-sticky-placeholder] {
|
|
14707
14910
|
opacity: 0 !important;
|
|
14708
14911
|
width: auto !important;
|
|
14709
14912
|
height: auto !important;
|
|
14710
14913
|
position: static !important;
|
|
14711
14914
|
}
|
|
14712
14915
|
`;
|
|
14713
|
-
|
|
14916
|
+
|
|
14917
|
+
const initPositionSticky = (element) => {
|
|
14714
14918
|
import.meta.css = [css, "@jsenv/dom/src/position/position_sticky.js"];
|
|
14715
14919
|
const computedStyle = getComputedStyle(element);
|
|
14716
14920
|
const topCssValue = computedStyle.top;
|
|
@@ -14735,10 +14939,20 @@ const initPositionSticky = element => {
|
|
|
14735
14939
|
break;
|
|
14736
14940
|
}
|
|
14737
14941
|
const style = getComputedStyle(scrollContainer);
|
|
14738
|
-
if (
|
|
14942
|
+
if (
|
|
14943
|
+
xScrollContainer === null &&
|
|
14944
|
+
(style.overflowX === "auto" ||
|
|
14945
|
+
style.overflowX === "hidden" ||
|
|
14946
|
+
style.overflowX === "scroll")
|
|
14947
|
+
) {
|
|
14739
14948
|
xScrollContainer = scrollContainer;
|
|
14740
14949
|
}
|
|
14741
|
-
if (
|
|
14950
|
+
if (
|
|
14951
|
+
yScrollContainer === null &&
|
|
14952
|
+
(style.overflowY === "auto" ||
|
|
14953
|
+
style.overflowY === "hidden" ||
|
|
14954
|
+
style.overflowY === "scroll")
|
|
14955
|
+
) {
|
|
14742
14956
|
yScrollContainer = scrollContainer;
|
|
14743
14957
|
}
|
|
14744
14958
|
}
|
|
@@ -14747,6 +14961,7 @@ const initPositionSticky = element => {
|
|
|
14747
14961
|
if (!needsPolyfillX && !needsPolyfillY) {
|
|
14748
14962
|
return () => {}; // Native sticky will work fine on both axes
|
|
14749
14963
|
}
|
|
14964
|
+
|
|
14750
14965
|
const cleanupCallbackSet = new Set();
|
|
14751
14966
|
const cleanup = () => {
|
|
14752
14967
|
for (const cleanupCallback of cleanupCallbackSet) {
|
|
@@ -14754,6 +14969,7 @@ const initPositionSticky = element => {
|
|
|
14754
14969
|
}
|
|
14755
14970
|
cleanupCallbackSet.clear();
|
|
14756
14971
|
};
|
|
14972
|
+
|
|
14757
14973
|
const parentElement = element.parentElement;
|
|
14758
14974
|
const createPlaceholderClone = () => {
|
|
14759
14975
|
const clone = element.cloneNode(true);
|
|
@@ -14761,13 +14977,16 @@ const initPositionSticky = element => {
|
|
|
14761
14977
|
clone.removeAttribute("data-sticky");
|
|
14762
14978
|
return clone;
|
|
14763
14979
|
};
|
|
14980
|
+
|
|
14764
14981
|
let placeholder = createPlaceholderClone();
|
|
14765
14982
|
parentElement.insertBefore(placeholder, element);
|
|
14766
14983
|
cleanupCallbackSet.add(() => {
|
|
14767
14984
|
placeholder.remove();
|
|
14768
14985
|
});
|
|
14986
|
+
|
|
14769
14987
|
let width = getWidth(element);
|
|
14770
14988
|
let height = getHeight(element);
|
|
14989
|
+
|
|
14771
14990
|
const updateSize = () => {
|
|
14772
14991
|
const newPlaceholder = createPlaceholderClone();
|
|
14773
14992
|
parentElement.replaceChild(newPlaceholder, placeholder);
|
|
@@ -14776,12 +14995,14 @@ const initPositionSticky = element => {
|
|
|
14776
14995
|
height = getHeight(placeholder);
|
|
14777
14996
|
updatePosition();
|
|
14778
14997
|
};
|
|
14998
|
+
|
|
14779
14999
|
const updatePosition = () => {
|
|
14780
15000
|
// Ensure placeholder dimensions match element
|
|
14781
15001
|
setStyles(placeholder, {
|
|
14782
15002
|
width: `${width}px`,
|
|
14783
|
-
height: `${height}px
|
|
15003
|
+
height: `${height}px`,
|
|
14784
15004
|
});
|
|
15005
|
+
|
|
14785
15006
|
const placeholderRect = placeholder.getBoundingClientRect();
|
|
14786
15007
|
const parentRect = parentElement.getBoundingClientRect();
|
|
14787
15008
|
|
|
@@ -14795,12 +15016,12 @@ const initPositionSticky = element => {
|
|
|
14795
15016
|
// -420 <= 250 → stuck → element.style.left = 250px (main's left edge). ✓
|
|
14796
15017
|
//
|
|
14797
15018
|
// If no intermediate scroll container exists, use 0 (document/viewport edge).
|
|
14798
|
-
const yContainerRect = yScrollContainer
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
const xContainerRect = xScrollContainer
|
|
14802
|
-
|
|
14803
|
-
|
|
15019
|
+
const yContainerRect = yScrollContainer
|
|
15020
|
+
? yScrollContainer.getBoundingClientRect()
|
|
15021
|
+
: { top: 0 };
|
|
15022
|
+
const xContainerRect = xScrollContainer
|
|
15023
|
+
? xScrollContainer.getBoundingClientRect()
|
|
15024
|
+
: { left: 0 };
|
|
14804
15025
|
const topThreshold = yContainerRect.top + top;
|
|
14805
15026
|
const leftThreshold = xContainerRect.left + left;
|
|
14806
15027
|
|
|
@@ -14843,6 +15064,7 @@ const initPositionSticky = element => {
|
|
|
14843
15064
|
} else {
|
|
14844
15065
|
leftPosition = placeholderRect.left;
|
|
14845
15066
|
}
|
|
15067
|
+
|
|
14846
15068
|
element.style.top = `${topPosition}px`;
|
|
14847
15069
|
element.style.left = `${Math.round(leftPosition)}px`;
|
|
14848
15070
|
element.style.width = `${width}px`;
|
|
@@ -14855,15 +15077,18 @@ const initPositionSticky = element => {
|
|
|
14855
15077
|
element.removeAttribute("data-sticky");
|
|
14856
15078
|
}
|
|
14857
15079
|
};
|
|
15080
|
+
|
|
14858
15081
|
{
|
|
14859
15082
|
const restorePositionStyle = forceStyles(element, {
|
|
14860
15083
|
"position": "fixed",
|
|
14861
15084
|
"z-index": 1,
|
|
14862
|
-
"will-change": "transform" // Hint for hardware acceleration
|
|
15085
|
+
"will-change": "transform", // Hint for hardware acceleration
|
|
14863
15086
|
});
|
|
14864
15087
|
cleanupCallbackSet.add(restorePositionStyle);
|
|
14865
15088
|
}
|
|
15089
|
+
|
|
14866
15090
|
updatePosition();
|
|
15091
|
+
|
|
14867
15092
|
{
|
|
14868
15093
|
const handleScroll = () => {
|
|
14869
15094
|
updatePosition();
|
|
@@ -14874,16 +15099,15 @@ const initPositionSticky = element => {
|
|
|
14874
15099
|
const listenTargets = new Set(scrollContainerSet);
|
|
14875
15100
|
listenTargets.add(document.documentElement);
|
|
14876
15101
|
for (const scrollTarget of listenTargets) {
|
|
14877
|
-
scrollTarget.addEventListener("scroll", handleScroll, {
|
|
14878
|
-
passive: true
|
|
14879
|
-
});
|
|
15102
|
+
scrollTarget.addEventListener("scroll", handleScroll, { passive: true });
|
|
14880
15103
|
cleanupCallbackSet.add(() => {
|
|
14881
15104
|
scrollTarget.removeEventListener("scroll", handleScroll, {
|
|
14882
|
-
passive: true
|
|
15105
|
+
passive: true,
|
|
14883
15106
|
});
|
|
14884
15107
|
});
|
|
14885
15108
|
}
|
|
14886
15109
|
}
|
|
15110
|
+
|
|
14887
15111
|
{
|
|
14888
15112
|
let animationFrame = null;
|
|
14889
15113
|
const resizeObserver = new ResizeObserver(() => {
|
|
@@ -14902,6 +15126,7 @@ const initPositionSticky = element => {
|
|
|
14902
15126
|
animationFrame = null;
|
|
14903
15127
|
});
|
|
14904
15128
|
}
|
|
15129
|
+
|
|
14905
15130
|
{
|
|
14906
15131
|
const mutationObserver = new MutationObserver(() => {
|
|
14907
15132
|
updateSize();
|
|
@@ -14909,12 +15134,13 @@ const initPositionSticky = element => {
|
|
|
14909
15134
|
mutationObserver.observe(element, {
|
|
14910
15135
|
childList: true,
|
|
14911
15136
|
subtree: true,
|
|
14912
|
-
characterData: true
|
|
15137
|
+
characterData: true,
|
|
14913
15138
|
});
|
|
14914
15139
|
cleanupCallbackSet.add(() => {
|
|
14915
15140
|
mutationObserver.disconnect();
|
|
14916
15141
|
});
|
|
14917
15142
|
}
|
|
15143
|
+
|
|
14918
15144
|
return cleanup;
|
|
14919
15145
|
};
|
|
14920
15146
|
|
|
@@ -15191,6 +15417,20 @@ window.addEventListener("resize", (event) => {
|
|
|
15191
15417
|
const getVisibleViewportRect = () => {
|
|
15192
15418
|
const visualViewport = window.visualViewport;
|
|
15193
15419
|
const documentElement = document.documentElement;
|
|
15420
|
+
// Read FIRST, before the visual viewport just below — and used again in the
|
|
15421
|
+
// branch further down, so it is a read this function needs either way.
|
|
15422
|
+
//
|
|
15423
|
+
// The visual viewport's own numbers are refreshed with the frame's geometry,
|
|
15424
|
+
// and reading the layout viewport is what brings that geometry up to date.
|
|
15425
|
+
// Read them the other way round and the answer is the state as it was before
|
|
15426
|
+
// the caller's own measuring — every caller here measures an element moments
|
|
15427
|
+
// later, with getBoundingClientRect/offsetHeight, which forces exactly that
|
|
15428
|
+
// update. The box would then be measured in one layout state and placed in
|
|
15429
|
+
// another; on a phone opening its keyboard the two are a screen apart, the
|
|
15430
|
+
// popup lands where the pre-keyboard viewport put it, and nothing after
|
|
15431
|
+
// corrects it. Costs no extra layout: it moves the one the caller triggers
|
|
15432
|
+
// anyway rather than adding a second.
|
|
15433
|
+
const layoutViewportHeight = window.innerHeight;
|
|
15194
15434
|
if (!visualViewport) {
|
|
15195
15435
|
return {
|
|
15196
15436
|
left: 0,
|
|
@@ -15217,8 +15457,8 @@ const getVisibleViewportRect = () => {
|
|
|
15217
15457
|
) {
|
|
15218
15458
|
offsetLeft = 0;
|
|
15219
15459
|
offsetTop = 0;
|
|
15220
|
-
if (
|
|
15221
|
-
height =
|
|
15460
|
+
if (layoutViewportHeight > height) {
|
|
15461
|
+
height = layoutViewportHeight;
|
|
15222
15462
|
}
|
|
15223
15463
|
}
|
|
15224
15464
|
return {
|
|
@@ -15830,7 +16070,23 @@ const visibleRectEffect = (
|
|
|
15830
16070
|
let currentOpenableAncestor = selfOrClosestOpenableAncestor(element);
|
|
15831
16071
|
while (currentOpenableAncestor) {
|
|
15832
16072
|
const openableAncestor = currentOpenableAncestor;
|
|
15833
|
-
|
|
16073
|
+
const openableAncestorIsOpen = isAncestorOpen(openableAncestor);
|
|
16074
|
+
// A closed openable that keeps `element` on screen is the *trigger* of
|
|
16075
|
+
// what is closed rather than the thing itself — a picker's root, an
|
|
16076
|
+
// expandable's header, a <summary> all carry aria-expanded/open for a
|
|
16077
|
+
// surface they are not part of, and `element` here belongs to the
|
|
16078
|
+
// façade they keep showing the whole time. Its closed state hides
|
|
16079
|
+
// nothing of this element, so it must not count as a state that does.
|
|
16080
|
+
//
|
|
16081
|
+
// Read once, here, and kept for the effect's lifetime: the layout only
|
|
16082
|
+
// answers this question honestly about an ancestor that is already
|
|
16083
|
+
// closed and settled. Asked at the instant a surface closes, it still
|
|
16084
|
+
// says "on screen" for as long as a close animation keeps a box for it,
|
|
16085
|
+
// which is the opposite answer — so a surface closing later goes on
|
|
16086
|
+
// counting, unasked.
|
|
16087
|
+
const elementIsInAncestorFacade =
|
|
16088
|
+
!openableAncestorIsOpen && isDisplayedDespiteClosedAncestor(element);
|
|
16089
|
+
if (!openableAncestorIsOpen && !elementIsInAncestorFacade) {
|
|
15834
16090
|
ancestorClosedCount++;
|
|
15835
16091
|
pauseResizeWatching();
|
|
15836
16092
|
}
|
|
@@ -15839,6 +16095,9 @@ const visibleRectEffect = (
|
|
|
15839
16095
|
// eslint-disable-next-line no-loop-func
|
|
15840
16096
|
({ isOpen, toggleEvent }) => {
|
|
15841
16097
|
if (!isOpen) {
|
|
16098
|
+
if (elementIsInAncestorFacade) {
|
|
16099
|
+
return;
|
|
16100
|
+
}
|
|
15842
16101
|
ancestorClosedCount++;
|
|
15843
16102
|
pauseResizeWatching();
|
|
15844
16103
|
// Invalidates check()'s own "did anything actually change"
|
|
@@ -15867,7 +16126,7 @@ const visibleRectEffect = (
|
|
|
15867
16126
|
);
|
|
15868
16127
|
return;
|
|
15869
16128
|
}
|
|
15870
|
-
if (ancestorClosedCount > 0) {
|
|
16129
|
+
if (!elementIsInAncestorFacade && ancestorClosedCount > 0) {
|
|
15871
16130
|
ancestorClosedCount--;
|
|
15872
16131
|
}
|
|
15873
16132
|
if (ancestorClosedCount === 0) {
|
|
@@ -16889,21 +17148,17 @@ const parseTransitionDurationMs = (element, cssVarName, fallbackMs) => {
|
|
|
16889
17148
|
* A second reposition landing mid-animation cancels the pending one and
|
|
16890
17149
|
* flushes its own registered callbacks immediately (same spirit as a real
|
|
16891
17150
|
* `transitioncancel`), so nothing is left waiting on a superseded `onEnd`.
|
|
16892
|
-
*
|
|
16893
|
-
*
|
|
16894
|
-
*
|
|
16895
|
-
*
|
|
16896
|
-
*
|
|
16897
|
-
*
|
|
17151
|
+
* Every second reposition, animated or not — see
|
|
17152
|
+
* cancelPendingPositionTransition, which applyNewPosition calls on the path
|
|
17153
|
+
* that does not come through here.
|
|
17154
|
+
*
|
|
17155
|
+
* `commitStyles()` below is what the specified `translate` reads once the
|
|
17156
|
+
* active duration elapses, so it must only ever run while this animation is
|
|
17157
|
+
* still the current placement: it writes THIS animation's target, which a
|
|
17158
|
+
* placement landing after it has already superseded.
|
|
16898
17159
|
*/
|
|
16899
17160
|
const notifyPositionTransition = (element, animation) => {
|
|
16900
|
-
|
|
16901
|
-
if (pending) {
|
|
16902
|
-
pending.animation.cancel();
|
|
16903
|
-
for (const callback of pending.endCallbacks) {
|
|
16904
|
-
callback();
|
|
16905
|
-
}
|
|
16906
|
-
}
|
|
17161
|
+
cancelPendingPositionTransition(element);
|
|
16907
17162
|
const endCallbacks = [];
|
|
16908
17163
|
dispatchCustomEvent(element, "navi_position_transition", {
|
|
16909
17164
|
onEnd: (callback) => {
|
|
@@ -16933,6 +17188,31 @@ const notifyPositionTransition = (element, animation) => {
|
|
|
16933
17188
|
});
|
|
16934
17189
|
};
|
|
16935
17190
|
|
|
17191
|
+
/**
|
|
17192
|
+
* Ends the placement animation in flight on `element`, if any, and flushes what
|
|
17193
|
+
* was waiting on it — what a new placement owes the old one, whether or not it
|
|
17194
|
+
* animates itself.
|
|
17195
|
+
*
|
|
17196
|
+
* The non-animated case is the one worth naming: a placement that just writes
|
|
17197
|
+
* `translate` leaves an animation started a moment earlier running, and that
|
|
17198
|
+
* animation commits ITS target when it ends, on top of the value written since.
|
|
17199
|
+
* The box then lands where a viewport that no longer exists put it, and nothing
|
|
17200
|
+
* comes after to correct it — a phone opening its keyboard fires exactly this
|
|
17201
|
+
* sequence, an animated "resize" followed by focus/size deliveries that do not
|
|
17202
|
+
* animate.
|
|
17203
|
+
*/
|
|
17204
|
+
const cancelPendingPositionTransition = (element) => {
|
|
17205
|
+
const pending = pendingPositionTransitions.get(element);
|
|
17206
|
+
if (!pending) {
|
|
17207
|
+
return;
|
|
17208
|
+
}
|
|
17209
|
+
pendingPositionTransitions.delete(element);
|
|
17210
|
+
pending.animation.cancel();
|
|
17211
|
+
for (const callback of pending.endCallbacks) {
|
|
17212
|
+
callback();
|
|
17213
|
+
}
|
|
17214
|
+
};
|
|
17215
|
+
|
|
16936
17216
|
/**
|
|
16937
17217
|
* Applies a `pickPositionRelativeTo` result to `element`, as a `translate`.
|
|
16938
17218
|
*
|
|
@@ -17055,6 +17335,8 @@ const applyNewPosition = (
|
|
|
17055
17335
|
},
|
|
17056
17336
|
);
|
|
17057
17337
|
notifyPositionTransition(element, animation);
|
|
17338
|
+
} else {
|
|
17339
|
+
cancelPendingPositionTransition(element);
|
|
17058
17340
|
}
|
|
17059
17341
|
// The specified translate is set to its final target right away,
|
|
17060
17342
|
// regardless of `shouldTransition` — the animation above only plays the
|