@jsenv/dom 0.17.0 → 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 +426 -76
  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,
@@ -4592,13 +4703,6 @@ const DEFAULT_BEHAVIORS = [
4592
4703
  },
4593
4704
  // no fallback: only claims Tab, other keys continue to next entries
4594
4705
  },
4595
- {
4596
- // Escape natively dismisses only <dialog> elements
4597
- test: (el) => el.tagName === "DIALOG" || Boolean(el.closest("dialog")),
4598
- keys: {
4599
- escape: "dismiss",
4600
- },
4601
- },
4602
4706
  {
4603
4707
  test: (el) => el.matches("input[type='radio'], input[type='checkbox']"),
4604
4708
  keys: {
@@ -4748,6 +4852,43 @@ const DEFAULT_BEHAVIORS = [
4748
4852
  enter: "activate",
4749
4853
  },
4750
4854
  },
4855
+ {
4856
+ // Escape natively dismisses only <dialog> elements. Deliberately late in
4857
+ // the list: the focused element gets first claim on Escape, because the
4858
+ // browser resolves the close request innermost-first. A non-empty
4859
+ // <input type="search"> inside a dialog consumes the first Escape to clear
4860
+ // itself and only a second one reaches the dialog — reporting "dismiss"
4861
+ // here would let our own Escape-to-close shortcuts fire on the first press.
4862
+ test: (el) => el.tagName === "DIALOG" || Boolean(el.closest("dialog")),
4863
+ keys: {
4864
+ escape: "dismiss",
4865
+ },
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
+ },
4751
4892
  {
4752
4893
  // Non-interactive elements: browser scrolls on Space and arrow keys
4753
4894
  test: () => true,
@@ -6028,9 +6169,15 @@ const trapFocusInside = (
6028
6169
  // A backdrop click is detected when the target is a <dialog> element —
6029
6170
  // the ::backdrop pseudo-element is not in the DOM, so the event target
6030
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") || "";
6031
6178
  const isBackdropClick =
6032
6179
  event.target.tagName === "DIALOG" ||
6033
- event.target.className.includes("backdrop");
6180
+ targetClass.includes("backdrop");
6034
6181
  if (!isBackdropClick) {
6035
6182
  event.stopImmediatePropagation();
6036
6183
  }
@@ -7224,9 +7371,14 @@ const getPaddingSizes = (element) => {
7224
7371
  *
7225
7372
  * @param {HTMLElement} element - The overlay element being shown. Its preceding
7226
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.
7227
7379
  * @returns {() => void} Cleanup function that restores all modified styles.
7228
7380
  */
7229
- const trapScrollInside = (element) => {
7381
+ const trapScrollInside = (element, { boundaryElement } = {}) => {
7230
7382
  const cleanupCallbackSet = new Set();
7231
7383
 
7232
7384
  // Collect every element to lock first (preceding scrollable siblings + all
@@ -7240,7 +7392,11 @@ const trapScrollInside = (element) => {
7240
7392
  previous = previous.previousSibling;
7241
7393
  }
7242
7394
  for (const selfOrAncestorScroll of getSelfAndAncestorScrolls(element)) {
7243
- elementsToLock.push(selfOrAncestorScroll.scrollContainer);
7395
+ const { scrollContainer } = selfOrAncestorScroll;
7396
+ if (boundaryElement && !boundaryElement.contains(scrollContainer)) {
7397
+ continue;
7398
+ }
7399
+ elementsToLock.push(scrollContainer);
7244
7400
  }
7245
7401
 
7246
7402
  // Phase 1 — MEASURE. Batch every layout/style read (scrollTop, scrollbar
@@ -10630,47 +10786,127 @@ const moveCSSVars = (vars, fromEl, toEl) => {
10630
10786
  };
10631
10787
 
10632
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. */
10633
10796
  .navi_drop_hint {
10634
- 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;
10635
10805
  top: var(--drop-hint-y);
10636
10806
  left: calc(var(--drop-target-left) + var(--drop-hint-margin-x, 0px));
10637
- z-index: 10;
10638
10807
  display: none;
10808
+ box-sizing: border-box;
10639
10809
  width: calc(var(--drop-target-width) - 2 * var(--drop-hint-margin-x, 0px));
10640
10810
  height: var(--drop-hint-size, 3px);
10811
+ margin: 0;
10812
+ padding: 0;
10813
+ color: inherit;
10641
10814
  background: var(--drop-hint-background-color, #4476ff);
10815
+ border: none;
10642
10816
  border-radius: var(--drop-hint-border-radius, 2px);
10643
10817
  transform: translateY(-50%);
10644
10818
  pointer-events: none;
10819
+ overflow: visible;
10645
10820
  }
10646
- [data-drop-edge="top"] > .navi_drop_hint {
10821
+ .navi_drop_hint[data-drop-edge]:popover-open {
10647
10822
  display: block;
10823
+ }
10824
+ .navi_drop_hint[data-drop-edge="top"] {
10648
10825
  --drop-hint-y: calc(
10649
10826
  var(--drop-target-top) - var(--drop-hint-margin-y, 0px)
10650
10827
  );
10651
10828
  }
10652
- [data-drop-edge="bottom"] > .navi_drop_hint {
10653
- display: block;
10829
+ .navi_drop_hint[data-drop-edge="bottom"] {
10654
10830
  --drop-hint-y: calc(
10655
10831
  var(--drop-target-bottom) + var(--drop-hint-margin-y, 0px)
10656
10832
  );
10657
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
+ }
10658
10883
 
10659
10884
  [navi-drag-clone-source] {
10660
10885
  visibility: hidden;
10661
10886
  }
10662
10887
 
10663
10888
  [navi-drag-clone-wrapper] {
10664
- 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;
10665
10895
  top: var(--clone-top);
10666
10896
  left: var(--clone-left);
10667
- z-index: 9999;
10897
+ box-sizing: border-box;
10668
10898
  width: var(--clone-width);
10669
10899
  height: var(--clone-height);
10900
+ margin: 0;
10901
+ padding: 0;
10902
+ color: inherit;
10903
+ background: transparent;
10904
+ border: none;
10670
10905
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.22);
10671
10906
  opacity: 0.95;
10672
10907
  transition: box-shadow 0.15s ease;
10673
10908
  pointer-events: none;
10909
+ overflow: visible;
10674
10910
  }
10675
10911
 
10676
10912
  [navi-drag-clone] {
@@ -10689,7 +10925,10 @@ installImportMetaCssBuild(import.meta);const css$1 = /* css */`
10689
10925
  }
10690
10926
  }
10691
10927
  `;
10692
- 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"];
10693
10932
 
10694
10933
  /**
10695
10934
  * Starts a drag-to-reorder interaction on a list item.
@@ -10755,7 +10994,11 @@ const startDragToReorder = (event, {
10755
10994
  },
10756
10995
  ...options
10757
10996
  }) => {
10758
- 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
+ }
10759
11002
  event.preventDefault();
10760
11003
  return dragAfterThreshold(event, () => {
10761
11004
  const cloneWrapper = createDragClone(draggedElement, event);
@@ -10775,21 +11018,23 @@ const startDragToReorder = (event, {
10775
11018
  // getDropTargetInfo uses gestureInfo.elementImpacted to compute the dragged rect.
10776
11019
  // Point it at the clone so drop detection tracks the clone's current position.
10777
11020
  dragGesture.gestureInfo.elementImpacted = cloneWrapper;
10778
- const scrollContainer = dragGesture.gestureInfo.scrollContainer;
10779
- const dropHintEl = document.createElement("div");
10780
- dropHintEl.className = "navi_drop_hint";
10781
- 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();
10782
11027
 
10783
11028
  // currentBeforeElement: element before which the grabbed item will be inserted (null = end)
10784
11029
  // currentReleaseElement: the actual hovered drop target — used to snap the clone on release
10785
11030
  let currentBeforeElement;
10786
11031
  let currentReleaseElement;
10787
11032
  const clearDropHintDOM = () => {
10788
- scrollContainer.removeAttribute("data-drop-edge");
10789
- scrollContainer.style.removeProperty("--drop-target-top");
10790
- scrollContainer.style.removeProperty("--drop-target-bottom");
10791
- scrollContainer.style.removeProperty("--drop-target-left");
10792
- 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");
10793
11038
  };
10794
11039
  const clearDropHint = () => {
10795
11040
  currentBeforeElement = undefined;
@@ -10840,16 +11085,15 @@ const startDragToReorder = (event, {
10840
11085
  // beforeElement = X → insert before X (hint at top edge of X)
10841
11086
  const anchorEl = beforeElement || items[items.length - 1];
10842
11087
  const anchorEdge = beforeElement !== null ? "top" : "bottom";
10843
- 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.
10844
11091
  const anchorRect = anchorEl.getBoundingClientRect();
10845
- const isPositioned = getComputedStyle(scrollContainer).position !== "static";
10846
- const scrollOffsetLeft = isPositioned ? scrollContainer.scrollLeft : 0;
10847
- const scrollOffsetTop = isPositioned ? scrollContainer.scrollTop : 0;
10848
- scrollContainer.setAttribute("data-drop-edge", anchorEdge);
10849
- scrollContainer.style.setProperty("--drop-target-top", `${anchorRect.top - containerRect.top + scrollOffsetTop}px`);
10850
- scrollContainer.style.setProperty("--drop-target-bottom", `${anchorRect.bottom - containerRect.top + scrollOffsetTop}px`);
10851
- scrollContainer.style.setProperty("--drop-target-left", `${anchorRect.left - containerRect.left + scrollOffsetLeft}px`);
10852
- 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`);
10853
11097
  });
10854
11098
  dragGesture.addReleaseCallback(async gestureInfo => {
10855
11099
  clearDropHintDOM();
@@ -10859,7 +11103,7 @@ const startDragToReorder = (event, {
10859
11103
  const clone = cloneWrapper.firstElementChild;
10860
11104
  // Bake the current visual position (transform included) into the CSS vars
10861
11105
  // so the clone stays where the user released it when we clear the transform.
10862
- setCloneDocumentRect(cloneWrapper, cloneWrapper);
11106
+ setCloneViewportRect(cloneWrapper, cloneWrapper);
10863
11107
  gestureInfo.cancelPosition();
10864
11108
  const fromId = getItemId(draggedElement);
10865
11109
  const toId = currentBeforeElement ? getItemId(currentBeforeElement) : null;
@@ -10868,7 +11112,7 @@ const startDragToReorder = (event, {
10868
11112
  const syncCloneWithDropTarget = () => {
10869
11113
  // Snap the CSS-var position to the drop target rect so the browser
10870
11114
  // captures the "new" state at the landing position.
10871
- setCloneDocumentRect(cloneWrapper, currentReleaseElement);
11115
+ setCloneViewportRect(cloneWrapper, currentReleaseElement);
10872
11116
  // Removing this attr drops the CSS scale(1.15), so the browser
10873
11117
  // captures the clone at scale 1 as the "new" state.
10874
11118
  clone.removeAttribute("navi-drag-clone");
@@ -10882,15 +11126,13 @@ const startDragToReorder = (event, {
10882
11126
  });
10883
11127
  };
10884
11128
 
10885
- // getBoundingClientRect() returns viewport-relative coords.
10886
- // The clone wrapper is position:absolute inside document.body, so we need
10887
- // document-relative coords (viewport coords + current page scroll).
10888
- 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) => {
10889
11133
  const rect = el.getBoundingClientRect();
10890
- const scrollLeft = document.documentElement.scrollLeft;
10891
- const scrollTop = document.documentElement.scrollTop;
10892
- cloneWrapper.style.setProperty("--clone-top", `${rect.top + scrollTop}px`);
10893
- 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`);
10894
11136
  cloneWrapper.style.setProperty("--clone-width", `${rect.width}px`);
10895
11137
  cloneWrapper.style.setProperty("--clone-height", `${rect.height}px`);
10896
11138
  };
@@ -10909,12 +11151,43 @@ const setCloneDocumentRect = (cloneWrapper, el) => {
10909
11151
  // so the element expands naturally from where the user clicked.
10910
11152
  // On release, the `navi-drag-clone` attribute is removed inside
10911
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
+ };
10912
11182
  const createDragClone = (element, pointerEvent) => {
10913
11183
  const rect = element.getBoundingClientRect();
10914
11184
  const wrapper = document.createElement("div");
10915
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");
10916
11189
  wrapper.viewTransitionName = "navi-drag-clone-wrapper";
10917
- setCloneDocumentRect(wrapper, element);
11190
+ setCloneViewportRect(wrapper, element);
10918
11191
  // Grab point within the element — used as transform-origin so the
10919
11192
  // scale(1.15) expands from where the user clicked, not the element center.
10920
11193
  // These offsets are element-relative so viewport coords are correct here.
@@ -11008,7 +11281,19 @@ const getResizeDirection = (element) => {
11008
11281
  // directions: hide when a container closes, recheck when it reopens) — the
11009
11282
  // selector/open-detection/timing primitives are identical for both, only
11010
11283
  // what each does with a transition differs.
11011
- 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
+ };
11012
11297
 
11013
11298
  const closestOpenableAncestor = (element) => {
11014
11299
  const parentElement = element.parentElement;
@@ -11018,7 +11303,7 @@ const closestOpenableAncestor = (element) => {
11018
11303
  if (!parentElement.closest) {
11019
11304
  return null;
11020
11305
  }
11021
- return parentElement.closest(ANCESTOR_OPEN_SELECTOR);
11306
+ return parentElement.closest(OPENABLE_SELECTOR);
11022
11307
  };
11023
11308
 
11024
11309
  const isAncestorOpen = (ancestor) => {
@@ -11568,6 +11853,13 @@ const MIN_CONTENT_VISIBILITY_RATIO = 0.6;
11568
11853
  *
11569
11854
  * A bit like https://tetherjs.dev/ but different
11570
11855
  */
11856
+ // The event type observeSize() reports with — recognized by check() as "the
11857
+ // change is in another element, not in the tracked rect".
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";
11862
+
11571
11863
  const visibleRectEffect = (
11572
11864
  element,
11573
11865
  update,
@@ -11636,7 +11928,12 @@ const visibleRectEffect = (
11636
11928
  resizeWatchingPaused = false;
11637
11929
  publishResizeWatchingPausedChange(false);
11638
11930
  };
11639
- 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) => {
11640
11937
 
11641
11938
  // visualViewport, not window.innerWidth/Height: the layout viewport
11642
11939
  // doesn't shrink when the on-screen keyboard opens (same reasoning as
@@ -11810,6 +12107,18 @@ const visibleRectEffect = (
11810
12107
  });
11811
12108
  };
11812
12109
 
12110
+ // An observeSize() delivery reports a size change in some *other*
12111
+ // element — this one's own rect and the viewport are both typically
12112
+ // untouched by it, so the dedup below would skip every single one,
12113
+ // defeating the whole point of observeSize (a popover reconsidering its
12114
+ // placement once its own content shrinks/grows, a callout re-measuring
12115
+ // against its message body).
12116
+ if (event.type === ELEMENT_SIZE_CHANGE) {
12117
+ lastVisibleRect = visibleRect;
12118
+ lastViewportRect = viewportRect;
12119
+ notify();
12120
+ return;
12121
+ }
11813
12122
  const visibleRectChanged =
11814
12123
  !lastVisibleRect ||
11815
12124
  lastVisibleRect.left !== visibleRect.left ||
@@ -12041,7 +12350,12 @@ const visibleRectEffect = (
12041
12350
  });
12042
12351
  }
12043
12352
  {
12044
- 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);
12045
12359
  while (currentOpenableAncestor) {
12046
12360
  const openableAncestor = currentOpenableAncestor;
12047
12361
  if (!isAncestorOpen(openableAncestor)) {
@@ -12214,7 +12528,7 @@ const visibleRectEffect = (
12214
12528
  pendingFrame = requestAnimationFrame(() => {
12215
12529
  pendingFrame = null;
12216
12530
  check(
12217
- new CustomEvent("observed_element_size_change", {
12531
+ new CustomEvent(ELEMENT_SIZE_CHANGE, {
12218
12532
  detail: { width, height },
12219
12533
  }),
12220
12534
  );
@@ -12476,7 +12790,7 @@ const toContainerAlignedPosition = (value) => {
12476
12790
  * edges instead of the page viewport's, on both axes (the Y axis otherwise has no such
12477
12791
  * clamp at all — see the clamp's own comment) — that part *is* gated on `hasValidAnchor`,
12478
12792
  * unlike the coordinate-space conversion itself.
12479
- * @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 }}
12480
12794
  */
12481
12795
  const pickPositionRelativeTo = (
12482
12796
  element,
@@ -12989,22 +13303,34 @@ const pickPositionRelativeTo = (
12989
13303
  // so the usable space includes the anchor dimension.
12990
13304
  // marginWithAnchor (gap between anchor and element) and marginWithContainer are subtracted
12991
13305
  // so callers get the net usable space directly.
12992
- const effectiveSpaceAbove =
12993
- (finalY === "inset-bottom" ? spaceAbove + anchorHeight : spaceAbove) -
12994
- (finalY === "top" ? marginWithAnchor : 0) -
12995
- marginWithContainer;
12996
- const effectiveSpaceBelow =
12997
- (finalY === "inset-top" ? spaceBelow + anchorHeight : spaceBelow) -
12998
- (finalY === "bottom" ? marginWithAnchor : 0) -
12999
- marginWithContainer;
13000
- const effectiveSpaceLeft =
13001
- (finalX === "inset-right" ? spaceLeft + anchorWidth : spaceLeft) -
13002
- (finalX === "left" ? marginWithAnchor : 0) -
13003
- marginWithContainer;
13004
- const effectiveSpaceRight =
13005
- (finalX === "inset-left" ? spaceRight + anchorWidth : spaceRight) -
13006
- (finalX === "right" ? marginWithAnchor : 0) -
13007
- 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;
13008
13334
 
13009
13335
  return {
13010
13336
  // Whether a real anchor actually ended up used — false when there's no
@@ -13027,6 +13353,12 @@ const pickPositionRelativeTo = (
13027
13353
  spaceRight: effectiveSpaceRight,
13028
13354
  spaceAbove: effectiveSpaceAbove,
13029
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,
13030
13362
  };
13031
13363
  };
13032
13364
 
@@ -13147,8 +13479,16 @@ const applyNewPosition = (
13147
13479
  spaceRight,
13148
13480
  spaceAbove,
13149
13481
  spaceBelow,
13482
+ containerWidthAvailable,
13483
+ containerHeightAvailable,
13150
13484
  },
13151
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.
13152
13492
  if (positionY === "top" || positionY === "inset-bottom") {
13153
13493
  element.style.setProperty(
13154
13494
  "--container-position-remaining-height",
@@ -13159,8 +13499,13 @@ const applyNewPosition = (
13159
13499
  "--container-position-remaining-height",
13160
13500
  `${spaceBelow}px`,
13161
13501
  );
13162
- } else {
13502
+ } else if (containerHeightAvailable === undefined) {
13163
13503
  element.style.removeProperty("--container-position-remaining-height");
13504
+ } else {
13505
+ element.style.setProperty(
13506
+ "--container-position-remaining-height",
13507
+ `${containerHeightAvailable}px`,
13508
+ );
13164
13509
  }
13165
13510
  if (positionX === "left" || positionX === "inset-right") {
13166
13511
  element.style.setProperty(
@@ -13172,8 +13517,13 @@ const applyNewPosition = (
13172
13517
  "--container-position-remaining-width",
13173
13518
  `${spaceRight}px`,
13174
13519
  );
13175
- } else {
13520
+ } else if (containerWidthAvailable === undefined) {
13176
13521
  element.style.removeProperty("--container-position-remaining-width");
13522
+ } else {
13523
+ element.style.setProperty(
13524
+ "--container-position-remaining-width",
13525
+ `${containerWidthAvailable}px`,
13526
+ );
13177
13527
  }
13178
13528
 
13179
13529
  // A single implicit keyframe turned out not to work here: the WAAPI
@@ -16463,4 +16813,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
16463
16813
  };
16464
16814
  };
16465
16815
 
16466
- 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.0",
3
+ "version": "0.17.2",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {