@@ -4475,6 +4475,20 @@ const waitForPressHeld = (
44754475) => {
44764476 const { pointerId, clientX, clientY } = pressEvent;
44774477
4478+ /* The one capture that says nothing about the press. A pointer that cannot
4479+ hover is given a capture the moment it lands, by the browser and to the
4480+ element it landed on, without anybody asking — and it is announced like any
4481+ other, just before the first pointer event that follows. EVERY touch press
4482+ has it, so it cannot be read as this one having been settled: read that way,
4483+ the wait dies at the first pixel of a finger that was only resting on the
4484+ glass, which is what a finger does. That is the hold that fails one time in
4485+ two, on the pointer this whole module exists for.
4486+ Asked rather than assumed, and asked HERE: the press is the one moment where
4487+ a capture can only be the browser's, since nothing has taken one yet. */
4488+ const implicitCaptureHolder = pressEvent.target.hasPointerCapture?.(pointerId)
4489+ ? pressEvent.target
4490+ : null;
4491+
44784492 const pressCleanupCallbacks = [];
44794493 // Who is still holding the refusal. The finger holds it because the finger is
44804494 // what the system's menu answers; the caller holds it from the moment the press
@@ -4598,6 +4612,12 @@ const waitForPressHeld = (
45984612 if (captureEvent.pointerId !== pointerId) {
45994613 return;
46004614 }
4615+ if (captureEvent.target === implicitCaptureHolder) {
4616+ // The capture the press was born with, not one somebody took (see above).
4617+ // A gesture taking that same element instead changes nothing for the
4618+ // browser, so it announces nothing, and there is nothing to miss here.
4619+ return;
4620+ }
46014621 cancelPress(captureEvent);
46024622 };
46034623 // On window rather than on the element: the finger can leave it, and the
@@ -15050,6 +15070,9 @@ const isPressDisputedByDrag = (element) => {
1505015070installImportMetaCssBuild(import.meta);
1505115071
1505215072const SURFACE_ATTRIBUTE = "data-pan-zoom-surface";
15073+ // The same word a carried element says while the gesture has it (see drag_to.js):
15074+ // a surface holding the hand is grabbed, and one thing held is like another.
15075+ const GRABBED_ATTRIBUTE = "data-grabbed";
1505315076
1505415077const css$1 = /* css */ `[data-pan-zoom-surface] {
1505515078 touch-action: none;
@@ -15089,6 +15112,13 @@ const YIELDED_SELECTOR = `${DRAG_EXCLUDED_SELECTOR},[data-drag-source],[data-dra
1508915112 * The zoom changed by `factor` (above 1 is in) around the point `x`/`y` of the
1509015113 * surface, measured inside its border. Left out, a wheel over the surface is
1509115114 * left to the page, and two fingers only pan.
15115+ * @param {(detail: {event: PointerEvent}) => void} [options.onGrab]
15116+ * The surface has the hand: the travel proved it, the hold landed, or a second
15117+ * pointer came down. Told once, before the first report, and `data-grabbed` is
15118+ * on the element for as long as it lasts.
15119+ * @param {(detail: {event: PointerEvent|undefined}) => void} [options.onRelease]
15120+ * The last pointer is gone — let go of, taken away, or the surface itself
15121+ * taken down under the hand, which is the one case with no event to show.
1509215122 * @param {number} [options.threshold=5] How far a pointer travels before it pans.
1509315123 * @param {boolean} [options.afterHold=false] Whether a FINGER must be held still
1509415124 * before it pans, the page keeping its scroll until then. For a surface
@@ -15097,7 +15127,7 @@ const YIELDED_SELECTOR = `${DRAG_EXCLUDED_SELECTOR},[data-drag-source],[data-dra
1509715127 */
1509815128const installPanZoom = (
1509915129 element,
15100- { onPan, onZoom, threshold = 5, afterHold } = {},
15130+ { onPan, onZoom, onGrab, onRelease, threshold = 5, afterHold } = {},
1510115131) => {
1510215132 element.setAttribute(SURFACE_ATTRIBUTE, afterHold ? "after-hold" : "");
1510315133 // A travelling box above must not take the press this reads (see
@@ -15142,14 +15172,20 @@ const installPanZoom = (
1514215172 return hand;
1514315173 };
1514415174
15145- const activate = (anchorWhere) => {
15175+ const activate = (anchorWhere, event ) => {
1514615176 active = true;
1514715177 for (const pointerId of pointers.keys()) {
1514815178 element.setPointerCapture(pointerId);
1514915179 }
1515015180 anchor = readHand(anchorWhere);
1515115181 // The click the release leaves behind is not for what is under the hand.
1515215182 disarmClickSuppression = suppressClickAfterGesture();
15183+ // The surface has the hand, and this is the only place that knows (see the
15184+ // top of this file). Said in the DOM first, so a stylesheet alone can draw
15185+ // it, and before the first report, so what is drawn is drawn before the
15186+ // surface has moved under it.
15187+ element.setAttribute(GRABBED_ATTRIBUTE, "");
15188+ onGrab?.({ event });
1515315189 };
1515415190
1515515191 const report = (event) => {
@@ -15168,7 +15204,7 @@ const installPanZoom = (
1516815204 anchor = hand;
1516915205 };
1517015206
15171- const end = () => {
15207+ const end = (event ) => {
1517215208 for (const pointer of pointers.values()) {
1517315209 pointer.holdWait?.cancel();
1517415210 }
@@ -15180,6 +15216,8 @@ const installPanZoom = (
1518015216 anchor = null;
1518115217 disarmClickSuppression();
1518215218 disarmClickSuppression = null;
15219+ element.removeAttribute(GRABBED_ATTRIBUTE);
15220+ onRelease?.({ event });
1518315221 }
1518415222 };
1518515223
@@ -15217,16 +15255,16 @@ const installPanZoom = (
1521715255 return;
1521815256 }
1521915257 if (pointers.size >= 2) {
15220- activate("now");
15258+ activate("now", event );
1522115259 return;
1522215260 }
1522315261 if (afterHold && event.pointerType === "touch") {
1522415262 pointer.waitsForHold = true;
1522515263 pointer.holdWait = waitForPressHeld(event, {
1522615264 // Anchored where the finger IS: it has barely moved, so there is
1522715265 // nothing to catch up with.
15228- onPressHeld: () => {
15229- activate("now");
15266+ onPressHeld: (pressEvent ) => {
15267+ activate("now", pressEvent );
1523015268 },
1523115269 });
1523215270 }
@@ -15253,7 +15291,7 @@ const installPanZoom = (
1525315291 // Anchored where the hand LANDED: the pixels that proved the intent are
1525415292 // replayed by the first report, so the surface catches up with the finger
1525515293 // rather than starting from under it.
15256- activate("start");
15294+ activate("start", event );
1525715295 }
1525815296 report(event);
1525915297 };
@@ -15269,7 +15307,7 @@ const installPanZoom = (
1526915307 // context menu back (see press_held.js).
1527015308 pointer.holdWait?.cancel();
1527115309 if (pointers.size === 0) {
15272- end();
15310+ end(event );
1527315311 return;
1527415312 }
1527515313 if (active) {
0 commit comments