@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.
- package/dist/jsenv_dom.js +426 -76
- 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
|
-
|
|
4414
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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"]
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
|
|
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
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
10886
|
-
//
|
|
10887
|
-
//
|
|
10888
|
-
const
|
|
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
|
-
|
|
10891
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
12993
|
-
|
|
12994
|
-
|
|
12995
|
-
|
|
12996
|
-
|
|
12997
|
-
|
|
12998
|
-
|
|
12999
|
-
|
|
13000
|
-
const
|
|
13001
|
-
|
|
13002
|
-
(
|
|
13003
|
-
|
|
13004
|
-
|
|
13005
|
-
|
|
13006
|
-
|
|
13007
|
-
|
|
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 };
|