@jsenv/dom 0.17.93 → 0.17.95

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.
Files changed (2) hide show
  1. package/dist/jsenv_dom.js +109 -48
  2. package/package.json +1 -1
package/dist/jsenv_dom.js CHANGED
@@ -13253,6 +13253,14 @@ const startDragToTravel = (
13253
13253
  if (movingAxis && !axesLeft.includes(movingAxis)) {
13254
13254
  return null;
13255
13255
  }
13256
+ // A link and an image are draggable without anyone asking, and the browser
13257
+ // starts that drag about 3px in, under the threshold below: it cancels the
13258
+ // pointer and keeps the rest of the press, so the travel never forms. Refused
13259
+ // for this press only — one excluded above, or left to a box inside, keeps it.
13260
+ const refuseNativeDrag = (dragStartEvent) => {
13261
+ dragStartEvent.preventDefault();
13262
+ };
13263
+ element.addEventListener("dragstart", refuseNativeDrag);
13256
13264
  const startThreshold =
13257
13265
  pointerDownEvent.pointerType === "mouse"
13258
13266
  ? DRAG_START_THRESHOLD
@@ -13278,6 +13286,7 @@ const startDragToTravel = (
13278
13286
  clearTimeout(holdTimeout);
13279
13287
  document.documentElement.removeAttribute(GESTURE_ATTRIBUTE);
13280
13288
  document.documentElement.removeAttribute(WALKING_ATTRIBUTE);
13289
+ element.removeEventListener("dragstart", refuseNativeDrag);
13281
13290
  window.removeEventListener("pointerup", onPressOver);
13282
13291
  window.removeEventListener("pointercancel", onPressOver);
13283
13292
  };
@@ -14091,10 +14100,11 @@ const findPanZoomSurface = (element) => {
14091
14100
  * @param {boolean|"kept"} [options.afterHold=false] Whether a FINGER must be held
14092
14101
  * still before it pans, the page keeping its scroll until then. For a surface
14093
14102
  * standing in something that scrolls; a mouse pans by travelling either way.
14094
- * `"kept"` asks for the wait once: from the moment the surface has the hand it
14095
- * pans on contact, and it asks again only after a pointer has gone down away
14096
- * from it. `data-hand-kept` is on the element for as long as it holds the hand
14097
- * that way, a hand touching it or not.
14103
+ * `"kept"` asks for the wait once: from the moment a finger has given the
14104
+ * surface the hand it pans on contact, and it asks again only after a pointer
14105
+ * has gone down away from it. `data-hand-kept` is on the element for as long
14106
+ * as it holds the hand that way, a hand touching it or not; a mouse pan never
14107
+ * sets it.
14098
14108
  * @param {"auto"|"always"} [options.wheelZoom="auto"] Whether a BARE wheel zooms.
14099
14109
  * `"auto"` gives it to whatever scrolls around the surface when there is one,
14100
14110
  * and zooms when there is none; `"always"` takes it back, for a surface that
@@ -14115,8 +14125,8 @@ const installPanZoom = (
14115
14125
  } = {},
14116
14126
  ) => {
14117
14127
  // Whether a finger still owes the wait. Constant under a plain `afterHold`;
14118
- // under `"kept"` it is spent the first time the surface is given the hand and
14119
- // asked for again once the hand has gone elsewhere.
14128
+ // under `"kept"` it is spent the first time a finger gives the surface the hand
14129
+ // and asked for again once the hand has gone elsewhere.
14120
14130
  let holdIsOwed = Boolean(afterHold);
14121
14131
  const reflectHoldOwed = () => {
14122
14132
  // What a touch may do is settled before it lands, so the mode is in the DOM
@@ -14195,10 +14205,14 @@ const installPanZoom = (
14195
14205
 
14196
14206
  const activate = (anchorWhere, event) => {
14197
14207
  active = true;
14198
- if (afterHold === "kept" && holdIsOwed && onPan) {
14199
- // Asked for and given: whatever proved it — the hold, a second finger, a
14200
- // mouse travelling — the surface is the hand's from here. What it keeps
14201
- // is the pan on contact, which a surface that does not pan has none of.
14208
+ if (
14209
+ afterHold === "kept" &&
14210
+ [...pointers.values()].some((pointer) => pointer.waitsForHold)
14211
+ ) {
14212
+ // Spent by the finger that owed it, whether the hold or a second finger
14213
+ // proved it: the surface is the hand's from here. A mouse never owed the
14214
+ // wait, so it neither spends it nor marks it — and a surface that does not
14215
+ // pan asks no finger to wait, having no pan on contact to keep.
14202
14216
  keepTheHand();
14203
14217
  }
14204
14218
  for (const pointer of pointers.values()) {
@@ -14379,6 +14393,16 @@ const installPanZoom = (
14379
14393
  }
14380
14394
  };
14381
14395
 
14396
+ // A link and an image are draggable without anyone asking, and the browser
14397
+ // starts that drag about 3px in, under `threshold`: it cancels the pointer and
14398
+ // keeps the rest of the press, so the pan never forms. Refused only while the
14399
+ // surface reads a press — one yielded to something inside never got here.
14400
+ const refuseNativeDrag = (dragStartEvent) => {
14401
+ if (pointers.size > 0) {
14402
+ dragStartEvent.preventDefault();
14403
+ }
14404
+ };
14405
+
14382
14406
  // What the first event of the burst going on settled: a key let go of (or
14383
14407
  // pressed) halfway through must not hand a zoom to the page mid-gesture, and
14384
14408
  // a burst has no end but a silence.
@@ -14448,6 +14472,10 @@ const installPanZoom = (
14448
14472
  element.addEventListener("touchmove", preventTouchScroll, {
14449
14473
  passive: false,
14450
14474
  });
14475
+ // Without a pan, one pointer is whatever else reads it, its drag included.
14476
+ if (onPan) {
14477
+ element.addEventListener("dragstart", refuseNativeDrag);
14478
+ }
14451
14479
  if (onZoom) {
14452
14480
  element.addEventListener("wheel", onWheel, { passive: false });
14453
14481
  }
@@ -14459,6 +14487,7 @@ const installPanZoom = (
14459
14487
  element.removeEventListener("pointerdown", onPointerDown);
14460
14488
  element.removeEventListener("lostpointercapture", onLostPointerCapture);
14461
14489
  element.removeEventListener("touchmove", preventTouchScroll);
14490
+ element.removeEventListener("dragstart", refuseNativeDrag);
14462
14491
  element.removeEventListener("wheel", onWheel);
14463
14492
  window.removeEventListener("pointerdown", onPointerDownAway, true);
14464
14493
  element.removeAttribute(SURFACE_ATTRIBUTE);
@@ -16384,62 +16413,94 @@ const areLevel = (a, b) => {
16384
16413
 
16385
16414
  /**
16386
16415
  * The item the copy is at, and its `side` in the order of the list: "start" for
16387
- * before it, "end" for after it. Down a list, read by the copy's leading edge
16388
- * (see getDropTargetInfo); along a row, by its middle against the item's — how
16389
- * high the copy is says nothing there, and the leading edge assumes a copy as
16390
- * large as what it passes over: items of a row are as wide as their content,
16391
- * and one wider than its neighbour could never be put after it.
16392
- *
16393
- * Beyond an end of the list, over no item, is a place too: above the first line
16394
- * or below the last, and along a row beside the first item or past the last.
16416
+ * before it, "end" for after it.
16417
+ *
16418
+ * Down a list, the item under the copy, read by the copy's leading edge (see
16419
+ * getDropTargetInfo); over no item, above the first one is the start of the
16420
+ * list and below the last one its end.
16421
+ *
16422
+ * Along a row, the copy's middle decides, the way a click puts the caret in a
16423
+ * text: on the nearest line, before the first item whose middle it has not
16424
+ * reached. How high the copy is says which line and nothing more, its size
16425
+ * nothing at all — the leading edge assumes a copy as large as what it passes
16426
+ * over, and items of a row are as wide as their content: read by its edge, one
16427
+ * wider than its neighbour could never be put after it.
16395
16428
  */
16396
16429
  const findPlaceInList = (gestureInfo, items, flow) => {
16397
16430
  const dragElementRect = gestureInfo.elementImpacted.getBoundingClientRect();
16431
+ if (flow !== "down") {
16432
+ return findPlaceOnNearestLine(dragElementRect, items, flow);
16433
+ }
16398
16434
  const dropTargetInfo = getDropTargetInfo(gestureInfo, items);
16399
16435
  if (dropTargetInfo) {
16400
- const { element } = dropTargetInfo;
16401
- if (flow === "down") {
16402
- return { element, side: dropTargetInfo.elementSide.y };
16403
- }
16404
- const elementRect = element.getBoundingClientRect();
16405
- const dragMiddle = dragElementRect.left + dragElementRect.width / 2;
16406
- const elementMiddle = elementRect.left + elementRect.width / 2;
16407
- const beforeItsMiddle =
16408
- flow === "right"
16409
- ? dragMiddle < elementMiddle
16410
- : dragMiddle > elementMiddle;
16411
- return { element, side: beforeItsMiddle ? "start" : "end" };
16436
+ return {
16437
+ element: dropTargetInfo.element,
16438
+ side: dropTargetInfo.elementSide.y,
16439
+ };
16412
16440
  }
16413
16441
  const firstItem = items[0];
16414
16442
  if (!firstItem) {
16415
16443
  return null;
16416
16444
  }
16417
16445
  const lastItem = items[items.length - 1];
16418
- const firstRect = firstItem.getBoundingClientRect();
16419
- const lastRect = lastItem.getBoundingClientRect();
16420
- if (
16421
- dragElementRect.bottom < firstRect.top ||
16422
- (flow !== "down" &&
16423
- areLevel(dragElementRect, firstRect) &&
16424
- isBeyond(dragElementRect, firstRect, flow === "right" ? "left" : "right"))
16425
- ) {
16446
+ if (dragElementRect.bottom < firstItem.getBoundingClientRect().top) {
16426
16447
  return { element: firstItem, side: "start" };
16427
16448
  }
16428
- if (
16429
- dragElementRect.top > lastRect.bottom ||
16430
- (flow !== "down" &&
16431
- areLevel(dragElementRect, lastRect) &&
16432
- isBeyond(dragElementRect, lastRect, flow))
16433
- ) {
16449
+ if (dragElementRect.top > lastItem.getBoundingClientRect().bottom) {
16434
16450
  return { element: lastItem, side: "end" };
16435
16451
  }
16436
16452
  return null;
16437
16453
  };
16438
16454
 
16439
- const isBeyond = (dragElementRect, itemRect, towards) => {
16440
- return towards === "left"
16441
- ? dragElementRect.right < itemRect.left
16442
- : dragElementRect.left > itemRect.right;
16455
+ const findPlaceOnNearestLine = (dragElementRect, items, flow) => {
16456
+ const lines = [];
16457
+ let previousRect;
16458
+ for (const item of items) {
16459
+ const rect = item.getBoundingClientRect();
16460
+ // A box that takes no room stands on no line (see readItemFlow).
16461
+ if (rect.width === 0 && rect.height === 0) {
16462
+ continue;
16463
+ }
16464
+ if (!previousRect || !areLevel(previousRect, rect)) {
16465
+ lines.push({ top: rect.top, bottom: rect.bottom, entries: [] });
16466
+ }
16467
+ const line = lines[lines.length - 1];
16468
+ if (rect.top < line.top) {
16469
+ line.top = rect.top;
16470
+ }
16471
+ if (rect.bottom > line.bottom) {
16472
+ line.bottom = rect.bottom;
16473
+ }
16474
+ line.entries.push({ item, middle: rect.left + rect.width / 2 });
16475
+ previousRect = rect;
16476
+ }
16477
+ const dragMiddleY = dragElementRect.top + dragElementRect.height / 2;
16478
+ let nearestLine;
16479
+ let nearestDistance;
16480
+ for (const line of lines) {
16481
+ const distance =
16482
+ dragMiddleY < line.top
16483
+ ? line.top - dragMiddleY
16484
+ : dragMiddleY > line.bottom
16485
+ ? dragMiddleY - line.bottom
16486
+ : 0;
16487
+ if (nearestLine === undefined || distance < nearestDistance) {
16488
+ nearestLine = line;
16489
+ nearestDistance = distance;
16490
+ }
16491
+ }
16492
+ if (!nearestLine) {
16493
+ return null;
16494
+ }
16495
+ const dragMiddleX = dragElementRect.left + dragElementRect.width / 2;
16496
+ for (const { item, middle } of nearestLine.entries) {
16497
+ const notReached =
16498
+ flow === "right" ? middle > dragMiddleX : middle < dragMiddleX;
16499
+ if (notReached) {
16500
+ return { element: item, side: "start" };
16501
+ }
16502
+ }
16503
+ return { element: nearestLine.entries.at(-1).item, side: "end" };
16443
16504
  };
16444
16505
 
16445
16506
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/dom",
3
- "version": "0.17.93",
3
+ "version": "0.17.95",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {