Skip to content

Commit 6ca5a21

Browse files
committed
work
1 parent 49902b7 commit 6ca5a21

17 files changed

Lines changed: 658 additions & 84 deletions

packages/frontend/dom/dist/jsenv_dom.js

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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) => {
1505015070
installImportMetaCssBuild(import.meta);
1505115071

1505215072
const 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

1505415077
const 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
*/
1509815128
const 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) {

packages/frontend/dom/src/interaction/drag/pan_zoom.js

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@
3030
* landing is intent enough on its own. The first pixels are not lost: the
3131
* surface catches up with the finger the moment the pan begins.
3232
*
33+
* WHEN IT HAS THE HAND is told, and it is the one thing nobody else can see. The
34+
* capture taken at that instant is not that word: the browser announces one just
35+
* before the NEXT pointer event, so a finger held still and then kept still is
36+
* announced nothing at all, and a finger that moves hears it at the same moment
37+
* the surface is already panning under it — the answer arrives with the movement
38+
* it was supposed to precede. So `onGrab` says it where it happens, and
39+
* `data-grabbed` says the same in the DOM, since what it is usually for is a
40+
* contour and a veil.
41+
*
3342
* A SURFACE STANDING IN SOMETHING THAT SCROLLS: `afterHold`.
3443
*
3544
* Travel is only unambiguous where there is no scroll to tell it apart from. A
@@ -54,6 +63,9 @@ import { isPrimaryButtonEvent } from "./drag_gesture.js";
5463
import { DRAG_EXCLUDED_SELECTOR } from "./drag_to_travel.js";
5564

5665
const SURFACE_ATTRIBUTE = "data-pan-zoom-surface";
66+
// The same word a carried element says while the gesture has it (see drag_to.js):
67+
// a surface holding the hand is grabbed, and one thing held is like another.
68+
const GRABBED_ATTRIBUTE = "data-grabbed";
5769

5870
const css = /* css */ `
5971
[data-pan-zoom-surface] {
@@ -106,6 +118,13 @@ const YIELDED_SELECTOR = `${DRAG_EXCLUDED_SELECTOR},[data-drag-source],[data-dra
106118
* The zoom changed by `factor` (above 1 is in) around the point `x`/`y` of the
107119
* surface, measured inside its border. Left out, a wheel over the surface is
108120
* left to the page, and two fingers only pan.
121+
* @param {(detail: {event: PointerEvent}) => void} [options.onGrab]
122+
* The surface has the hand: the travel proved it, the hold landed, or a second
123+
* pointer came down. Told once, before the first report, and `data-grabbed` is
124+
* on the element for as long as it lasts.
125+
* @param {(detail: {event: PointerEvent|undefined}) => void} [options.onRelease]
126+
* The last pointer is gone — let go of, taken away, or the surface itself
127+
* taken down under the hand, which is the one case with no event to show.
109128
* @param {number} [options.threshold=5] How far a pointer travels before it pans.
110129
* @param {boolean} [options.afterHold=false] Whether a FINGER must be held still
111130
* before it pans, the page keeping its scroll until then. For a surface
@@ -114,7 +133,7 @@ const YIELDED_SELECTOR = `${DRAG_EXCLUDED_SELECTOR},[data-drag-source],[data-dra
114133
*/
115134
export const installPanZoom = (
116135
element,
117-
{ onPan, onZoom, threshold = 5, afterHold } = {},
136+
{ onPan, onZoom, onGrab, onRelease, threshold = 5, afterHold } = {},
118137
) => {
119138
element.setAttribute(SURFACE_ATTRIBUTE, afterHold ? "after-hold" : "");
120139
// A travelling box above must not take the press this reads (see
@@ -159,14 +178,20 @@ export const installPanZoom = (
159178
return hand;
160179
};
161180

162-
const activate = (anchorWhere) => {
181+
const activate = (anchorWhere, event) => {
163182
active = true;
164183
for (const pointerId of pointers.keys()) {
165184
element.setPointerCapture(pointerId);
166185
}
167186
anchor = readHand(anchorWhere);
168187
// The click the release leaves behind is not for what is under the hand.
169188
disarmClickSuppression = suppressClickAfterGesture();
189+
// The surface has the hand, and this is the only place that knows (see the
190+
// top of this file). Said in the DOM first, so a stylesheet alone can draw
191+
// it, and before the first report, so what is drawn is drawn before the
192+
// surface has moved under it.
193+
element.setAttribute(GRABBED_ATTRIBUTE, "");
194+
onGrab?.({ event });
170195
};
171196

172197
const report = (event) => {
@@ -185,7 +210,7 @@ export const installPanZoom = (
185210
anchor = hand;
186211
};
187212

188-
const end = () => {
213+
const end = (event) => {
189214
for (const pointer of pointers.values()) {
190215
pointer.holdWait?.cancel();
191216
}
@@ -197,6 +222,8 @@ export const installPanZoom = (
197222
anchor = null;
198223
disarmClickSuppression();
199224
disarmClickSuppression = null;
225+
element.removeAttribute(GRABBED_ATTRIBUTE);
226+
onRelease?.({ event });
200227
}
201228
};
202229

@@ -234,16 +261,16 @@ export const installPanZoom = (
234261
return;
235262
}
236263
if (pointers.size >= 2) {
237-
activate("now");
264+
activate("now", event);
238265
return;
239266
}
240267
if (afterHold && event.pointerType === "touch") {
241268
pointer.waitsForHold = true;
242269
pointer.holdWait = waitForPressHeld(event, {
243270
// Anchored where the finger IS: it has barely moved, so there is
244271
// nothing to catch up with.
245-
onPressHeld: () => {
246-
activate("now");
272+
onPressHeld: (pressEvent) => {
273+
activate("now", pressEvent);
247274
},
248275
});
249276
}
@@ -270,7 +297,7 @@ export const installPanZoom = (
270297
// Anchored where the hand LANDED: the pixels that proved the intent are
271298
// replayed by the first report, so the surface catches up with the finger
272299
// rather than starting from under it.
273-
activate("start");
300+
activate("start", event);
274301
}
275302
report(event);
276303
};
@@ -286,7 +313,7 @@ export const installPanZoom = (
286313
// context menu back (see press_held.js).
287314
pointer.holdWait?.cancel();
288315
if (pointers.size === 0) {
289-
end();
316+
end(event);
290317
return;
291318
}
292319
if (active) {

0 commit comments

Comments
 (0)