@jsenv/dom 0.17.43 → 0.17.45
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 +1345 -1176
- package/package.json +1 -1
package/dist/jsenv_dom.js
CHANGED
|
@@ -8487,17 +8487,9 @@ const isolateInteractions = (elements) => {
|
|
|
8487
8487
|
};
|
|
8488
8488
|
};
|
|
8489
8489
|
|
|
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 {
|
|
8490
|
+
installImportMetaCssBuild(import.meta);
|
|
8491
|
+
|
|
8492
|
+
const css$5 = /* css */ `.navi_drag_gesture_backdrop {
|
|
8501
8493
|
touch-action: none;
|
|
8502
8494
|
user-select: none;
|
|
8503
8495
|
position: fixed;
|
|
@@ -8521,6 +8513,51 @@ import.meta.css = [css$5, "@jsenv/dom/src/interaction/drag/drag_gesture.js"];
|
|
|
8521
8513
|
* apart: `lostpointercapture` says the same thing either way.
|
|
8522
8514
|
*/
|
|
8523
8515
|
const captureHolderByPointerId = new Map();
|
|
8516
|
+
|
|
8517
|
+
/**
|
|
8518
|
+
* The drag loop: it takes the pointer, follows it, and says when it is let go of.
|
|
8519
|
+
* What the movement MEANS is the caller's — this one carries nothing, moves
|
|
8520
|
+
* nothing and commits nothing on its own, which is what makes it the primitive
|
|
8521
|
+
* for a gesture whose product is a value (an angle, a scale, an offset) rather
|
|
8522
|
+
* than an element that ends up somewhere.
|
|
8523
|
+
*
|
|
8524
|
+
* While it runs it owns the document: everything but the dragged element (and the
|
|
8525
|
+
* `[data-droppable]` places) goes inert, a backdrop unifies the cursor and takes
|
|
8526
|
+
* the pointer events, the focus moves to what is held and comes back at the
|
|
8527
|
+
* release, and the selection is refused for the length of the gesture.
|
|
8528
|
+
*
|
|
8529
|
+
* Whether the element may be grabbed AT ALL is decided before the finger lands
|
|
8530
|
+
* and elsewhere: see `markDragSource` in drag_after_intent.js for the
|
|
8531
|
+
* touch-action half, and `dragAfterIntent` for when a press becomes a drag.
|
|
8532
|
+
*
|
|
8533
|
+
* @param {object} [options]
|
|
8534
|
+
* @param {string} [options.name] Carried on `gestureInfo`, for whoever reads a gesture they did not start.
|
|
8535
|
+
* @param {(gestureInfo: object) => void} [options.onGrab]
|
|
8536
|
+
* The pointer went down: there is a gesture, which may still turn out to be a press.
|
|
8537
|
+
* @param {(gestureInfo: object) => void} [options.onDragStart] The threshold was crossed — this one IS a drag.
|
|
8538
|
+
* @param {(gestureInfo: object, someLayoutChange: boolean) => void} [options.onDrag]
|
|
8539
|
+
* Every move, and every scroll of the container: `someLayoutChange` is false when
|
|
8540
|
+
* nothing about the element changed, which still matters to whatever sticks to the scroll.
|
|
8541
|
+
* @param {(gestureInfo: object) => void} [options.onRelease]
|
|
8542
|
+
* The hand let go — or did not: `gestureInfo.cancelled` says the gesture was taken away
|
|
8543
|
+
* (the browser took the touch to scroll with, another gesture took the pointer), and a
|
|
8544
|
+
* release nobody asked for says nothing about where things belong, so nothing read from
|
|
8545
|
+
* it may be committed.
|
|
8546
|
+
* @param {number} [options.threshold=5] How far (px) the pointer travels before the drag starts. `0` starts it at the grab.
|
|
8547
|
+
* @param {{x: boolean, y: boolean}} [options.direction] Which axes the gesture reads.
|
|
8548
|
+
* @param {"auto"|"manual"} [options.documentInteractions="auto"]
|
|
8549
|
+
* `"manual"` leaves the document alone — no inertness, no backdrop, no focus taken. For a
|
|
8550
|
+
* gesture that only measures and hands over (see `dragAfterIntent`).
|
|
8551
|
+
* @param {boolean} [options.backdrop=true]
|
|
8552
|
+
* @param {number} [options.backdropZIndex=999999]
|
|
8553
|
+
* @returns {{grab: function, grabViaPointer: function}}
|
|
8554
|
+
* `grabViaPointer(pointerdownEvent, { element, direction, cursor, scrollContainer })` for a
|
|
8555
|
+
* gesture a hand starts, `grab({ element, grabX, grabY })` for one nothing pressed. Both give
|
|
8556
|
+
* back the gesture — `gestureInfo` (its `layout` with `xDelta`/`yDelta`, its velocity, the way
|
|
8557
|
+
* it is going), `drag`, `release`, and `addDragCallback`/`addReleaseCallback` for whoever joins
|
|
8558
|
+
* after it started. `grabViaPointer` gives back `null` when the press is not one it can read: a
|
|
8559
|
+
* secondary button, a text node.
|
|
8560
|
+
*/
|
|
8524
8561
|
const createDragGestureController = (options = {}) => {
|
|
8525
8562
|
const {
|
|
8526
8563
|
name,
|
|
@@ -8529,18 +8566,17 @@ const createDragGestureController = (options = {}) => {
|
|
|
8529
8566
|
onDrag,
|
|
8530
8567
|
onRelease,
|
|
8531
8568
|
threshold = 5,
|
|
8532
|
-
direction: defaultDirection = {
|
|
8533
|
-
x: true,
|
|
8534
|
-
y: true
|
|
8535
|
-
},
|
|
8569
|
+
direction: defaultDirection = { x: true, y: true },
|
|
8536
8570
|
documentInteractions = "auto",
|
|
8537
8571
|
backdrop = true,
|
|
8538
|
-
backdropZIndex = 999999
|
|
8572
|
+
backdropZIndex = 999999,
|
|
8539
8573
|
} = options;
|
|
8574
|
+
|
|
8540
8575
|
const dragGestureController = {
|
|
8541
8576
|
grab: null,
|
|
8542
|
-
gravViaPointer: null
|
|
8577
|
+
gravViaPointer: null,
|
|
8543
8578
|
};
|
|
8579
|
+
|
|
8544
8580
|
const grab = ({
|
|
8545
8581
|
element,
|
|
8546
8582
|
direction = defaultDirection,
|
|
@@ -8550,7 +8586,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8550
8586
|
cursor = "grabbing",
|
|
8551
8587
|
scrollContainer = document.documentElement,
|
|
8552
8588
|
layoutScrollableLeft: scrollableLeftAtGrab = 0,
|
|
8553
|
-
layoutScrollableTop: scrollableTopAtGrab = 0
|
|
8589
|
+
layoutScrollableTop: scrollableTopAtGrab = 0,
|
|
8554
8590
|
} = {}) => {
|
|
8555
8591
|
if (!element) {
|
|
8556
8592
|
throw new Error("element is required");
|
|
@@ -8558,6 +8594,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8558
8594
|
if (!direction.x && !direction.y) {
|
|
8559
8595
|
return null;
|
|
8560
8596
|
}
|
|
8597
|
+
|
|
8561
8598
|
const [publishBeforeDrag, addBeforeDragCallback] = createPubSub();
|
|
8562
8599
|
const [publishDrag, addDragCallback] = createPubSub();
|
|
8563
8600
|
const [publishRelease, addReleaseCallback] = createPubSub();
|
|
@@ -8567,15 +8604,13 @@ const createDragGestureController = (options = {}) => {
|
|
|
8567
8604
|
if (onRelease) {
|
|
8568
8605
|
addReleaseCallback(onRelease);
|
|
8569
8606
|
}
|
|
8607
|
+
|
|
8570
8608
|
const scrollLeftAtGrab = scrollContainer.scrollLeft;
|
|
8571
8609
|
const scrollTopAtGrab = scrollContainer.scrollTop;
|
|
8572
8610
|
const leftAtGrab = scrollLeftAtGrab + scrollableLeftAtGrab;
|
|
8573
8611
|
const topAtGrab = scrollTopAtGrab + scrollableTopAtGrab;
|
|
8574
8612
|
const createLayout = (x, y) => {
|
|
8575
|
-
const {
|
|
8576
|
-
scrollLeft,
|
|
8577
|
-
scrollTop
|
|
8578
|
-
} = scrollContainer;
|
|
8613
|
+
const { scrollLeft, scrollTop } = scrollContainer;
|
|
8579
8614
|
const left = scrollableLeftAtGrab + x;
|
|
8580
8615
|
const top = scrollableTopAtGrab + y;
|
|
8581
8616
|
const scrollableLeft = left - scrollLeft;
|
|
@@ -8595,30 +8630,33 @@ const createDragGestureController = (options = {}) => {
|
|
|
8595
8630
|
top,
|
|
8596
8631
|
// Delta since grab (number representing how much we dragged)
|
|
8597
8632
|
xDelta: left - leftAtGrab,
|
|
8598
|
-
yDelta: top - topAtGrab
|
|
8633
|
+
yDelta: top - topAtGrab,
|
|
8599
8634
|
};
|
|
8600
8635
|
return layoutProps;
|
|
8601
8636
|
};
|
|
8602
|
-
|
|
8637
|
+
|
|
8638
|
+
const grabLayout = createLayout(
|
|
8639
|
+
grabX + scrollContainer.scrollLeft,
|
|
8640
|
+
grabY + scrollContainer.scrollTop,
|
|
8641
|
+
);
|
|
8603
8642
|
const gestureInfo = {
|
|
8604
8643
|
name,
|
|
8605
8644
|
direction,
|
|
8606
8645
|
started: !threshold,
|
|
8607
8646
|
status: "grabbed",
|
|
8647
|
+
|
|
8608
8648
|
element,
|
|
8609
8649
|
scrollContainer,
|
|
8610
|
-
grabX,
|
|
8611
|
-
//
|
|
8612
|
-
grabY,
|
|
8613
|
-
// y grab coordinate (excluding scroll)
|
|
8650
|
+
grabX, // x grab coordinate (excluding scroll)
|
|
8651
|
+
grabY, // y grab coordinate (excluding scroll)
|
|
8614
8652
|
grabLayout,
|
|
8615
8653
|
leftAtGrab,
|
|
8616
8654
|
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)
|
|
8655
|
+
|
|
8656
|
+
dragX: grabX, // coordinate of the last drag (excluding scroll of the scrollContainer)
|
|
8657
|
+
dragY: grabY, // coordinate of the last drag (excluding scroll of the scrollContainer)
|
|
8621
8658
|
layout: grabLayout,
|
|
8659
|
+
|
|
8622
8660
|
isGoingUp: undefined,
|
|
8623
8661
|
isGoingDown: undefined,
|
|
8624
8662
|
isGoingLeft: undefined,
|
|
@@ -8627,11 +8665,13 @@ const createDragGestureController = (options = {}) => {
|
|
|
8627
8665
|
intentGoingDown: false,
|
|
8628
8666
|
intentGoingLeft: false,
|
|
8629
8667
|
intentGoingRight: false,
|
|
8668
|
+
|
|
8630
8669
|
// How fast the pointer is going, in px/ms, signed per axis
|
|
8631
8670
|
// (see measureVelocity)
|
|
8632
8671
|
velocityX: 0,
|
|
8633
8672
|
velocityY: 0,
|
|
8634
8673
|
velocity: 0,
|
|
8674
|
+
|
|
8635
8675
|
// metadata about interaction sources
|
|
8636
8676
|
grabEvent: event,
|
|
8637
8677
|
dragEvent: null,
|
|
@@ -8640,7 +8680,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8640
8680
|
// the touch to scroll with, another gesture took the pointer. Whoever
|
|
8641
8681
|
// reads it must not commit anything — a release that was not asked for
|
|
8642
8682
|
// says nothing about where things belong.
|
|
8643
|
-
cancelled: false
|
|
8683
|
+
cancelled: false,
|
|
8644
8684
|
};
|
|
8645
8685
|
definePropertyAsReadOnly(gestureInfo, "name");
|
|
8646
8686
|
definePropertyAsReadOnly(gestureInfo, "direction");
|
|
@@ -8656,6 +8696,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8656
8696
|
// matter of speed, and the gesture is the only place that sees the timing of
|
|
8657
8697
|
// the events it receives.
|
|
8658
8698
|
const measureVelocity = createVelocityMeter(grabX, grabY);
|
|
8699
|
+
|
|
8659
8700
|
document_interactions: {
|
|
8660
8701
|
if (documentInteractions === "manual") {
|
|
8661
8702
|
break document_interactions;
|
|
@@ -8668,18 +8709,23 @@ const createDragGestureController = (options = {}) => {
|
|
|
8668
8709
|
2. Break the visual feedback (inconsistent cursors, hover states)
|
|
8669
8710
|
3. Cause unwanted scrolling (keyboard shortcuts, wheel events in restricted directions)
|
|
8670
8711
|
4. Create accessibility issues (focus jumping, screen reader confusion)
|
|
8671
|
-
|
|
8712
|
+
|
|
8713
|
+
STRATEGY: Create a controlled interaction environment by:
|
|
8672
8714
|
1. VISUAL CONTROL: Use a backdrop to unify cursor appearance and block pointer events
|
|
8673
8715
|
2. INTERACTION ISOLATION: Make non-dragged elements inert to prevent interference
|
|
8674
8716
|
3. FOCUS MANAGEMENT: Control focus location and prevent focus changes during drag
|
|
8675
8717
|
4. SELECTIVE SCROLLING: Allow scrolling only in directions supported by the drag gesture
|
|
8676
|
-
|
|
8718
|
+
|
|
8719
|
+
IMPLEMENTATION:
|
|
8677
8720
|
*/
|
|
8678
8721
|
|
|
8679
8722
|
// 1. INTERACTION ISOLATION: Make everything except the dragged element inert
|
|
8680
8723
|
// This prevents keyboard events, pointer interactions, and screen reader navigation
|
|
8681
8724
|
// on non-relevant elements during the drag operation
|
|
8682
|
-
const cleanupInert = isolateInteractions([
|
|
8725
|
+
const cleanupInert = isolateInteractions([
|
|
8726
|
+
element,
|
|
8727
|
+
...Array.from(document.querySelectorAll("[data-droppable]")),
|
|
8728
|
+
]);
|
|
8683
8729
|
addReleaseCallback(() => {
|
|
8684
8730
|
cleanupInert();
|
|
8685
8731
|
});
|
|
@@ -8696,14 +8742,14 @@ const createDragGestureController = (options = {}) => {
|
|
|
8696
8742
|
// Handle wheel events on backdrop for directionally-constrained drag gestures
|
|
8697
8743
|
// (e.g., table column resize should only allow horizontal scrolling)
|
|
8698
8744
|
if (!direction.x || !direction.y) {
|
|
8699
|
-
backdropElement.onwheel = e => {
|
|
8745
|
+
backdropElement.onwheel = (e) => {
|
|
8700
8746
|
e.preventDefault();
|
|
8701
8747
|
const scrollX = direction.x ? e.deltaX : 0;
|
|
8702
8748
|
const scrollY = direction.y ? e.deltaY : 0;
|
|
8703
8749
|
scrollContainer.scrollBy({
|
|
8704
8750
|
left: scrollX,
|
|
8705
8751
|
top: scrollY,
|
|
8706
|
-
behavior: "auto"
|
|
8752
|
+
behavior: "auto",
|
|
8707
8753
|
});
|
|
8708
8754
|
};
|
|
8709
8755
|
}
|
|
@@ -8714,9 +8760,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8714
8760
|
}
|
|
8715
8761
|
|
|
8716
8762
|
// 3. FOCUS MANAGEMENT: Control and stabilize focus during drag
|
|
8717
|
-
const {
|
|
8718
|
-
activeElement
|
|
8719
|
-
} = document;
|
|
8763
|
+
const { activeElement } = document;
|
|
8720
8764
|
const focusableElement = findFocusable(element);
|
|
8721
8765
|
// Focus the dragged element (or document.body as fallback) to establish clear focus context
|
|
8722
8766
|
// This also ensure any keydown event listened by the currently focused element
|
|
@@ -8724,17 +8768,17 @@ const createDragGestureController = (options = {}) => {
|
|
|
8724
8768
|
const elementToFocus = focusableElement || document.body;
|
|
8725
8769
|
elementToFocus.setAttribute("data-drag-focus", "");
|
|
8726
8770
|
elementToFocus.focus({
|
|
8727
|
-
preventScroll: true
|
|
8771
|
+
preventScroll: true,
|
|
8728
8772
|
});
|
|
8729
8773
|
addReleaseCallback(() => {
|
|
8730
8774
|
elementToFocus.removeAttribute("data-drag-focus");
|
|
8731
8775
|
// Restore original focus on release
|
|
8732
8776
|
activeElement.focus({
|
|
8733
|
-
preventScroll: true
|
|
8777
|
+
preventScroll: true,
|
|
8734
8778
|
});
|
|
8735
8779
|
});
|
|
8736
8780
|
// Prevent Tab navigation entirely (focus should stay stable)
|
|
8737
|
-
const onkeydown = e => {
|
|
8781
|
+
const onkeydown = (e) => {
|
|
8738
8782
|
if (e.key === "Tab") {
|
|
8739
8783
|
e.preventDefault();
|
|
8740
8784
|
return;
|
|
@@ -8747,16 +8791,27 @@ const createDragGestureController = (options = {}) => {
|
|
|
8747
8791
|
|
|
8748
8792
|
// 4. SELECTIVE SCROLLING: Allow keyboard scrolling only in supported directions
|
|
8749
8793
|
{
|
|
8750
|
-
const onDocumentKeydown = keyboardEvent => {
|
|
8794
|
+
const onDocumentKeydown = (keyboardEvent) => {
|
|
8751
8795
|
// Vertical scrolling keys - prevent if vertical movement not supported
|
|
8752
|
-
if (
|
|
8796
|
+
if (
|
|
8797
|
+
keyboardEvent.key === "ArrowUp" ||
|
|
8798
|
+
keyboardEvent.key === "ArrowDown" ||
|
|
8799
|
+
keyboardEvent.key === " " ||
|
|
8800
|
+
keyboardEvent.key === "PageUp" ||
|
|
8801
|
+
keyboardEvent.key === "PageDown" ||
|
|
8802
|
+
keyboardEvent.key === "Home" ||
|
|
8803
|
+
keyboardEvent.key === "End"
|
|
8804
|
+
) {
|
|
8753
8805
|
if (!direction.y) {
|
|
8754
8806
|
keyboardEvent.preventDefault();
|
|
8755
8807
|
}
|
|
8756
8808
|
return;
|
|
8757
8809
|
}
|
|
8758
8810
|
// Horizontal scrolling keys - prevent if horizontal movement not supported
|
|
8759
|
-
if (
|
|
8811
|
+
if (
|
|
8812
|
+
keyboardEvent.key === "ArrowLeft" ||
|
|
8813
|
+
keyboardEvent.key === "ArrowRight"
|
|
8814
|
+
) {
|
|
8760
8815
|
if (!direction.x) {
|
|
8761
8816
|
keyboardEvent.preventDefault();
|
|
8762
8817
|
}
|
|
@@ -8773,38 +8828,36 @@ const createDragGestureController = (options = {}) => {
|
|
|
8773
8828
|
// Set up scroll event handling to adjust drag position when scrolling occurs
|
|
8774
8829
|
{
|
|
8775
8830
|
let isHandlingScroll = false;
|
|
8776
|
-
const handleScroll = scrollEvent => {
|
|
8831
|
+
const handleScroll = (scrollEvent) => {
|
|
8777
8832
|
if (isHandlingScroll) {
|
|
8778
8833
|
return;
|
|
8779
8834
|
}
|
|
8780
8835
|
isHandlingScroll = true;
|
|
8781
|
-
drag(gestureInfo.dragX, gestureInfo.dragY, {
|
|
8782
|
-
event: scrollEvent
|
|
8783
|
-
});
|
|
8836
|
+
drag(gestureInfo.dragX, gestureInfo.dragY, { event: scrollEvent });
|
|
8784
8837
|
isHandlingScroll = false;
|
|
8785
8838
|
};
|
|
8786
|
-
const scrollEventReceiver =
|
|
8839
|
+
const scrollEventReceiver =
|
|
8840
|
+
scrollContainer === document.documentElement
|
|
8841
|
+
? document
|
|
8842
|
+
: scrollContainer;
|
|
8787
8843
|
scrollEventReceiver.addEventListener("scroll", handleScroll, {
|
|
8788
|
-
passive: true
|
|
8844
|
+
passive: true,
|
|
8789
8845
|
});
|
|
8790
8846
|
addReleaseCallback(() => {
|
|
8791
8847
|
scrollEventReceiver.removeEventListener("scroll", handleScroll, {
|
|
8792
|
-
passive: true
|
|
8848
|
+
passive: true,
|
|
8793
8849
|
});
|
|
8794
8850
|
});
|
|
8795
8851
|
}
|
|
8852
|
+
|
|
8796
8853
|
const determineDragData = ({
|
|
8797
8854
|
dragX,
|
|
8798
8855
|
dragY,
|
|
8799
8856
|
dragEvent,
|
|
8800
|
-
isRelease = false
|
|
8857
|
+
isRelease = false,
|
|
8801
8858
|
}) => {
|
|
8802
8859
|
// === ÉTAT INITIAL (au moment du grab) ===
|
|
8803
|
-
const {
|
|
8804
|
-
grabX,
|
|
8805
|
-
grabY,
|
|
8806
|
-
grabLayout
|
|
8807
|
-
} = gestureInfo;
|
|
8860
|
+
const { grabX, grabY, grabLayout } = gestureInfo;
|
|
8808
8861
|
// === CE QUI EST DEMANDÉ (où on veut aller) ===
|
|
8809
8862
|
// Calcul de la direction basé sur le mouvement précédent
|
|
8810
8863
|
// (ne tient pas compte du mouvement final une fois les contraintes appliquées)
|
|
@@ -8816,38 +8869,56 @@ const createDragGestureController = (options = {}) => {
|
|
|
8816
8869
|
const isGoingRight = dragX > currentDragX;
|
|
8817
8870
|
const isGoingUp = dragY < currentDragY;
|
|
8818
8871
|
const isGoingDown = dragY > currentDragY;
|
|
8819
|
-
|
|
8820
|
-
const
|
|
8872
|
+
|
|
8873
|
+
const layoutXRequested = direction.x
|
|
8874
|
+
? scrollContainer.scrollLeft + (dragX - grabX)
|
|
8875
|
+
: grabLayout.scrollLeft;
|
|
8876
|
+
const layoutYRequested = direction.y
|
|
8877
|
+
? scrollContainer.scrollTop + (dragY - grabY)
|
|
8878
|
+
: grabLayout.scrollTop;
|
|
8821
8879
|
const layoutRequested = createLayout(layoutXRequested, layoutYRequested);
|
|
8822
8880
|
const currentLayout = gestureInfo.layout;
|
|
8823
8881
|
let layout;
|
|
8824
|
-
if (
|
|
8882
|
+
if (
|
|
8883
|
+
layoutRequested.x === currentLayout.x &&
|
|
8884
|
+
layoutRequested.y === currentLayout.y
|
|
8885
|
+
) {
|
|
8825
8886
|
layout = currentLayout;
|
|
8826
8887
|
} else {
|
|
8827
8888
|
// === APPLICATION DES CONTRAINTES ===
|
|
8828
8889
|
let layoutConstrained = layoutRequested;
|
|
8829
8890
|
const limitLayout = (left, top) => {
|
|
8830
|
-
layoutConstrained = createLayout(
|
|
8891
|
+
layoutConstrained = createLayout(
|
|
8892
|
+
left === undefined
|
|
8893
|
+
? layoutConstrained.x
|
|
8894
|
+
: left - scrollableLeftAtGrab,
|
|
8895
|
+
top === undefined ? layoutConstrained.y : top - scrollableTopAtGrab,
|
|
8896
|
+
);
|
|
8831
8897
|
};
|
|
8898
|
+
|
|
8832
8899
|
publishBeforeDrag(layoutRequested, currentLayout, limitLayout, {
|
|
8833
8900
|
dragEvent,
|
|
8834
|
-
isRelease
|
|
8901
|
+
isRelease,
|
|
8835
8902
|
});
|
|
8836
8903
|
// === ÉTAT FINAL ===
|
|
8837
8904
|
layout = layoutConstrained;
|
|
8838
8905
|
}
|
|
8906
|
+
|
|
8839
8907
|
const dragData = {
|
|
8840
8908
|
dragX,
|
|
8841
8909
|
dragY,
|
|
8842
8910
|
layout,
|
|
8911
|
+
|
|
8843
8912
|
isGoingLeft,
|
|
8844
8913
|
isGoingRight,
|
|
8845
8914
|
isGoingUp,
|
|
8846
8915
|
isGoingDown,
|
|
8916
|
+
|
|
8847
8917
|
status: isRelease ? "released" : "dragging",
|
|
8848
8918
|
dragEvent: isRelease ? gestureInfo.dragEvent : dragEvent,
|
|
8849
|
-
releaseEvent: isRelease ? dragEvent : null
|
|
8919
|
+
releaseEvent: isRelease ? dragEvent : null,
|
|
8850
8920
|
};
|
|
8921
|
+
|
|
8851
8922
|
if (isRelease) {
|
|
8852
8923
|
return dragData;
|
|
8853
8924
|
}
|
|
@@ -8872,6 +8943,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8872
8943
|
}
|
|
8873
8944
|
return dragData;
|
|
8874
8945
|
};
|
|
8946
|
+
|
|
8875
8947
|
const markAsStarted = () => {
|
|
8876
8948
|
const clickSuppressionIsOver = suppressClickAfterGesture();
|
|
8877
8949
|
addReleaseCallback(clickSuppressionIsOver);
|
|
@@ -8884,7 +8956,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
8884
8956
|
// would then outlive it: what one sees is a click swallowed long after
|
|
8885
8957
|
// the drag it belonged to.
|
|
8886
8958
|
dispatchPublicCustomEvent(element, "navi_drag_start", {
|
|
8887
|
-
gestureInfo
|
|
8959
|
+
gestureInfo,
|
|
8888
8960
|
});
|
|
8889
8961
|
onDragStart?.(gestureInfo);
|
|
8890
8962
|
};
|
|
@@ -8899,19 +8971,18 @@ const createDragGestureController = (options = {}) => {
|
|
|
8899
8971
|
gestureInfo.started = true;
|
|
8900
8972
|
markAsStarted();
|
|
8901
8973
|
};
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
} = {}) => {
|
|
8974
|
+
|
|
8975
|
+
const drag = (
|
|
8976
|
+
dragX = gestureInfo.dragX, // Scroll container relative X coordinate
|
|
8977
|
+
dragY = gestureInfo.dragY, // Scroll container relative Y coordinate
|
|
8978
|
+
{ event = new CustomEvent("programmatic"), isRelease = false } = {},
|
|
8979
|
+
) => {
|
|
8980
|
+
|
|
8910
8981
|
const dragData = determineDragData({
|
|
8911
8982
|
dragX,
|
|
8912
8983
|
dragY,
|
|
8913
8984
|
dragEvent: event,
|
|
8914
|
-
isRelease
|
|
8985
|
+
isRelease,
|
|
8915
8986
|
});
|
|
8916
8987
|
const [velocityX, velocityY] = measureVelocity(dragX, dragY);
|
|
8917
8988
|
const startedPrevious = gestureInfo.started;
|
|
@@ -8941,36 +9012,33 @@ const createDragGestureController = (options = {}) => {
|
|
|
8941
9012
|
const someLayoutChange = gestureInfo.layout !== layoutPrevious;
|
|
8942
9013
|
dispatchPublicCustomEvent(element, "navi_drag", {
|
|
8943
9014
|
gestureInfo,
|
|
8944
|
-
someLayoutChange
|
|
9015
|
+
someLayoutChange,
|
|
8945
9016
|
});
|
|
8946
|
-
publishDrag(
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
9017
|
+
publishDrag(
|
|
9018
|
+
gestureInfo,
|
|
9019
|
+
// we still publish drag event even when unchanged
|
|
9020
|
+
// because UI might need to adjust when document scrolls
|
|
9021
|
+
// even if nothing truly changes visually the element
|
|
9022
|
+
// can decide to stick to the scroll for example
|
|
9023
|
+
someLayoutChange,
|
|
9024
|
+
);
|
|
8952
9025
|
};
|
|
9026
|
+
|
|
8953
9027
|
const release = ({
|
|
8954
9028
|
event = new CustomEvent("programmatic"),
|
|
8955
9029
|
cancelled = event.type === "pointercancel",
|
|
8956
9030
|
releaseX = gestureInfo.dragX,
|
|
8957
|
-
releaseY = gestureInfo.dragY
|
|
9031
|
+
releaseY = gestureInfo.dragY,
|
|
8958
9032
|
} = {}) => {
|
|
8959
9033
|
// Written before the last drag is reported, so the callbacks reading the
|
|
8960
9034
|
// end of the gesture all see the same one.
|
|
8961
9035
|
gestureInfo.cancelled = cancelled;
|
|
8962
|
-
drag(releaseX, releaseY, {
|
|
8963
|
-
|
|
8964
|
-
isRelease: true
|
|
8965
|
-
});
|
|
8966
|
-
dispatchPublicCustomEvent(element, "navi_drag_release", {
|
|
8967
|
-
gestureInfo
|
|
8968
|
-
});
|
|
9036
|
+
drag(releaseX, releaseY, { event, isRelease: true });
|
|
9037
|
+
dispatchPublicCustomEvent(element, "navi_drag_release", { gestureInfo });
|
|
8969
9038
|
publishRelease(gestureInfo);
|
|
8970
9039
|
};
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
});
|
|
9040
|
+
|
|
9041
|
+
dispatchPublicCustomEvent(element, "navi_drag_grab", { gestureInfo });
|
|
8974
9042
|
onGrab?.(gestureInfo);
|
|
8975
9043
|
const dragGesture = {
|
|
8976
9044
|
gestureInfo,
|
|
@@ -8979,11 +9047,12 @@ const createDragGestureController = (options = {}) => {
|
|
|
8979
9047
|
addReleaseCallback,
|
|
8980
9048
|
start,
|
|
8981
9049
|
drag,
|
|
8982
|
-
release
|
|
9050
|
+
release,
|
|
8983
9051
|
};
|
|
8984
9052
|
return dragGesture;
|
|
8985
9053
|
};
|
|
8986
9054
|
dragGestureController.grab = grab;
|
|
9055
|
+
|
|
8987
9056
|
const initDragByPointer = (grabEvent, dragOptions, initializer) => {
|
|
8988
9057
|
if (!isPrimaryButtonEvent(grabEvent)) {
|
|
8989
9058
|
return null;
|
|
@@ -8993,11 +9062,8 @@ const createDragGestureController = (options = {}) => {
|
|
|
8993
9062
|
// target is a text node
|
|
8994
9063
|
return null;
|
|
8995
9064
|
}
|
|
8996
|
-
const mouseEventCoords = mouseEvent => {
|
|
8997
|
-
const {
|
|
8998
|
-
clientX,
|
|
8999
|
-
clientY
|
|
9000
|
-
} = mouseEvent;
|
|
9065
|
+
const mouseEventCoords = (mouseEvent) => {
|
|
9066
|
+
const { clientX, clientY } = mouseEvent;
|
|
9001
9067
|
return [clientX, clientY];
|
|
9002
9068
|
};
|
|
9003
9069
|
const [grabX, grabY] = mouseEventCoords(grabEvent);
|
|
@@ -9005,23 +9071,21 @@ const createDragGestureController = (options = {}) => {
|
|
|
9005
9071
|
grabX,
|
|
9006
9072
|
grabY,
|
|
9007
9073
|
event: grabEvent,
|
|
9008
|
-
...dragOptions
|
|
9074
|
+
...dragOptions,
|
|
9009
9075
|
});
|
|
9010
|
-
const dragViaPointer = dragEvent => {
|
|
9076
|
+
const dragViaPointer = (dragEvent) => {
|
|
9011
9077
|
const [mouseDragX, mouseDragY] = mouseEventCoords(dragEvent);
|
|
9012
9078
|
dragGesture.drag(mouseDragX, mouseDragY, {
|
|
9013
|
-
event: dragEvent
|
|
9079
|
+
event: dragEvent,
|
|
9014
9080
|
});
|
|
9015
9081
|
};
|
|
9016
|
-
const releaseViaPointer = (pointerEvent, {
|
|
9017
|
-
cancelled
|
|
9018
|
-
} = {}) => {
|
|
9082
|
+
const releaseViaPointer = (pointerEvent, { cancelled } = {}) => {
|
|
9019
9083
|
const [mouseReleaseX, mouseReleaseY] = mouseEventCoords(pointerEvent);
|
|
9020
9084
|
dragGesture.release({
|
|
9021
9085
|
event: pointerEvent,
|
|
9022
9086
|
cancelled,
|
|
9023
9087
|
releaseX: mouseReleaseX,
|
|
9024
|
-
releaseY: mouseReleaseY
|
|
9088
|
+
releaseY: mouseReleaseY,
|
|
9025
9089
|
});
|
|
9026
9090
|
};
|
|
9027
9091
|
dragGesture.dragViaPointer = dragViaPointer;
|
|
@@ -9040,7 +9104,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
9040
9104
|
* everybody: the element would stop being selectable even when nobody is
|
|
9041
9105
|
* dragging it. Here it is refused for the length of one gesture.
|
|
9042
9106
|
*/
|
|
9043
|
-
const preventSelectStart = selectStartEvent => {
|
|
9107
|
+
const preventSelectStart = (selectStartEvent) => {
|
|
9044
9108
|
selectStartEvent.preventDefault();
|
|
9045
9109
|
};
|
|
9046
9110
|
document.addEventListener("selectstart", preventSelectStart);
|
|
@@ -9075,7 +9139,7 @@ const createDragGestureController = (options = {}) => {
|
|
|
9075
9139
|
* waiting for a click that never comes costs nothing until then.
|
|
9076
9140
|
*/
|
|
9077
9141
|
removePendingMultiClickListener();
|
|
9078
|
-
const onClick = clickEvent => {
|
|
9142
|
+
const onClick = (clickEvent) => {
|
|
9079
9143
|
const clickCount = clickEvent.detail;
|
|
9080
9144
|
if (clickCount < 2) {
|
|
9081
9145
|
return;
|
|
@@ -9091,7 +9155,10 @@ const createDragGestureController = (options = {}) => {
|
|
|
9091
9155
|
// is doing its own selecting — a second opinion there would only fight
|
|
9092
9156
|
// it.
|
|
9093
9157
|
const clickTarget = clickEvent.target;
|
|
9094
|
-
if (
|
|
9158
|
+
if (
|
|
9159
|
+
clickTarget !== grabEvent.target &&
|
|
9160
|
+
!clickTarget.contains(grabEvent.target)
|
|
9161
|
+
) {
|
|
9095
9162
|
return;
|
|
9096
9163
|
}
|
|
9097
9164
|
// Text the page says is not selectable stays not selectable: a
|
|
@@ -9117,204 +9184,204 @@ const createDragGestureController = (options = {}) => {
|
|
|
9117
9184
|
onMove: dragViaPointer,
|
|
9118
9185
|
onRelease: releaseViaPointer,
|
|
9119
9186
|
gestureInfo: dragGesture.gestureInfo,
|
|
9120
|
-
dragGesture
|
|
9187
|
+
dragGesture,
|
|
9121
9188
|
});
|
|
9122
9189
|
dragGesture.addReleaseCallback(() => {
|
|
9123
9190
|
cleanup();
|
|
9124
9191
|
});
|
|
9125
9192
|
return dragGesture;
|
|
9126
9193
|
};
|
|
9194
|
+
|
|
9127
9195
|
const grabViaPointer = (grabEvent, options) => {
|
|
9128
9196
|
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;
|
|
9197
|
+
return initDragByPointer(
|
|
9198
|
+
grabEvent,
|
|
9199
|
+
options,
|
|
9200
|
+
({ onMove, onRelease, gestureInfo, dragGesture }) => {
|
|
9201
|
+
// Captured on something that will still be there at the end of the
|
|
9202
|
+
// gesture: the browser releases the capture when its element leaves the
|
|
9203
|
+
// document, and a gesture whose own effect replaces the DOM under the
|
|
9204
|
+
// finger would lose the pointer at its first move. Callers whose target
|
|
9205
|
+
// is stable have nothing to say and keep it.
|
|
9206
|
+
const target = options?.pointerCaptureElement || grabEvent.target;
|
|
9207
|
+
// The capture is ONE per pointer for the whole document: taking it is
|
|
9208
|
+
// taking it away from whoever had it, who is then told the exact same
|
|
9209
|
+
// thing it is told when its own gesture ends. So it is taken when this
|
|
9210
|
+
// gesture is established and not a moment earlier — for most callers
|
|
9211
|
+
// that is the grab itself (the intent was settled before, by a handle
|
|
9212
|
+
// or a long press), and for a caller that is still deciding what the
|
|
9213
|
+
// press means, it is whenever it says so (see capturePointer).
|
|
9214
|
+
let captured = false;
|
|
9215
|
+
dragGesture.capturePointer = () => {
|
|
9216
|
+
captured = true;
|
|
9217
|
+
// Written down before it is taken: this is the only place a capture
|
|
9218
|
+
// is ever taken from, so what this map says is who asked for it
|
|
9219
|
+
// last — which is what tells a hand-over from a capture the browser
|
|
9220
|
+
// dropped on its own (see onCaptureLost).
|
|
9221
|
+
captureHolderByPointerId.set(grabEvent.pointerId, dragGesture);
|
|
9222
|
+
target.setPointerCapture(grabEvent.pointerId);
|
|
9223
|
+
};
|
|
9224
|
+
if (!options?.pointerCaptureDeferred) {
|
|
9225
|
+
dragGesture.capturePointer();
|
|
9242
9226
|
}
|
|
9243
|
-
|
|
9244
|
-
|
|
9227
|
+
/*
|
|
9228
|
+
* A touchmove left alone is the browser deciding the touch belongs to
|
|
9229
|
+
* it: it takes it to scroll with, and a touch it has taken is a pointer
|
|
9230
|
+
* stream it CANCELS — the gesture dies mid-move, the finger is still
|
|
9231
|
+
* down, and nothing reads it anymore.
|
|
9232
|
+
*
|
|
9233
|
+
* Refused only once the gesture is established (a `touch-action: none`
|
|
9234
|
+
* would take the touch from everyone who merely brushes past the
|
|
9235
|
+
* element), but LISTENED FOR from the grab: whether a touchmove can be
|
|
9236
|
+
* refused at all is decided when the touch begins, from the listeners
|
|
9237
|
+
* present at that moment. Registered later, the listener is handed
|
|
9238
|
+
* events that are already `cancelable: false` — refusing them does
|
|
9239
|
+
* nothing, and the reason is invisible in the code that refuses.
|
|
9240
|
+
*
|
|
9241
|
+
* On the window in capture AND on the grabbed element: a touch keeps
|
|
9242
|
+
* being dispatched at the node it started on, and a gesture may take
|
|
9243
|
+
* that node out of the document (a page that travels navigates) — from
|
|
9244
|
+
* then on the event never passes through the window on its way
|
|
9245
|
+
* anywhere.
|
|
9246
|
+
*/
|
|
9247
|
+
const preventTouchScroll = (touchMoveEvent) => {
|
|
9248
|
+
if (gestureInfo.started && touchMoveEvent.cancelable) {
|
|
9249
|
+
touchMoveEvent.preventDefault();
|
|
9250
|
+
}
|
|
9251
|
+
};
|
|
9252
|
+
const grabTarget = grabEvent.target;
|
|
9253
|
+
window.addEventListener("touchmove", preventTouchScroll, {
|
|
9254
|
+
passive: false,
|
|
9255
|
+
capture: true,
|
|
9245
9256
|
});
|
|
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
|
|
9257
|
+
grabTarget.addEventListener("touchmove", preventTouchScroll, {
|
|
9258
|
+
passive: false,
|
|
9288
9259
|
});
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
//
|
|
9297
|
-
//
|
|
9298
|
-
//
|
|
9299
|
-
//
|
|
9300
|
-
//
|
|
9301
|
-
//
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9260
|
+
// Only OUR capture ending is this gesture's business:
|
|
9261
|
+
// lostpointercapture bubbles, so a descendant giving up its own capture
|
|
9262
|
+
// walks straight into this listener. That is not a rare shape — it is
|
|
9263
|
+
// exactly what happens when a gesture hands over to another one (a
|
|
9264
|
+
// press that becomes a drag releases its intermediate gesture, held on
|
|
9265
|
+
// the pressed element, while the real one is being held on a container
|
|
9266
|
+
// above it), and taken as our own it kills the new gesture one
|
|
9267
|
+
// millisecond after it started.
|
|
9268
|
+
//
|
|
9269
|
+
// And when it IS ours, it is a loss and never an end: the ends a
|
|
9270
|
+
// gesture has are the pointer going up and the pointer being
|
|
9271
|
+
// cancelled, both listened for below. What a loss MEANS is the
|
|
9272
|
+
// question, and the event does not answer it — two very different
|
|
9273
|
+
// things arrive as the same one:
|
|
9274
|
+
//
|
|
9275
|
+
// - it was HANDED OVER: another gesture took the pointer, or the
|
|
9276
|
+
// element it was held on left the document. There is nothing to go
|
|
9277
|
+
// on with, and what was being carried must go back rather than land
|
|
9278
|
+
// wherever the hand happened to be.
|
|
9279
|
+
// - it was simply LET GO OF by the browser, with the hand still down
|
|
9280
|
+
// and still moving. It happens, and not rarely: the capture is a
|
|
9281
|
+
// guarantee that events keep coming to one element, and the browser
|
|
9282
|
+
// drops it for reasons of its own that no code here can see. Killing
|
|
9283
|
+
// the gesture for that is dropping an object mid-air — the copy
|
|
9284
|
+
// vanishes, the place the hint had lit up is thrown away, and the
|
|
9285
|
+
// hand is left having done nothing.
|
|
9286
|
+
//
|
|
9287
|
+
// They are told apart by who asked (see captureHolderByPointerId): a
|
|
9288
|
+
// capture nobody here took, on an element still in the document, was
|
|
9289
|
+
// let go of. The gesture does not need it — every move and the release
|
|
9290
|
+
// are read at the WINDOW, not at the element — so it goes on.
|
|
9291
|
+
const onCaptureLost = (pointerEvent) => {
|
|
9292
|
+
if (!captured || pointerEvent.target !== target) {
|
|
9293
|
+
return;
|
|
9294
|
+
}
|
|
9295
|
+
const handedOver =
|
|
9296
|
+
captureHolderByPointerId.get(grabEvent.pointerId) !== dragGesture;
|
|
9297
|
+
if (!handedOver && target.isConnected) {
|
|
9298
|
+
// Nobody took it and the element it was held on is still there:
|
|
9299
|
+
// the browser let the capture go by itself, which it does — a
|
|
9300
|
+
// node moved by a re-render and put straight back, a decision of
|
|
9301
|
+
// its own we are not told the reason for. The hand has not let go
|
|
9302
|
+
// of anything, so neither does the gesture: it is a guarantee that
|
|
9303
|
+
// was lost, not the gesture. Every move and the release are read
|
|
9304
|
+
// at the window (see below), so it goes on without it rather than
|
|
9305
|
+
// dropping what is still being carried — and the drop the hand was
|
|
9306
|
+
// aiming at, which the hint had already lit up, still happens.
|
|
9307
|
+
captured = false;
|
|
9308
|
+
return;
|
|
9309
|
+
}
|
|
9310
|
+
onRelease(pointerEvent, { cancelled: true });
|
|
9311
|
+
};
|
|
9312
|
+
target.addEventListener("lostpointercapture", onCaptureLost);
|
|
9313
|
+
target.addEventListener("pointercancel", onRelease);
|
|
9314
|
+
target.addEventListener("pointerup", onRelease);
|
|
9315
|
+
// Read from the window rather than from the element while the pointer
|
|
9316
|
+
// is not this gesture's: without a capture a move is delivered to
|
|
9317
|
+
// whatever is under the pointer, and a hand that has left the element
|
|
9318
|
+
// is exactly the hand this gesture is trying to make sense of. The
|
|
9319
|
+
// capture phase reaches the window before anything else, captured or
|
|
9320
|
+
// not, so there is one place to read whichever way the gesture ends up.
|
|
9321
|
+
const onPointerMove = (pointerEvent) => {
|
|
9322
|
+
if (pointerEvent.pointerId !== grabEvent.pointerId) {
|
|
9323
|
+
return;
|
|
9324
|
+
}
|
|
9325
|
+
onMove(pointerEvent);
|
|
9326
|
+
};
|
|
9327
|
+
window.addEventListener("pointermove", onPointerMove, true);
|
|
9328
|
+
// The end of the pointer is also listened for on the window, because
|
|
9329
|
+
// the end is the one event a gesture cannot afford to miss and the
|
|
9330
|
+
// element it is captured on is not always on its way: a pointer can
|
|
9331
|
+
// be delivered somewhere else entirely (a browser view transition
|
|
9332
|
+
// sends presses to the document root), and a cancel dispatched there
|
|
9333
|
+
// never passes through this element. Missed, the gesture never ends —
|
|
9334
|
+
// whatever it was holding stays held.
|
|
9335
|
+
let released = false;
|
|
9336
|
+
const onPointerEnd = (pointerEvent) => {
|
|
9337
|
+
if (pointerEvent.pointerId !== grabEvent.pointerId || released) {
|
|
9338
|
+
return;
|
|
9339
|
+
}
|
|
9340
|
+
released = true;
|
|
9341
|
+
onRelease(pointerEvent);
|
|
9342
|
+
};
|
|
9343
|
+
window.addEventListener("pointerup", onPointerEnd, true);
|
|
9344
|
+
window.addEventListener("pointercancel", onPointerEnd, true);
|
|
9345
|
+
return () => {
|
|
9346
|
+
// Listeners first, capture last: giving the pointer back is the
|
|
9347
|
+
// one thing here that can throw, and a gesture that fails to clean
|
|
9348
|
+
// up half way is worse than one that never cleaned up at all — its
|
|
9349
|
+
// listeners stay on the element and answer the NEXT gesture, from
|
|
9350
|
+
// a gesture whose pointer is long gone.
|
|
9351
|
+
window.removeEventListener("touchmove", preventTouchScroll, {
|
|
9352
|
+
capture: true,
|
|
9353
|
+
});
|
|
9354
|
+
grabTarget.removeEventListener("touchmove", preventTouchScroll);
|
|
9355
|
+
target.removeEventListener("lostpointercapture", onCaptureLost);
|
|
9356
|
+
target.removeEventListener("pointercancel", onRelease);
|
|
9357
|
+
window.removeEventListener("pointermove", onPointerMove, true);
|
|
9358
|
+
target.removeEventListener("pointerup", onRelease);
|
|
9359
|
+
window.removeEventListener("pointerup", onPointerEnd, true);
|
|
9360
|
+
window.removeEventListener("pointercancel", onPointerEnd, true);
|
|
9361
|
+
// Only what this gesture took, and only while there is something to
|
|
9362
|
+
// give back: a capture on that element may be someone else's (two
|
|
9363
|
+
// gestures reading the same press hold the same node), and a pointer
|
|
9364
|
+
// that is up no longer exists — the browser has already dropped the
|
|
9365
|
+
// capture with it, and asking again throws ("No active pointer with
|
|
9366
|
+
// the given id is found") on the most ordinary release there is.
|
|
9367
|
+
if (
|
|
9368
|
+
captureHolderByPointerId.get(grabEvent.pointerId) === dragGesture
|
|
9369
|
+
) {
|
|
9370
|
+
captureHolderByPointerId.delete(grabEvent.pointerId);
|
|
9371
|
+
}
|
|
9372
|
+
if (captured && target.hasPointerCapture(grabEvent.pointerId)) {
|
|
9373
|
+
target.releasePointerCapture(grabEvent.pointerId);
|
|
9374
|
+
}
|
|
9375
|
+
};
|
|
9376
|
+
},
|
|
9377
|
+
);
|
|
9310
9378
|
}
|
|
9311
9379
|
if (grabEvent.type === "mousedown") {
|
|
9312
|
-
console.warn(
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
const onPointerUp = pointerEvent => {
|
|
9380
|
+
console.warn(
|
|
9381
|
+
`Received "mousedown" event, "pointerdown" events are recommended to perform drag gestures.`,
|
|
9382
|
+
);
|
|
9383
|
+
return initDragByPointer(grabEvent, options, ({ onMove, onRelease }) => {
|
|
9384
|
+
const onPointerUp = (pointerEvent) => {
|
|
9318
9385
|
// <button disabled> for example does not emit mouseup if we release mouse over it
|
|
9319
9386
|
// -> we add "pointerup" to catch mouseup occuring on disabled element
|
|
9320
9387
|
if (pointerEvent.pointerType === "mouse") {
|
|
@@ -9331,15 +9398,19 @@ const createDragGestureController = (options = {}) => {
|
|
|
9331
9398
|
};
|
|
9332
9399
|
});
|
|
9333
9400
|
}
|
|
9334
|
-
throw new Error(
|
|
9401
|
+
throw new Error(
|
|
9402
|
+
`Unsupported "${grabEvent.type}" evenet passed to grabViaPointer. "pointerdown" was expected.`,
|
|
9403
|
+
);
|
|
9335
9404
|
};
|
|
9336
9405
|
dragGestureController.grabViaPointer = grabViaPointer;
|
|
9406
|
+
|
|
9337
9407
|
return dragGestureController;
|
|
9338
9408
|
};
|
|
9339
9409
|
|
|
9340
9410
|
// Only the primary button drags: a right click (or any secondary button) opens
|
|
9341
9411
|
// a context menu, it never grabs anything.
|
|
9342
|
-
const isPrimaryButtonEvent = event =>
|
|
9412
|
+
const isPrimaryButtonEvent = (event) =>
|
|
9413
|
+
event.button === undefined || event.button === 0;
|
|
9343
9414
|
|
|
9344
9415
|
/*
|
|
9345
9416
|
* Speed over the last VELOCITY_WINDOW_MS rather than between the last two
|
|
@@ -9352,18 +9423,11 @@ const isPrimaryButtonEvent = event => event.button === undefined || event.button
|
|
|
9352
9423
|
*/
|
|
9353
9424
|
const VELOCITY_WINDOW_MS = 100;
|
|
9354
9425
|
const createVelocityMeter = (grabX, grabY) => {
|
|
9355
|
-
const samples = [{
|
|
9356
|
-
|
|
9357
|
-
x: grabX,
|
|
9358
|
-
y: grabY
|
|
9359
|
-
}];
|
|
9426
|
+
const samples = [{ time: performance.now(), x: grabX, y: grabY }];
|
|
9427
|
+
|
|
9360
9428
|
const measureVelocity = (x, y) => {
|
|
9361
9429
|
const time = performance.now();
|
|
9362
|
-
samples.push({
|
|
9363
|
-
time,
|
|
9364
|
-
x,
|
|
9365
|
-
y
|
|
9366
|
-
});
|
|
9430
|
+
samples.push({ time, x, y });
|
|
9367
9431
|
while (samples.length > 2 && time - samples[1].time > VELOCITY_WINDOW_MS) {
|
|
9368
9432
|
samples.shift();
|
|
9369
9433
|
}
|
|
@@ -9374,14 +9438,17 @@ const createVelocityMeter = (grabX, grabY) => {
|
|
|
9374
9438
|
}
|
|
9375
9439
|
return [(x - oldestSample.x) / elapsed, (y - oldestSample.y) / elapsed];
|
|
9376
9440
|
};
|
|
9441
|
+
|
|
9377
9442
|
return measureVelocity;
|
|
9378
9443
|
};
|
|
9444
|
+
|
|
9379
9445
|
const definePropertyAsReadOnly = (object, propertyName) => {
|
|
9380
9446
|
Object.defineProperty(object, propertyName, {
|
|
9381
9447
|
writable: false,
|
|
9382
|
-
value: object[propertyName]
|
|
9448
|
+
value: object[propertyName],
|
|
9383
9449
|
});
|
|
9384
9450
|
};
|
|
9451
|
+
|
|
9385
9452
|
const NOOP = () => {};
|
|
9386
9453
|
let removePendingMultiClickListener = NOOP;
|
|
9387
9454
|
|
|
@@ -9427,12 +9494,11 @@ const selectParagraphAtPoint = (x, y) => {
|
|
|
9427
9494
|
paragraphRange.selectNodeContents(block);
|
|
9428
9495
|
selection.addRange(paragraphRange);
|
|
9429
9496
|
};
|
|
9430
|
-
|
|
9497
|
+
|
|
9498
|
+
const getClosestBlock = (node) => {
|
|
9431
9499
|
let element = node.nodeType === 1 ? node : node.parentElement;
|
|
9432
9500
|
while (element) {
|
|
9433
|
-
const {
|
|
9434
|
-
display
|
|
9435
|
-
} = window.getComputedStyle(element);
|
|
9501
|
+
const { display } = window.getComputedStyle(element);
|
|
9436
9502
|
// Everything that is not laid out as a line inside another line: the first
|
|
9437
9503
|
// ancestor that breaks the flow is the one whose text reads as a paragraph
|
|
9438
9504
|
// of its own.
|
|
@@ -9443,6 +9509,7 @@ const getClosestBlock = node => {
|
|
|
9443
9509
|
}
|
|
9444
9510
|
return null;
|
|
9445
9511
|
};
|
|
9512
|
+
|
|
9446
9513
|
const createCaretRange = (x, y) => {
|
|
9447
9514
|
if (document.caretPositionFromPoint) {
|
|
9448
9515
|
const caretPosition = document.caretPositionFromPoint(x, y);
|
|
@@ -9459,7 +9526,8 @@ const createCaretRange = (x, y) => {
|
|
|
9459
9526
|
}
|
|
9460
9527
|
return null;
|
|
9461
9528
|
};
|
|
9462
|
-
|
|
9529
|
+
|
|
9530
|
+
const isSelectable = (element) => {
|
|
9463
9531
|
if (!element || element.nodeType !== 1) {
|
|
9464
9532
|
return true;
|
|
9465
9533
|
}
|
|
@@ -9467,6 +9535,7 @@ const isSelectable = element => {
|
|
|
9467
9535
|
const userSelect = computedStyle.userSelect || computedStyle.webkitUserSelect;
|
|
9468
9536
|
return userSelect !== "none";
|
|
9469
9537
|
};
|
|
9538
|
+
|
|
9470
9539
|
const collapseSelection = () => {
|
|
9471
9540
|
const selection = window.getSelection();
|
|
9472
9541
|
if (selection && !selection.isCollapsed) {
|
|
@@ -9474,53 +9543,7 @@ const collapseSelection = () => {
|
|
|
9474
9543
|
}
|
|
9475
9544
|
};
|
|
9476
9545
|
|
|
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
|
-
*/
|
|
9546
|
+
installImportMetaCssBuild(import.meta);
|
|
9524
9547
|
|
|
9525
9548
|
/* At module scope, and on the markers rather than on the pressed element: both
|
|
9526
9549
|
rules below have to be true BEFORE the finger lands — a stylesheet, never a
|
|
@@ -9541,7 +9564,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
9541
9564
|
lets the page scroll and still makes the refusal effective — provided the
|
|
9542
9565
|
listener that will refuse is already known too, which is markDragSource's
|
|
9543
9566
|
half of the same rule. */
|
|
9544
|
-
const css$4 = /* css
|
|
9567
|
+
const css$4 = /* css */ `[data-drag-handle], [data-drag-source] {
|
|
9545
9568
|
-webkit-touch-callout: none;
|
|
9546
9569
|
}
|
|
9547
9570
|
|
|
@@ -9632,9 +9655,7 @@ const keepTouchRefusable = () => {
|
|
|
9632
9655
|
*/
|
|
9633
9656
|
const markDragSource = (element, axes = "xy") => {
|
|
9634
9657
|
element.setAttribute("data-drag-source", axes);
|
|
9635
|
-
element.addEventListener("touchmove", keepTouchRefusable, {
|
|
9636
|
-
passive: false
|
|
9637
|
-
});
|
|
9658
|
+
element.addEventListener("touchmove", keepTouchRefusable, { passive: false });
|
|
9638
9659
|
return () => {
|
|
9639
9660
|
element.removeAttribute("data-drag-source");
|
|
9640
9661
|
element.removeEventListener("touchmove", keepTouchRefusable);
|
|
@@ -9671,40 +9692,49 @@ const markDragSource = (element, axes = "xy") => {
|
|
|
9671
9692
|
* @param {function} [options.onPress]
|
|
9672
9693
|
* The wait completed and the object is now held (haptics, scale…).
|
|
9673
9694
|
*/
|
|
9674
|
-
const dragAfterIntent = (
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9695
|
+
const dragAfterIntent = (
|
|
9696
|
+
grabEvent,
|
|
9697
|
+
dragGestureInitializer,
|
|
9698
|
+
{
|
|
9699
|
+
threshold = 5,
|
|
9700
|
+
longPress = "if-touch",
|
|
9701
|
+
longPressDelay = 400,
|
|
9702
|
+
longPressSlop = 8,
|
|
9703
|
+
onPressStart,
|
|
9704
|
+
onPressCancel,
|
|
9705
|
+
onPress,
|
|
9706
|
+
} = {},
|
|
9707
|
+
) => {
|
|
9683
9708
|
if (!isPrimaryButtonEvent(grabEvent)) {
|
|
9684
9709
|
return;
|
|
9685
9710
|
}
|
|
9686
9711
|
const target = grabEvent.target;
|
|
9687
|
-
const isDedicatedHandle =
|
|
9712
|
+
const isDedicatedHandle =
|
|
9713
|
+
target.closest && target.closest("[data-drag-handle]");
|
|
9688
9714
|
if (isDedicatedHandle) {
|
|
9689
9715
|
startDragGesture(dragGestureInitializer);
|
|
9690
9716
|
return;
|
|
9691
9717
|
}
|
|
9692
|
-
const startsOnLongPress =
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9718
|
+
const startsOnLongPress =
|
|
9719
|
+
longPress === true ||
|
|
9720
|
+
(longPress === "if-touch" &&
|
|
9721
|
+
grabEvent.pointerType === "touch" &&
|
|
9722
|
+
// The wait tells a scroll from a drag, and here there is no scroll to tell
|
|
9723
|
+
// it from — see [data-drag-on-contact] at the top of this file.
|
|
9724
|
+
!(target.closest && target.closest("[data-drag-on-contact]")));
|
|
9696
9725
|
if (startsOnLongPress) {
|
|
9697
9726
|
dragAfterLongPress(grabEvent, dragGestureInitializer, {
|
|
9698
9727
|
longPressDelay,
|
|
9699
9728
|
longPressSlop,
|
|
9700
9729
|
onPressStart,
|
|
9701
9730
|
onPressCancel,
|
|
9702
|
-
onPress
|
|
9731
|
+
onPress,
|
|
9703
9732
|
});
|
|
9704
9733
|
return;
|
|
9705
9734
|
}
|
|
9706
9735
|
dragAfterDistance(grabEvent, dragGestureInitializer, threshold);
|
|
9707
9736
|
};
|
|
9737
|
+
|
|
9708
9738
|
const startDragGesture = (dragGestureInitializer, catchUpEvent) => {
|
|
9709
9739
|
const dragGesture = dragGestureInitializer();
|
|
9710
9740
|
if (!dragGesture) {
|
|
@@ -9718,6 +9748,7 @@ const startDragGesture = (dragGestureInitializer, catchUpEvent) => {
|
|
|
9718
9748
|
}
|
|
9719
9749
|
return dragGesture;
|
|
9720
9750
|
};
|
|
9751
|
+
|
|
9721
9752
|
const dragAfterDistance = (grabEvent, dragGestureInitializer, threshold) => {
|
|
9722
9753
|
const significantDragGestureController = createDragGestureController({
|
|
9723
9754
|
threshold,
|
|
@@ -9725,28 +9756,28 @@ const dragAfterDistance = (grabEvent, dragGestureInitializer, threshold) => {
|
|
|
9725
9756
|
// user should still be able to scroll or interact with the document
|
|
9726
9757
|
// only once the gesture is significant we take control
|
|
9727
9758
|
documentInteractions: "manual",
|
|
9728
|
-
onDragStart: gestureInfo => {
|
|
9759
|
+
onDragStart: (gestureInfo) => {
|
|
9729
9760
|
significantDragGesture.release(); // kill that gesture
|
|
9730
9761
|
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
|
|
9762
|
+
},
|
|
9741
9763
|
});
|
|
9764
|
+
const significantDragGesture =
|
|
9765
|
+
significantDragGestureController.grabViaPointer(grabEvent, {
|
|
9766
|
+
element: grabEvent.target,
|
|
9767
|
+
// This one owns nothing: it measures a distance to find out whether there
|
|
9768
|
+
// is a gesture at all, and it is over the moment there is. Taking the
|
|
9769
|
+
// pointer for that would take it from whoever is already holding this same
|
|
9770
|
+
// element for a gesture of their own — and giving it back at the threshold
|
|
9771
|
+
// would tell them theirs is over.
|
|
9772
|
+
pointerCaptureDeferred: true,
|
|
9773
|
+
});
|
|
9742
9774
|
};
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
onPressCancel,
|
|
9748
|
-
|
|
9749
|
-
}) => {
|
|
9775
|
+
|
|
9776
|
+
const dragAfterLongPress = (
|
|
9777
|
+
grabEvent,
|
|
9778
|
+
dragGestureInitializer,
|
|
9779
|
+
{ longPressDelay, longPressSlop, onPressStart, onPressCancel, onPress },
|
|
9780
|
+
) => {
|
|
9750
9781
|
/*
|
|
9751
9782
|
* Nothing is done here to keep the touch refusable: whether it can be refused
|
|
9752
9783
|
* at all was settled when the finger landed, from what the element already
|
|
@@ -9760,9 +9791,7 @@ const dragAfterLongPress = (grabEvent, dragGestureInitializer, {
|
|
|
9760
9791
|
slop: longPressSlop,
|
|
9761
9792
|
onPressStart,
|
|
9762
9793
|
onPressCancel,
|
|
9763
|
-
onPressHeld: (pressEvent, {
|
|
9764
|
-
endPress
|
|
9765
|
-
}) => {
|
|
9794
|
+
onPressHeld: (pressEvent, { endPress }) => {
|
|
9766
9795
|
onPress?.(pressEvent);
|
|
9767
9796
|
const dragGesture = startDragGesture(dragGestureInitializer);
|
|
9768
9797
|
if (!dragGesture) {
|
|
@@ -9772,7 +9801,7 @@ const dragAfterLongPress = (grabEvent, dragGestureInitializer, {
|
|
|
9772
9801
|
dragGesture.addReleaseCallback(() => {
|
|
9773
9802
|
endPress();
|
|
9774
9803
|
});
|
|
9775
|
-
}
|
|
9804
|
+
},
|
|
9776
9805
|
});
|
|
9777
9806
|
};
|
|
9778
9807
|
|
|
@@ -10292,7 +10321,9 @@ const getDragCoordinates = (
|
|
|
10292
10321
|
return [leftRelativeToScrollContainer, topRelativeToScrollContainer];
|
|
10293
10322
|
};
|
|
10294
10323
|
|
|
10295
|
-
installImportMetaCssBuild(import.meta);
|
|
10324
|
+
installImportMetaCssBuild(import.meta);
|
|
10325
|
+
|
|
10326
|
+
const css$3 = /* css */ `.navi_constraint_feedback_line {
|
|
10296
10327
|
z-index: 9998;
|
|
10297
10328
|
visibility: hidden;
|
|
10298
10329
|
transform-origin: 0;
|
|
@@ -10306,6 +10337,7 @@ installImportMetaCssBuild(import.meta);const css$3 = /* css */`.navi_constraint_
|
|
|
10306
10337
|
visibility: visible;
|
|
10307
10338
|
}
|
|
10308
10339
|
`;
|
|
10340
|
+
|
|
10309
10341
|
const setupConstraintFeedbackLine = () => {
|
|
10310
10342
|
import.meta.css = [css$3, "@jsenv/dom/src/interaction/drag/constraint_feedback_line.js"];
|
|
10311
10343
|
const constraintFeedbackLine = createConstraintFeedbackLine();
|
|
@@ -10315,17 +10347,18 @@ const setupConstraintFeedbackLine = () => {
|
|
|
10315
10347
|
let lastMouseY = null;
|
|
10316
10348
|
|
|
10317
10349
|
// Internal function to update constraint feedback line
|
|
10318
|
-
const onDrag = gestureInfo => {
|
|
10319
|
-
const {
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10350
|
+
const onDrag = (gestureInfo) => {
|
|
10351
|
+
const { grabEvent, dragEvent } = gestureInfo;
|
|
10352
|
+
if (
|
|
10353
|
+
grabEvent.type === "programmatic" ||
|
|
10354
|
+
// dragEvent can be null when only mousedown without yet any mousemove
|
|
10355
|
+
!dragEvent ||
|
|
10356
|
+
dragEvent.type === "programmatic"
|
|
10357
|
+
) {
|
|
10326
10358
|
// programmatic drag
|
|
10327
10359
|
return;
|
|
10328
10360
|
}
|
|
10361
|
+
|
|
10329
10362
|
const mouseX = dragEvent.clientX;
|
|
10330
10363
|
const mouseY = dragEvent.clientY;
|
|
10331
10364
|
// Use last known position if current position not available (e.g., during scroll)
|
|
@@ -10338,6 +10371,7 @@ const setupConstraintFeedbackLine = () => {
|
|
|
10338
10371
|
// Store current mouse position for potential use during scroll
|
|
10339
10372
|
lastMouseX = mouseX;
|
|
10340
10373
|
lastMouseY = mouseY;
|
|
10374
|
+
|
|
10341
10375
|
const grabClientX = grabEvent.clientX;
|
|
10342
10376
|
const grabClientY = grabEvent.clientY;
|
|
10343
10377
|
|
|
@@ -10366,28 +10400,33 @@ const setupConstraintFeedbackLine = () => {
|
|
|
10366
10400
|
constraintFeedbackLine.style.opacity = `${maxOpacity * opacityFactor}`;
|
|
10367
10401
|
constraintFeedbackLine.setAttribute("data-visible", "");
|
|
10368
10402
|
};
|
|
10403
|
+
|
|
10369
10404
|
return {
|
|
10370
10405
|
onDrag,
|
|
10371
10406
|
onRelease: () => {
|
|
10372
10407
|
constraintFeedbackLine.remove();
|
|
10373
|
-
}
|
|
10408
|
+
},
|
|
10374
10409
|
};
|
|
10375
10410
|
};
|
|
10411
|
+
|
|
10376
10412
|
const createConstraintFeedbackLine = () => {
|
|
10377
10413
|
const line = document.createElement("div");
|
|
10378
10414
|
line.className = "navi_constraint_feedback_line";
|
|
10379
|
-
line.title =
|
|
10415
|
+
line.title =
|
|
10416
|
+
"Constraint feedback - shows distance between mouse and moving grab point";
|
|
10380
10417
|
document.body.appendChild(line);
|
|
10381
10418
|
return line;
|
|
10382
10419
|
};
|
|
10383
10420
|
|
|
10384
|
-
installImportMetaCssBuild(import.meta)
|
|
10421
|
+
installImportMetaCssBuild(import.meta);
|
|
10385
10422
|
const MARKER_SIZE = 12;
|
|
10423
|
+
|
|
10386
10424
|
let currentDebugMarkers = [];
|
|
10387
10425
|
let currentConstraintMarkers = [];
|
|
10388
10426
|
let currentReferenceElementMarker = null;
|
|
10389
10427
|
let currentElementMarker = null;
|
|
10390
|
-
|
|
10428
|
+
|
|
10429
|
+
const css$2 = /* css */ `.navi_debug_markers_container {
|
|
10391
10430
|
z-index: 999998;
|
|
10392
10431
|
pointer-events: none;
|
|
10393
10432
|
--marker-size: ${MARKER_SIZE}px;
|
|
@@ -10554,9 +10593,8 @@ const css$2 = /* css */`.navi_debug_markers_container {
|
|
|
10554
10593
|
left: 0;
|
|
10555
10594
|
}
|
|
10556
10595
|
`;
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
}) => {
|
|
10596
|
+
|
|
10597
|
+
const setupDragDebugMarkers = (dragGesture, { referenceElement }) => {
|
|
10560
10598
|
import.meta.css = [css$2, "@jsenv/dom/src/interaction/drag/drag_debug_markers.js"];
|
|
10561
10599
|
|
|
10562
10600
|
// Clean up any existing persistent markers from previous drag gestures
|
|
@@ -10567,27 +10605,29 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10567
10605
|
container.innerHTML = ""; // Clear all markers efficiently
|
|
10568
10606
|
}
|
|
10569
10607
|
}
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
} = dragGesture.gestureInfo;
|
|
10608
|
+
|
|
10609
|
+
const { direction, scrollContainer } = dragGesture.gestureInfo;
|
|
10610
|
+
|
|
10574
10611
|
return {
|
|
10575
|
-
onConstraints: (
|
|
10576
|
-
|
|
10577
|
-
top,
|
|
10578
|
-
|
|
10579
|
-
bottom,
|
|
10580
|
-
autoScrollArea
|
|
10581
|
-
}) => {
|
|
10612
|
+
onConstraints: (
|
|
10613
|
+
constraints,
|
|
10614
|
+
{ left, top, right, bottom, autoScrollArea },
|
|
10615
|
+
) => {
|
|
10582
10616
|
// Schedule removal of previous markers if they exist
|
|
10583
10617
|
const previousDebugMarkers = [...currentDebugMarkers];
|
|
10584
10618
|
const previousConstraintMarkers = [...currentConstraintMarkers];
|
|
10585
10619
|
const previousReferenceElementMarker = currentReferenceElementMarker;
|
|
10586
10620
|
const previousElementMarker = currentElementMarker;
|
|
10587
|
-
|
|
10621
|
+
|
|
10622
|
+
if (
|
|
10623
|
+
previousDebugMarkers.length > 0 ||
|
|
10624
|
+
previousConstraintMarkers.length > 0 ||
|
|
10625
|
+
previousReferenceElementMarker ||
|
|
10626
|
+
previousElementMarker
|
|
10627
|
+
) {
|
|
10588
10628
|
setTimeout(() => {
|
|
10589
|
-
previousDebugMarkers.forEach(marker => marker.remove());
|
|
10590
|
-
previousConstraintMarkers.forEach(marker => marker.remove());
|
|
10629
|
+
previousDebugMarkers.forEach((marker) => marker.remove());
|
|
10630
|
+
previousConstraintMarkers.forEach((marker) => marker.remove());
|
|
10591
10631
|
if (previousReferenceElementMarker) {
|
|
10592
10632
|
previousReferenceElementMarker.remove();
|
|
10593
10633
|
}
|
|
@@ -10616,7 +10656,7 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10616
10656
|
bottom,
|
|
10617
10657
|
scrollContainer,
|
|
10618
10658
|
label: elementLabel,
|
|
10619
|
-
color: elementColor
|
|
10659
|
+
color: elementColor,
|
|
10620
10660
|
});
|
|
10621
10661
|
|
|
10622
10662
|
// Create reference element marker if reference element exists
|
|
@@ -10626,47 +10666,52 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10626
10666
|
top,
|
|
10627
10667
|
right,
|
|
10628
10668
|
bottom,
|
|
10629
|
-
scrollContainer
|
|
10669
|
+
scrollContainer,
|
|
10630
10670
|
});
|
|
10631
10671
|
}
|
|
10632
10672
|
|
|
10633
10673
|
// Collect all markers to be created, then merge duplicates
|
|
10634
10674
|
const markersToCreate = [];
|
|
10675
|
+
|
|
10635
10676
|
{
|
|
10636
10677
|
if (direction.x) {
|
|
10637
10678
|
markersToCreate.push({
|
|
10638
|
-
name: autoScrollArea.paddingLeft
|
|
10679
|
+
name: autoScrollArea.paddingLeft
|
|
10680
|
+
? `autoscroll.left + padding(${autoScrollArea.paddingLeft})`
|
|
10681
|
+
: "autoscroll.left",
|
|
10639
10682
|
x: autoScrollArea.left,
|
|
10640
10683
|
y: 0,
|
|
10641
|
-
color: "0 128 0",
|
|
10642
|
-
|
|
10643
|
-
side: "left"
|
|
10684
|
+
color: "0 128 0", // green
|
|
10685
|
+
side: "left",
|
|
10644
10686
|
});
|
|
10645
10687
|
markersToCreate.push({
|
|
10646
|
-
name: autoScrollArea.paddingRight
|
|
10688
|
+
name: autoScrollArea.paddingRight
|
|
10689
|
+
? `autoscroll.right + padding(${autoScrollArea.paddingRight})`
|
|
10690
|
+
: "autoscroll.right",
|
|
10647
10691
|
x: autoScrollArea.right,
|
|
10648
10692
|
y: 0,
|
|
10649
|
-
color: "0 128 0",
|
|
10650
|
-
|
|
10651
|
-
side: "right"
|
|
10693
|
+
color: "0 128 0", // green
|
|
10694
|
+
side: "right",
|
|
10652
10695
|
});
|
|
10653
10696
|
}
|
|
10654
10697
|
if (direction.y) {
|
|
10655
10698
|
markersToCreate.push({
|
|
10656
|
-
name: autoScrollArea.paddingTop
|
|
10699
|
+
name: autoScrollArea.paddingTop
|
|
10700
|
+
? `autoscroll.top + padding(${autoScrollArea.paddingTop})`
|
|
10701
|
+
: "autoscroll.top",
|
|
10657
10702
|
x: 0,
|
|
10658
10703
|
y: autoScrollArea.top,
|
|
10659
|
-
color: "255 0 0",
|
|
10660
|
-
|
|
10661
|
-
side: "top"
|
|
10704
|
+
color: "255 0 0", // red
|
|
10705
|
+
side: "top",
|
|
10662
10706
|
});
|
|
10663
10707
|
markersToCreate.push({
|
|
10664
|
-
name: autoScrollArea.paddingBottom
|
|
10708
|
+
name: autoScrollArea.paddingBottom
|
|
10709
|
+
? `autoscroll.bottom + padding(${autoScrollArea.paddingBottom})`
|
|
10710
|
+
: "autoscroll.bottom",
|
|
10665
10711
|
x: 0,
|
|
10666
10712
|
y: autoScrollArea.bottom,
|
|
10667
|
-
color: "255 165 0",
|
|
10668
|
-
|
|
10669
|
-
side: "bottom"
|
|
10713
|
+
color: "255 165 0", // orange
|
|
10714
|
+
side: "bottom",
|
|
10670
10715
|
});
|
|
10671
10716
|
}
|
|
10672
10717
|
}
|
|
@@ -10674,9 +10719,7 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10674
10719
|
// Process each constraint individually to preserve names
|
|
10675
10720
|
for (const constraint of constraints) {
|
|
10676
10721
|
if (constraint.type === "bounds") {
|
|
10677
|
-
const {
|
|
10678
|
-
bounds
|
|
10679
|
-
} = constraint;
|
|
10722
|
+
const { bounds } = constraint;
|
|
10680
10723
|
|
|
10681
10724
|
// Create individual markers for each bound with constraint name
|
|
10682
10725
|
if (direction.x) {
|
|
@@ -10685,9 +10728,8 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10685
10728
|
name: `${constraint.name}.left`,
|
|
10686
10729
|
x: bounds.left,
|
|
10687
10730
|
y: 0,
|
|
10688
|
-
color: "128 0 128",
|
|
10689
|
-
|
|
10690
|
-
side: "left"
|
|
10731
|
+
color: "128 0 128", // purple
|
|
10732
|
+
side: "left",
|
|
10691
10733
|
});
|
|
10692
10734
|
}
|
|
10693
10735
|
if (bounds.right !== undefined) {
|
|
@@ -10697,9 +10739,8 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10697
10739
|
name: `${constraint.name}.right`,
|
|
10698
10740
|
x: bounds.right,
|
|
10699
10741
|
y: 0,
|
|
10700
|
-
color: "128 0 128",
|
|
10701
|
-
|
|
10702
|
-
side: "right"
|
|
10742
|
+
color: "128 0 128", // purple
|
|
10743
|
+
side: "right",
|
|
10703
10744
|
});
|
|
10704
10745
|
}
|
|
10705
10746
|
}
|
|
@@ -10709,9 +10750,8 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10709
10750
|
name: `${constraint.name}.top`,
|
|
10710
10751
|
x: 0,
|
|
10711
10752
|
y: bounds.top,
|
|
10712
|
-
color: "128 0 128",
|
|
10713
|
-
|
|
10714
|
-
side: "top"
|
|
10753
|
+
color: "128 0 128", // purple
|
|
10754
|
+
side: "top",
|
|
10715
10755
|
});
|
|
10716
10756
|
}
|
|
10717
10757
|
if (bounds.bottom !== undefined) {
|
|
@@ -10721,28 +10761,37 @@ const setupDragDebugMarkers = (dragGesture, {
|
|
|
10721
10761
|
name: `${constraint.name}.bottom`,
|
|
10722
10762
|
x: 0,
|
|
10723
10763
|
y: bounds.bottom,
|
|
10724
|
-
color: "128 0 128",
|
|
10725
|
-
|
|
10726
|
-
side: "bottom"
|
|
10764
|
+
color: "128 0 128", // purple
|
|
10765
|
+
side: "bottom",
|
|
10727
10766
|
});
|
|
10728
10767
|
}
|
|
10729
10768
|
}
|
|
10730
10769
|
} else if (constraint.type === "obstacle") {
|
|
10731
|
-
const obstacleMarker = createObstacleMarker(
|
|
10770
|
+
const obstacleMarker = createObstacleMarker(
|
|
10771
|
+
constraint,
|
|
10772
|
+
scrollContainer,
|
|
10773
|
+
);
|
|
10732
10774
|
currentConstraintMarkers.push(obstacleMarker);
|
|
10733
10775
|
}
|
|
10734
10776
|
}
|
|
10735
10777
|
|
|
10736
10778
|
// Create markers with merging for overlapping positions
|
|
10737
|
-
const createdMarkers = createMergedMarkers(
|
|
10738
|
-
|
|
10739
|
-
|
|
10779
|
+
const createdMarkers = createMergedMarkers(
|
|
10780
|
+
markersToCreate,
|
|
10781
|
+
scrollContainer,
|
|
10782
|
+
);
|
|
10783
|
+
currentDebugMarkers.push(
|
|
10784
|
+
...createdMarkers.filter((m) => m.type !== "constraint"),
|
|
10785
|
+
);
|
|
10786
|
+
currentConstraintMarkers.push(
|
|
10787
|
+
...createdMarkers.filter((m) => m.type === "constraint"),
|
|
10788
|
+
);
|
|
10740
10789
|
},
|
|
10741
10790
|
onRelease: () => {
|
|
10742
10791
|
{
|
|
10743
10792
|
return;
|
|
10744
10793
|
}
|
|
10745
|
-
}
|
|
10794
|
+
},
|
|
10746
10795
|
};
|
|
10747
10796
|
};
|
|
10748
10797
|
|
|
@@ -10761,9 +10810,8 @@ const getMarkersContainer = () => {
|
|
|
10761
10810
|
// Convert document-relative coordinates to viewport coordinates for marker positioning
|
|
10762
10811
|
// Takes the scroll container into account for proper positioning relative to the container
|
|
10763
10812
|
const getDebugMarkerPos = (x, y, scrollContainer, side = null) => {
|
|
10764
|
-
const {
|
|
10765
|
-
|
|
10766
|
-
} = document;
|
|
10813
|
+
const { documentElement } = document;
|
|
10814
|
+
|
|
10767
10815
|
const leftWithoutScroll = x - scrollContainer.scrollLeft;
|
|
10768
10816
|
const topWithoutScroll = y - scrollContainer.scrollTop;
|
|
10769
10817
|
let baseX;
|
|
@@ -10795,6 +10843,7 @@ const getDebugMarkerPos = (x, y, scrollContainer, side = null) => {
|
|
|
10795
10843
|
// For obstacles and other markers: use converted coordinates directly
|
|
10796
10844
|
return [baseX, baseY];
|
|
10797
10845
|
};
|
|
10846
|
+
|
|
10798
10847
|
const createMergedMarkers = (markersToCreate, scrollContainer) => {
|
|
10799
10848
|
const mergedMarkers = [];
|
|
10800
10849
|
const positionMap = new Map();
|
|
@@ -10802,6 +10851,7 @@ const createMergedMarkers = (markersToCreate, scrollContainer) => {
|
|
|
10802
10851
|
// Group markers by position and side
|
|
10803
10852
|
for (const marker of markersToCreate) {
|
|
10804
10853
|
const key = `${marker.x},${marker.y},${marker.side}`;
|
|
10854
|
+
|
|
10805
10855
|
if (!positionMap.has(key)) {
|
|
10806
10856
|
positionMap.set(key, []);
|
|
10807
10857
|
}
|
|
@@ -10819,36 +10869,43 @@ const createMergedMarkers = (markersToCreate, scrollContainer) => {
|
|
|
10819
10869
|
} else {
|
|
10820
10870
|
// Multiple markers at same position - merge labels
|
|
10821
10871
|
const firstMarker = markers[0];
|
|
10822
|
-
const combinedName = markers.map(m => m.name).join(" + ");
|
|
10872
|
+
const combinedName = markers.map((m) => m.name).join(" + ");
|
|
10823
10873
|
|
|
10824
10874
|
// Use the first marker's color, or mix colors if needed
|
|
10825
|
-
const domMarker = createDebugMarker(
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10875
|
+
const domMarker = createDebugMarker(
|
|
10876
|
+
{
|
|
10877
|
+
...firstMarker,
|
|
10878
|
+
name: combinedName,
|
|
10879
|
+
},
|
|
10880
|
+
scrollContainer,
|
|
10881
|
+
);
|
|
10882
|
+
domMarker.type = markers.some((m) => m.name.includes("Bound"))
|
|
10883
|
+
? "constraint"
|
|
10884
|
+
: "visible";
|
|
10830
10885
|
mergedMarkers.push(domMarker);
|
|
10831
10886
|
}
|
|
10832
10887
|
}
|
|
10888
|
+
|
|
10833
10889
|
return mergedMarkers;
|
|
10834
10890
|
};
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
x,
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
side
|
|
10841
|
-
}, scrollContainer) => {
|
|
10891
|
+
|
|
10892
|
+
const createDebugMarker = (
|
|
10893
|
+
{ name, x, y, color = "255 0 0", side },
|
|
10894
|
+
scrollContainer,
|
|
10895
|
+
) => {
|
|
10842
10896
|
// Convert coordinates from document-relative to viewport
|
|
10843
10897
|
const [viewportX, viewportY] = getDebugMarkerPos(x, y, scrollContainer, side);
|
|
10898
|
+
|
|
10844
10899
|
const marker = document.createElement("div");
|
|
10845
10900
|
marker.className = `navi_debug_marker`;
|
|
10846
10901
|
marker.setAttribute(`data-${side}`, "");
|
|
10847
10902
|
// Set the color as a CSS custom property
|
|
10848
10903
|
marker.style.setProperty("--marker-color", `rgb(${color})`);
|
|
10849
10904
|
// Position markers exactly at the boundary coordinates
|
|
10850
|
-
marker.style.left =
|
|
10851
|
-
|
|
10905
|
+
marker.style.left =
|
|
10906
|
+
side === "right" ? `${viewportX - MARKER_SIZE}px` : `${viewportX}px`;
|
|
10907
|
+
marker.style.top =
|
|
10908
|
+
side === "bottom" ? `${viewportY - MARKER_SIZE}px` : `${viewportY}px`;
|
|
10852
10909
|
marker.title = name;
|
|
10853
10910
|
|
|
10854
10911
|
// Add label
|
|
@@ -10856,6 +10913,7 @@ const createDebugMarker = ({
|
|
|
10856
10913
|
label.className = `navi_debug_marker_label`;
|
|
10857
10914
|
label.textContent = name;
|
|
10858
10915
|
marker.appendChild(label);
|
|
10916
|
+
|
|
10859
10917
|
const container = getMarkersContainer();
|
|
10860
10918
|
container.appendChild(marker);
|
|
10861
10919
|
return marker;
|
|
@@ -10865,7 +10923,13 @@ const createObstacleMarker = (obstacleObj, scrollContainer) => {
|
|
|
10865
10923
|
const height = obstacleObj.bounds.bottom - obstacleObj.bounds.top;
|
|
10866
10924
|
|
|
10867
10925
|
// Convert document-relative coordinates to viewport coordinates
|
|
10868
|
-
const [x, y] = getDebugMarkerPos(
|
|
10926
|
+
const [x, y] = getDebugMarkerPos(
|
|
10927
|
+
obstacleObj.bounds.left,
|
|
10928
|
+
obstacleObj.bounds.top,
|
|
10929
|
+
scrollContainer,
|
|
10930
|
+
"obstacle",
|
|
10931
|
+
);
|
|
10932
|
+
|
|
10869
10933
|
const marker = document.createElement("div");
|
|
10870
10934
|
marker.className = "navi_obstacle_marker";
|
|
10871
10935
|
marker.style.left = `${x}px`;
|
|
@@ -10879,10 +10943,12 @@ const createObstacleMarker = (obstacleObj, scrollContainer) => {
|
|
|
10879
10943
|
label.className = "navi_obstacle_marker_label";
|
|
10880
10944
|
label.textContent = obstacleObj.name;
|
|
10881
10945
|
marker.appendChild(label);
|
|
10946
|
+
|
|
10882
10947
|
const container = getMarkersContainer();
|
|
10883
10948
|
container.appendChild(marker);
|
|
10884
10949
|
return marker;
|
|
10885
10950
|
};
|
|
10951
|
+
|
|
10886
10952
|
const createElementMarker = ({
|
|
10887
10953
|
left,
|
|
10888
10954
|
top,
|
|
@@ -10890,12 +10956,13 @@ const createElementMarker = ({
|
|
|
10890
10956
|
bottom,
|
|
10891
10957
|
scrollContainer,
|
|
10892
10958
|
label = "Element",
|
|
10893
|
-
color = "0, 200, 0" // Default green color
|
|
10959
|
+
color = "0, 200, 0", // Default green color
|
|
10894
10960
|
}) => {
|
|
10895
10961
|
const width = right - left;
|
|
10896
10962
|
const height = bottom - top;
|
|
10897
10963
|
// Convert document-relative coordinates to viewport coordinates
|
|
10898
10964
|
const [x, y] = getDebugMarkerPos(left, top, scrollContainer, "element");
|
|
10965
|
+
|
|
10899
10966
|
const marker = document.createElement("div");
|
|
10900
10967
|
marker.className = "navi_element_marker";
|
|
10901
10968
|
marker.style.left = `${x}px`;
|
|
@@ -10913,21 +10980,24 @@ const createElementMarker = ({
|
|
|
10913
10980
|
labelEl.className = "navi_element_marker_label";
|
|
10914
10981
|
labelEl.textContent = label;
|
|
10915
10982
|
marker.appendChild(labelEl);
|
|
10983
|
+
|
|
10916
10984
|
const container = getMarkersContainer();
|
|
10917
10985
|
container.appendChild(marker);
|
|
10918
10986
|
return marker;
|
|
10919
10987
|
};
|
|
10988
|
+
|
|
10920
10989
|
const createReferenceElementMarker = ({
|
|
10921
10990
|
left,
|
|
10922
10991
|
top,
|
|
10923
10992
|
right,
|
|
10924
10993
|
bottom,
|
|
10925
|
-
scrollContainer
|
|
10994
|
+
scrollContainer,
|
|
10926
10995
|
}) => {
|
|
10927
10996
|
const width = right - left;
|
|
10928
10997
|
const height = bottom - top;
|
|
10929
10998
|
// Convert document-relative coordinates to viewport coordinates
|
|
10930
10999
|
const [x, y] = getDebugMarkerPos(left, top, scrollContainer, "reference");
|
|
11000
|
+
|
|
10931
11001
|
const marker = document.createElement("div");
|
|
10932
11002
|
marker.className = "navi_reference_element_marker";
|
|
10933
11003
|
marker.style.left = `${x}px`;
|
|
@@ -10941,6 +11011,7 @@ const createReferenceElementMarker = ({
|
|
|
10941
11011
|
label.className = "navi_reference_element_marker_label";
|
|
10942
11012
|
label.textContent = "Reference Element";
|
|
10943
11013
|
marker.appendChild(label);
|
|
11014
|
+
|
|
10944
11015
|
const container = getMarkersContainer();
|
|
10945
11016
|
container.appendChild(marker);
|
|
10946
11017
|
return marker;
|
|
@@ -11847,39 +11918,8 @@ const createStickyFrontierOnAxis = (
|
|
|
11847
11918
|
return matchingStickyFrontiers;
|
|
11848
11919
|
};
|
|
11849
11920
|
|
|
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
|
-
*/
|
|
11921
|
+
installImportMetaCssBuild(import.meta);
|
|
11922
|
+
|
|
11883
11923
|
const dragStyleController = createStyleController("drag_to_move");
|
|
11884
11924
|
|
|
11885
11925
|
// How long the copy takes to leave the screen, and to come back. Written into the
|
|
@@ -11888,7 +11928,8 @@ const dragStyleController = createStyleController("drag_to_move");
|
|
|
11888
11928
|
const TOSS_DURATION_MS = 320;
|
|
11889
11929
|
// Far enough to be off any screen, in the direction the hand was going.
|
|
11890
11930
|
const TOSS_DISTANCE = 900;
|
|
11891
|
-
|
|
11931
|
+
|
|
11932
|
+
const css$1 = /* css */ `.navi_drop_hint {
|
|
11892
11933
|
inset: auto;
|
|
11893
11934
|
top: var(--drop-hint-y);
|
|
11894
11935
|
left: calc(var(--drop-target-left) + var(--drop-hint-margin-x, 0px));
|
|
@@ -12052,7 +12093,8 @@ import.meta.css = [css$1, "@jsenv/dom/src/interaction/drag/drag_to.js"];
|
|
|
12052
12093
|
// stays a descendant of whatever it is anchored in (a callout next to a button,
|
|
12053
12094
|
// in the card that carries both), and the press bubbles through the surface as
|
|
12054
12095
|
// if it had landed on it.
|
|
12055
|
-
const DRAG_IGNORED_SELECTOR =
|
|
12096
|
+
const DRAG_IGNORED_SELECTOR =
|
|
12097
|
+
'[data-drag-ignore],[data-self-interactions~="drag"],[data-self-interactions~="*"],[popover],dialog';
|
|
12056
12098
|
|
|
12057
12099
|
/**
|
|
12058
12100
|
* Starts a drag-to-reorder interaction on a list item.
|
|
@@ -12213,13 +12255,12 @@ const createDragToMoveGestureController = ({
|
|
|
12213
12255
|
cancelAnimationEasing = "ease-out",
|
|
12214
12256
|
...options
|
|
12215
12257
|
} = {}) => {
|
|
12216
|
-
const initGrabToMoveElement = (
|
|
12217
|
-
|
|
12218
|
-
referenceElement,
|
|
12219
|
-
|
|
12220
|
-
convertScrollablePosition
|
|
12221
|
-
}) => {
|
|
12258
|
+
const initGrabToMoveElement = (
|
|
12259
|
+
dragGesture,
|
|
12260
|
+
{ element, referenceElement, elementToMove, convertScrollablePosition },
|
|
12261
|
+
) => {
|
|
12222
12262
|
const scrollContainer = dragGesture.gestureInfo.scrollContainer;
|
|
12263
|
+
|
|
12223
12264
|
const direction = dragGesture.gestureInfo.direction;
|
|
12224
12265
|
// elementImpacted is either an externally provided elementToMove (e.g. a drag clone)
|
|
12225
12266
|
const elementImpacted = elementToMove || element;
|
|
@@ -12227,9 +12268,13 @@ const createDragToMoveGestureController = ({
|
|
|
12227
12268
|
// (e.g. a drag clone passed by the caller) or the element itself.
|
|
12228
12269
|
// Capture any pre-existing translate so we can accumulate on top of it
|
|
12229
12270
|
// rather than resetting it to zero on the first drag event.
|
|
12230
|
-
const transformAtGrab = dragStyleController.getUnderlyingValue(
|
|
12271
|
+
const transformAtGrab = dragStyleController.getUnderlyingValue(
|
|
12272
|
+
elementImpacted,
|
|
12273
|
+
"transform",
|
|
12274
|
+
);
|
|
12231
12275
|
const translateXAtGrab = transformAtGrab.translateX;
|
|
12232
12276
|
const translateYAtGrab = transformAtGrab.translateY;
|
|
12277
|
+
|
|
12233
12278
|
const cancelPosition = () => {
|
|
12234
12279
|
dragStyleController.clear(elementImpacted);
|
|
12235
12280
|
};
|
|
@@ -12239,7 +12284,7 @@ const createDragToMoveGestureController = ({
|
|
|
12239
12284
|
// animation has only to bridge the two.
|
|
12240
12285
|
const cancelPositionAnimated = ({
|
|
12241
12286
|
duration = cancelAnimationDuration,
|
|
12242
|
-
easing = cancelAnimationEasing
|
|
12287
|
+
easing = cancelAnimationEasing,
|
|
12243
12288
|
} = {}) => {
|
|
12244
12289
|
const transformWhileHeld = getComputedStyle(elementImpacted).transform;
|
|
12245
12290
|
cancelPosition();
|
|
@@ -12249,14 +12294,10 @@ const createDragToMoveGestureController = ({
|
|
|
12249
12294
|
}
|
|
12250
12295
|
// No fill: the element already sits at its resting transform, the
|
|
12251
12296
|
// animation only replays the way back to it.
|
|
12252
|
-
return elementImpacted.animate(
|
|
12253
|
-
transform: transformWhileHeld
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
}], {
|
|
12257
|
-
duration,
|
|
12258
|
-
easing
|
|
12259
|
-
});
|
|
12297
|
+
return elementImpacted.animate(
|
|
12298
|
+
[{ transform: transformWhileHeld }, { transform: transformAtRest }],
|
|
12299
|
+
{ duration, easing },
|
|
12300
|
+
);
|
|
12260
12301
|
};
|
|
12261
12302
|
const commitPosition = () => {
|
|
12262
12303
|
dragStyleController.commit(elementImpacted);
|
|
@@ -12264,6 +12305,7 @@ const createDragToMoveGestureController = ({
|
|
|
12264
12305
|
dragGesture.gestureInfo.cancelPosition = cancelPosition;
|
|
12265
12306
|
dragGesture.gestureInfo.cancelPositionAnimated = cancelPositionAnimated;
|
|
12266
12307
|
dragGesture.gestureInfo.commitPosition = commitPosition;
|
|
12308
|
+
|
|
12267
12309
|
dragGesture.addReleaseCallback(() => {
|
|
12268
12310
|
if (releasePositionEffect === "cancel") {
|
|
12269
12311
|
cancelPosition();
|
|
@@ -12274,6 +12316,7 @@ const createDragToMoveGestureController = ({
|
|
|
12274
12316
|
}
|
|
12275
12317
|
// "manual": caller handles cleanup, do nothing.
|
|
12276
12318
|
});
|
|
12319
|
+
|
|
12277
12320
|
let elementWidth;
|
|
12278
12321
|
let elementHeight;
|
|
12279
12322
|
{
|
|
@@ -12285,6 +12328,7 @@ const createDragToMoveGestureController = ({
|
|
|
12285
12328
|
updateElementDimension();
|
|
12286
12329
|
dragGesture.addBeforeDragCallback(updateElementDimension);
|
|
12287
12330
|
}
|
|
12331
|
+
|
|
12288
12332
|
let scrollArea;
|
|
12289
12333
|
{
|
|
12290
12334
|
// Snapshot at grab time so that DOM mutations during dragging
|
|
@@ -12293,9 +12337,10 @@ const createDragToMoveGestureController = ({
|
|
|
12293
12337
|
left: 0,
|
|
12294
12338
|
top: 0,
|
|
12295
12339
|
right: scrollContainer.scrollWidth,
|
|
12296
|
-
bottom: scrollContainer.scrollHeight
|
|
12340
|
+
bottom: scrollContainer.scrollHeight,
|
|
12297
12341
|
};
|
|
12298
12342
|
}
|
|
12343
|
+
|
|
12299
12344
|
let scrollport;
|
|
12300
12345
|
let autoScrollArea;
|
|
12301
12346
|
{
|
|
@@ -12306,11 +12351,14 @@ const createDragToMoveGestureController = ({
|
|
|
12306
12351
|
scrollport = getScrollport(scrollBox, scrollContainer);
|
|
12307
12352
|
autoScrollArea = scrollport;
|
|
12308
12353
|
if (stickyFrontiers) {
|
|
12309
|
-
autoScrollArea = applyStickyFrontiersToAutoScrollArea(
|
|
12310
|
-
|
|
12311
|
-
|
|
12312
|
-
|
|
12313
|
-
|
|
12354
|
+
autoScrollArea = applyStickyFrontiersToAutoScrollArea(
|
|
12355
|
+
autoScrollArea,
|
|
12356
|
+
{
|
|
12357
|
+
scrollContainer,
|
|
12358
|
+
direction,
|
|
12359
|
+
// dragGestureName,
|
|
12360
|
+
},
|
|
12361
|
+
);
|
|
12314
12362
|
}
|
|
12315
12363
|
if (autoScrollAreaPadding > 0) {
|
|
12316
12364
|
autoScrollArea = {
|
|
@@ -12321,7 +12369,7 @@ const createDragToMoveGestureController = ({
|
|
|
12321
12369
|
left: autoScrollArea.left + autoScrollAreaPadding,
|
|
12322
12370
|
top: autoScrollArea.top + autoScrollAreaPadding,
|
|
12323
12371
|
right: autoScrollArea.right - autoScrollAreaPadding,
|
|
12324
|
-
bottom: autoScrollArea.bottom - autoScrollAreaPadding
|
|
12372
|
+
bottom: autoScrollArea.bottom - autoScrollAreaPadding,
|
|
12325
12373
|
};
|
|
12326
12374
|
}
|
|
12327
12375
|
};
|
|
@@ -12344,77 +12392,97 @@ const createDragToMoveGestureController = ({
|
|
|
12344
12392
|
obstacleAttributeName,
|
|
12345
12393
|
showConstraintFeedbackLine,
|
|
12346
12394
|
showDebugMarkers,
|
|
12347
|
-
referenceElement
|
|
12395
|
+
referenceElement,
|
|
12348
12396
|
});
|
|
12349
|
-
dragGesture.addBeforeDragCallback(
|
|
12350
|
-
dragEvent
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
} =
|
|
12397
|
+
dragGesture.addBeforeDragCallback(
|
|
12398
|
+
(layoutRequested, currentLayout, limitLayout, { dragEvent }) => {
|
|
12399
|
+
dragConstraints.applyConstraints(
|
|
12400
|
+
layoutRequested,
|
|
12401
|
+
currentLayout,
|
|
12402
|
+
limitLayout,
|
|
12403
|
+
{
|
|
12404
|
+
elementWidth,
|
|
12405
|
+
elementHeight,
|
|
12406
|
+
scrollArea,
|
|
12407
|
+
scrollport,
|
|
12408
|
+
hasCrossedScrollportLeftOnce,
|
|
12409
|
+
hasCrossedScrollportTopOnce,
|
|
12410
|
+
autoScrollArea,
|
|
12411
|
+
dragEvent,
|
|
12412
|
+
},
|
|
12413
|
+
);
|
|
12414
|
+
},
|
|
12415
|
+
);
|
|
12416
|
+
|
|
12417
|
+
const dragToMove = (gestureInfo) => {
|
|
12418
|
+
const { isGoingDown, isGoingUp, isGoingLeft, isGoingRight, layout } =
|
|
12419
|
+
gestureInfo;
|
|
12371
12420
|
const left = layout.left;
|
|
12372
12421
|
const top = layout.top;
|
|
12373
12422
|
const right = left + elementWidth;
|
|
12374
12423
|
const bottom = top + elementHeight;
|
|
12424
|
+
|
|
12375
12425
|
{
|
|
12376
|
-
hasCrossedScrollportLeftOnce =
|
|
12377
|
-
|
|
12378
|
-
|
|
12426
|
+
hasCrossedScrollportLeftOnce =
|
|
12427
|
+
hasCrossedScrollportLeftOnce || left < scrollport.left;
|
|
12428
|
+
hasCrossedScrollportTopOnce =
|
|
12429
|
+
hasCrossedScrollportTopOnce || top < scrollport.top;
|
|
12430
|
+
|
|
12431
|
+
const getScrollMove = (axis) => {
|
|
12379
12432
|
const isGoingPositive = axis === "x" ? isGoingRight : isGoingDown;
|
|
12380
12433
|
if (isGoingPositive) {
|
|
12381
12434
|
const elementEnd = axis === "x" ? right : bottom;
|
|
12382
|
-
const autoScrollAreaEnd =
|
|
12435
|
+
const autoScrollAreaEnd =
|
|
12436
|
+
axis === "x" ? autoScrollArea.right : autoScrollArea.bottom;
|
|
12437
|
+
|
|
12383
12438
|
if (elementEnd <= autoScrollAreaEnd) {
|
|
12384
12439
|
return 0;
|
|
12385
12440
|
}
|
|
12386
12441
|
const scrollAmountNeeded = elementEnd - autoScrollAreaEnd;
|
|
12387
12442
|
return scrollAmountNeeded;
|
|
12388
12443
|
}
|
|
12444
|
+
|
|
12389
12445
|
const isGoingNegative = axis === "x" ? isGoingLeft : isGoingUp;
|
|
12390
12446
|
if (!isGoingNegative) {
|
|
12391
12447
|
return 0;
|
|
12392
12448
|
}
|
|
12449
|
+
|
|
12393
12450
|
const referenceOrEl = referenceElement || element;
|
|
12394
|
-
const canAutoScrollNegative =
|
|
12451
|
+
const canAutoScrollNegative =
|
|
12452
|
+
axis === "x"
|
|
12453
|
+
? !referenceOrEl.hasAttribute("data-sticky-left") ||
|
|
12454
|
+
hasCrossedScrollportLeftOnce
|
|
12455
|
+
: !referenceOrEl.hasAttribute("data-sticky-top") ||
|
|
12456
|
+
hasCrossedScrollportTopOnce;
|
|
12395
12457
|
if (!canAutoScrollNegative) {
|
|
12396
12458
|
return 0;
|
|
12397
12459
|
}
|
|
12460
|
+
|
|
12398
12461
|
const elementStart = axis === "x" ? left : top;
|
|
12399
|
-
const autoScrollAreaStart =
|
|
12462
|
+
const autoScrollAreaStart =
|
|
12463
|
+
axis === "x" ? autoScrollArea.left : autoScrollArea.top;
|
|
12400
12464
|
if (elementStart >= autoScrollAreaStart) {
|
|
12401
12465
|
return 0;
|
|
12402
12466
|
}
|
|
12467
|
+
|
|
12403
12468
|
const scrollAmountNeeded = autoScrollAreaStart - elementStart;
|
|
12404
12469
|
return -scrollAmountNeeded;
|
|
12405
12470
|
};
|
|
12471
|
+
|
|
12406
12472
|
let scrollLeftTarget;
|
|
12407
12473
|
let scrollTopTarget;
|
|
12408
12474
|
if (direction.x) {
|
|
12409
12475
|
const containerScrollLeftMove = getScrollMove("x");
|
|
12410
12476
|
if (containerScrollLeftMove) {
|
|
12411
|
-
scrollLeftTarget =
|
|
12477
|
+
scrollLeftTarget =
|
|
12478
|
+
scrollContainer.scrollLeft + containerScrollLeftMove;
|
|
12412
12479
|
}
|
|
12413
12480
|
}
|
|
12414
12481
|
if (direction.y) {
|
|
12415
12482
|
const containerScrollTopMove = getScrollMove("y");
|
|
12416
12483
|
if (containerScrollTopMove) {
|
|
12417
|
-
scrollTopTarget =
|
|
12484
|
+
scrollTopTarget =
|
|
12485
|
+
scrollContainer.scrollTop + containerScrollTopMove;
|
|
12418
12486
|
}
|
|
12419
12487
|
}
|
|
12420
12488
|
// now we know what to do, do it
|
|
@@ -12425,12 +12493,13 @@ const createDragToMoveGestureController = ({
|
|
|
12425
12493
|
scrollContainer.scrollTop = scrollTopTarget;
|
|
12426
12494
|
}
|
|
12427
12495
|
}
|
|
12496
|
+
|
|
12428
12497
|
{
|
|
12429
|
-
const {
|
|
12498
|
+
const { scrollableLeft, scrollableTop } = layout;
|
|
12499
|
+
const [positionedLeft, positionedTop] = convertScrollablePosition(
|
|
12430
12500
|
scrollableLeft,
|
|
12431
|
-
scrollableTop
|
|
12432
|
-
|
|
12433
|
-
const [positionedLeft, positionedTop] = convertScrollablePosition(scrollableLeft, scrollableTop);
|
|
12501
|
+
scrollableTop,
|
|
12502
|
+
);
|
|
12434
12503
|
// Build the transform to apply, preserving any transforms that were
|
|
12435
12504
|
// already on the element before the grab (e.g. rotate from another
|
|
12436
12505
|
// controller), and accumulating from the pre-grab translate baseline.
|
|
@@ -12442,32 +12511,33 @@ const createDragToMoveGestureController = ({
|
|
|
12442
12511
|
// the distance covered. Dragging moves things on screen, so its translate
|
|
12443
12512
|
// has to come first, whatever else the element carries. The spread still
|
|
12444
12513
|
// 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
|
-
};
|
|
12514
|
+
const transform = { translateX: 0, translateY: 0, ...transformAtGrab };
|
|
12450
12515
|
if (direction.x) {
|
|
12451
12516
|
const leftTarget = positionedLeft;
|
|
12452
12517
|
const leftAtGrab = dragGesture.gestureInfo.leftAtGrab;
|
|
12453
12518
|
const leftDelta = leftTarget - leftAtGrab;
|
|
12454
|
-
const translateX = translateXAtGrab
|
|
12519
|
+
const translateX = translateXAtGrab
|
|
12520
|
+
? translateXAtGrab + leftDelta
|
|
12521
|
+
: leftDelta;
|
|
12455
12522
|
transform.translateX = translateX;
|
|
12456
12523
|
}
|
|
12457
12524
|
if (direction.y) {
|
|
12458
12525
|
const topTarget = positionedTop;
|
|
12459
12526
|
const topAtGrab = dragGesture.gestureInfo.topAtGrab;
|
|
12460
12527
|
const topDelta = topTarget - topAtGrab;
|
|
12461
|
-
const translateY = translateYAtGrab
|
|
12528
|
+
const translateY = translateYAtGrab
|
|
12529
|
+
? translateYAtGrab + topDelta
|
|
12530
|
+
: topDelta;
|
|
12462
12531
|
transform.translateY = translateY;
|
|
12463
12532
|
}
|
|
12464
12533
|
dragStyleController.set(elementImpacted, {
|
|
12465
|
-
transform
|
|
12534
|
+
transform,
|
|
12466
12535
|
});
|
|
12467
12536
|
}
|
|
12468
12537
|
};
|
|
12469
12538
|
dragGesture.addDragCallback(dragToMove);
|
|
12470
12539
|
};
|
|
12540
|
+
|
|
12471
12541
|
const dragGestureController = createDragGestureController(options);
|
|
12472
12542
|
const grab = dragGestureController.grab;
|
|
12473
12543
|
dragGestureController.grab = ({
|
|
@@ -12478,23 +12548,28 @@ const createDragToMoveGestureController = ({
|
|
|
12478
12548
|
...rest
|
|
12479
12549
|
} = {}) => {
|
|
12480
12550
|
const scrollContainer = getScrollContainer(referenceElement || element);
|
|
12481
|
-
const [
|
|
12551
|
+
const [
|
|
12552
|
+
elementScrollableLeft,
|
|
12553
|
+
elementScrollableTop,
|
|
12554
|
+
convertScrollablePosition,
|
|
12555
|
+
] = createDragElementPositioner(element, referenceElement, elementToMove);
|
|
12482
12556
|
const dragGesture = grab({
|
|
12483
12557
|
element,
|
|
12484
12558
|
scrollContainer,
|
|
12485
12559
|
layoutScrollableLeft: elementScrollableLeft,
|
|
12486
12560
|
layoutScrollableTop: elementScrollableTop,
|
|
12487
12561
|
event,
|
|
12488
|
-
...rest
|
|
12562
|
+
...rest,
|
|
12489
12563
|
});
|
|
12490
12564
|
initGrabToMoveElement(dragGesture, {
|
|
12491
12565
|
element,
|
|
12492
12566
|
referenceElement,
|
|
12493
12567
|
elementToMove,
|
|
12494
|
-
convertScrollablePosition
|
|
12568
|
+
convertScrollablePosition,
|
|
12495
12569
|
});
|
|
12496
12570
|
return dragGesture;
|
|
12497
12571
|
};
|
|
12572
|
+
|
|
12498
12573
|
return dragGestureController;
|
|
12499
12574
|
};
|
|
12500
12575
|
|
|
@@ -12547,10 +12622,11 @@ const createDragToMoveGestureController = ({
|
|
|
12547
12622
|
* The gesture holds its copy until what `onReorder` returns settles, so returning
|
|
12548
12623
|
* the transition is what makes the landing continuous.
|
|
12549
12624
|
*/
|
|
12550
|
-
const startDragTo = (
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12625
|
+
const startDragTo = (
|
|
12626
|
+
event,
|
|
12627
|
+
effects,
|
|
12628
|
+
{ draggedElement = event.currentTarget, ...options } = {},
|
|
12629
|
+
) => {
|
|
12554
12630
|
// An area that opted out of dragging (a text one wants to select, a control that
|
|
12555
12631
|
// owns the gesture, a callout): the press there is none of our business.
|
|
12556
12632
|
if (isPressIgnored(event.target, draggedElement)) {
|
|
@@ -12569,13 +12645,10 @@ const startDragTo = (event, effects, {
|
|
|
12569
12645
|
canReorder,
|
|
12570
12646
|
canToss,
|
|
12571
12647
|
canLand,
|
|
12572
|
-
...options
|
|
12648
|
+
...options,
|
|
12573
12649
|
});
|
|
12574
12650
|
}
|
|
12575
|
-
return startDragToMoveElement(event, {
|
|
12576
|
-
draggedElement,
|
|
12577
|
-
...options
|
|
12578
|
-
});
|
|
12651
|
+
return startDragToMoveElement(event, { draggedElement, ...options });
|
|
12579
12652
|
};
|
|
12580
12653
|
|
|
12581
12654
|
/**
|
|
@@ -12584,70 +12657,71 @@ const startDragTo = (event, effects, {
|
|
|
12584
12657
|
* No copy, unlike the two others: what is being moved is the thing and not a
|
|
12585
12658
|
* stand-in for it, so there is nothing to put back and nothing to reveal.
|
|
12586
12659
|
*/
|
|
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
|
-
}, {
|
|
12660
|
+
const startDragToMoveElement = (
|
|
12661
|
+
event,
|
|
12662
|
+
{
|
|
12663
|
+
draggedElement,
|
|
12664
|
+
onMove,
|
|
12637
12665
|
threshold,
|
|
12638
12666
|
longPress,
|
|
12639
12667
|
longPressDelay,
|
|
12640
12668
|
longPressSlop,
|
|
12641
12669
|
onPressStart,
|
|
12642
12670
|
onPressCancel,
|
|
12643
|
-
onPress
|
|
12644
|
-
|
|
12671
|
+
onPress,
|
|
12672
|
+
...options
|
|
12673
|
+
},
|
|
12674
|
+
) => {
|
|
12675
|
+
event.preventDefault();
|
|
12676
|
+
return dragAfterIntent(
|
|
12677
|
+
event,
|
|
12678
|
+
() => {
|
|
12679
|
+
const gestureController = createDragToMoveGestureController({
|
|
12680
|
+
releasePositionEffect: "manual",
|
|
12681
|
+
...options,
|
|
12682
|
+
});
|
|
12683
|
+
const dragGesture = gestureController.grabViaPointer(event, {
|
|
12684
|
+
element: draggedElement,
|
|
12685
|
+
});
|
|
12686
|
+
if (!dragGesture) {
|
|
12687
|
+
return null;
|
|
12688
|
+
}
|
|
12689
|
+
dragGesture.addReleaseCallback(async (gestureInfo) => {
|
|
12690
|
+
const { xDelta, yDelta } = gestureInfo.layout;
|
|
12691
|
+
if (!xDelta && !yDelta) {
|
|
12692
|
+
// Picked up and put back down: nothing moved, so nobody is told.
|
|
12693
|
+
gestureInfo.cancelPosition();
|
|
12694
|
+
return;
|
|
12695
|
+
}
|
|
12696
|
+
// Committed before the answer rather than after: the hand let go of it
|
|
12697
|
+
// there, and a thing that snaps home while a request is in flight says the
|
|
12698
|
+
// gesture was not understood.
|
|
12699
|
+
gestureInfo.commitPosition();
|
|
12700
|
+
try {
|
|
12701
|
+
await onMove?.({ gestureInfo, x: xDelta, y: yDelta });
|
|
12702
|
+
} catch {
|
|
12703
|
+
gestureInfo.cancelPositionAnimated();
|
|
12704
|
+
}
|
|
12705
|
+
});
|
|
12706
|
+
return dragGesture;
|
|
12707
|
+
},
|
|
12708
|
+
{
|
|
12709
|
+
threshold,
|
|
12710
|
+
longPress,
|
|
12711
|
+
longPressDelay,
|
|
12712
|
+
longPressSlop,
|
|
12713
|
+
onPressStart,
|
|
12714
|
+
onPressCancel,
|
|
12715
|
+
onPress,
|
|
12716
|
+
},
|
|
12717
|
+
);
|
|
12645
12718
|
};
|
|
12646
12719
|
|
|
12647
12720
|
// Far and fast, both at once: one without the other is moving the thing while
|
|
12648
12721
|
// hesitating, and nothing is thrown away on a hesitation — it comes back.
|
|
12649
12722
|
const TOSS_DISTANCE_TO_COMMIT = 110;
|
|
12650
12723
|
const TOSS_SPEED_TO_COMMIT = 0.45;
|
|
12724
|
+
|
|
12651
12725
|
const resolveDropMeaning = ({
|
|
12652
12726
|
gestureInfo,
|
|
12653
12727
|
hasDropTarget,
|
|
@@ -12655,7 +12729,7 @@ const resolveDropMeaning = ({
|
|
|
12655
12729
|
canToss,
|
|
12656
12730
|
canLand,
|
|
12657
12731
|
tossDistance = TOSS_DISTANCE_TO_COMMIT,
|
|
12658
|
-
tossSpeed = TOSS_SPEED_TO_COMMIT
|
|
12732
|
+
tossSpeed = TOSS_SPEED_TO_COMMIT,
|
|
12659
12733
|
}) => {
|
|
12660
12734
|
if (gestureInfo.cancelled) {
|
|
12661
12735
|
// Nobody let go of anything: the gesture was taken away mid-air (the
|
|
@@ -12664,10 +12738,7 @@ const resolveDropMeaning = ({
|
|
|
12664
12738
|
return "cancel";
|
|
12665
12739
|
}
|
|
12666
12740
|
if (canToss) {
|
|
12667
|
-
const {
|
|
12668
|
-
xDelta,
|
|
12669
|
-
yDelta
|
|
12670
|
-
} = gestureInfo.layout;
|
|
12741
|
+
const { xDelta, yDelta } = gestureInfo.layout;
|
|
12671
12742
|
const distance = Math.hypot(xDelta, yDelta);
|
|
12672
12743
|
if (distance > tossDistance && gestureInfo.velocity > tossSpeed) {
|
|
12673
12744
|
return "toss";
|
|
@@ -12692,47 +12763,44 @@ const resolveDropMeaning = ({
|
|
|
12692
12763
|
* for the opposite reason: the original stays until the answer says it is really
|
|
12693
12764
|
* gone.
|
|
12694
12765
|
*/
|
|
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
|
-
y: true
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
|
|
12766
|
+
const startDragToCarryCopy = (
|
|
12767
|
+
event,
|
|
12768
|
+
{
|
|
12769
|
+
draggedElement,
|
|
12770
|
+
canReorder,
|
|
12771
|
+
canToss,
|
|
12772
|
+
canLand,
|
|
12773
|
+
// Something that can be thrown away has to be able to LEAVE. The default of
|
|
12774
|
+
// the layer below keeps what is dragged inside its scroll area, which is right
|
|
12775
|
+
// for a reorder (a row belongs to its list) and makes a throw impossible — the
|
|
12776
|
+
// copy hits the edge of the list and no distance is ever covered, so no throw
|
|
12777
|
+
// ever happens and no sideways movement is even visible.
|
|
12778
|
+
// Destructured with the default here rather than written at the call below: a
|
|
12779
|
+
// caller passing `areaConstraint: undefined` (which is what saying nothing
|
|
12780
|
+
// through an options object looks like) would otherwise put the layer below
|
|
12781
|
+
// back on its own default and undo this.
|
|
12782
|
+
areaConstraint = canToss ? "none" : undefined,
|
|
12783
|
+
containerElement = draggedElement.parentElement,
|
|
12784
|
+
itemSelector,
|
|
12785
|
+
getItemId,
|
|
12786
|
+
onReorder,
|
|
12787
|
+
onLand,
|
|
12788
|
+
onToss,
|
|
12789
|
+
tossDistance,
|
|
12790
|
+
tossSpeed,
|
|
12791
|
+
// A list runs one way and reordering walks it; a board has places all around,
|
|
12792
|
+
// so something landing on one of them goes wherever the hand takes it.
|
|
12793
|
+
direction = canLand ? { x: true, y: true } : { x: false, y: true },
|
|
12794
|
+
threshold,
|
|
12795
|
+
longPress,
|
|
12796
|
+
longPressDelay,
|
|
12797
|
+
longPressSlop,
|
|
12798
|
+
onPressStart,
|
|
12799
|
+
onPressCancel,
|
|
12800
|
+
onPress,
|
|
12801
|
+
...options
|
|
12726
12802
|
},
|
|
12727
|
-
|
|
12728
|
-
longPress,
|
|
12729
|
-
longPressDelay,
|
|
12730
|
-
longPressSlop,
|
|
12731
|
-
onPressStart,
|
|
12732
|
-
onPressCancel,
|
|
12733
|
-
onPress,
|
|
12734
|
-
...options
|
|
12735
|
-
}) => {
|
|
12803
|
+
) => {
|
|
12736
12804
|
// An area that opted out of dragging (a text one wants to select, a control
|
|
12737
12805
|
// that owns the gesture, a callout): the press there is none of our business.
|
|
12738
12806
|
if (isPressIgnored(event.target, draggedElement)) {
|
|
@@ -12748,243 +12816,310 @@ const startDragToCarryCopy = (event, {
|
|
|
12748
12816
|
// had got to.
|
|
12749
12817
|
const startCarry = (pointerEvent, cloneWrapperCaught, onCarryStart) => {
|
|
12750
12818
|
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;
|
|
12819
|
+
return dragAfterIntent(
|
|
12820
|
+
pointerEvent,
|
|
12821
|
+
() => {
|
|
12822
|
+
// Here and nowhere else is where a press has turned into a carry — the
|
|
12823
|
+
// one moment both ways in (a finger held still, a mouse travelled)
|
|
12824
|
+
// agree on.
|
|
12825
|
+
onCarryStart?.();
|
|
12826
|
+
const cloneWrapper =
|
|
12827
|
+
cloneWrapperCaught || createDragClone(draggedElement, pointerEvent);
|
|
12828
|
+
if (cloneWrapperCaught) {
|
|
12829
|
+
liftDragClone(cloneWrapperCaught, pointerEvent);
|
|
12802
12830
|
}
|
|
12803
|
-
|
|
12804
|
-
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12831
|
+
draggedElement.setAttribute("navi-drag-clone-source", "");
|
|
12832
|
+
|
|
12833
|
+
const gestureController = createDragToMoveGestureController({
|
|
12834
|
+
direction,
|
|
12835
|
+
releasePositionEffect: "manual",
|
|
12836
|
+
areaConstraint,
|
|
12837
|
+
...options,
|
|
12838
|
+
});
|
|
12839
|
+
const dragGesture = gestureController.grabViaPointer(pointerEvent, {
|
|
12840
|
+
element: draggedElement,
|
|
12841
|
+
elementToMove: cloneWrapper,
|
|
12842
|
+
});
|
|
12843
|
+
// getDropTargetInfo uses gestureInfo.elementImpacted to compute the dragged rect.
|
|
12844
|
+
// Point it at the clone so drop detection tracks the clone's current position.
|
|
12845
|
+
dragGesture.gestureInfo.elementImpacted = cloneWrapper;
|
|
12846
|
+
|
|
12847
|
+
// No place to land, no hint: an element that can only be thrown away has
|
|
12848
|
+
// nowhere to be put. What the hint LOOKS like follows what a place is here —
|
|
12849
|
+
// a line in the gap between two items, or the place itself lit up.
|
|
12850
|
+
const dropHintEl = canLand
|
|
12851
|
+
? createDropSurface()
|
|
12852
|
+
: canReorder
|
|
12853
|
+
? createDropHint()
|
|
12854
|
+
: null;
|
|
12855
|
+
if (dropHintEl) {
|
|
12856
|
+
// In the container it draws into, which is where its own vars are set: the
|
|
12857
|
+
// shape of a drop hint is a property of the list or board it belongs to,
|
|
12858
|
+
// and reading it from there is inheritance rather than a hand-off.
|
|
12859
|
+
draggedElement.parentElement.appendChild(dropHintEl);
|
|
12819
12860
|
}
|
|
12820
|
-
|
|
12821
|
-
|
|
12822
|
-
|
|
12823
|
-
|
|
12824
|
-
|
|
12825
|
-
|
|
12826
|
-
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
12861
|
+
// The hint first, the clone second: that order is what stacks them in the
|
|
12862
|
+
// top layer. A copy taken back in hand is already up there, and the hint
|
|
12863
|
+
// just went above it — shown again it returns to the top, where what the
|
|
12864
|
+
// hand carries belongs.
|
|
12865
|
+
dropHintEl?.showPopover();
|
|
12866
|
+
if (cloneWrapperCaught) {
|
|
12867
|
+
cloneWrapper.hidePopover();
|
|
12868
|
+
}
|
|
12869
|
+
cloneWrapper.showPopover();
|
|
12870
|
+
|
|
12871
|
+
// currentBeforeElement: element before which the grabbed item will be inserted (null = end)
|
|
12872
|
+
// currentReleaseElement: the actual hovered drop target — used to snap the clone on release
|
|
12873
|
+
let currentBeforeElement;
|
|
12874
|
+
let currentReleaseElement;
|
|
12875
|
+
|
|
12876
|
+
const clearDropHintDOM = () => {
|
|
12877
|
+
if (!dropHintEl) {
|
|
12878
|
+
return;
|
|
12830
12879
|
}
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12880
|
+
dropHintEl.removeAttribute("data-drop-edge");
|
|
12881
|
+
dropHintEl.removeAttribute("data-drop-over");
|
|
12882
|
+
dropHintEl.style.removeProperty("--drop-target-top");
|
|
12883
|
+
dropHintEl.style.removeProperty("--drop-target-bottom");
|
|
12884
|
+
dropHintEl.style.removeProperty("--drop-target-left");
|
|
12885
|
+
dropHintEl.style.removeProperty("--drop-target-width");
|
|
12886
|
+
dropHintEl.style.removeProperty("--drop-target-height");
|
|
12887
|
+
};
|
|
12888
|
+
|
|
12889
|
+
const clearDropHint = () => {
|
|
12890
|
+
currentBeforeElement = undefined;
|
|
12891
|
+
currentReleaseElement = undefined;
|
|
12892
|
+
clearDropHintDOM();
|
|
12893
|
+
};
|
|
12894
|
+
|
|
12895
|
+
dragGesture.addDragCallback((gestureInfo) => {
|
|
12896
|
+
if (!dropHintEl) {
|
|
12897
|
+
return;
|
|
12834
12898
|
}
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
12848
|
-
|
|
12849
|
-
|
|
12850
|
-
|
|
12851
|
-
|
|
12899
|
+
const allItems = [];
|
|
12900
|
+
const items = [];
|
|
12901
|
+
for (const el of containerElement.querySelectorAll(itemSelector)) {
|
|
12902
|
+
// The copy in hand is a clone of an item, attributes included, and it
|
|
12903
|
+
// lives in this same parent: it matches the selector without being
|
|
12904
|
+
// an item. Excluded here rather than by stripping attributes off the
|
|
12905
|
+
// clone, because a custom itemSelector (a class, a tag) would still
|
|
12906
|
+
// match it.
|
|
12907
|
+
if (cloneWrapper.contains(el)) {
|
|
12908
|
+
continue;
|
|
12909
|
+
}
|
|
12910
|
+
allItems.push(el);
|
|
12911
|
+
if (el !== draggedElement) {
|
|
12912
|
+
items.push(el);
|
|
12913
|
+
}
|
|
12914
|
+
}
|
|
12915
|
+
|
|
12916
|
+
const dropTargetInfo = getDropTargetInfo(gestureInfo, items, {
|
|
12917
|
+
// The edges of a LIST: above the first row means the top of it, below
|
|
12918
|
+
// the last one means the end of it. A board has no such reading — away
|
|
12919
|
+
// from every place is away from every place.
|
|
12920
|
+
fallbackToEdge: !canLand,
|
|
12921
|
+
});
|
|
12922
|
+
gestureInfo.dropTargetInfo = dropTargetInfo || null;
|
|
12923
|
+
if (!dropTargetInfo) {
|
|
12924
|
+
clearDropHint();
|
|
12852
12925
|
return;
|
|
12853
12926
|
}
|
|
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
|
-
|
|
12927
|
+
if (canLand) {
|
|
12928
|
+
// The whole element is the target, so which of its edges the copy came
|
|
12929
|
+
// in by says nothing: there is no gap to be on one side of.
|
|
12930
|
+
const dropElement = dropTargetInfo.element;
|
|
12931
|
+
if (dropElement === currentReleaseElement) {
|
|
12932
|
+
return;
|
|
12933
|
+
}
|
|
12934
|
+
currentReleaseElement = dropElement;
|
|
12935
|
+
const dropRect = dropElement.getBoundingClientRect();
|
|
12936
|
+
dropHintEl.setAttribute("data-drop-over", "");
|
|
12937
|
+
dropHintEl.style.setProperty(
|
|
12938
|
+
"--drop-target-top",
|
|
12939
|
+
`${dropRect.top}px`,
|
|
12940
|
+
);
|
|
12941
|
+
dropHintEl.style.setProperty(
|
|
12942
|
+
"--drop-target-left",
|
|
12943
|
+
`${dropRect.left}px`,
|
|
12944
|
+
);
|
|
12945
|
+
dropHintEl.style.setProperty(
|
|
12946
|
+
"--drop-target-width",
|
|
12947
|
+
`${dropRect.width}px`,
|
|
12948
|
+
);
|
|
12949
|
+
dropHintEl.style.setProperty(
|
|
12950
|
+
"--drop-target-height",
|
|
12951
|
+
`${dropRect.height}px`,
|
|
12952
|
+
);
|
|
12953
|
+
return;
|
|
12954
|
+
}
|
|
12955
|
+
// Convert {element, edge} to a beforeElement using the items array
|
|
12956
|
+
// (not nextElementSibling, which breaks if non-item elements exist between items).
|
|
12957
|
+
// edge "start" → insert before the hovered element
|
|
12958
|
+
// edge "end" → insert before the next item (null = append at end)
|
|
12959
|
+
const edge = dropTargetInfo.elementSide.y;
|
|
12960
|
+
const hoveredIndex = items.indexOf(dropTargetInfo.element);
|
|
12961
|
+
const beforeElement =
|
|
12962
|
+
edge === "start"
|
|
12963
|
+
? dropTargetInfo.element
|
|
12964
|
+
: (items[hoveredIndex + 1] ?? null);
|
|
12965
|
+
// Detect no-op: result would leave the grabbed element in the same position.
|
|
12966
|
+
const elementIndex = allItems.indexOf(draggedElement);
|
|
12967
|
+
const elementNextItem = allItems[elementIndex + 1] ?? null;
|
|
12968
|
+
const isNoop = beforeElement === elementNextItem;
|
|
12969
|
+
if (isNoop) {
|
|
12970
|
+
clearDropHint();
|
|
12971
|
+
return;
|
|
12972
|
+
}
|
|
12973
|
+
// Early return if nothing changed.
|
|
12974
|
+
const releaseElement = dropTargetInfo.element;
|
|
12975
|
+
if (
|
|
12976
|
+
beforeElement === currentBeforeElement &&
|
|
12977
|
+
releaseElement === currentReleaseElement
|
|
12978
|
+
) {
|
|
12979
|
+
return;
|
|
12980
|
+
}
|
|
12981
|
+
currentBeforeElement = beforeElement;
|
|
12982
|
+
currentReleaseElement = releaseElement;
|
|
12983
|
+
// Update drop hint CSS vars.
|
|
12984
|
+
// beforeElement = null → insert at end (hint after last item)
|
|
12985
|
+
// beforeElement = X → insert before X (hint at top edge of X)
|
|
12986
|
+
const anchorEl = beforeElement || items[items.length - 1];
|
|
12987
|
+
const anchorEdge = beforeElement !== null ? "top" : "bottom";
|
|
12988
|
+
// Viewport coordinates, straight from the anchor row: the hint is fixed
|
|
12989
|
+
// in the page (see its CSS), so there is no container box to be relative
|
|
12990
|
+
// to and no scroll offset to add back.
|
|
12991
|
+
const anchorRect = anchorEl.getBoundingClientRect();
|
|
12992
|
+
dropHintEl.setAttribute("data-drop-edge", anchorEdge);
|
|
12993
|
+
dropHintEl.style.setProperty(
|
|
12994
|
+
"--drop-target-top",
|
|
12995
|
+
`${anchorRect.top}px`,
|
|
12996
|
+
);
|
|
12997
|
+
dropHintEl.style.setProperty(
|
|
12998
|
+
"--drop-target-bottom",
|
|
12999
|
+
`${anchorRect.bottom}px`,
|
|
13000
|
+
);
|
|
13001
|
+
dropHintEl.style.setProperty(
|
|
13002
|
+
"--drop-target-left",
|
|
13003
|
+
`${anchorRect.left}px`,
|
|
13004
|
+
);
|
|
13005
|
+
dropHintEl.style.setProperty(
|
|
13006
|
+
"--drop-target-width",
|
|
13007
|
+
`${anchorRect.width}px`,
|
|
13008
|
+
);
|
|
12917
13009
|
});
|
|
12918
13010
|
|
|
12919
|
-
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
const
|
|
12928
|
-
|
|
12929
|
-
|
|
12930
|
-
|
|
12931
|
-
|
|
12932
|
-
|
|
12933
|
-
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
|
|
12941
|
-
|
|
13011
|
+
dragGesture.addReleaseCallback(async (gestureInfo) => {
|
|
13012
|
+
clearDropHintDOM();
|
|
13013
|
+
dropHintEl?.remove();
|
|
13014
|
+
|
|
13015
|
+
// What THIS release means, from what the element said it can answer. A
|
|
13016
|
+
// throw is asked about first: it is the more insistent of the two, and a
|
|
13017
|
+
// hand that sent the thing across the screen has not asked for it to swap
|
|
13018
|
+
// places with whatever it happened to fly over.
|
|
13019
|
+
const hasDropTarget = canLand
|
|
13020
|
+
? currentReleaseElement !== undefined
|
|
13021
|
+
: currentBeforeElement !== undefined;
|
|
13022
|
+
const dropMeans = resolveDropMeaning({
|
|
13023
|
+
gestureInfo,
|
|
13024
|
+
hasDropTarget,
|
|
13025
|
+
canReorder,
|
|
13026
|
+
canToss,
|
|
13027
|
+
canLand,
|
|
13028
|
+
tossDistance,
|
|
13029
|
+
tossSpeed,
|
|
13030
|
+
});
|
|
13031
|
+
|
|
13032
|
+
// Let go of and still on the screen: from here until it is taken away
|
|
13033
|
+
// the copy can be taken back in hand (see letCopyBeCaught).
|
|
13034
|
+
const copyLetGoOf = letCopyBeCaught(
|
|
13035
|
+
cloneWrapper,
|
|
13036
|
+
(pointerDownEvent, whenCarried) =>
|
|
13037
|
+
startCarry(pointerDownEvent, cloneWrapper, whenCarried),
|
|
13038
|
+
);
|
|
13039
|
+
|
|
13040
|
+
// The copy stops where the hand left it, and the answer is given a way to
|
|
13041
|
+
// take it the rest of the way — synchronously, inside a view transition, so
|
|
13042
|
+
// it is captured where it lands rather than where it was let go of.
|
|
13043
|
+
const landCopyOn = async (targetElement, answer) => {
|
|
13044
|
+
const clone = cloneWrapper.firstElementChild;
|
|
13045
|
+
// Bake the current visual position (transform included) into the CSS vars
|
|
13046
|
+
// so the copy stays where the user released it when the transform goes.
|
|
13047
|
+
setCloneViewportRect(cloneWrapper, cloneWrapper);
|
|
13048
|
+
gestureInfo.cancelPosition();
|
|
13049
|
+
// Where the copy comes down is not always the thing it came down ON: a
|
|
13050
|
+
// place of a board can be larger than what stands on it, and the copy
|
|
13051
|
+
// has to keep its own size and land where the item will be. Said with
|
|
13052
|
+
// an element, because the caller has one — the piece already standing
|
|
13053
|
+
// there, the empty slot waiting.
|
|
13054
|
+
const syncCloneWithDropTarget = (
|
|
13055
|
+
landingElement = targetElement,
|
|
13056
|
+
) => {
|
|
13057
|
+
setCloneViewportRect(cloneWrapper, landingElement);
|
|
13058
|
+
// Removing this attr drops the CSS scale, so the browser captures the
|
|
13059
|
+
// copy at scale 1 as the "new" state.
|
|
13060
|
+
clone.removeAttribute("navi-drag-clone");
|
|
13061
|
+
};
|
|
13062
|
+
await answer(syncCloneWithDropTarget);
|
|
12942
13063
|
};
|
|
12943
|
-
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
13064
|
+
|
|
13065
|
+
if (dropMeans === "toss") {
|
|
13066
|
+
// Bake the position the hand left it at, so the flight starts from
|
|
13067
|
+
// there rather than from where the clone was declared.
|
|
13068
|
+
setCloneViewportRect(cloneWrapper, cloneWrapper);
|
|
13069
|
+
gestureInfo.cancelPosition();
|
|
13070
|
+
const gone = await tossCloneAway(cloneWrapper, gestureInfo, onToss);
|
|
13071
|
+
if (!gone) {
|
|
13072
|
+
// It still exists, so the screen has to say so: the copy comes back
|
|
13073
|
+
// over the original, and taking it away then reveals the row in
|
|
13074
|
+
// place.
|
|
13075
|
+
await settleCloneBack(cloneWrapper, draggedElement);
|
|
13076
|
+
}
|
|
13077
|
+
} else if (dropMeans === "land") {
|
|
13078
|
+
await landCopyOn(currentReleaseElement, (syncCloneWithDropTarget) =>
|
|
13079
|
+
onLand(
|
|
13080
|
+
getItemId(draggedElement),
|
|
13081
|
+
getItemId(currentReleaseElement),
|
|
13082
|
+
syncCloneWithDropTarget,
|
|
13083
|
+
),
|
|
13084
|
+
);
|
|
13085
|
+
} else if (dropMeans === "reorder") {
|
|
13086
|
+
await landCopyOn(currentReleaseElement, (syncCloneWithDropTarget) =>
|
|
13087
|
+
onReorder(
|
|
13088
|
+
getItemId(draggedElement),
|
|
13089
|
+
currentBeforeElement ? getItemId(currentBeforeElement) : null,
|
|
13090
|
+
syncCloneWithDropTarget,
|
|
13091
|
+
),
|
|
13092
|
+
);
|
|
12956
13093
|
}
|
|
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
|
-
});
|
|
13094
|
+
if (await copyLetGoOf.settled()) {
|
|
13095
|
+
// In a hand again: the copy, and the place kept for it, are the new
|
|
13096
|
+
// gesture's — this one has nothing left to take away.
|
|
13097
|
+
return;
|
|
13098
|
+
}
|
|
13099
|
+
draggedElement.removeAttribute("navi-drag-clone-source");
|
|
13100
|
+
cloneWrapper.remove();
|
|
13101
|
+
});
|
|
13102
|
+
|
|
13103
|
+
return dragGesture;
|
|
13104
|
+
},
|
|
13105
|
+
{
|
|
13106
|
+
threshold,
|
|
13107
|
+
// A copy caught on its way home is not an ambiguous press: the hand
|
|
13108
|
+
// reached for something moving, and the press was already matched
|
|
13109
|
+
// against the copy's own box before it got here. The wait a finger is
|
|
13110
|
+
// asked for elsewhere tells a scroll from a drag, and there is no scroll
|
|
13111
|
+
// to tell it from — the copy covers that spot from the top layer. Asked
|
|
13112
|
+
// for anyway it cannot even be answered: the wait is about as long as the
|
|
13113
|
+
// journey, so the thing is home before the proof is done, while a mouse
|
|
13114
|
+
// takes it in five pixels.
|
|
13115
|
+
longPress: cloneWrapperCaught ? false : longPress,
|
|
13116
|
+
longPressDelay,
|
|
13117
|
+
longPressSlop,
|
|
13118
|
+
onPressStart,
|
|
13119
|
+
onPressCancel,
|
|
13120
|
+
onPress,
|
|
13121
|
+
},
|
|
13122
|
+
);
|
|
12988
13123
|
};
|
|
12989
13124
|
return startCarry(event);
|
|
12990
13125
|
};
|
|
@@ -13017,7 +13152,7 @@ const setCloneViewportRect = (cloneWrapper, el) => {
|
|
|
13017
13152
|
|
|
13018
13153
|
// The chevron is the one the table's column drop preview uses, rotated by the
|
|
13019
13154
|
// CSS above so each cap points into the line.
|
|
13020
|
-
const dropHintTemplate = /* html
|
|
13155
|
+
const dropHintTemplate = /* html */ `
|
|
13021
13156
|
<div
|
|
13022
13157
|
class="navi_drop_hint"
|
|
13023
13158
|
popover="manual"
|
|
@@ -13043,6 +13178,7 @@ const createDropHint = () => {
|
|
|
13043
13178
|
div.innerHTML = dropHintTemplate.trim();
|
|
13044
13179
|
return div.firstElementChild;
|
|
13045
13180
|
};
|
|
13181
|
+
|
|
13046
13182
|
const createDropSurface = () => {
|
|
13047
13183
|
const div = document.createElement("div");
|
|
13048
13184
|
div.className = "navi_drop_surface";
|
|
@@ -13061,17 +13197,14 @@ const createDropSurface = () => {
|
|
|
13061
13197
|
* that means "get rid of this" — nobody waits to watch it go.
|
|
13062
13198
|
*/
|
|
13063
13199
|
const tossCloneAway = async (cloneWrapper, gestureInfo, onToss) => {
|
|
13064
|
-
const {
|
|
13065
|
-
xDelta,
|
|
13066
|
-
yDelta
|
|
13067
|
-
} = gestureInfo.layout;
|
|
13200
|
+
const { xDelta, yDelta } = gestureInfo.layout;
|
|
13068
13201
|
const distance = Math.hypot(xDelta, yDelta) || 1;
|
|
13069
13202
|
cloneWrapper.dataset.tossed = "away";
|
|
13070
|
-
cloneWrapper.style.translate = `${xDelta / distance * TOSS_DISTANCE}px ${
|
|
13203
|
+
cloneWrapper.style.translate = `${(xDelta / distance) * TOSS_DISTANCE}px ${
|
|
13204
|
+
(yDelta / distance) * TOSS_DISTANCE
|
|
13205
|
+
}px`;
|
|
13071
13206
|
try {
|
|
13072
|
-
await onToss?.({
|
|
13073
|
-
gestureInfo
|
|
13074
|
-
});
|
|
13207
|
+
await onToss?.({ gestureInfo });
|
|
13075
13208
|
return true;
|
|
13076
13209
|
} catch {
|
|
13077
13210
|
return false;
|
|
@@ -13088,11 +13221,17 @@ const tossCloneAway = async (cloneWrapper, gestureInfo, onToss) => {
|
|
|
13088
13221
|
*/
|
|
13089
13222
|
const settleCloneBack = (cloneWrapper, sourceElement) => {
|
|
13090
13223
|
const sourceRect = sourceElement.getBoundingClientRect();
|
|
13091
|
-
const releaseLeft = parseFloat(
|
|
13092
|
-
|
|
13224
|
+
const releaseLeft = parseFloat(
|
|
13225
|
+
cloneWrapper.style.getPropertyValue("--clone-left"),
|
|
13226
|
+
);
|
|
13227
|
+
const releaseTop = parseFloat(
|
|
13228
|
+
cloneWrapper.style.getPropertyValue("--clone-top"),
|
|
13229
|
+
);
|
|
13093
13230
|
cloneWrapper.dataset.tossed = "back";
|
|
13094
|
-
cloneWrapper.style.translate = `${sourceRect.left - releaseLeft}px ${
|
|
13095
|
-
|
|
13231
|
+
cloneWrapper.style.translate = `${sourceRect.left - releaseLeft}px ${
|
|
13232
|
+
sourceRect.top - releaseTop
|
|
13233
|
+
}px`;
|
|
13234
|
+
return new Promise((resolve) => {
|
|
13096
13235
|
setTimeout(resolve, TOSS_DURATION_MS);
|
|
13097
13236
|
});
|
|
13098
13237
|
};
|
|
@@ -13132,23 +13271,20 @@ const settleCloneBack = (cloneWrapper, sourceElement) => {
|
|
|
13132
13271
|
const letCopyBeCaught = (cloneWrapper, carryAgain) => {
|
|
13133
13272
|
let caught = false;
|
|
13134
13273
|
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) {
|
|
13274
|
+
const onPointerDown = (pointerDownEvent) => {
|
|
13275
|
+
const { left, right, top, bottom } = cloneWrapper.getBoundingClientRect();
|
|
13276
|
+
const { clientX, clientY } = pointerDownEvent;
|
|
13277
|
+
if (
|
|
13278
|
+
clientX < left ||
|
|
13279
|
+
clientX > right ||
|
|
13280
|
+
clientY < top ||
|
|
13281
|
+
clientY > bottom
|
|
13282
|
+
) {
|
|
13147
13283
|
// Somewhere else on the page: this press is not about the copy.
|
|
13148
13284
|
return;
|
|
13149
13285
|
}
|
|
13150
13286
|
let pressIsOverResolve;
|
|
13151
|
-
pressIsOver = new Promise(resolve => {
|
|
13287
|
+
pressIsOver = new Promise((resolve) => {
|
|
13152
13288
|
pressIsOverResolve = resolve;
|
|
13153
13289
|
});
|
|
13154
13290
|
const onPointerEnd = () => {
|
|
@@ -13176,9 +13312,8 @@ const letCopyBeCaught = (cloneWrapper, carryAgain) => {
|
|
|
13176
13312
|
// caught with a finger and can with a mouse.
|
|
13177
13313
|
const root = document.documentElement;
|
|
13178
13314
|
root.setAttribute("data-drag-catchable", "");
|
|
13179
|
-
root.addEventListener("touchmove", keepTouchRefusable, {
|
|
13180
|
-
|
|
13181
|
-
});
|
|
13315
|
+
root.addEventListener("touchmove", keepTouchRefusable, { passive: false });
|
|
13316
|
+
|
|
13182
13317
|
return {
|
|
13183
13318
|
settled: async () => {
|
|
13184
13319
|
// A hand that lets go and presses again while the copy is still there is
|
|
@@ -13193,7 +13328,7 @@ const letCopyBeCaught = (cloneWrapper, carryAgain) => {
|
|
|
13193
13328
|
root.removeAttribute("data-drag-catchable");
|
|
13194
13329
|
root.removeEventListener("touchmove", keepTouchRefusable);
|
|
13195
13330
|
return caught;
|
|
13196
|
-
}
|
|
13331
|
+
},
|
|
13197
13332
|
};
|
|
13198
13333
|
};
|
|
13199
13334
|
|
|
@@ -13203,11 +13338,16 @@ const letCopyBeCaught = (cloneWrapper, carryAgain) => {
|
|
|
13203
13338
|
// again is somewhere else on it — so taking one back hands it both again.
|
|
13204
13339
|
const liftDragClone = (cloneWrapper, pointerEvent) => {
|
|
13205
13340
|
const rect = cloneWrapper.getBoundingClientRect();
|
|
13206
|
-
cloneWrapper.style.setProperty(
|
|
13341
|
+
cloneWrapper.style.setProperty(
|
|
13342
|
+
"--drag-origin",
|
|
13343
|
+
`${pointerEvent.clientX - rect.left}px ${pointerEvent.clientY - rect.top}px`,
|
|
13344
|
+
);
|
|
13207
13345
|
cloneWrapper.firstElementChild.setAttribute("navi-drag-clone", "");
|
|
13208
13346
|
};
|
|
13347
|
+
|
|
13209
13348
|
const createDragClone = (element, pointerEvent) => {
|
|
13210
13349
|
const rect = element.getBoundingClientRect();
|
|
13350
|
+
|
|
13211
13351
|
const wrapper = document.createElement("div");
|
|
13212
13352
|
wrapper.setAttribute("navi-drag-clone-wrapper", "");
|
|
13213
13353
|
// A copy can be caught on its way home (see settleCloneBack), which is a press
|
|
@@ -13223,7 +13363,10 @@ const createDragClone = (element, pointerEvent) => {
|
|
|
13223
13363
|
// Grab point within the element — used as transform-origin so the
|
|
13224
13364
|
// scale(1.15) expands from where the user clicked, not the element center.
|
|
13225
13365
|
// These offsets are element-relative so viewport coords are correct here.
|
|
13226
|
-
wrapper.style.setProperty(
|
|
13366
|
+
wrapper.style.setProperty(
|
|
13367
|
+
"--drag-origin",
|
|
13368
|
+
`${pointerEvent.clientX - rect.left}px ${pointerEvent.clientY - rect.top}px`,
|
|
13369
|
+
);
|
|
13227
13370
|
const elementClone = element.cloneNode(true);
|
|
13228
13371
|
// A deep copy copies the ids too, and two elements answering to one id is a
|
|
13229
13372
|
// document that lies: getElementById picks whichever comes first, an anchor
|
|
@@ -13240,6 +13383,7 @@ const createDragClone = (element, pointerEvent) => {
|
|
|
13240
13383
|
// (The original wears it too, but it is hidden — see navi-drag-clone-source.)
|
|
13241
13384
|
elementClone.setAttribute("data-grabbed", "");
|
|
13242
13385
|
elementClone.style.viewTransitionName = "navi-drag-clone";
|
|
13386
|
+
|
|
13243
13387
|
wrapper.appendChild(elementClone);
|
|
13244
13388
|
// Beside the thing it copies, so it stands where that thing stands: every
|
|
13245
13389
|
// inherited value and every custom property the original reads, the copy reads
|
|
@@ -13248,6 +13392,7 @@ const createDragClone = (element, pointerEvent) => {
|
|
|
13248
13392
|
// whatever its depth in the tree, and being fixed keeps it out of the
|
|
13249
13393
|
// scrollable overflow of the list it sits in.
|
|
13250
13394
|
element.parentElement.appendChild(wrapper);
|
|
13395
|
+
|
|
13251
13396
|
return wrapper;
|
|
13252
13397
|
};
|
|
13253
13398
|
|
|
@@ -13329,44 +13474,7 @@ const getResizeDirection = (element) => {
|
|
|
13329
13474
|
return { x, y };
|
|
13330
13475
|
};
|
|
13331
13476
|
|
|
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
|
-
*/
|
|
13477
|
+
installImportMetaCssBuild(import.meta);
|
|
13370
13478
|
|
|
13371
13479
|
// While a pointer is on something that travels: said on the document, because
|
|
13372
13480
|
// what has to be told is the document.
|
|
@@ -13375,7 +13483,8 @@ const GESTURE_ATTRIBUTE = "data-drag-travel-gesture";
|
|
|
13375
13483
|
// …and while one is actually travelling something, which is a later moment and
|
|
13376
13484
|
// takes more away (see the CSS).
|
|
13377
13485
|
const WALKING_ATTRIBUTE = "data-drag-travel-walking";
|
|
13378
|
-
|
|
13486
|
+
|
|
13487
|
+
import.meta.css = /* css */ [`
|
|
13379
13488
|
:root[${GESTURE_ATTRIBUTE}] {
|
|
13380
13489
|
/* The bounce the browser plays when a gesture reaches the end of a page —
|
|
13381
13490
|
and the swipe that goes back in history with it. Both are the browser
|
|
@@ -13535,24 +13644,42 @@ const DRAG_RESISTANCE = 0.3;
|
|
|
13535
13644
|
// press, and nothing hands that back mid-gesture — the travel would drag a
|
|
13536
13645
|
// selection along with it. With a finger the hold is the selection UI itself, so
|
|
13537
13646
|
// 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
|
-
|
|
13647
|
+
const PRESSED_INPUT_TYPES = [
|
|
13648
|
+
'[type="checkbox"]',
|
|
13649
|
+
'[type="radio"]',
|
|
13650
|
+
'[type="button"]',
|
|
13651
|
+
'[type="submit"]',
|
|
13652
|
+
'[type="reset"]',
|
|
13653
|
+
'[type="image"]',
|
|
13654
|
+
'[type="color"]',
|
|
13655
|
+
'[type="file"]',
|
|
13656
|
+
].join(",");
|
|
13657
|
+
const NATIVE_POINTER_GESTURES = [
|
|
13658
|
+
{
|
|
13659
|
+
// The value follows the pointer, pixel by pixel: a thumb along its rail, a
|
|
13660
|
+
// spinner clicked up and down. There is no moment in that which could be
|
|
13661
|
+
// given away — the gesture IS the pointer.
|
|
13662
|
+
selector: 'input[type="range"],input[type="number"]',
|
|
13663
|
+
},
|
|
13664
|
+
{
|
|
13665
|
+
// The text answers the pointer, and differently per hand: a mouse drags a
|
|
13666
|
+
// caret through it (a selection), a finger drags the field's own content
|
|
13667
|
+
// sideways when it overflows, and a finger held still opens the selection
|
|
13668
|
+
// UI. Three things a field is for, against one travel that has a whole box
|
|
13669
|
+
// to start from.
|
|
13670
|
+
selector: [
|
|
13671
|
+
`input:not(${PRESSED_INPUT_TYPES},[type="range"],[type="number"])`,
|
|
13672
|
+
"textarea",
|
|
13673
|
+
'[contenteditable=""]',
|
|
13674
|
+
'[contenteditable="true"]',
|
|
13675
|
+
].join(","),
|
|
13676
|
+
},
|
|
13677
|
+
{
|
|
13678
|
+
// A menu opens under the pointer and the browser keeps the rest of the
|
|
13679
|
+
// gesture: dragged over, it picks an option.
|
|
13680
|
+
selector: "select",
|
|
13681
|
+
},
|
|
13682
|
+
];
|
|
13556
13683
|
|
|
13557
13684
|
// …and what an element says against its own tag. Every line above is about what
|
|
13558
13685
|
// an element DOES, and a tag only says what it IS: a picker's façade is an
|
|
@@ -13569,9 +13696,9 @@ const NATIVE_POINTER_GESTURES = [{
|
|
|
13569
13696
|
// that can really be typed in) has not made a travel possible, it has taken the
|
|
13570
13697
|
// gesture the user was making away from them.
|
|
13571
13698
|
const PRESS_ONLY_ATTRIBUTE = "data-press-only";
|
|
13572
|
-
const NATIVE_POINTER_GESTURE_SELECTOR = NATIVE_POINTER_GESTURES.map(
|
|
13573
|
-
selector
|
|
13574
|
-
|
|
13699
|
+
const NATIVE_POINTER_GESTURE_SELECTOR = NATIVE_POINTER_GESTURES.map(
|
|
13700
|
+
({ selector }) => `:is(${selector}):not([${PRESS_ONLY_ATTRIBUTE}])`,
|
|
13701
|
+
).join(",");
|
|
13575
13702
|
|
|
13576
13703
|
// What a drag must not start on: what answers the pointer natively (above),
|
|
13577
13704
|
// plus what has said the gesture is its own. A drag SOURCE is not in the list:
|
|
@@ -13583,7 +13710,15 @@ const NATIVE_POINTER_GESTURE_SELECTOR = NATIVE_POINTER_GESTURES.map(({
|
|
|
13583
13710
|
// through (see DRAG_IGNORED_SELECTOR in drag_to.js). And so is a popover or a
|
|
13584
13711
|
// dialog: a layer OVER the box, whose press only bubbles through the box because
|
|
13585
13712
|
// the layer is anchored in it.
|
|
13586
|
-
const DRAG_EXCLUDED_SELECTOR = [
|
|
13713
|
+
const DRAG_EXCLUDED_SELECTOR = [
|
|
13714
|
+
NATIVE_POINTER_GESTURE_SELECTOR,
|
|
13715
|
+
"[data-drag-handle]",
|
|
13716
|
+
"[data-no-drag-travel]",
|
|
13717
|
+
'[data-self-interactions~="drag"]',
|
|
13718
|
+
'[data-self-interactions~="*"]',
|
|
13719
|
+
"[popover]",
|
|
13720
|
+
"dialog",
|
|
13721
|
+
].join(",");
|
|
13587
13722
|
|
|
13588
13723
|
// Which axes a box travels on, one attribute per gesture, said in the DOM by
|
|
13589
13724
|
// whoever owns the box: it is what a box ABOVE another reads to know the
|
|
@@ -13603,8 +13738,12 @@ const DRAG_SOURCE_AXES_ATTRIBUTE = "data-drag-source";
|
|
|
13603
13738
|
// covers everything. So a gesture that happened on it is not the gesture of any
|
|
13604
13739
|
// box it merely sits on top of, and every walk up the tree from the pointer
|
|
13605
13740
|
// ends here: the boxes above are behind, and behind is not under the finger.
|
|
13606
|
-
const TOP_LAYER_SELECTOR = [
|
|
13607
|
-
|
|
13741
|
+
const TOP_LAYER_SELECTOR = [
|
|
13742
|
+
":popover-open",
|
|
13743
|
+
"dialog:modal",
|
|
13744
|
+
":fullscreen",
|
|
13745
|
+
].join(",");
|
|
13746
|
+
const isTopLayer = (element) => {
|
|
13608
13747
|
return element.matches(TOP_LAYER_SELECTOR);
|
|
13609
13748
|
};
|
|
13610
13749
|
|
|
@@ -13675,12 +13814,10 @@ const scrollRoomTowards = (fromElement, stopElement, axis, sign) => {
|
|
|
13675
13814
|
return false;
|
|
13676
13815
|
}
|
|
13677
13816
|
const size = axis === "x" ? element.clientWidth : element.clientHeight;
|
|
13678
|
-
const scrollSize =
|
|
13817
|
+
const scrollSize =
|
|
13818
|
+
axis === "x" ? element.scrollWidth : element.scrollHeight;
|
|
13679
13819
|
if (scrollSize > size + 1) {
|
|
13680
|
-
const {
|
|
13681
|
-
overflowX,
|
|
13682
|
-
overflowY
|
|
13683
|
-
} = getComputedStyle(element);
|
|
13820
|
+
const { overflowX, overflowY } = getComputedStyle(element);
|
|
13684
13821
|
const overflow = axis === "x" ? overflowX : overflowY;
|
|
13685
13822
|
if (overflow === "auto" || overflow === "scroll") {
|
|
13686
13823
|
const position = axis === "x" ? element.scrollLeft : element.scrollTop;
|
|
@@ -13706,7 +13843,7 @@ const travelsAfter = ({
|
|
|
13706
13843
|
size,
|
|
13707
13844
|
velocity,
|
|
13708
13845
|
towardsSomething,
|
|
13709
|
-
commitRatio
|
|
13846
|
+
commitRatio,
|
|
13710
13847
|
}) => {
|
|
13711
13848
|
if (!towardsSomething) {
|
|
13712
13849
|
return false;
|
|
@@ -13727,11 +13864,15 @@ const travelsAfter = ({
|
|
|
13727
13864
|
// other way. Without this the picture alone decides, and a screen caught at
|
|
13728
13865
|
// two thirds and thrown back still arrives: the gesture was read as the
|
|
13729
13866
|
// place it was let go of rather than as a movement.
|
|
13730
|
-
if (
|
|
13867
|
+
if (
|
|
13868
|
+
Math.abs(velocity) > DRAG_THROW_BACK_VELOCITY &&
|
|
13869
|
+
Math.sign(velocity) !== sign
|
|
13870
|
+
) {
|
|
13731
13871
|
return false;
|
|
13732
13872
|
}
|
|
13733
13873
|
// …and going towards it travels, however slowly: see DRAG_DRIFT_VELOCITY.
|
|
13734
|
-
const drifting =
|
|
13874
|
+
const drifting =
|
|
13875
|
+
Math.abs(velocity) > DRAG_DRIFT_VELOCITY && Math.sign(velocity) === sign;
|
|
13735
13876
|
if (drifting && Math.abs(pulled) > DRAG_DRIFT_DISTANCE) {
|
|
13736
13877
|
return true;
|
|
13737
13878
|
}
|
|
@@ -13799,17 +13940,20 @@ const travelsAfter = ({
|
|
|
13799
13940
|
* refused it. Nothing was painted and nothing has to be put back — this is
|
|
13800
13941
|
* only so the caller can forget the gesture it is holding.
|
|
13801
13942
|
*/
|
|
13802
|
-
const startDragToTravel = (
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13943
|
+
const startDragToTravel = (
|
|
13944
|
+
pointerDownEvent,
|
|
13945
|
+
{
|
|
13946
|
+
element,
|
|
13947
|
+
axes = "xy",
|
|
13948
|
+
immediate = false,
|
|
13949
|
+
commitRatio = DRAG_COMMIT_RATIO,
|
|
13950
|
+
onStart,
|
|
13951
|
+
onPull,
|
|
13952
|
+
onEnd,
|
|
13953
|
+
onEdge = () => false,
|
|
13954
|
+
onGiveUp = () => {},
|
|
13955
|
+
},
|
|
13956
|
+
) => {
|
|
13813
13957
|
const target = pointerDownEvent.target;
|
|
13814
13958
|
if (!target.closest || isPressExcluded(target, element)) {
|
|
13815
13959
|
return null;
|
|
@@ -13818,8 +13962,15 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13818
13962
|
// anything between them that is picked up and carried the same way: the
|
|
13819
13963
|
// gesture is theirs, and this one is left with the axes none of them walks —
|
|
13820
13964
|
// none at all, most of the time, and then there is no gesture here to read.
|
|
13821
|
-
const axesLeftByTravels = axesLeftBy(
|
|
13822
|
-
|
|
13965
|
+
const axesLeftByTravels = axesLeftBy(
|
|
13966
|
+
axes,
|
|
13967
|
+
target,
|
|
13968
|
+
element,
|
|
13969
|
+
DRAG_AXES_ATTRIBUTE,
|
|
13970
|
+
);
|
|
13971
|
+
const axesLeft =
|
|
13972
|
+
axesLeftByTravels &&
|
|
13973
|
+
axesLeftBy(axesLeftByTravels, target, element, DRAG_SOURCE_AXES_ATTRIBUTE);
|
|
13823
13974
|
if (!axesLeft) {
|
|
13824
13975
|
return null;
|
|
13825
13976
|
}
|
|
@@ -13835,6 +13986,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13835
13986
|
let travel = null;
|
|
13836
13987
|
let dragGesture = null;
|
|
13837
13988
|
let over = false;
|
|
13989
|
+
|
|
13838
13990
|
const finish = () => {
|
|
13839
13991
|
if (over) {
|
|
13840
13992
|
return;
|
|
@@ -13848,7 +14000,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13848
14000
|
// A press that never became a travel: the intent never resolved, or the axis
|
|
13849
14001
|
// it leaned on is not one this box walks. Nothing was painted and nothing has
|
|
13850
14002
|
// to be put back — the caller is only told so it can forget the gesture.
|
|
13851
|
-
const onPressOver = pointerEvent => {
|
|
14003
|
+
const onPressOver = (pointerEvent) => {
|
|
13852
14004
|
if (pointerEvent.pointerId !== pointerDownEvent.pointerId || travel) {
|
|
13853
14005
|
return;
|
|
13854
14006
|
}
|
|
@@ -13869,8 +14021,11 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13869
14021
|
// layout the gesture computes for something being carried: nothing is being
|
|
13870
14022
|
// carried here, and a scroll happening meanwhile must not read as a finger
|
|
13871
14023
|
// that moved.
|
|
13872
|
-
const coveredOn = (axis, gestureInfo) =>
|
|
13873
|
-
|
|
14024
|
+
const coveredOn = (axis, gestureInfo) =>
|
|
14025
|
+
axis === "x"
|
|
14026
|
+
? gestureInfo.dragX - gestureInfo.grabX
|
|
14027
|
+
: gestureInfo.dragY - gestureInfo.grabY;
|
|
14028
|
+
const pullOf = (gestureInfo) => {
|
|
13874
14029
|
const covered = coveredOn(travel.axis, gestureInfo);
|
|
13875
14030
|
return travel.slack + (covered - travel.origin);
|
|
13876
14031
|
};
|
|
@@ -13884,7 +14039,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13884
14039
|
const next = onEdge({
|
|
13885
14040
|
axis: travel.axis,
|
|
13886
14041
|
sign,
|
|
13887
|
-
event: gestureInfo.dragEvent
|
|
14042
|
+
event: gestureInfo.dragEvent,
|
|
13888
14043
|
});
|
|
13889
14044
|
if (!next || !next.size) {
|
|
13890
14045
|
return null;
|
|
@@ -13902,6 +14057,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13902
14057
|
travel.origin = coveredOn(travel.axis, gestureInfo) - pulled;
|
|
13903
14058
|
return pulled;
|
|
13904
14059
|
};
|
|
14060
|
+
|
|
13905
14061
|
const controller = createDragGestureController({
|
|
13906
14062
|
// The threshold is left at its default and never crossed: what says this
|
|
13907
14063
|
// press has become a gesture is the intent module below, which calls
|
|
@@ -13916,7 +14072,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13916
14072
|
onDragStart: () => {
|
|
13917
14073
|
document.documentElement.setAttribute(GESTURE_ATTRIBUTE, "");
|
|
13918
14074
|
},
|
|
13919
|
-
onDrag: gestureInfo => {
|
|
14075
|
+
onDrag: (gestureInfo) => {
|
|
13920
14076
|
// Releasing a gesture reports one last move, so giving one up would come
|
|
13921
14077
|
// back through here and give it up again, forever.
|
|
13922
14078
|
if (over) {
|
|
@@ -13982,7 +14138,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
13982
14138
|
axis,
|
|
13983
14139
|
sign,
|
|
13984
14140
|
target,
|
|
13985
|
-
event: gestureInfo.dragEvent
|
|
14141
|
+
event: gestureInfo.dragEvent,
|
|
13986
14142
|
});
|
|
13987
14143
|
if (!started || !started.size) {
|
|
13988
14144
|
giveUp();
|
|
@@ -14003,17 +14159,21 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
14003
14159
|
// established before the press (see immediate): there was no
|
|
14004
14160
|
// threshold to cross, so every pixel since the grab is the hand's
|
|
14005
14161
|
// and is owed to it.
|
|
14006
|
-
origin: immediate
|
|
14007
|
-
|
|
14162
|
+
origin: immediate
|
|
14163
|
+
? 0
|
|
14164
|
+
: covered > DRAG_START_THRESHOLD
|
|
14165
|
+
? DRAG_START_THRESHOLD
|
|
14166
|
+
: covered < -DRAG_START_THRESHOLD
|
|
14167
|
+
? -DRAG_START_THRESHOLD
|
|
14168
|
+
: covered,
|
|
14169
|
+
pulled: started.slack || 0,
|
|
14008
14170
|
};
|
|
14009
14171
|
document.documentElement.setAttribute(WALKING_ATTRIBUTE, axis);
|
|
14010
14172
|
// The travel exists: from here the pointer is this box's, and it is
|
|
14011
14173
|
// followed wherever it goes.
|
|
14012
14174
|
dragGesture.capturePointer();
|
|
14013
14175
|
}
|
|
14014
|
-
const {
|
|
14015
|
-
axis
|
|
14016
|
-
} = travel;
|
|
14176
|
+
const { axis } = travel;
|
|
14017
14177
|
let pulled = pullOf(gestureInfo);
|
|
14018
14178
|
// Which side is being pulled in: dragging to the right brings in what is
|
|
14019
14179
|
// on the left, which is what comes BEFORE.
|
|
@@ -14051,7 +14211,8 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
14051
14211
|
// from where the finger IS once it has reached the end, so turning
|
|
14052
14212
|
// around moves the picture at once instead of first walking back
|
|
14053
14213
|
// over the distance the hand went too far.
|
|
14054
|
-
travel.origin =
|
|
14214
|
+
travel.origin =
|
|
14215
|
+
coveredOn(axis, gestureInfo) - (pulled - travel.slack);
|
|
14055
14216
|
} else {
|
|
14056
14217
|
pulled = relayed;
|
|
14057
14218
|
size = travel.size;
|
|
@@ -14064,46 +14225,42 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
14064
14225
|
pulled,
|
|
14065
14226
|
size,
|
|
14066
14227
|
progress: pulled / size,
|
|
14067
|
-
event: gestureInfo.dragEvent
|
|
14228
|
+
event: gestureInfo.dragEvent,
|
|
14068
14229
|
});
|
|
14069
14230
|
},
|
|
14070
|
-
onRelease: gestureInfo => {
|
|
14231
|
+
onRelease: (gestureInfo) => {
|
|
14071
14232
|
if (over || !travel) {
|
|
14072
14233
|
return;
|
|
14073
14234
|
}
|
|
14074
14235
|
finish();
|
|
14075
|
-
const {
|
|
14076
|
-
axis,
|
|
14077
|
-
size,
|
|
14078
|
-
pulled,
|
|
14079
|
-
slack
|
|
14080
|
-
} = travel;
|
|
14236
|
+
const { axis, size, pulled, slack } = travel;
|
|
14081
14237
|
const towardsSomething = pulled > 0 ? travel.travelBack : travel.travelOn;
|
|
14082
|
-
const velocity =
|
|
14238
|
+
const velocity =
|
|
14239
|
+
axis === "x" ? gestureInfo.velocityX : gestureInfo.velocityY;
|
|
14083
14240
|
// A gesture taken away rather than let go of (the browser scrolling
|
|
14084
14241
|
// something else, a call coming in, another gesture taking the pointer)
|
|
14085
14242
|
// said nothing: things go back.
|
|
14086
14243
|
const releaseEvent = gestureInfo.releaseEvent || gestureInfo.dragEvent;
|
|
14087
|
-
const {
|
|
14088
|
-
cancelled
|
|
14089
|
-
} = gestureInfo;
|
|
14244
|
+
const { cancelled } = gestureInfo;
|
|
14090
14245
|
onEnd({
|
|
14091
14246
|
axis,
|
|
14092
14247
|
pulled,
|
|
14093
14248
|
size,
|
|
14094
14249
|
sign: pulled > 0 ? 1 : -1,
|
|
14095
|
-
travels:
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
|
|
14102
|
-
|
|
14250
|
+
travels:
|
|
14251
|
+
!cancelled &&
|
|
14252
|
+
travelsAfter({
|
|
14253
|
+
pulled,
|
|
14254
|
+
slack,
|
|
14255
|
+
size,
|
|
14256
|
+
velocity,
|
|
14257
|
+
towardsSomething,
|
|
14258
|
+
commitRatio,
|
|
14259
|
+
}),
|
|
14103
14260
|
cancelled,
|
|
14104
|
-
event: releaseEvent
|
|
14261
|
+
event: releaseEvent,
|
|
14105
14262
|
});
|
|
14106
|
-
}
|
|
14263
|
+
},
|
|
14107
14264
|
});
|
|
14108
14265
|
|
|
14109
14266
|
// When a press becomes a gesture, and by which rule. A travel is a swipe, so
|
|
@@ -14125,7 +14282,7 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
14125
14282
|
// would be taken away from whoever else is reading the same press for
|
|
14126
14283
|
// gestures that give themselves up one event later. It is claimed once
|
|
14127
14284
|
// the travel exists, in onDrag below.
|
|
14128
|
-
pointerCaptureDeferred: true
|
|
14285
|
+
pointerCaptureDeferred: true,
|
|
14129
14286
|
});
|
|
14130
14287
|
return dragGesture;
|
|
14131
14288
|
};
|
|
@@ -14138,16 +14295,17 @@ const startDragToTravel = (pointerDownEvent, {
|
|
|
14138
14295
|
} else {
|
|
14139
14296
|
dragAfterIntent(pointerDownEvent, grab, {
|
|
14140
14297
|
longPress: false,
|
|
14141
|
-
threshold: DRAG_START_THRESHOLD
|
|
14298
|
+
threshold: DRAG_START_THRESHOLD,
|
|
14142
14299
|
});
|
|
14143
14300
|
}
|
|
14144
14301
|
window.addEventListener("pointerup", onPressOver);
|
|
14145
14302
|
window.addEventListener("pointercancel", onPressOver);
|
|
14303
|
+
|
|
14146
14304
|
return {
|
|
14147
14305
|
stop: () => {
|
|
14148
14306
|
finish();
|
|
14149
14307
|
dragGesture?.release();
|
|
14150
|
-
}
|
|
14308
|
+
},
|
|
14151
14309
|
};
|
|
14152
14310
|
};
|
|
14153
14311
|
|
|
@@ -14219,11 +14377,9 @@ const WHEEL_REGROW_RUN = 2;
|
|
|
14219
14377
|
* scrolls, and pushing content to the right reveals its left.
|
|
14220
14378
|
* @returns {() => void} stop listening.
|
|
14221
14379
|
*/
|
|
14222
|
-
const watchWheelTravel = (element, {
|
|
14223
|
-
axes = "xy",
|
|
14224
|
-
onStep
|
|
14225
|
-
}) => {
|
|
14380
|
+
const watchWheelTravel = (element, { axes = "xy", onStep }) => {
|
|
14226
14381
|
let gesture = null;
|
|
14382
|
+
|
|
14227
14383
|
const forgetGesture = () => {
|
|
14228
14384
|
gesture = null;
|
|
14229
14385
|
document.documentElement.removeAttribute(GESTURE_ATTRIBUTE);
|
|
@@ -14236,10 +14392,8 @@ const watchWheelTravel = (element, {
|
|
|
14236
14392
|
// pseudo-elements are told about pointer-events. Heard on the box alone, a
|
|
14237
14393
|
// gesture that sets a travel off loses every event after the first — and the
|
|
14238
14394
|
// page scrolls behind the travel with everything that was not taken.
|
|
14239
|
-
const isOverElement = wheelEvent => {
|
|
14240
|
-
const {
|
|
14241
|
-
target
|
|
14242
|
-
} = wheelEvent;
|
|
14395
|
+
const isOverElement = (wheelEvent) => {
|
|
14396
|
+
const { target } = wheelEvent;
|
|
14243
14397
|
if (element.contains(target)) {
|
|
14244
14398
|
return true;
|
|
14245
14399
|
}
|
|
@@ -14253,26 +14407,22 @@ const watchWheelTravel = (element, {
|
|
|
14253
14407
|
if (!target.contains(element)) {
|
|
14254
14408
|
return false;
|
|
14255
14409
|
}
|
|
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;
|
|
14410
|
+
const { left, right, top, bottom } = element.getBoundingClientRect();
|
|
14411
|
+
const { clientX, clientY } = wheelEvent;
|
|
14412
|
+
return (
|
|
14413
|
+
clientX >= left && clientX <= right && clientY >= top && clientY <= bottom
|
|
14414
|
+
);
|
|
14267
14415
|
};
|
|
14268
|
-
|
|
14416
|
+
|
|
14417
|
+
const onWheel = (wheelEvent) => {
|
|
14269
14418
|
// The burst is already somebody else's — the box inside this one, a wheel
|
|
14270
14419
|
// picker, whoever answered its first event. It is theirs to the end of it,
|
|
14271
14420
|
// wherever the pointer has drifted since (see wheel_gesture.js).
|
|
14272
14421
|
if (wheelGestureIsTakenFrom(element)) {
|
|
14273
14422
|
return;
|
|
14274
14423
|
}
|
|
14275
|
-
const axis =
|
|
14424
|
+
const axis =
|
|
14425
|
+
Math.abs(wheelEvent.deltaX) > Math.abs(wheelEvent.deltaY) ? "x" : "y";
|
|
14276
14426
|
const delta = axis === "x" ? wheelEvent.deltaX : wheelEvent.deltaY;
|
|
14277
14427
|
if (!delta) {
|
|
14278
14428
|
return;
|
|
@@ -14297,15 +14447,16 @@ const watchWheelTravel = (element, {
|
|
|
14297
14447
|
// it — the same claims a press is read against (see the top of this
|
|
14298
14448
|
// file), and all of them are answered by giving the gesture up whole:
|
|
14299
14449
|
// nothing is prevented and the browser scrolls as it would have.
|
|
14300
|
-
const {
|
|
14301
|
-
|
|
14302
|
-
|
|
14303
|
-
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14450
|
+
const { target } = wheelEvent;
|
|
14451
|
+
if (
|
|
14452
|
+
(target.closest && isPressExcluded(target, element)) ||
|
|
14453
|
+
scrollRoomTowards(target, element, axis, sign) ||
|
|
14454
|
+
// …plus the third: a box below this one that travels on this axis. Its
|
|
14455
|
+
// watcher hears the same wheel event this one does — they all listen at
|
|
14456
|
+
// the document — so without this both step, and one push moves two
|
|
14457
|
+
// things.
|
|
14458
|
+
!axesLeftBy(axis, target, element, WHEEL_AXES_ATTRIBUTE)
|
|
14459
|
+
) {
|
|
14309
14460
|
return;
|
|
14310
14461
|
}
|
|
14311
14462
|
gesture = {
|
|
@@ -14316,7 +14467,7 @@ const watchWheelTravel = (element, {
|
|
|
14316
14467
|
fadeRun: 0,
|
|
14317
14468
|
growRun: 0,
|
|
14318
14469
|
faded: false,
|
|
14319
|
-
stepped: false
|
|
14470
|
+
stepped: false,
|
|
14320
14471
|
};
|
|
14321
14472
|
document.documentElement.setAttribute(GESTURE_ATTRIBUTE, "");
|
|
14322
14473
|
document.documentElement.setAttribute(WALKING_ATTRIBUTE, axis);
|
|
@@ -14345,9 +14496,7 @@ const watchWheelTravel = (element, {
|
|
|
14345
14496
|
}
|
|
14346
14497
|
// …and said on every event on our axis, because a claim nobody renews is a
|
|
14347
14498
|
// gesture that is over.
|
|
14348
|
-
claimWheelGesture(element, {
|
|
14349
|
-
onEnd: forgetGesture
|
|
14350
|
-
});
|
|
14499
|
+
claimWheelGesture(element, { onEnd: forgetGesture });
|
|
14351
14500
|
if (sign !== gesture.sign) {
|
|
14352
14501
|
// Turned around: what was adding up was going the other way.
|
|
14353
14502
|
gesture.sign = sign;
|
|
@@ -14364,11 +14513,7 @@ const watchWheelTravel = (element, {
|
|
|
14364
14513
|
// happen pushes harder rather than waiting. Everything a threshold could
|
|
14365
14514
|
// buy here is bought by what a screen AFTER this one costs.
|
|
14366
14515
|
gesture.stepped = true;
|
|
14367
|
-
onStep({
|
|
14368
|
-
axis: gesture.axis,
|
|
14369
|
-
sign: gesture.sign,
|
|
14370
|
-
event: wheelEvent
|
|
14371
|
-
});
|
|
14516
|
+
onStep({ axis: gesture.axis, sign: gesture.sign, event: wheelEvent });
|
|
14372
14517
|
return;
|
|
14373
14518
|
}
|
|
14374
14519
|
const magnitude = Math.abs(delta);
|
|
@@ -14393,11 +14538,7 @@ const watchWheelTravel = (element, {
|
|
|
14393
14538
|
gesture.faded = false;
|
|
14394
14539
|
gesture.growRun = 0;
|
|
14395
14540
|
gesture.pushed = 0;
|
|
14396
|
-
onStep({
|
|
14397
|
-
axis: gesture.axis,
|
|
14398
|
-
sign: gesture.sign,
|
|
14399
|
-
event: wheelEvent
|
|
14400
|
-
});
|
|
14541
|
+
onStep({ axis: gesture.axis, sign: gesture.sign, event: wheelEvent });
|
|
14401
14542
|
return;
|
|
14402
14543
|
}
|
|
14403
14544
|
if (gesture.fadeRun >= WHEEL_FADE_RUN) {
|
|
@@ -14408,20 +14549,15 @@ const watchWheelTravel = (element, {
|
|
|
14408
14549
|
return;
|
|
14409
14550
|
}
|
|
14410
14551
|
gesture.pushed = 0;
|
|
14411
|
-
onStep({
|
|
14412
|
-
axis: gesture.axis,
|
|
14413
|
-
sign: gesture.sign,
|
|
14414
|
-
event: wheelEvent
|
|
14415
|
-
});
|
|
14552
|
+
onStep({ axis: gesture.axis, sign: gesture.sign, event: wheelEvent });
|
|
14416
14553
|
};
|
|
14554
|
+
|
|
14417
14555
|
document.addEventListener("wheel", onWheel, {
|
|
14418
14556
|
passive: false,
|
|
14419
|
-
capture: true
|
|
14557
|
+
capture: true,
|
|
14420
14558
|
});
|
|
14421
14559
|
return () => {
|
|
14422
|
-
document.removeEventListener("wheel", onWheel, {
|
|
14423
|
-
capture: true
|
|
14424
|
-
});
|
|
14560
|
+
document.removeEventListener("wheel", onWheel, { capture: true });
|
|
14425
14561
|
// Handed back rather than left to lapse: a box that is gone must not hold a
|
|
14426
14562
|
// gesture the boxes still there are asking about.
|
|
14427
14563
|
releaseWheelGesture(element);
|
|
@@ -14462,11 +14598,13 @@ const isPressExcluded = (target, element) => {
|
|
|
14462
14598
|
* @param {Element} element Where the press landed.
|
|
14463
14599
|
* @returns {boolean}
|
|
14464
14600
|
*/
|
|
14465
|
-
const isPressDisputedByDrag = element => {
|
|
14601
|
+
const isPressDisputedByDrag = (element) => {
|
|
14466
14602
|
if (!element || typeof element.closest !== "function") {
|
|
14467
14603
|
return false;
|
|
14468
14604
|
}
|
|
14469
|
-
const dragged = element.closest(
|
|
14605
|
+
const dragged = element.closest(
|
|
14606
|
+
`[${DRAG_AXES_ATTRIBUTE}],[${DRAG_SOURCE_AXES_ATTRIBUTE}]`,
|
|
14607
|
+
);
|
|
14470
14608
|
if (!dragged) {
|
|
14471
14609
|
return false;
|
|
14472
14610
|
}
|
|
@@ -14681,36 +14819,17 @@ const getWidth = (element) => {
|
|
|
14681
14819
|
return width;
|
|
14682
14820
|
};
|
|
14683
14821
|
|
|
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] {
|
|
14822
|
+
installImportMetaCssBuild(import.meta);
|
|
14823
|
+
|
|
14824
|
+
const css = /* css */ `[data-position-sticky-placeholder] {
|
|
14707
14825
|
opacity: 0 !important;
|
|
14708
14826
|
width: auto !important;
|
|
14709
14827
|
height: auto !important;
|
|
14710
14828
|
position: static !important;
|
|
14711
14829
|
}
|
|
14712
14830
|
`;
|
|
14713
|
-
|
|
14831
|
+
|
|
14832
|
+
const initPositionSticky = (element) => {
|
|
14714
14833
|
import.meta.css = [css, "@jsenv/dom/src/position/position_sticky.js"];
|
|
14715
14834
|
const computedStyle = getComputedStyle(element);
|
|
14716
14835
|
const topCssValue = computedStyle.top;
|
|
@@ -14735,10 +14854,20 @@ const initPositionSticky = element => {
|
|
|
14735
14854
|
break;
|
|
14736
14855
|
}
|
|
14737
14856
|
const style = getComputedStyle(scrollContainer);
|
|
14738
|
-
if (
|
|
14857
|
+
if (
|
|
14858
|
+
xScrollContainer === null &&
|
|
14859
|
+
(style.overflowX === "auto" ||
|
|
14860
|
+
style.overflowX === "hidden" ||
|
|
14861
|
+
style.overflowX === "scroll")
|
|
14862
|
+
) {
|
|
14739
14863
|
xScrollContainer = scrollContainer;
|
|
14740
14864
|
}
|
|
14741
|
-
if (
|
|
14865
|
+
if (
|
|
14866
|
+
yScrollContainer === null &&
|
|
14867
|
+
(style.overflowY === "auto" ||
|
|
14868
|
+
style.overflowY === "hidden" ||
|
|
14869
|
+
style.overflowY === "scroll")
|
|
14870
|
+
) {
|
|
14742
14871
|
yScrollContainer = scrollContainer;
|
|
14743
14872
|
}
|
|
14744
14873
|
}
|
|
@@ -14747,6 +14876,7 @@ const initPositionSticky = element => {
|
|
|
14747
14876
|
if (!needsPolyfillX && !needsPolyfillY) {
|
|
14748
14877
|
return () => {}; // Native sticky will work fine on both axes
|
|
14749
14878
|
}
|
|
14879
|
+
|
|
14750
14880
|
const cleanupCallbackSet = new Set();
|
|
14751
14881
|
const cleanup = () => {
|
|
14752
14882
|
for (const cleanupCallback of cleanupCallbackSet) {
|
|
@@ -14754,6 +14884,7 @@ const initPositionSticky = element => {
|
|
|
14754
14884
|
}
|
|
14755
14885
|
cleanupCallbackSet.clear();
|
|
14756
14886
|
};
|
|
14887
|
+
|
|
14757
14888
|
const parentElement = element.parentElement;
|
|
14758
14889
|
const createPlaceholderClone = () => {
|
|
14759
14890
|
const clone = element.cloneNode(true);
|
|
@@ -14761,13 +14892,16 @@ const initPositionSticky = element => {
|
|
|
14761
14892
|
clone.removeAttribute("data-sticky");
|
|
14762
14893
|
return clone;
|
|
14763
14894
|
};
|
|
14895
|
+
|
|
14764
14896
|
let placeholder = createPlaceholderClone();
|
|
14765
14897
|
parentElement.insertBefore(placeholder, element);
|
|
14766
14898
|
cleanupCallbackSet.add(() => {
|
|
14767
14899
|
placeholder.remove();
|
|
14768
14900
|
});
|
|
14901
|
+
|
|
14769
14902
|
let width = getWidth(element);
|
|
14770
14903
|
let height = getHeight(element);
|
|
14904
|
+
|
|
14771
14905
|
const updateSize = () => {
|
|
14772
14906
|
const newPlaceholder = createPlaceholderClone();
|
|
14773
14907
|
parentElement.replaceChild(newPlaceholder, placeholder);
|
|
@@ -14776,12 +14910,14 @@ const initPositionSticky = element => {
|
|
|
14776
14910
|
height = getHeight(placeholder);
|
|
14777
14911
|
updatePosition();
|
|
14778
14912
|
};
|
|
14913
|
+
|
|
14779
14914
|
const updatePosition = () => {
|
|
14780
14915
|
// Ensure placeholder dimensions match element
|
|
14781
14916
|
setStyles(placeholder, {
|
|
14782
14917
|
width: `${width}px`,
|
|
14783
|
-
height: `${height}px
|
|
14918
|
+
height: `${height}px`,
|
|
14784
14919
|
});
|
|
14920
|
+
|
|
14785
14921
|
const placeholderRect = placeholder.getBoundingClientRect();
|
|
14786
14922
|
const parentRect = parentElement.getBoundingClientRect();
|
|
14787
14923
|
|
|
@@ -14795,12 +14931,12 @@ const initPositionSticky = element => {
|
|
|
14795
14931
|
// -420 <= 250 → stuck → element.style.left = 250px (main's left edge). ✓
|
|
14796
14932
|
//
|
|
14797
14933
|
// If no intermediate scroll container exists, use 0 (document/viewport edge).
|
|
14798
|
-
const yContainerRect = yScrollContainer
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
const xContainerRect = xScrollContainer
|
|
14802
|
-
|
|
14803
|
-
|
|
14934
|
+
const yContainerRect = yScrollContainer
|
|
14935
|
+
? yScrollContainer.getBoundingClientRect()
|
|
14936
|
+
: { top: 0 };
|
|
14937
|
+
const xContainerRect = xScrollContainer
|
|
14938
|
+
? xScrollContainer.getBoundingClientRect()
|
|
14939
|
+
: { left: 0 };
|
|
14804
14940
|
const topThreshold = yContainerRect.top + top;
|
|
14805
14941
|
const leftThreshold = xContainerRect.left + left;
|
|
14806
14942
|
|
|
@@ -14843,6 +14979,7 @@ const initPositionSticky = element => {
|
|
|
14843
14979
|
} else {
|
|
14844
14980
|
leftPosition = placeholderRect.left;
|
|
14845
14981
|
}
|
|
14982
|
+
|
|
14846
14983
|
element.style.top = `${topPosition}px`;
|
|
14847
14984
|
element.style.left = `${Math.round(leftPosition)}px`;
|
|
14848
14985
|
element.style.width = `${width}px`;
|
|
@@ -14855,15 +14992,18 @@ const initPositionSticky = element => {
|
|
|
14855
14992
|
element.removeAttribute("data-sticky");
|
|
14856
14993
|
}
|
|
14857
14994
|
};
|
|
14995
|
+
|
|
14858
14996
|
{
|
|
14859
14997
|
const restorePositionStyle = forceStyles(element, {
|
|
14860
14998
|
"position": "fixed",
|
|
14861
14999
|
"z-index": 1,
|
|
14862
|
-
"will-change": "transform" // Hint for hardware acceleration
|
|
15000
|
+
"will-change": "transform", // Hint for hardware acceleration
|
|
14863
15001
|
});
|
|
14864
15002
|
cleanupCallbackSet.add(restorePositionStyle);
|
|
14865
15003
|
}
|
|
15004
|
+
|
|
14866
15005
|
updatePosition();
|
|
15006
|
+
|
|
14867
15007
|
{
|
|
14868
15008
|
const handleScroll = () => {
|
|
14869
15009
|
updatePosition();
|
|
@@ -14874,16 +15014,15 @@ const initPositionSticky = element => {
|
|
|
14874
15014
|
const listenTargets = new Set(scrollContainerSet);
|
|
14875
15015
|
listenTargets.add(document.documentElement);
|
|
14876
15016
|
for (const scrollTarget of listenTargets) {
|
|
14877
|
-
scrollTarget.addEventListener("scroll", handleScroll, {
|
|
14878
|
-
passive: true
|
|
14879
|
-
});
|
|
15017
|
+
scrollTarget.addEventListener("scroll", handleScroll, { passive: true });
|
|
14880
15018
|
cleanupCallbackSet.add(() => {
|
|
14881
15019
|
scrollTarget.removeEventListener("scroll", handleScroll, {
|
|
14882
|
-
passive: true
|
|
15020
|
+
passive: true,
|
|
14883
15021
|
});
|
|
14884
15022
|
});
|
|
14885
15023
|
}
|
|
14886
15024
|
}
|
|
15025
|
+
|
|
14887
15026
|
{
|
|
14888
15027
|
let animationFrame = null;
|
|
14889
15028
|
const resizeObserver = new ResizeObserver(() => {
|
|
@@ -14902,6 +15041,7 @@ const initPositionSticky = element => {
|
|
|
14902
15041
|
animationFrame = null;
|
|
14903
15042
|
});
|
|
14904
15043
|
}
|
|
15044
|
+
|
|
14905
15045
|
{
|
|
14906
15046
|
const mutationObserver = new MutationObserver(() => {
|
|
14907
15047
|
updateSize();
|
|
@@ -14909,12 +15049,13 @@ const initPositionSticky = element => {
|
|
|
14909
15049
|
mutationObserver.observe(element, {
|
|
14910
15050
|
childList: true,
|
|
14911
15051
|
subtree: true,
|
|
14912
|
-
characterData: true
|
|
15052
|
+
characterData: true,
|
|
14913
15053
|
});
|
|
14914
15054
|
cleanupCallbackSet.add(() => {
|
|
14915
15055
|
mutationObserver.disconnect();
|
|
14916
15056
|
});
|
|
14917
15057
|
}
|
|
15058
|
+
|
|
14918
15059
|
return cleanup;
|
|
14919
15060
|
};
|
|
14920
15061
|
|
|
@@ -15229,6 +15370,22 @@ const getVisibleViewportRect = () => {
|
|
|
15229
15370
|
};
|
|
15230
15371
|
};
|
|
15231
15372
|
|
|
15373
|
+
// The window is not always all the app's: an app simulating a narrower
|
|
15374
|
+
// screen (say a 600px column centered in a wide window) wants everything
|
|
15375
|
+
// pickPositionRelativeTo places against the viewport — dialogs, popovers,
|
|
15376
|
+
// side panels — kept to its own rectangle rather than the window's. The
|
|
15377
|
+
// embedder declares that rectangle here, as insets from the window's edges
|
|
15378
|
+
// ({ left, top, right, bottom } in px — see @jsenv/navi's
|
|
15379
|
+
// --navi-app-inset-*, the CSS twin of this). A callback rather than a
|
|
15380
|
+
// value so it stays live without a subscription; read on each placement.
|
|
15381
|
+
// Placement only: visibleRectEffect keeps reading the real viewport —
|
|
15382
|
+
// whether something is on screen is a fact about the window, not about
|
|
15383
|
+
// the rectangle an app confines itself to.
|
|
15384
|
+
let getPlacementViewportInsets = null;
|
|
15385
|
+
const setPlacementViewportInsets = (getInsets) => {
|
|
15386
|
+
getPlacementViewportInsets = getInsets;
|
|
15387
|
+
};
|
|
15388
|
+
|
|
15232
15389
|
const isEditableElement = (element) => {
|
|
15233
15390
|
if (!element) {
|
|
15234
15391
|
return false;
|
|
@@ -16268,12 +16425,24 @@ const pickPositionRelativeTo = (
|
|
|
16268
16425
|
} = {},
|
|
16269
16426
|
) => {
|
|
16270
16427
|
// Needed before hasValidAnchor below.
|
|
16271
|
-
|
|
16428
|
+
let {
|
|
16272
16429
|
left: viewportLeft,
|
|
16273
16430
|
top: viewportTop,
|
|
16274
16431
|
width: viewportWidth,
|
|
16275
16432
|
height: viewportHeight,
|
|
16276
16433
|
} = getVisibleViewportRect();
|
|
16434
|
+
// Narrowed to the embedder's own rectangle (see setPlacementViewportInsets)
|
|
16435
|
+
// before anything reads it: the available area, the flip decisions, the
|
|
16436
|
+
// clamps and the viewport-as-anchor rect below all follow, so an element
|
|
16437
|
+
// docked to an edge lands flush against the app's edge rather than the
|
|
16438
|
+
// window's.
|
|
16439
|
+
if (getPlacementViewportInsets) {
|
|
16440
|
+
const placementInsets = getPlacementViewportInsets();
|
|
16441
|
+
viewportLeft += placementInsets.left;
|
|
16442
|
+
viewportTop += placementInsets.top;
|
|
16443
|
+
viewportWidth -= placementInsets.left + placementInsets.right;
|
|
16444
|
+
viewportHeight -= placementInsets.top + placementInsets.bottom;
|
|
16445
|
+
}
|
|
16277
16446
|
|
|
16278
16447
|
// Resolved early: everything below that would otherwise reach for
|
|
16279
16448
|
// viewportLeft/Top/Width/Height instead uses these, so a "local" popover
|
|
@@ -20334,4 +20503,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
|
|
|
20334
20503
|
};
|
|
20335
20504
|
};
|
|
20336
20505
|
|
|
20337
|
-
export { EASING, ELEMENT_SIZE_CHANGE, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, applyNewPosition, canScroll, captureScrollState, chainEvent, claimWheelGesture, clickIsSuppressed, closestOpenableAncestor, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createEventGroupLogger, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dispatchCustomEvent, dispatchInternalCustomEvent, dispatchPublicCustomEvent, dragAfterIntent, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findEvent, findFocusDelegateTarget, findFocusable, findSelfOrAncestorFixedPosition, formatEventSideEffect, getAncestorOpenType, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getKeyboardEventDefaultAction, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPositioningScrollOffset, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollIntoViewScopedOffsets, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVirtualKeyboardOverlayHeight, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isAncestorOpen, isDisplayedDespiteClosedAncestor, isPressDisputedByDrag, isPrimaryButtonEvent, isSameColor, isScrollable, isTouchDrivenEvent, keepTouchRefusable, markDragSource, measureLongestVisualLineWidth, measureScrollbar, measureWidestChildRow, mergeOneStyle, mergeTwoStyles, normalizeKeyboardKey, normalizeStyle, normalizeStyles, observeAncestorOpenState, onAncestorReopen, parsePositionArea, parseStyle, performTabNavigation, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, releaseWheelGesture, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewThroughScrollables, scrollIntoViewWithStickyAwareness, scrollRoomTowards, setAttribute, setAttributes, setStyles, setVirtualKeyboardOverlaysContent, snapToPixel, startDragTo, startDragToResizeGesture, startDragToTravel, stickyAsRelativeCoords, stringifyStyle, subscribeVirtualKeyboardGeometryChange, subscribeVisualViewportResizeSettled, subscribeWindowResizeSettled, suppressClickAfterGesture, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect, waitForPressHeld, watchWheelTravel, wheelGestureIsTakenFrom };
|
|
20506
|
+
export { EASING, ELEMENT_SIZE_CHANGE, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, applyNewPosition, canScroll, captureScrollState, chainEvent, claimWheelGesture, clickIsSuppressed, closestOpenableAncestor, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createEventGroupLogger, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dispatchCustomEvent, dispatchInternalCustomEvent, dispatchPublicCustomEvent, dragAfterIntent, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findEvent, findFocusDelegateTarget, findFocusable, findSelfOrAncestorFixedPosition, formatEventSideEffect, getAncestorOpenType, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getKeyboardEventDefaultAction, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPositioningScrollOffset, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollIntoViewScopedOffsets, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVirtualKeyboardOverlayHeight, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isAncestorOpen, isDisplayedDespiteClosedAncestor, isPressDisputedByDrag, isPrimaryButtonEvent, isSameColor, isScrollable, isTouchDrivenEvent, keepTouchRefusable, markDragSource, measureLongestVisualLineWidth, measureScrollbar, measureWidestChildRow, mergeOneStyle, mergeTwoStyles, normalizeKeyboardKey, normalizeStyle, normalizeStyles, observeAncestorOpenState, onAncestorReopen, parsePositionArea, parseStyle, performTabNavigation, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, releaseWheelGesture, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewThroughScrollables, scrollIntoViewWithStickyAwareness, scrollRoomTowards, setAttribute, setAttributes, setPlacementViewportInsets, setStyles, setVirtualKeyboardOverlaysContent, snapToPixel, startDragTo, startDragToResizeGesture, startDragToTravel, stickyAsRelativeCoords, stringifyStyle, subscribeVirtualKeyboardGeometryChange, subscribeVisualViewportResizeSettled, subscribeWindowResizeSettled, suppressClickAfterGesture, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect, waitForPressHeld, watchWheelTravel, wheelGestureIsTakenFrom };
|