@jsenv/dom 0.17.1 → 0.17.2

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 +400 -71
  2. package/package.json +1 -1
package/dist/jsenv_dom.js CHANGED
@@ -146,6 +146,14 @@ const getElementSignature = (element) => {
146
146
  return `${tagName}#${elementId}`;
147
147
  }
148
148
  if (tagName === "button") {
149
+ // The label BEFORE the text: an icon button has no text worth reading
150
+ // (an svg, a zero-width space), and its aria-label is the one thing that
151
+ // says which button it is — which is the whole point of a signature in a
152
+ // log. A labelled button says so even when it also has text.
153
+ const label = element.getAttribute("aria-label");
154
+ if (label) {
155
+ return `button[aria-label="${label}"]`;
156
+ }
149
157
  const text = element.textContent.trim();
150
158
  if (text) {
151
159
  const excerpt = text.length > 10 ? `${text.slice(0, 10)}…` : text;
@@ -3904,6 +3912,14 @@ const getLuminance = (r, g, b) => {
3904
3912
  return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
3905
3913
  };
3906
3914
 
3915
+ /**
3916
+ * First ancestor of `node` matching `predicate`, walking parent by parent.
3917
+ * Starts at the parent — `node` itself is never a candidate.
3918
+ *
3919
+ * @param {Node} node
3920
+ * @param {(ancestor: Node) => boolean} predicate
3921
+ * @returns {Node|null}
3922
+ */
3907
3923
  const findAncestor = (node, predicate) => {
3908
3924
  let ancestor = node.parentNode;
3909
3925
  while (ancestor) {
@@ -3915,6 +3931,21 @@ const findAncestor = (node, predicate) => {
3915
3931
  return null;
3916
3932
  };
3917
3933
 
3934
+ /**
3935
+ * First descendant of `rootNode` matching `fn`, in document order (depth
3936
+ * first). The walk is bounded to the subtree: `rootNode` itself is not a
3937
+ * candidate, and a root with no children yields nothing — never the root's
3938
+ * siblings.
3939
+ *
3940
+ * @param {Node} rootNode
3941
+ * @param {(node: Node, skip: () => void) => boolean} fn - Return true to stop
3942
+ * on `node`. Call `skip()` to not descend into `node`'s children (the walk
3943
+ * goes on with its siblings).
3944
+ * @param {object} [options]
3945
+ * @param {Node} [options.skipRoot] - A subtree to leave out entirely, itself
3946
+ * included.
3947
+ * @returns {Node|null}
3948
+ */
3918
3949
  const findDescendant = (rootNode, fn, { skipRoot } = {}) => {
3919
3950
  const iterator = createNextNodeIterator(rootNode, rootNode, skipRoot);
3920
3951
  let { done, value: node } = iterator.next();
@@ -3935,6 +3966,18 @@ const findDescendant = (rootNode, fn, { skipRoot } = {}) => {
3935
3966
  return null;
3936
3967
  };
3937
3968
 
3969
+ /**
3970
+ * Last descendant of `rootNode` matching `fn` in document order — the walk
3971
+ * starts at the subtree's deepest final node and moves backwards, so the
3972
+ * first match it meets is the last one the document holds.
3973
+ *
3974
+ * @param {Node} rootNode
3975
+ * @param {(node: Node) => boolean} fn
3976
+ * @param {object} [options]
3977
+ * @param {Node} [options.skipRoot] - A subtree to leave out entirely, itself
3978
+ * included.
3979
+ * @returns {Node|null}
3980
+ */
3938
3981
  const findLastDescendant = (rootNode, fn, { skipRoot } = {}) => {
3939
3982
  const deepestNode = getDeepestNode(rootNode, skipRoot);
3940
3983
  if (deepestNode) {
@@ -3954,6 +3997,23 @@ const findLastDescendant = (rootNode, fn, { skipRoot } = {}) => {
3954
3997
  return null;
3955
3998
  };
3956
3999
 
4000
+ /**
4001
+ * First node after `from` in document order matching `predicate`. Unlike
4002
+ * findDescendant this is anchored to a position, not a container: the walk
4003
+ * leaves `from`'s subtree and goes on through its siblings and its ancestors'
4004
+ * siblings, until `root`'s subtree is exhausted.
4005
+ *
4006
+ * @param {Node} from - The position to search from; not a candidate itself.
4007
+ * @param {(node: Node) => boolean} predicate
4008
+ * @param {object} [options]
4009
+ * @param {Node} [options.root] - Bounds the walk to its subtree; null walks to
4010
+ * the end of the tree `from` belongs to.
4011
+ * @param {Node} [options.skipRoot] - A subtree to leave out entirely, itself
4012
+ * included. A `from` inside it starts right after it.
4013
+ * @param {boolean} [options.skipChildren] - Do not look inside `from`; start
4014
+ * at what follows it.
4015
+ * @returns {Node|null}
4016
+ */
3957
4017
  const findAfter = (
3958
4018
  from,
3959
4019
  predicate,
@@ -3970,6 +4030,21 @@ const findAfter = (
3970
4030
  return null;
3971
4031
  };
3972
4032
 
4033
+ /**
4034
+ * First node before `from` in reverse document order matching `predicate` —
4035
+ * what findAfter is to "next", this is to "previous". A step back lands on
4036
+ * the previous sibling's DEEPEST last node (document order walked backwards),
4037
+ * not on the sibling itself.
4038
+ *
4039
+ * @param {Node} from - The position to search from; not a candidate itself.
4040
+ * @param {(node: Node) => boolean} predicate
4041
+ * @param {object} [options]
4042
+ * @param {Node} [options.root] - Bounds the walk to its subtree; null walks
4043
+ * back to the start of the tree `from` belongs to.
4044
+ * @param {Node} [options.skipRoot] - A subtree to leave out entirely, itself
4045
+ * included. A `from` inside it starts right before it.
4046
+ * @returns {Node|null}
4047
+ */
3973
4048
  const findBefore = (
3974
4049
  from,
3975
4050
  predicate,
@@ -4002,6 +4077,15 @@ const getNextNode = (node, rootNode, skipChild = false, skipRoot = null) => {
4002
4077
  }
4003
4078
  }
4004
4079
 
4080
+ // The traversal is bounded to rootNode's subtree: the root's own siblings
4081
+ // are not part of it. Without this, a rootNode with no children (asking
4082
+ // findDescendant about an <input>, say) steps to its next sibling and walks
4083
+ // the rest of the document from there — the parentNode guard below never
4084
+ // catches it because the walk is already outside the root.
4085
+ if (node === rootNode) {
4086
+ return null;
4087
+ }
4088
+
4005
4089
  const nextSibling = node.nextSibling;
4006
4090
  if (nextSibling) {
4007
4091
  // If next sibling is skipRoot, skip it entirely
@@ -4410,8 +4494,13 @@ const canInteract = (element) => {
4410
4494
  if (element.disabled) {
4411
4495
  return false;
4412
4496
  }
4413
- if (element.hasAttribute("inert")) {
4414
- // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inert
4497
+ // closest, not hasAttribute: inert is inherited by the whole subtree — the
4498
+ // element itself may carry nothing and still be untouchable because something
4499
+ // above it is inert (a slide waiting off screen, the page behind a modal).
4500
+ // Focusing one of those does nothing at all, silently: the browser refuses and
4501
+ // the focus stays where it was, which reads as "the popup opened on nothing".
4502
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inert
4503
+ if (element.closest("[inert]")) {
4415
4504
  return false;
4416
4505
  }
4417
4506
  return true;
@@ -4509,7 +4598,12 @@ const findFocusable = (element, { exclude } = {}) => {
4509
4598
  * - `"value_change"` — key increments/decrements the field value (range, number, date…)
4510
4599
  * - `"cursor_move"` — key moves the text cursor within the field
4511
4600
  * - `"type"` — key produces or deletes text content
4512
- * - `"scroll"` — key scrolls the page or a scrollable container
4601
+ * - `"scroll"` — key would scroll the page: nothing on the element itself
4602
+ * claims it, so it is safe to intercept
4603
+ * - `"scroll_self"` — the focused element scrolls ITSELF that way (it really
4604
+ * overflows on that axis): the key is spoken for, and
4605
+ * taking it would leave a scrollable region no way to be
4606
+ * scrolled from the keyboard
4513
4607
  * - `""` — no meaningful browser default; safe to intercept freely
4514
4608
  */
4515
4609
  const normalizeKeyboardKey = (rawKey) => {
@@ -4583,6 +4677,23 @@ const isTypingIntent = (e) => {
4583
4677
  return false;
4584
4678
  };
4585
4679
 
4680
+ // Whether this element is what scrolls on that axis: it says it may (overflow)
4681
+ // and it has somewhere to go (it overflows). Both are needed — an "auto" box
4682
+ // whose content fits scrolls nothing, and the keys are then free.
4683
+ const canScrollSelf = (element, axis) => {
4684
+ if (!element || element.nodeType !== 1) {
4685
+ return false;
4686
+ }
4687
+ const style = getComputedStyle(element);
4688
+ const overflow = axis === "x" ? style.overflowX : style.overflowY;
4689
+ if (overflow !== "auto" && overflow !== "scroll") {
4690
+ return false;
4691
+ }
4692
+ return axis === "x"
4693
+ ? element.scrollWidth > element.clientWidth
4694
+ : element.scrollHeight > element.clientHeight;
4695
+ };
4696
+
4586
4697
  const DEFAULT_BEHAVIORS = [
4587
4698
  {
4588
4699
  test: () => true,
@@ -4753,6 +4864,31 @@ const DEFAULT_BEHAVIORS = [
4753
4864
  escape: "dismiss",
4754
4865
  },
4755
4866
  },
4867
+ {
4868
+ // An element that really scrolls — a slide's own body, a scrollable panel:
4869
+ // the browser gives it the arrows (and Home/End/PageUp/PageDown) so it can
4870
+ // be read from the keyboard, and that is not a key to take. Asked per axis
4871
+ // and per element, not from a class or an attribute: what makes it true is
4872
+ // that it overflows right now.
4873
+ test: (el) => canScrollSelf(el, "y") || canScrollSelf(el, "x"),
4874
+ keys: {
4875
+ arrowup: (e) =>
4876
+ canScrollSelf(e.target, "y") ? "scroll_self" : undefined,
4877
+ arrowdown: (e) =>
4878
+ canScrollSelf(e.target, "y") ? "scroll_self" : undefined,
4879
+ arrowleft: (e) =>
4880
+ canScrollSelf(e.target, "x") ? "scroll_self" : undefined,
4881
+ arrowright: (e) =>
4882
+ canScrollSelf(e.target, "x") ? "scroll_self" : undefined,
4883
+ pageup: (e) => (canScrollSelf(e.target, "y") ? "scroll_self" : undefined),
4884
+ pagedown: (e) =>
4885
+ canScrollSelf(e.target, "y") ? "scroll_self" : undefined,
4886
+ home: (e) => (canScrollSelf(e.target, "y") ? "scroll_self" : undefined),
4887
+ end: (e) => (canScrollSelf(e.target, "y") ? "scroll_self" : undefined),
4888
+ space: (e) => (canScrollSelf(e.target, "y") ? "scroll_self" : undefined),
4889
+ },
4890
+ // no fallback: only these keys are claimed, everything else keeps looking
4891
+ },
4756
4892
  {
4757
4893
  // Non-interactive elements: browser scrolls on Space and arrow keys
4758
4894
  test: () => true,
@@ -6033,9 +6169,15 @@ const trapFocusInside = (
6033
6169
  // A backdrop click is detected when the target is a <dialog> element —
6034
6170
  // the ::backdrop pseudo-element is not in the DOM, so the event target
6035
6171
  // becomes the dialog element itself when its content area is not hit.
6172
+ // Read through getAttribute rather than .className: on an SVG element
6173
+ // className is an SVGAnimatedString, not a string, and asking it for
6174
+ // .includes throws — which is how clicking an icon inside the trap
6175
+ // used to break. Still a substring test, because the real class names
6176
+ // are navi_dialog_backdrop / navi_popover_backdrop / ….
6177
+ const targetClass = event.target.getAttribute?.("class") || "";
6036
6178
  const isBackdropClick =
6037
6179
  event.target.tagName === "DIALOG" ||
6038
- event.target.className.includes("backdrop");
6180
+ targetClass.includes("backdrop");
6039
6181
  if (!isBackdropClick) {
6040
6182
  event.stopImmediatePropagation();
6041
6183
  }
@@ -7229,9 +7371,14 @@ const getPaddingSizes = (element) => {
7229
7371
  *
7230
7372
  * @param {HTMLElement} element - The overlay element being shown. Its preceding
7231
7373
  * siblings and all ancestor scroll containers will be scroll-locked.
7374
+ * @param {Object} [options]
7375
+ * @param {HTMLElement} [options.boundaryElement] - Only lock scroll containers
7376
+ * inside this element (itself included). For an overlay confined to a local
7377
+ * container rather than the viewport: the container's own scroll must stop,
7378
+ * the rest of the page keeps scrolling as usual.
7232
7379
  * @returns {() => void} Cleanup function that restores all modified styles.
7233
7380
  */
7234
- const trapScrollInside = (element) => {
7381
+ const trapScrollInside = (element, { boundaryElement } = {}) => {
7235
7382
  const cleanupCallbackSet = new Set();
7236
7383
 
7237
7384
  // Collect every element to lock first (preceding scrollable siblings + all
@@ -7245,7 +7392,11 @@ const trapScrollInside = (element) => {
7245
7392
  previous = previous.previousSibling;
7246
7393
  }
7247
7394
  for (const selfOrAncestorScroll of getSelfAndAncestorScrolls(element)) {
7248
- elementsToLock.push(selfOrAncestorScroll.scrollContainer);
7395
+ const { scrollContainer } = selfOrAncestorScroll;
7396
+ if (boundaryElement && !boundaryElement.contains(scrollContainer)) {
7397
+ continue;
7398
+ }
7399
+ elementsToLock.push(scrollContainer);
7249
7400
  }
7250
7401
 
7251
7402
  // Phase 1 — MEASURE. Batch every layout/style read (scrollTop, scrollbar
@@ -10635,47 +10786,127 @@ const moveCSSVars = (vars, fromEl, toEl) => {
10635
10786
  };
10636
10787
 
10637
10788
  installImportMetaCssBuild(import.meta);const css$1 = /* css */`
10789
+ /* IN THE PAGE, NOT IN THE LIST: the hint lands on the edge of a row, which
10790
+ for the last one is the very bottom of the scroll area — drawn inside it,
10791
+ the line would push the scrollable area a few pixels further and make a
10792
+ scrollbar appear (or hide the hint under it) exactly when one is trying to
10793
+ drop at the end. Placed in the body and positioned in viewport
10794
+ coordinates, it can sit anywhere, overhang the list, and cost nothing to
10795
+ the layout. Fixed, like the clone it accompanies. */
10638
10796
  .navi_drop_hint {
10639
- position: absolute;
10797
+ /* A popover, so it lands in the top layer: no z-index to bid against the
10798
+ page, and nothing it can be hidden behind. Shown BEFORE the clone, which
10799
+ is what puts the clone above it — the top layer stacks in the order
10800
+ things are shown, and the item being carried should pass over the line
10801
+ rather than under it. The UA styles for [popover] have to be undone:
10802
+ inset:0, margin:auto, a border and a background of its own. */
10803
+ position: fixed;
10804
+ inset: auto;
10640
10805
  top: var(--drop-hint-y);
10641
10806
  left: calc(var(--drop-target-left) + var(--drop-hint-margin-x, 0px));
10642
- z-index: 10;
10643
10807
  display: none;
10808
+ box-sizing: border-box;
10644
10809
  width: calc(var(--drop-target-width) - 2 * var(--drop-hint-margin-x, 0px));
10645
10810
  height: var(--drop-hint-size, 3px);
10811
+ margin: 0;
10812
+ padding: 0;
10813
+ color: inherit;
10646
10814
  background: var(--drop-hint-background-color, #4476ff);
10815
+ border: none;
10647
10816
  border-radius: var(--drop-hint-border-radius, 2px);
10648
10817
  transform: translateY(-50%);
10649
10818
  pointer-events: none;
10819
+ overflow: visible;
10650
10820
  }
10651
- [data-drop-edge="top"] > .navi_drop_hint {
10821
+ .navi_drop_hint[data-drop-edge]:popover-open {
10652
10822
  display: block;
10823
+ }
10824
+ .navi_drop_hint[data-drop-edge="top"] {
10653
10825
  --drop-hint-y: calc(
10654
10826
  var(--drop-target-top) - var(--drop-hint-margin-y, 0px)
10655
10827
  );
10656
10828
  }
10657
- [data-drop-edge="bottom"] > .navi_drop_hint {
10658
- display: block;
10829
+ .navi_drop_hint[data-drop-edge="bottom"] {
10659
10830
  --drop-hint-y: calc(
10660
10831
  var(--drop-target-bottom) + var(--drop-hint-margin-y, 0px)
10661
10832
  );
10662
10833
  }
10834
+ /* A chevron at each end, pointing in: the line alone is easy to lose against
10835
+ a list of borders and separators, two arrows read as "here" at a glance
10836
+ (same idea as the table's column drop preview). They overhang the line,
10837
+ which costs nothing now that the hint is out of the scrollable area — and
10838
+ the more they stick out, the easier they are to spot. */
10839
+ .navi_drop_hint_cap {
10840
+ position: absolute;
10841
+ top: 50%;
10842
+ display: flex;
10843
+ color: var(--drop-hint-background-color, #4476ff);
10844
+ translate: 0 -50%;
10845
+ }
10846
+ .navi_drop_hint_cap svg {
10847
+ width: var(--drop-hint-arrow-size, 11px);
10848
+ height: var(--drop-hint-arrow-size, 11px);
10849
+ }
10850
+ .navi_drop_hint_cap[data-side="start"] {
10851
+ left: calc(-1 * var(--drop-hint-arrow-size, 11px));
10852
+ rotate: -90deg;
10853
+ }
10854
+ .navi_drop_hint_cap[data-side="end"] {
10855
+ right: calc(-1 * var(--drop-hint-arrow-size, 11px));
10856
+ rotate: 90deg;
10857
+ }
10858
+
10859
+ /* WHO CAN START A DRAG, said in the cursor.
10860
+ A handle drags on the spot, so it shows the hand. A source only drags once
10861
+ the pointer has travelled a few pixels — a plain click stays a click — but
10862
+ the text inside it can no longer be selected (the gesture takes the
10863
+ pointer), so an I-beam over it would promise something that does not
10864
+ happen: it reads as a plain surface instead. An opted-out area keeps both
10865
+ its cursor and its selection, and never starts a drag (see the check in
10866
+ startDragToReorder).
10867
+ Controls inside a source keep their own cursor: cursor is inherited, and
10868
+ anything setting its own (a button's pointer) wins on itself.
10869
+ Only the resting cursor is set here: what it becomes once a drag is under
10870
+ way belongs to the gesture (see the backdrop in drag_gesture.js), the only
10871
+ thing that knows a drag actually started. */
10872
+ [data-drag-handle] {
10873
+ cursor: grab;
10874
+ }
10875
+ [data-drag-source] {
10876
+ cursor: default;
10877
+ user-select: none;
10878
+ }
10879
+ [data-drag-ignore] {
10880
+ cursor: auto;
10881
+ user-select: auto;
10882
+ }
10663
10883
 
10664
10884
  [navi-drag-clone-source] {
10665
10885
  visibility: hidden;
10666
10886
  }
10667
10887
 
10668
10888
  [navi-drag-clone-wrapper] {
10669
- position: absolute;
10889
+ /* Also a popover (see .navi_drop_hint): in the top layer it is over the
10890
+ page whatever the page's own stacking is, and the coordinates it is
10891
+ given are viewport ones — which is what the pointer carrying it works
10892
+ in. Same UA-style reset as the hint. */
10893
+ position: fixed;
10894
+ inset: auto;
10670
10895
  top: var(--clone-top);
10671
10896
  left: var(--clone-left);
10672
- z-index: 9999;
10897
+ box-sizing: border-box;
10673
10898
  width: var(--clone-width);
10674
10899
  height: var(--clone-height);
10900
+ margin: 0;
10901
+ padding: 0;
10902
+ color: inherit;
10903
+ background: transparent;
10904
+ border: none;
10675
10905
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.22);
10676
10906
  opacity: 0.95;
10677
10907
  transition: box-shadow 0.15s ease;
10678
10908
  pointer-events: none;
10909
+ overflow: visible;
10679
10910
  }
10680
10911
 
10681
10912
  [navi-drag-clone] {
@@ -10694,7 +10925,10 @@ installImportMetaCssBuild(import.meta);const css$1 = /* css */`
10694
10925
  }
10695
10926
  }
10696
10927
  `;
10697
- const dragCSSVars = ["--drop-hint-size", "--drop-hint-background-color", "--drop-hint-border-radius", "--drop-hint-margin-x", "--drop-hint-margin-y", "--drag-clone-scale"];
10928
+ // At module scope, not inside startDragToReorder: the cursor rules above say who
10929
+ // can start a drag, and they have to be true BEFORE anyone drags anything.
10930
+ import.meta.css = [css$1, "@jsenv/dom/src/interaction/drag/drag_to_reorder.js"];
10931
+ const dragCSSVars = ["--drop-hint-size", "--drop-hint-background-color", "--drop-hint-border-radius", "--drop-hint-margin-x", "--drop-hint-margin-y", "--drop-hint-arrow-size", "--drag-clone-scale"];
10698
10932
 
10699
10933
  /**
10700
10934
  * Starts a drag-to-reorder interaction on a list item.
@@ -10760,7 +10994,11 @@ const startDragToReorder = (event, {
10760
10994
  },
10761
10995
  ...options
10762
10996
  }) => {
10763
- import.meta.css = [css$1, "@jsenv/dom/src/interaction/drag/drag_to_reorder.js"];
10997
+ // An area that opted out of dragging (a text one wants to select, a control
10998
+ // that owns the gesture): the press there is none of our business.
10999
+ if (event.target.closest && event.target.closest("[data-drag-ignore]")) {
11000
+ return undefined;
11001
+ }
10764
11002
  event.preventDefault();
10765
11003
  return dragAfterThreshold(event, () => {
10766
11004
  const cloneWrapper = createDragClone(draggedElement, event);
@@ -10780,21 +11018,23 @@ const startDragToReorder = (event, {
10780
11018
  // getDropTargetInfo uses gestureInfo.elementImpacted to compute the dragged rect.
10781
11019
  // Point it at the clone so drop detection tracks the clone's current position.
10782
11020
  dragGesture.gestureInfo.elementImpacted = cloneWrapper;
10783
- const scrollContainer = dragGesture.gestureInfo.scrollContainer;
10784
- const dropHintEl = document.createElement("div");
10785
- dropHintEl.className = "navi_drop_hint";
10786
- scrollContainer.appendChild(dropHintEl);
11021
+ const dropHintEl = createDropHint();
11022
+ document.body.appendChild(dropHintEl);
11023
+ // The hint first, the clone second: that order is what stacks them in the
11024
+ // top layer.
11025
+ dropHintEl.showPopover();
11026
+ cloneWrapper.showPopover();
10787
11027
 
10788
11028
  // currentBeforeElement: element before which the grabbed item will be inserted (null = end)
10789
11029
  // currentReleaseElement: the actual hovered drop target — used to snap the clone on release
10790
11030
  let currentBeforeElement;
10791
11031
  let currentReleaseElement;
10792
11032
  const clearDropHintDOM = () => {
10793
- scrollContainer.removeAttribute("data-drop-edge");
10794
- scrollContainer.style.removeProperty("--drop-target-top");
10795
- scrollContainer.style.removeProperty("--drop-target-bottom");
10796
- scrollContainer.style.removeProperty("--drop-target-left");
10797
- scrollContainer.style.removeProperty("--drop-target-width");
11033
+ dropHintEl.removeAttribute("data-drop-edge");
11034
+ dropHintEl.style.removeProperty("--drop-target-top");
11035
+ dropHintEl.style.removeProperty("--drop-target-bottom");
11036
+ dropHintEl.style.removeProperty("--drop-target-left");
11037
+ dropHintEl.style.removeProperty("--drop-target-width");
10798
11038
  };
10799
11039
  const clearDropHint = () => {
10800
11040
  currentBeforeElement = undefined;
@@ -10845,16 +11085,15 @@ const startDragToReorder = (event, {
10845
11085
  // beforeElement = X → insert before X (hint at top edge of X)
10846
11086
  const anchorEl = beforeElement || items[items.length - 1];
10847
11087
  const anchorEdge = beforeElement !== null ? "top" : "bottom";
10848
- const containerRect = scrollContainer.getBoundingClientRect();
11088
+ // Viewport coordinates, straight from the anchor row: the hint is fixed
11089
+ // in the page (see its CSS), so there is no container box to be relative
11090
+ // to and no scroll offset to add back.
10849
11091
  const anchorRect = anchorEl.getBoundingClientRect();
10850
- const isPositioned = getComputedStyle(scrollContainer).position !== "static";
10851
- const scrollOffsetLeft = isPositioned ? scrollContainer.scrollLeft : 0;
10852
- const scrollOffsetTop = isPositioned ? scrollContainer.scrollTop : 0;
10853
- scrollContainer.setAttribute("data-drop-edge", anchorEdge);
10854
- scrollContainer.style.setProperty("--drop-target-top", `${anchorRect.top - containerRect.top + scrollOffsetTop}px`);
10855
- scrollContainer.style.setProperty("--drop-target-bottom", `${anchorRect.bottom - containerRect.top + scrollOffsetTop}px`);
10856
- scrollContainer.style.setProperty("--drop-target-left", `${anchorRect.left - containerRect.left + scrollOffsetLeft}px`);
10857
- scrollContainer.style.setProperty("--drop-target-width", `${anchorRect.width}px`);
11092
+ dropHintEl.setAttribute("data-drop-edge", anchorEdge);
11093
+ dropHintEl.style.setProperty("--drop-target-top", `${anchorRect.top}px`);
11094
+ dropHintEl.style.setProperty("--drop-target-bottom", `${anchorRect.bottom}px`);
11095
+ dropHintEl.style.setProperty("--drop-target-left", `${anchorRect.left}px`);
11096
+ dropHintEl.style.setProperty("--drop-target-width", `${anchorRect.width}px`);
10858
11097
  });
10859
11098
  dragGesture.addReleaseCallback(async gestureInfo => {
10860
11099
  clearDropHintDOM();
@@ -10864,7 +11103,7 @@ const startDragToReorder = (event, {
10864
11103
  const clone = cloneWrapper.firstElementChild;
10865
11104
  // Bake the current visual position (transform included) into the CSS vars
10866
11105
  // so the clone stays where the user released it when we clear the transform.
10867
- setCloneDocumentRect(cloneWrapper, cloneWrapper);
11106
+ setCloneViewportRect(cloneWrapper, cloneWrapper);
10868
11107
  gestureInfo.cancelPosition();
10869
11108
  const fromId = getItemId(draggedElement);
10870
11109
  const toId = currentBeforeElement ? getItemId(currentBeforeElement) : null;
@@ -10873,7 +11112,7 @@ const startDragToReorder = (event, {
10873
11112
  const syncCloneWithDropTarget = () => {
10874
11113
  // Snap the CSS-var position to the drop target rect so the browser
10875
11114
  // captures the "new" state at the landing position.
10876
- setCloneDocumentRect(cloneWrapper, currentReleaseElement);
11115
+ setCloneViewportRect(cloneWrapper, currentReleaseElement);
10877
11116
  // Removing this attr drops the CSS scale(1.15), so the browser
10878
11117
  // captures the clone at scale 1 as the "new" state.
10879
11118
  clone.removeAttribute("navi-drag-clone");
@@ -10887,15 +11126,13 @@ const startDragToReorder = (event, {
10887
11126
  });
10888
11127
  };
10889
11128
 
10890
- // getBoundingClientRect() returns viewport-relative coords.
10891
- // The clone wrapper is position:absolute inside document.body, so we need
10892
- // document-relative coords (viewport coords + current page scroll).
10893
- const setCloneDocumentRect = (cloneWrapper, el) => {
11129
+ // Viewport coordinates, as getBoundingClientRect gives them: the clone is a
11130
+ // fixed-position popover, so that is the space it lives in — and the one the
11131
+ // pointer dragging it works in too.
11132
+ const setCloneViewportRect = (cloneWrapper, el) => {
10894
11133
  const rect = el.getBoundingClientRect();
10895
- const scrollLeft = document.documentElement.scrollLeft;
10896
- const scrollTop = document.documentElement.scrollTop;
10897
- cloneWrapper.style.setProperty("--clone-top", `${rect.top + scrollTop}px`);
10898
- cloneWrapper.style.setProperty("--clone-left", `${rect.left + scrollLeft}px`);
11134
+ cloneWrapper.style.setProperty("--clone-top", `${rect.top}px`);
11135
+ cloneWrapper.style.setProperty("--clone-left", `${rect.left}px`);
10899
11136
  cloneWrapper.style.setProperty("--clone-width", `${rect.width}px`);
10900
11137
  cloneWrapper.style.setProperty("--clone-height", `${rect.height}px`);
10901
11138
  };
@@ -10914,12 +11151,43 @@ const setCloneDocumentRect = (cloneWrapper, el) => {
10914
11151
  // so the element expands naturally from where the user clicked.
10915
11152
  // On release, the `navi-drag-clone` attribute is removed inside
10916
11153
  // startViewTransition to drop the scale back to 1 as the "new" state.
11154
+ // The chevron is the one the table's column drop preview uses, rotated by the
11155
+ // CSS above so each cap points into the line.
11156
+ const dropHintTemplate = /* html */`
11157
+ <div
11158
+ class="navi_drop_hint"
11159
+ popover="manual"
11160
+ >
11161
+ <span class="navi_drop_hint_cap" data-side="start">
11162
+ <svg fill="currentColor" viewBox="0 0 30.727 30.727">
11163
+ <path
11164
+ d="M29.994,10.183L15.363,24.812L0.733,10.184c-0.977-0.978-0.977-2.561,0-3.536c0.977-0.977,2.559-0.976,3.536,0l11.095,11.093L26.461,6.647c0.977-0.976,2.559-0.976,3.535,0C30.971,7.624,30.971,9.206,29.994,10.183z"
11165
+ />
11166
+ </svg>
11167
+ </span>
11168
+ <span class="navi_drop_hint_cap" data-side="end">
11169
+ <svg fill="currentColor" viewBox="0 0 30.727 30.727">
11170
+ <path
11171
+ d="M29.994,10.183L15.363,24.812L0.733,10.184c-0.977-0.978-0.977-2.561,0-3.536c0.977-0.977,2.559-0.976,3.536,0l11.095,11.093L26.461,6.647c0.977-0.976,2.559-0.976,3.535,0C30.971,7.624,30.971,9.206,29.994,10.183z"
11172
+ />
11173
+ </svg>
11174
+ </span>
11175
+ </div>
11176
+ `;
11177
+ const createDropHint = () => {
11178
+ const div = document.createElement("div");
11179
+ div.innerHTML = dropHintTemplate.trim();
11180
+ return div.firstElementChild;
11181
+ };
10917
11182
  const createDragClone = (element, pointerEvent) => {
10918
11183
  const rect = element.getBoundingClientRect();
10919
11184
  const wrapper = document.createElement("div");
10920
11185
  wrapper.setAttribute("navi-drag-clone-wrapper", "");
11186
+ // Manual: it is opened and closed with the drag, and must survive an Escape
11187
+ // or a click elsewhere (light dismiss would take it away mid-gesture).
11188
+ wrapper.setAttribute("popover", "manual");
10921
11189
  wrapper.viewTransitionName = "navi-drag-clone-wrapper";
10922
- setCloneDocumentRect(wrapper, element);
11190
+ setCloneViewportRect(wrapper, element);
10923
11191
  // Grab point within the element — used as transform-origin so the
10924
11192
  // scale(1.15) expands from where the user clicked, not the element center.
10925
11193
  // These offsets are element-relative so viewport coords are correct here.
@@ -11013,7 +11281,19 @@ const getResizeDirection = (element) => {
11013
11281
  // directions: hide when a container closes, recheck when it reopens) — the
11014
11282
  // selector/open-detection/timing primitives are identical for both, only
11015
11283
  // what each does with a transition differs.
11016
- const ANCESTOR_OPEN_SELECTOR = "dialog, details, [popover], [aria-expanded]";
11284
+ const OPENABLE_SELECTOR = "dialog, details, [popover], [aria-expanded]";
11285
+
11286
+ // An element that IS openable is closed in exactly the same way an element
11287
+ // inside one is — which matters for anything positioned against it, e.g. a
11288
+ // callout anchored to a dialog rather than to a field inside it. Same selector
11289
+ // as the walk up: whatever counts as openable above counts as openable here,
11290
+ // custom [aria-expanded] nodes included.
11291
+ const selfOrClosestOpenableAncestor = (element) => {
11292
+ if (element.matches?.(OPENABLE_SELECTOR)) {
11293
+ return element;
11294
+ }
11295
+ return closestOpenableAncestor(element);
11296
+ };
11017
11297
 
11018
11298
  const closestOpenableAncestor = (element) => {
11019
11299
  const parentElement = element.parentElement;
@@ -11023,7 +11303,7 @@ const closestOpenableAncestor = (element) => {
11023
11303
  if (!parentElement.closest) {
11024
11304
  return null;
11025
11305
  }
11026
- return parentElement.closest(ANCESTOR_OPEN_SELECTOR);
11306
+ return parentElement.closest(OPENABLE_SELECTOR);
11027
11307
  };
11028
11308
 
11029
11309
  const isAncestorOpen = (ancestor) => {
@@ -11575,7 +11855,10 @@ const MIN_CONTENT_VISIBILITY_RATIO = 0.6;
11575
11855
  */
11576
11856
  // The event type observeSize() reports with — recognized by check() as "the
11577
11857
  // change is in another element, not in the tracked rect".
11578
- const OBSERVED_ELEMENT_SIZE_CHANGE = "observed_element_size_change";
11858
+ // Exported: a caller that resized the element itself (a callout whose message
11859
+ // just changed, say) has to re-check with this rather than with nothing — its
11860
+ // own rect may not have moved at all, and the dedup would drop the check.
11861
+ const ELEMENT_SIZE_CHANGE = "observed_element_size_change";
11579
11862
 
11580
11863
  const visibleRectEffect = (
11581
11864
  element,
@@ -11645,7 +11928,12 @@ const visibleRectEffect = (
11645
11928
  resizeWatchingPaused = false;
11646
11929
  publishResizeWatchingPausedChange(false);
11647
11930
  };
11648
- const check = (event) => {
11931
+ // Only so the reads below have something to read: a caller that describes
11932
+ // nothing gets no special treatment, it goes through the same dedup as any
11933
+ // other check. A caller that needs the dedup bypassed says so by passing the
11934
+ // event that means it (ELEMENT_SIZE_CHANGE).
11935
+ const UNSET_EVENT = { type: "unset" };
11936
+ const check = (event = UNSET_EVENT) => {
11649
11937
 
11650
11938
  // visualViewport, not window.innerWidth/Height: the layout viewport
11651
11939
  // doesn't shrink when the on-screen keyboard opens (same reasoning as
@@ -11825,7 +12113,7 @@ const visibleRectEffect = (
11825
12113
  // defeating the whole point of observeSize (a popover reconsidering its
11826
12114
  // placement once its own content shrinks/grows, a callout re-measuring
11827
12115
  // against its message body).
11828
- if (event.type === OBSERVED_ELEMENT_SIZE_CHANGE) {
12116
+ if (event.type === ELEMENT_SIZE_CHANGE) {
11829
12117
  lastVisibleRect = visibleRect;
11830
12118
  lastViewportRect = viewportRect;
11831
12119
  notify();
@@ -12062,7 +12350,12 @@ const visibleRectEffect = (
12062
12350
  });
12063
12351
  }
12064
12352
  {
12065
- let currentOpenableAncestor = closestOpenableAncestor(element);
12353
+ // Self-inclusive on the first step only: `element` can itself be the
12354
+ // dialog/popover that closes (a callout anchored to a dialog rather than
12355
+ // to a field inside it), and its own close hides it just as much as an
12356
+ // ancestor's would. The walk up below starts from parentElement, so the
12357
+ // chain still advances.
12358
+ let currentOpenableAncestor = selfOrClosestOpenableAncestor(element);
12066
12359
  while (currentOpenableAncestor) {
12067
12360
  const openableAncestor = currentOpenableAncestor;
12068
12361
  if (!isAncestorOpen(openableAncestor)) {
@@ -12235,7 +12528,7 @@ const visibleRectEffect = (
12235
12528
  pendingFrame = requestAnimationFrame(() => {
12236
12529
  pendingFrame = null;
12237
12530
  check(
12238
- new CustomEvent(OBSERVED_ELEMENT_SIZE_CHANGE, {
12531
+ new CustomEvent(ELEMENT_SIZE_CHANGE, {
12239
12532
  detail: { width, height },
12240
12533
  }),
12241
12534
  );
@@ -12497,7 +12790,7 @@ const toContainerAlignedPosition = (value) => {
12497
12790
  * edges instead of the page viewport's, on both axes (the Y axis otherwise has no such
12498
12791
  * clamp at all — see the clamp's own comment) — that part *is* gated on `hasValidAnchor`,
12499
12792
  * unlike the coordinate-space conversion itself.
12500
- * @returns {{ hasValidAnchor, shouldTransition, positionX, positionY, left, top, width, height, anchorLeft, anchorTop, anchorRight, anchorBottom, spaceLeft, spaceRight, spaceAbove, spaceBelow }}
12793
+ * @returns {{ hasValidAnchor, shouldTransition, positionX, positionY, left, top, width, height, anchorLeft, anchorTop, anchorRight, anchorBottom, spaceLeft, spaceRight, spaceAbove, spaceBelow, containerWidthAvailable, containerHeightAvailable }}
12501
12794
  */
12502
12795
  const pickPositionRelativeTo = (
12503
12796
  element,
@@ -13010,22 +13303,34 @@ const pickPositionRelativeTo = (
13010
13303
  // so the usable space includes the anchor dimension.
13011
13304
  // marginWithAnchor (gap between anchor and element) and marginWithContainer are subtracted
13012
13305
  // so callers get the net usable space directly.
13013
- const effectiveSpaceAbove =
13014
- (finalY === "inset-bottom" ? spaceAbove + anchorHeight : spaceAbove) -
13015
- (finalY === "top" ? marginWithAnchor : 0) -
13016
- marginWithContainer;
13017
- const effectiveSpaceBelow =
13018
- (finalY === "inset-top" ? spaceBelow + anchorHeight : spaceBelow) -
13019
- (finalY === "bottom" ? marginWithAnchor : 0) -
13020
- marginWithContainer;
13021
- const effectiveSpaceLeft =
13022
- (finalX === "inset-right" ? spaceLeft + anchorWidth : spaceLeft) -
13023
- (finalX === "left" ? marginWithAnchor : 0) -
13024
- marginWithContainer;
13025
- const effectiveSpaceRight =
13026
- (finalX === "inset-left" ? spaceRight + anchorWidth : spaceRight) -
13027
- (finalX === "right" ? marginWithAnchor : 0) -
13028
- marginWithContainer;
13306
+ const containerWidthAvailable = availableWidth - 2 * marginWithContainer;
13307
+ const containerHeightAvailable = availableHeight - 2 * marginWithContainer;
13308
+ // Docked to a container (no real anchor): the element is kept inside the
13309
+ // container's margin on BOTH sides — that is what the !hasValidAnchor clamp
13310
+ // above enforces — so what it has to work with is the container net of both.
13311
+ // The anchor-relative formulas below count the margin once, which is right
13312
+ // when the space really is bounded by the anchor on the other side, and
13313
+ // wrong here: it would let the far edge grow flush against the container.
13314
+ const effectiveSpaceAbove = !hasValidAnchor
13315
+ ? containerHeightAvailable
13316
+ : (finalY === "inset-bottom" ? spaceAbove + anchorHeight : spaceAbove) -
13317
+ (finalY === "top" ? marginWithAnchor : 0) -
13318
+ marginWithContainer;
13319
+ const effectiveSpaceBelow = !hasValidAnchor
13320
+ ? containerHeightAvailable
13321
+ : (finalY === "inset-top" ? spaceBelow + anchorHeight : spaceBelow) -
13322
+ (finalY === "bottom" ? marginWithAnchor : 0) -
13323
+ marginWithContainer;
13324
+ const effectiveSpaceLeft = !hasValidAnchor
13325
+ ? containerWidthAvailable
13326
+ : (finalX === "inset-right" ? spaceLeft + anchorWidth : spaceLeft) -
13327
+ (finalX === "left" ? marginWithAnchor : 0) -
13328
+ marginWithContainer;
13329
+ const effectiveSpaceRight = !hasValidAnchor
13330
+ ? containerWidthAvailable
13331
+ : (finalX === "inset-left" ? spaceRight + anchorWidth : spaceRight) -
13332
+ (finalX === "right" ? marginWithAnchor : 0) -
13333
+ marginWithContainer;
13029
13334
 
13030
13335
  return {
13031
13336
  // Whether a real anchor actually ended up used — false when there's no
@@ -13048,6 +13353,12 @@ const pickPositionRelativeTo = (
13048
13353
  spaceRight: effectiveSpaceRight,
13049
13354
  spaceAbove: effectiveSpaceAbove,
13050
13355
  spaceBelow: effectiveSpaceBelow,
13356
+ // What a centered axis has to work with: the whole container, net of the
13357
+ // margin kept on both sides. spaceLeft/spaceRight can't answer that — they
13358
+ // are measured from the anchor, which for a container-docked element is
13359
+ // the container itself, so they collapse to -marginWithContainer.
13360
+ containerWidthAvailable,
13361
+ containerHeightAvailable,
13051
13362
  };
13052
13363
  };
13053
13364
 
@@ -13168,8 +13479,16 @@ const applyNewPosition = (
13168
13479
  spaceRight,
13169
13480
  spaceAbove,
13170
13481
  spaceBelow,
13482
+ containerWidthAvailable,
13483
+ containerHeightAvailable,
13171
13484
  },
13172
13485
  ) => {
13486
+ // A centered axis is published too, from the container's own extent: leaving
13487
+ // the property unset lets the consumer's size cap fall back to its viewport
13488
+ // default, which overflows any container smaller than the viewport (a
13489
+ // dialog/popover confined to a positioned ancestor). It stays a "remaining
13490
+ // space" either way — docked: what is left on that side, centered: the whole
13491
+ // container minus its margins.
13173
13492
  if (positionY === "top" || positionY === "inset-bottom") {
13174
13493
  element.style.setProperty(
13175
13494
  "--container-position-remaining-height",
@@ -13180,8 +13499,13 @@ const applyNewPosition = (
13180
13499
  "--container-position-remaining-height",
13181
13500
  `${spaceBelow}px`,
13182
13501
  );
13183
- } else {
13502
+ } else if (containerHeightAvailable === undefined) {
13184
13503
  element.style.removeProperty("--container-position-remaining-height");
13504
+ } else {
13505
+ element.style.setProperty(
13506
+ "--container-position-remaining-height",
13507
+ `${containerHeightAvailable}px`,
13508
+ );
13185
13509
  }
13186
13510
  if (positionX === "left" || positionX === "inset-right") {
13187
13511
  element.style.setProperty(
@@ -13193,8 +13517,13 @@ const applyNewPosition = (
13193
13517
  "--container-position-remaining-width",
13194
13518
  `${spaceRight}px`,
13195
13519
  );
13196
- } else {
13520
+ } else if (containerWidthAvailable === undefined) {
13197
13521
  element.style.removeProperty("--container-position-remaining-width");
13522
+ } else {
13523
+ element.style.setProperty(
13524
+ "--container-position-remaining-width",
13525
+ `${containerWidthAvailable}px`,
13526
+ );
13198
13527
  }
13199
13528
 
13200
13529
  // A single implicit keyframe turned out not to work here: the WAAPI
@@ -16484,4 +16813,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
16484
16813
  };
16485
16814
  };
16486
16815
 
16487
- export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, applyNewPosition, captureScrollState, chainEvent, closestOpenableAncestor, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createEventGroupLogger, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dispatchCustomEvent, dispatchInternalCustomEvent, dispatchPublicCustomEvent, dragAfterThreshold, 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, isSameColor, isScrollable, measureLongestVisualLineWidth, measureScrollbar, measureWidestChildRow, mergeOneStyle, mergeTwoStyles, normalizeKeyboardKey, normalizeStyle, normalizeStyles, observeAncestorOpenState, onAncestorReopen, parsePositionArea, parseStyle, performTabNavigation, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, setAttribute, setAttributes, setStyles, snapToPixel, startDragToReorder, startDragToResizeGesture, stickyAsRelativeCoords, stringifyStyle, subscribeVisualViewportResizeSettled, subscribeWindowResizeSettled, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
16816
+ export { EASING, ELEMENT_SIZE_CHANGE, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, applyNewPosition, captureScrollState, chainEvent, closestOpenableAncestor, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createEventGroupLogger, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dispatchCustomEvent, dispatchInternalCustomEvent, dispatchPublicCustomEvent, dragAfterThreshold, 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, isSameColor, isScrollable, measureLongestVisualLineWidth, measureScrollbar, measureWidestChildRow, mergeOneStyle, mergeTwoStyles, normalizeKeyboardKey, normalizeStyle, normalizeStyles, observeAncestorOpenState, onAncestorReopen, parsePositionArea, parseStyle, performTabNavigation, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, setAttribute, setAttributes, setStyles, snapToPixel, startDragToReorder, startDragToResizeGesture, stickyAsRelativeCoords, stringifyStyle, subscribeVisualViewportResizeSettled, subscribeWindowResizeSettled, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/dom",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {