@jsenv/dom 0.17.19 → 0.17.21

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 +271 -66
  2. package/package.json +1 -1
package/dist/jsenv_dom.js CHANGED
@@ -261,11 +261,22 @@ const chainEvent = (customEvent, parentEvent) => {
261
261
  if (!parentEvent) {
262
262
  return customEvent;
263
263
  }
264
- if (!customEvent.detail) {
265
- console.warn(
266
- `Event "${customEvent.type}" has no detail object. Cannot chain to parent event "${parentEvent.type}".`,
267
- );
268
- return customEvent;
264
+ if (!customEvent.detail || typeof customEvent.detail !== "object") {
265
+ // A native event has nowhere to hang the chain: `Event` has no detail at
266
+ // all and `UIEvent` (so `InputEvent` too) exposes it as a readonly number.
267
+ // Give it an own detail object, shadowing the prototype getter, so a
268
+ // synthetic event dispatched on behalf of a gesture can still say what
269
+ // caused it.
270
+ if (nativeDetailHasMeaning(customEvent)) {
271
+ console.warn(
272
+ `Chaining "${customEvent.type}" to "${parentEvent.type}" replaces its native detail (${customEvent.detail}), which carries the click count on this event type. Chain a custom event instead, or read the click count before chaining.`,
273
+ );
274
+ }
275
+ Object.defineProperty(customEvent, "detail", {
276
+ value: {},
277
+ configurable: true,
278
+ enumerable: true,
279
+ });
269
280
  }
270
281
  // Always build eventChain from the first wrapping so callers can rely on it
271
282
  // being present whenever `parentEvent` is set.
@@ -309,6 +320,24 @@ const findEvent = (event, predicate) => {
309
320
  return undefined;
310
321
  };
311
322
 
323
+ // `detail` is a click count on the pointer events that define one, and 0
324
+ // everywhere else (`input`, `focus`, `wheel`…). Overwriting it there loses the
325
+ // only way to tell a real click from a keyboard/programmatic one (detail === 0),
326
+ // so those events must not be chained.
327
+ const EVENT_TYPES_WITH_MEANINGFUL_DETAIL = new Set([
328
+ "click",
329
+ "auxclick",
330
+ "dblclick",
331
+ "mousedown",
332
+ "mouseup",
333
+ ]);
334
+ const nativeDetailHasMeaning = (event) => {
335
+ if (EVENT_TYPES_WITH_MEANINGFUL_DETAIL.has(event.type)) {
336
+ return true;
337
+ }
338
+ return typeof event.detail === "number" && event.detail !== 0;
339
+ };
340
+
312
341
  const resolveEventPredicate = (predicate) => {
313
342
  if (typeof predicate === "string") {
314
343
  return (e) => e.type === predicate;
@@ -9117,12 +9146,16 @@ const css$4 = /* css */`
9117
9146
  /* A source taken by long press must let the scroll through until the grab —
9118
9147
  which is exactly what the long press is there to tell apart. Zoom has
9119
9148
  nothing to do with the gesture and nobody should lose it by resting a
9120
- finger on a word. */
9149
+ finger on a word.
9150
+
9151
+ Vertical, because that is the way the page and the lists in it go: a
9152
+ source dragged along one axis is surrounded by something scrolling along
9153
+ that same axis (a row of a list runs the way the list scrolls), and a
9154
+ source dragged both ways sits on the usual vertical page. */
9121
9155
  touch-action: pan-y pinch-zoom;
9122
9156
  }
9123
9157
  [data-drag-source="x"] {
9124
- /* The axis is the one thing the caller has to say, being the only one who
9125
- knows which way what surrounds the source scrolls. */
9158
+ /* …and the sideways one, for the same reason read the other way. */
9126
9159
  touch-action: pan-x pinch-zoom;
9127
9160
  }
9128
9161
  [data-drag-on-contact] [data-drag-source],
@@ -9158,23 +9191,36 @@ import.meta.css = [css$4, "@jsenv/dom/src/interaction/drag/drag_after_intent.js"
9158
9191
  *
9159
9192
  * On the element and not on the window, so the rest of the page keeps its
9160
9193
  * touches on the compositor's fast path.
9194
+ *
9195
+ * Exported because a drag does not always begin on a drag source: a copy caught
9196
+ * on its way home is pressed through the pictures of a view transition, and the
9197
+ * touch lands on the document root (see letCopyBeCaught in drag_to.js). Same
9198
+ * rule, other element — and it has to be the same function, or the listener put
9199
+ * down is not the one taken back off.
9161
9200
  */
9162
9201
  const keepTouchRefusable = () => {
9163
9202
  // Being registered IS the whole of it — see above.
9164
9203
  };
9165
9204
 
9166
9205
  /**
9167
- * Says an element is something a drag can start from.
9206
+ * Says an element is something a drag can start from, and which way that drag
9207
+ * goes.
9208
+ *
9209
+ * The axes are written in the DOM rather than kept here because they are what
9210
+ * someone ELSE reads: a box above this one that travels under the same finger
9211
+ * (a row of slides, a sheet pushed down to close it) has to know which axes are
9212
+ * already spoken for before it answers the press — the same thing a travel says
9213
+ * about itself with `data-travel-by-drag`. It is also what leaves the browser
9214
+ * the pan it may still do until the grab (see the stylesheet above).
9168
9215
  *
9169
9216
  * @param {Element} element
9170
- * @param {string} [axes]
9171
- * Which way the SURROUNDINGS scroll, so the other axis is left to them until
9172
- * the grab: `"x"` for a source inside something travelling sideways, anything
9173
- * else for the usual vertical page.
9217
+ * @param {"x"|"y"|"xy"} [axes="xy"]
9218
+ * Which way the drag walks. A list reordered along its own line says `"y"`;
9219
+ * something carried across a board, or thrown, goes both ways.
9174
9220
  * @returns {function} Takes the mark back off.
9175
9221
  */
9176
- const markDragSource = (element, axes) => {
9177
- element.setAttribute("data-drag-source", axes === "x" ? "x" : "");
9222
+ const markDragSource = (element, axes = "xy") => {
9223
+ element.setAttribute("data-drag-source", axes);
9178
9224
  element.addEventListener("touchmove", keepTouchRefusable, {
9179
9225
  passive: false
9180
9226
  });
@@ -11569,6 +11615,18 @@ const css$1 = /* css */`
11569
11615
  pointer-events: auto;
11570
11616
  }
11571
11617
 
11618
+ /* …and a FINGER reaching for it does not land on it: the pictures of the
11619
+ transition cover the page, so as far as the browser is concerned the touch
11620
+ began on the document root. What a touch may do is decided there and at that
11621
+ moment, so the root says it for as long as the copy can be caught — the pan
11622
+ is ours (nothing should scroll while something is landing), zoom stays the
11623
+ reader's. Half of a pair: without the non-passive listener put down at the
11624
+ same moment (see letCopyBeCaught) every touchmove arrives already
11625
+ non-cancelable and refusing it does nothing. */
11626
+ [data-drag-catchable] {
11627
+ touch-action: pinch-zoom;
11628
+ }
11629
+
11572
11630
  /* Ce qui a été lancé: il continue dans la direction du geste jusqu'à sortir de
11573
11631
  l'écran, et revient par le même chemin si la réponse refuse. */
11574
11632
  [navi-drag-clone-wrapper][data-tossed] {
@@ -12514,7 +12572,15 @@ const startDragToCarryCopy = (event, {
12514
12572
  return dragGesture;
12515
12573
  }, {
12516
12574
  threshold,
12517
- longPress,
12575
+ // A copy caught on its way home is not an ambiguous press: the hand
12576
+ // reached for something moving, and the press was already matched
12577
+ // against the copy's own box before it got here. The wait a finger is
12578
+ // asked for elsewhere tells a scroll from a drag, and there is no scroll
12579
+ // to tell it from — the copy covers that spot from the top layer. Asked
12580
+ // for anyway it cannot even be answered: the wait is about as long as the
12581
+ // journey, so the thing is home before the proof is done, while a mouse
12582
+ // takes it in five pixels.
12583
+ longPress: cloneWrapperCaught ? false : longPress,
12518
12584
  longPressDelay,
12519
12585
  longPressSlop,
12520
12586
  onPressStart,
@@ -12704,6 +12770,17 @@ const letCopyBeCaught = (cloneWrapper, carryAgain) => {
12704
12770
  // gesture holds what it grabbed rather than whatever was behind.
12705
12771
  cloneWrapper.setAttribute("data-catchable", "");
12706
12772
  document.addEventListener("pointerdown", onPointerDown, true);
12773
+ // The touch half of the same reach, said on the root because that is where a
12774
+ // finger pressing through the pictures lands (see the stylesheet). Both go
12775
+ // down before the copy sets off, since what a touch may do is settled when it
12776
+ // begins: put down later, the press is still read, the carry still starts, and
12777
+ // the browser cancels the pointer one move afterwards — a copy that cannot be
12778
+ // caught with a finger and can with a mouse.
12779
+ const root = document.documentElement;
12780
+ root.setAttribute("data-drag-catchable", "");
12781
+ root.addEventListener("touchmove", keepTouchRefusable, {
12782
+ passive: false
12783
+ });
12707
12784
  return {
12708
12785
  settled: async () => {
12709
12786
  // A hand that lets go and presses again while the copy is still there is
@@ -12715,6 +12792,8 @@ const letCopyBeCaught = (cloneWrapper, carryAgain) => {
12715
12792
  }
12716
12793
  cloneWrapper.removeAttribute("data-catchable");
12717
12794
  document.removeEventListener("pointerdown", onPointerDown, true);
12795
+ root.removeAttribute("data-drag-catchable");
12796
+ root.removeEventListener("touchmove", keepTouchRefusable);
12718
12797
  return caught;
12719
12798
  }
12720
12799
  };
@@ -13006,12 +13085,13 @@ const DRAG_FLICK_DISTANCE = 8;
13006
13085
  // way. Let go and it comes back — a wall one can lean on, never walk through.
13007
13086
  const DRAG_RESISTANCE = 0.3;
13008
13087
 
13009
- // What a drag must not start on: something that reads the pointer itself. A
13010
- // button or a link is not in the list — dragging from one travels, and the
13011
- // click it would have made is swallowed on the way out. A drag source is: it
13012
- // answers the same press, and a travel starting there takes the pointer capture
13013
- // away from a gesture already carrying something.
13014
- const DRAG_EXCLUDED_SELECTOR = ["input", "textarea", "select", '[contenteditable=""]', '[contenteditable="true"]', "[data-drag-source]", "[data-drag-handle]", "[data-no-drag-travel]"].join(",");
13088
+ // What a drag must not start on: something that reads the pointer itself, whole,
13089
+ // with no axis left to share. A button or a link is not in the list — dragging
13090
+ // from one travels, and the click it would have made is swallowed on the way
13091
+ // out. A drag SOURCE is not either: it says which way it goes and only takes
13092
+ // that (see DRAG_SOURCE_AXES_ATTRIBUTE) — but a dedicated handle is, being a
13093
+ // place whose only purpose is to be taken hold of, from the first pixel.
13094
+ const DRAG_EXCLUDED_SELECTOR = ["input", "textarea", "select", '[contenteditable=""]', '[contenteditable="true"]', "[data-drag-handle]", "[data-no-drag-travel]"].join(",");
13015
13095
 
13016
13096
  // Which axes a box travels on, one attribute per gesture, said in the DOM by
13017
13097
  // whoever owns the box: it is what a box ABOVE another reads to know the
@@ -13019,6 +13099,12 @@ const DRAG_EXCLUDED_SELECTOR = ["input", "textarea", "select", '[contenteditable
13019
13099
  // from the outside.
13020
13100
  const DRAG_AXES_ATTRIBUTE = "data-travel-by-drag";
13021
13101
  const WHEEL_AXES_ATTRIBUTE = "data-travel-by-wheel";
13102
+ // The same thing said by something that is PICKED UP rather than travelled: a
13103
+ // row taken out of a list, a card carried across a board (see markDragSource).
13104
+ // It holds the pointer from the press exactly as a nested travel does, so it is
13105
+ // read exactly as one — a list reordered along its own line takes the axis it
13106
+ // runs on and leaves the other to whoever is above.
13107
+ const DRAG_SOURCE_AXES_ATTRIBUTE = "data-drag-source";
13022
13108
 
13023
13109
  // A surface the browser paints in the top layer: it is still a DOM descendant
13024
13110
  // of whatever it was written in, and it is nowhere near it on screen — it
@@ -13234,10 +13320,12 @@ const startDragToTravel = (pointerDownEvent, {
13234
13320
  if (!target.closest || target.closest(DRAG_EXCLUDED_SELECTOR)) {
13235
13321
  return null;
13236
13322
  }
13237
- // A box between the finger and this one that travels the same way: the
13238
- // gesture is its, and this one is left with the axes it does not walk — none
13239
- // at all, most of the time, and then there is no gesture here to read.
13240
- const axesLeft = axesLeftBy(axes, target, element, DRAG_AXES_ATTRIBUTE);
13323
+ // A box between the finger and this one that travels the same way, and then
13324
+ // anything between them that is picked up and carried the same way: the
13325
+ // gesture is theirs, and this one is left with the axes none of them walks
13326
+ // none at all, most of the time, and then there is no gesture here to read.
13327
+ const axesLeftByTravels = axesLeftBy(axes, target, element, DRAG_AXES_ATTRIBUTE);
13328
+ const axesLeft = axesLeftByTravels && axesLeftBy(axesLeftByTravels, target, element, DRAG_SOURCE_AXES_ATTRIBUTE);
13241
13329
  if (!axesLeft) {
13242
13330
  return null;
13243
13331
  }
@@ -14275,6 +14363,90 @@ const stickyAsRelativeCoords = (
14275
14363
  return [leftPosition, topPosition];
14276
14364
  };
14277
14365
 
14366
+ /**
14367
+ * The on-screen keyboard, when the app takes it over.
14368
+ *
14369
+ * By default a mobile browser answers the keyboard by shrinking the VISUAL
14370
+ * viewport, and everything sized against that viewport follows for free —
14371
+ * which is what the whole positioning layer here already relies on (see
14372
+ * pickPositionRelativeTo's own visualViewport reads). The VirtualKeyboard API
14373
+ * (Chromium only — no Firefox, no Safari) offers the other deal:
14374
+ * `overlaysContent = true` and the keyboard stops resizing anything, painting
14375
+ * over the page instead, while its geometry becomes readable — `boundingRect`
14376
+ * and a `geometrychange` event here, `env(keyboard-inset-*)` in CSS.
14377
+ *
14378
+ * That deal has to be taken whole: the instant the viewport stops shrinking,
14379
+ * whoever was sizing against it is sizing against a rectangle the keyboard now
14380
+ * covers. So this module answers ONE question — how many pixels at the bottom
14381
+ * of the visual viewport the keyboard covers — and the positioning layer
14382
+ * subtracts it. The answer is 0 in every other case (unsupported, never opted
14383
+ * in, keyboard closed), which is exactly what makes the two paths one path:
14384
+ * where the browser shrinks the viewport itself, there is nothing left to
14385
+ * subtract.
14386
+ *
14387
+ * Why take the deal at all, then, if the outcome is meant to match? Because a
14388
+ * resizing viewport is a resize of EVERYTHING, whether or not it had anything
14389
+ * to do with the field being typed into — the page reflows, fixed bars move,
14390
+ * and a mobile browser fires that resize transiently as focus goes from one
14391
+ * input to the next. Overlaying leaves the layout alone and hands over a
14392
+ * number instead. So navi takes it by default (see its own index.js) and only
14393
+ * offers a way back out, for an app whose own layout was built around the
14394
+ * viewport shrinking.
14395
+ */
14396
+
14397
+ const virtualKeyboard = window.navigator.virtualKeyboard;
14398
+
14399
+ /**
14400
+ * Whether the keyboard overlays the content instead of resizing the viewport.
14401
+ * Returns whether it applies at all — false means the browser has no
14402
+ * VirtualKeyboard API and keeps shrinking the visual viewport, which is the
14403
+ * behavior everything here already follows, so there is nothing to report to
14404
+ * the caller beyond "not this way".
14405
+ */
14406
+ const setVirtualKeyboardOverlaysContent = (value) => {
14407
+ if (!virtualKeyboard) {
14408
+ return false;
14409
+ }
14410
+ virtualKeyboard.overlaysContent = value;
14411
+ return true;
14412
+ };
14413
+
14414
+ /**
14415
+ * How many pixels at the bottom of the visual viewport the keyboard currently
14416
+ * covers — 0 unless the app opted in above AND the keyboard is up.
14417
+ *
14418
+ * `boundingRect` is all-zero when the keyboard is hidden, and also while
14419
+ * `overlaysContent` is false: a keyboard that resized the viewport covers
14420
+ * nothing that is left of it, so the zero is the right answer rather than a
14421
+ * missing one.
14422
+ */
14423
+ const getVirtualKeyboardOverlayHeight = () => {
14424
+ if (!virtualKeyboard) {
14425
+ return 0;
14426
+ }
14427
+ const { height } = virtualKeyboard.boundingRect;
14428
+ return height > 0 ? height : 0;
14429
+ };
14430
+
14431
+ /**
14432
+ * Calls `callback` whenever the keyboard shows, hides or resizes. Returns an
14433
+ * unsubscribe function; a no-op (never calls back) without support.
14434
+ *
14435
+ * Undebounced on purpose, unlike window/visualViewport resize
14436
+ * (window_size.js): "geometrychange" is not the transient storm those are —
14437
+ * it fires on the keyboard itself changing, not on the layout reacting to it,
14438
+ * which is the whole point of overlaying.
14439
+ */
14440
+ const subscribeVirtualKeyboardGeometryChange = (callback) => {
14441
+ if (!virtualKeyboard) {
14442
+ return () => {};
14443
+ }
14444
+ virtualKeyboard.addEventListener("geometrychange", callback);
14445
+ return () => {
14446
+ virtualKeyboard.removeEventListener("geometrychange", callback);
14447
+ };
14448
+ };
14449
+
14278
14450
  // Both "resize" sources fire transiently on mobile (keyboard/UI chrome
14279
14451
  // briefly shifting when focus moves between inputs) — debounced so
14280
14452
  // consumers skip that in-between state. One shared timer per source (not
@@ -14289,17 +14461,29 @@ const [publishVisualViewportResize, subscribeVisualViewportResizeSettled] =
14289
14461
  createPubSub();
14290
14462
  const [publishWindowResize, subscribeWindowResizeSettled] = createPubSub();
14291
14463
 
14464
+ let visualViewportResizeTimeoutId;
14465
+ const scheduleVisualViewportResize = (event) => {
14466
+ visualViewportResizePending = true;
14467
+ clearTimeout(visualViewportResizeTimeoutId);
14468
+ visualViewportResizeTimeoutId = setTimeout(() => {
14469
+ visualViewportResizePending = false;
14470
+ publishVisualViewportResize(event);
14471
+ }, RESIZE_SETTLE_MS);
14472
+ };
14292
14473
  if (window.visualViewport) {
14293
- let timeoutId;
14294
- window.visualViewport.addEventListener("resize", (event) => {
14295
- visualViewportResizePending = true;
14296
- clearTimeout(timeoutId);
14297
- timeoutId = setTimeout(() => {
14298
- visualViewportResizePending = false;
14299
- publishVisualViewportResize(event);
14300
- }, RESIZE_SETTLE_MS);
14301
- });
14474
+ window.visualViewport.addEventListener(
14475
+ "resize",
14476
+ scheduleVisualViewportResize,
14477
+ );
14302
14478
  }
14479
+ // The same event, said differently: where the keyboard overlays the content
14480
+ // (virtual_keyboard.js) there is no visualViewport resize at all when it
14481
+ // opens — the room left to place anything in changed
14482
+ // all the same, and every consumer here asks the same question either way
14483
+ // (getVisibleViewportRect in visible_rect.js already subtracts it). Through
14484
+ // the same debounce, and for the same reason: going straight from one input
14485
+ // to the next hides and re-shows the keyboard.
14486
+ subscribeVirtualKeyboardGeometryChange(scheduleVisualViewportResize);
14303
14487
 
14304
14488
  let windowResizeTimeoutId;
14305
14489
  window.addEventListener("resize", (event) => {
@@ -14318,6 +14502,39 @@ window.addEventListener("resize", (event) => {
14318
14502
  }, RESIZE_SETTLE_MS);
14319
14503
  });
14320
14504
 
14505
+ /**
14506
+ * The part of the viewport something can actually be placed in.
14507
+ *
14508
+ * visualViewport, not the layout viewport: only the visual one shrinks when
14509
+ * the on-screen keyboard opens (where the browser is the one shrinking it —
14510
+ * see below). Its offsetLeft/Top matter too, for pinch-zoom/pan.
14511
+ *
14512
+ * document.documentElement.clientWidth/Height is the fallback without
14513
+ * visualViewport support — the layout viewport net of any classic scrollbar,
14514
+ * which is what visualViewport itself reports, unlike window.innerWidth/Height
14515
+ * which counts the scrollbar in. Both readings existed here, one per call
14516
+ * site, for no reason anyone stated; they only ever differed by that scrollbar
14517
+ * and only on browsers with no visualViewport at all.
14518
+ *
14519
+ * The keyboard is then subtracted rather than assumed to have already shrunk
14520
+ * the viewport: with `overlaysContent` (virtual_keyboard.js, navi turns it on)
14521
+ * the viewport stays full height and the keyboard is painted over its bottom.
14522
+ * Zero everywhere else, the browser having done the subtraction itself.
14523
+ */
14524
+ const getVisibleViewportRect = () => {
14525
+ const visualViewport = window.visualViewport;
14526
+ const documentElement = document.documentElement;
14527
+ const height = visualViewport
14528
+ ? visualViewport.height
14529
+ : documentElement.clientHeight;
14530
+ return {
14531
+ left: visualViewport ? visualViewport.offsetLeft : 0,
14532
+ top: visualViewport ? visualViewport.offsetTop : 0,
14533
+ width: visualViewport ? visualViewport.width : documentElement.clientWidth,
14534
+ height: Math.max(0, height - getVirtualKeyboardOverlayHeight()),
14535
+ };
14536
+ };
14537
+
14321
14538
  // Minimum fraction of element width/height that must be visible on the preferred side
14322
14539
  // before flipping to the opposite side. Prevents flickering near the flip threshold.
14323
14540
  const MIN_CONTENT_VISIBILITY_RATIO = 0.6;
@@ -14440,23 +14657,17 @@ const visibleRectEffect = (
14440
14657
  const UNSET_EVENT = { type: "unset" };
14441
14658
  const check = (event = UNSET_EVENT) => {
14442
14659
 
14443
- // visualViewport, not window.innerWidth/Height: the layout viewport
14444
- // doesn't shrink when the on-screen keyboard opens (same reasoning as
14445
- // pickPositionRelativeTo's own identical choice). offsetLeft/Top matter
14446
- // too, for pinch-zoom/pan. Computed here regardless of scroll container
14447
- // (not just where the non-document branch below needs it) because a
14448
- // keyboard opening can change pickPositionRelativeTo's available space
14449
- // without moving this element's own visibleRect at all — see
14450
- // viewportRectChanged further down.
14451
- const visualViewport = window.visualViewport;
14452
- const viewportWidth = visualViewport
14453
- ? visualViewport.width
14454
- : window.innerWidth;
14455
- const viewportHeight = visualViewport
14456
- ? visualViewport.height
14457
- : window.innerHeight;
14458
- const viewportOffsetLeft = visualViewport ? visualViewport.offsetLeft : 0;
14459
- const viewportOffsetTop = visualViewport ? visualViewport.offsetTop : 0;
14660
+ // Computed here regardless of scroll container (not just where the
14661
+ // non-document branch below needs it) because a keyboard opening can
14662
+ // change pickPositionRelativeTo's available space without moving this
14663
+ // element's own visibleRect at all see viewportRectChanged further
14664
+ // down.
14665
+ const {
14666
+ left: viewportOffsetLeft,
14667
+ top: viewportOffsetTop,
14668
+ width: viewportWidth,
14669
+ height: viewportHeight,
14670
+ } = getVisibleViewportRect();
14460
14671
 
14461
14672
  // 1. Calculate element position relative to scrollable parent
14462
14673
  const { scrollLeft, scrollTop } = scrollContainer;
@@ -15313,19 +15524,13 @@ const pickPositionRelativeTo = (
15313
15524
  container,
15314
15525
  } = {},
15315
15526
  ) => {
15316
- // Needed before hasValidAnchor below. visualViewport, not
15317
- // document.documentElement.clientWidth/Height: the layout viewport
15318
- // doesn't shrink when the on-screen keyboard opens, only the visual one
15319
- // does.
15320
- const visualViewport = window.visualViewport;
15321
- const viewportWidth = visualViewport
15322
- ? visualViewport.width
15323
- : document.documentElement.clientWidth;
15324
- const viewportHeight = visualViewport
15325
- ? visualViewport.height
15326
- : document.documentElement.clientHeight;
15327
- const viewportLeft = visualViewport ? visualViewport.offsetLeft : 0;
15328
- const viewportTop = visualViewport ? visualViewport.offsetTop : 0;
15527
+ // Needed before hasValidAnchor below.
15528
+ const {
15529
+ left: viewportLeft,
15530
+ top: viewportTop,
15531
+ width: viewportWidth,
15532
+ height: viewportHeight,
15533
+ } = getVisibleViewportRect();
15329
15534
 
15330
15535
  // Resolved early: everything below that would otherwise reach for
15331
15536
  // viewportLeft/Top/Width/Height instead uses these, so a "local" popover
@@ -19318,4 +19523,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
19318
19523
  };
19319
19524
  };
19320
19525
 
19321
- export { EASING, ELEMENT_SIZE_CHANGE, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, applyNewPosition, canScroll, captureScrollState, chainEvent, claimWheelGesture, 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, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isAncestorOpen, isPrimaryButtonEvent, isSameColor, isScrollable, 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, scrollIntoViewWithStickyAwareness, scrollRoomTowards, setAttribute, setAttributes, setStyles, snapToPixel, startDragTo, startDragToResizeGesture, startDragToTravel, stickyAsRelativeCoords, stringifyStyle, subscribeVisualViewportResizeSettled, subscribeWindowResizeSettled, suppressClickAfterGesture, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect, waitForPressHeld, watchWheelTravel, wheelGestureIsTakenFrom };
19526
+ export { EASING, ELEMENT_SIZE_CHANGE, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, applyNewPosition, canScroll, captureScrollState, chainEvent, claimWheelGesture, 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, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVirtualKeyboardOverlayHeight, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isAncestorOpen, isPrimaryButtonEvent, isSameColor, isScrollable, 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, 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/dom",
3
- "version": "0.17.19",
3
+ "version": "0.17.21",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {