@jsenv/dom 0.17.1 → 0.17.3
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 +627 -277
- 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;
|
|
@@ -2043,16 +2051,12 @@ const parseCSSTransform = (transformString, normalize) => {
|
|
|
2043
2051
|
|
|
2044
2052
|
const transformObj = {};
|
|
2045
2053
|
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
while ((match = transformPattern.exec(transformString)) !== null) {
|
|
2051
|
-
const [, functionName, value] = match;
|
|
2052
|
-
|
|
2054
|
+
for (const { functionName, value, source } of readTransformFunctions(
|
|
2055
|
+
transformString,
|
|
2056
|
+
)) {
|
|
2053
2057
|
// Handle matrix functions specially
|
|
2054
2058
|
if (functionName === "matrix" || functionName === "matrix3d") {
|
|
2055
|
-
const matrixComponents = parseMatrixTransform(
|
|
2059
|
+
const matrixComponents = parseMatrixTransform(source);
|
|
2056
2060
|
if (matrixComponents) {
|
|
2057
2061
|
// Only add non-default values to preserve original information
|
|
2058
2062
|
Object.assign(transformObj, matrixComponents);
|
|
@@ -2071,6 +2075,42 @@ const parseCSSTransform = (transformString, normalize) => {
|
|
|
2071
2075
|
// Return undefined if no properties were extracted (preserves original information)
|
|
2072
2076
|
return Object.keys(transformObj).length > 0 ? transformObj : undefined;
|
|
2073
2077
|
};
|
|
2078
|
+
|
|
2079
|
+
// Cuts "translateX(10px) translateY(env(safe-area-inset-top))" into its
|
|
2080
|
+
// functions. Parentheses are counted rather than matched with a regex: a
|
|
2081
|
+
// transform value may hold calc(), env(), min()… and stopping at the first ")"
|
|
2082
|
+
// truncates them.
|
|
2083
|
+
const TRANSFORM_FUNCTION_START_REGEX = /(\w+)\(/g;
|
|
2084
|
+
const readTransformFunctions = (transformString) => {
|
|
2085
|
+
const transformFunctions = [];
|
|
2086
|
+
TRANSFORM_FUNCTION_START_REGEX.lastIndex = 0;
|
|
2087
|
+
let match;
|
|
2088
|
+
while ((match = TRANSFORM_FUNCTION_START_REGEX.exec(transformString))) {
|
|
2089
|
+
const valueStart = match.index + match[0].length;
|
|
2090
|
+
let depth = 1;
|
|
2091
|
+
let index = valueStart;
|
|
2092
|
+
while (index < transformString.length && depth > 0) {
|
|
2093
|
+
const char = transformString[index];
|
|
2094
|
+
if (char === "(") {
|
|
2095
|
+
depth++;
|
|
2096
|
+
} else if (char === ")") {
|
|
2097
|
+
depth--;
|
|
2098
|
+
}
|
|
2099
|
+
index++;
|
|
2100
|
+
}
|
|
2101
|
+
if (depth > 0) {
|
|
2102
|
+
// Unbalanced: nothing reliable left to read after this point.
|
|
2103
|
+
break;
|
|
2104
|
+
}
|
|
2105
|
+
transformFunctions.push({
|
|
2106
|
+
functionName: match[1],
|
|
2107
|
+
value: transformString.slice(valueStart, index - 1),
|
|
2108
|
+
source: transformString.slice(match.index, index),
|
|
2109
|
+
});
|
|
2110
|
+
TRANSFORM_FUNCTION_START_REGEX.lastIndex = index;
|
|
2111
|
+
}
|
|
2112
|
+
return transformFunctions;
|
|
2113
|
+
};
|
|
2074
2114
|
// Parse a matrix transform and extract simple transform components when possible
|
|
2075
2115
|
const parseMatrixTransform = (matrixString) => {
|
|
2076
2116
|
// Match matrix() or matrix3d() functions
|
|
@@ -2316,33 +2356,6 @@ const globalCSSKeywordSet = new Set([
|
|
|
2316
2356
|
"unset",
|
|
2317
2357
|
"revert",
|
|
2318
2358
|
]);
|
|
2319
|
-
// Keywords that should NOT get automatic units when used with properties from:
|
|
2320
|
-
// - pxPropertySet (width, height, fontSize, etc.)
|
|
2321
|
-
// - degPropertySet (rotate, skew, etc.)
|
|
2322
|
-
// - unitlessPropertySet (opacity, zIndex, etc.)
|
|
2323
|
-
// This prevents auto-unit addition: e.g., width: "auto" stays "auto", not "autopx"
|
|
2324
|
-
const unitlessKeywordSet = new Set([
|
|
2325
|
-
...globalCSSKeywordSet,
|
|
2326
|
-
// Size/dimension keywords for pxPropertySet properties
|
|
2327
|
-
"fit-content",
|
|
2328
|
-
"min-content",
|
|
2329
|
-
"max-content",
|
|
2330
|
-
// Font size keywords for fontSize
|
|
2331
|
-
"medium",
|
|
2332
|
-
"small",
|
|
2333
|
-
"large",
|
|
2334
|
-
"x-small",
|
|
2335
|
-
"x-large",
|
|
2336
|
-
"xx-small",
|
|
2337
|
-
"xx-large",
|
|
2338
|
-
"smaller",
|
|
2339
|
-
"larger",
|
|
2340
|
-
// Border width keywords for borderWidth properties
|
|
2341
|
-
"thin",
|
|
2342
|
-
"thick",
|
|
2343
|
-
// Line height keyword (though lineHeight is handled specially)
|
|
2344
|
-
"normal",
|
|
2345
|
-
]);
|
|
2346
2359
|
// Keywords for backgroundImage property that should NOT be wrapped in url()
|
|
2347
2360
|
// Used to prevent: background: "none" becoming background: "url(none)"
|
|
2348
2361
|
const backgroundKeywordSet = new Set([
|
|
@@ -2379,10 +2392,28 @@ const getUnit = (value) => {
|
|
|
2379
2392
|
}
|
|
2380
2393
|
return "";
|
|
2381
2394
|
};
|
|
2382
|
-
// Check if value already has a unit
|
|
2383
|
-
const isUnitless = (value) => getUnit(value) === "";
|
|
2384
2395
|
const hasCSSSizeUnit = (value) => cssSizeUnitSet.has(getUnit(value));
|
|
2385
2396
|
|
|
2397
|
+
// A single number and nothing else — the only shape a unit may be appended to.
|
|
2398
|
+
// Everything else already says what it is: a unit ("2em"), a keyword ("auto"),
|
|
2399
|
+
// a CSS expression ("env(safe-area-inset-top)", "calc(…)") or a list of those
|
|
2400
|
+
// ("0 auto", "10px env(safe-area-inset-right)"). Asking "is this one number"
|
|
2401
|
+
// covers them all at once; listing what must be left alone (keywords, then
|
|
2402
|
+
// functions, then lists…) only ever covers what someone thought of.
|
|
2403
|
+
const isBareNumber = (value) => {
|
|
2404
|
+
if (value === "") {
|
|
2405
|
+
return false;
|
|
2406
|
+
}
|
|
2407
|
+
return !isNaN(Number(value));
|
|
2408
|
+
};
|
|
2409
|
+
// The same number, carrying exactly the unit asked for, and nothing else.
|
|
2410
|
+
const isBareNumberWithUnit = (value, unit) => {
|
|
2411
|
+
if (!value.endsWith(unit)) {
|
|
2412
|
+
return false;
|
|
2413
|
+
}
|
|
2414
|
+
return isBareNumber(value.slice(0, -unit.length));
|
|
2415
|
+
};
|
|
2416
|
+
|
|
2386
2417
|
// url(
|
|
2387
2418
|
// linear-gradient(
|
|
2388
2419
|
// radial-gradient(
|
|
@@ -2642,23 +2673,21 @@ const isCSSKeyword = (value) => {
|
|
|
2642
2673
|
};
|
|
2643
2674
|
const normalizeNumber = (value, { unit, propertyName, preferedType }) => {
|
|
2644
2675
|
if (typeof value === "string") {
|
|
2645
|
-
|
|
2646
|
-
if (isCSSFunction(value)) {
|
|
2647
|
-
return value;
|
|
2648
|
-
}
|
|
2649
|
-
// Keep strings as-is (including %, em, rem, auto, none, etc.)
|
|
2676
|
+
value = value.trim();
|
|
2650
2677
|
if (preferedType === "string") {
|
|
2651
|
-
|
|
2678
|
+
// Everything that is not a lone number already carries its own meaning
|
|
2679
|
+
// and goes to the DOM untouched: "2em", "auto", "env(safe-area-inset-top)",
|
|
2680
|
+
// "calc(…)", "0 auto", "10px env(safe-area-inset-right)".
|
|
2681
|
+
if (unit && isBareNumber(value)) {
|
|
2652
2682
|
return `${value}${unit}`;
|
|
2653
2683
|
}
|
|
2654
2684
|
return value;
|
|
2655
2685
|
}
|
|
2656
|
-
//
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
}
|
|
2686
|
+
// A number to work with, only when the value is exactly one:
|
|
2687
|
+
// "12px" -> 12, "0.5" -> 0.5. "10px 20px" is a list and "calc(…)" is an
|
|
2688
|
+
// expression — parseFloat would silently keep their first number.
|
|
2689
|
+
if (unit ? isBareNumberWithUnit(value, unit) : isBareNumber(value)) {
|
|
2690
|
+
return parseFloat(value);
|
|
2662
2691
|
}
|
|
2663
2692
|
return value;
|
|
2664
2693
|
}
|
|
@@ -3904,6 +3933,14 @@ const getLuminance = (r, g, b) => {
|
|
|
3904
3933
|
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
3905
3934
|
};
|
|
3906
3935
|
|
|
3936
|
+
/**
|
|
3937
|
+
* First ancestor of `node` matching `predicate`, walking parent by parent.
|
|
3938
|
+
* Starts at the parent — `node` itself is never a candidate.
|
|
3939
|
+
*
|
|
3940
|
+
* @param {Node} node
|
|
3941
|
+
* @param {(ancestor: Node) => boolean} predicate
|
|
3942
|
+
* @returns {Node|null}
|
|
3943
|
+
*/
|
|
3907
3944
|
const findAncestor = (node, predicate) => {
|
|
3908
3945
|
let ancestor = node.parentNode;
|
|
3909
3946
|
while (ancestor) {
|
|
@@ -3915,6 +3952,21 @@ const findAncestor = (node, predicate) => {
|
|
|
3915
3952
|
return null;
|
|
3916
3953
|
};
|
|
3917
3954
|
|
|
3955
|
+
/**
|
|
3956
|
+
* First descendant of `rootNode` matching `fn`, in document order (depth
|
|
3957
|
+
* first). The walk is bounded to the subtree: `rootNode` itself is not a
|
|
3958
|
+
* candidate, and a root with no children yields nothing — never the root's
|
|
3959
|
+
* siblings.
|
|
3960
|
+
*
|
|
3961
|
+
* @param {Node} rootNode
|
|
3962
|
+
* @param {(node: Node, skip: () => void) => boolean} fn - Return true to stop
|
|
3963
|
+
* on `node`. Call `skip()` to not descend into `node`'s children (the walk
|
|
3964
|
+
* goes on with its siblings).
|
|
3965
|
+
* @param {object} [options]
|
|
3966
|
+
* @param {Node} [options.skipRoot] - A subtree to leave out entirely, itself
|
|
3967
|
+
* included.
|
|
3968
|
+
* @returns {Node|null}
|
|
3969
|
+
*/
|
|
3918
3970
|
const findDescendant = (rootNode, fn, { skipRoot } = {}) => {
|
|
3919
3971
|
const iterator = createNextNodeIterator(rootNode, rootNode, skipRoot);
|
|
3920
3972
|
let { done, value: node } = iterator.next();
|
|
@@ -3935,6 +3987,18 @@ const findDescendant = (rootNode, fn, { skipRoot } = {}) => {
|
|
|
3935
3987
|
return null;
|
|
3936
3988
|
};
|
|
3937
3989
|
|
|
3990
|
+
/**
|
|
3991
|
+
* Last descendant of `rootNode` matching `fn` in document order — the walk
|
|
3992
|
+
* starts at the subtree's deepest final node and moves backwards, so the
|
|
3993
|
+
* first match it meets is the last one the document holds.
|
|
3994
|
+
*
|
|
3995
|
+
* @param {Node} rootNode
|
|
3996
|
+
* @param {(node: Node) => boolean} fn
|
|
3997
|
+
* @param {object} [options]
|
|
3998
|
+
* @param {Node} [options.skipRoot] - A subtree to leave out entirely, itself
|
|
3999
|
+
* included.
|
|
4000
|
+
* @returns {Node|null}
|
|
4001
|
+
*/
|
|
3938
4002
|
const findLastDescendant = (rootNode, fn, { skipRoot } = {}) => {
|
|
3939
4003
|
const deepestNode = getDeepestNode(rootNode, skipRoot);
|
|
3940
4004
|
if (deepestNode) {
|
|
@@ -3954,6 +4018,23 @@ const findLastDescendant = (rootNode, fn, { skipRoot } = {}) => {
|
|
|
3954
4018
|
return null;
|
|
3955
4019
|
};
|
|
3956
4020
|
|
|
4021
|
+
/**
|
|
4022
|
+
* First node after `from` in document order matching `predicate`. Unlike
|
|
4023
|
+
* findDescendant this is anchored to a position, not a container: the walk
|
|
4024
|
+
* leaves `from`'s subtree and goes on through its siblings and its ancestors'
|
|
4025
|
+
* siblings, until `root`'s subtree is exhausted.
|
|
4026
|
+
*
|
|
4027
|
+
* @param {Node} from - The position to search from; not a candidate itself.
|
|
4028
|
+
* @param {(node: Node) => boolean} predicate
|
|
4029
|
+
* @param {object} [options]
|
|
4030
|
+
* @param {Node} [options.root] - Bounds the walk to its subtree; null walks to
|
|
4031
|
+
* the end of the tree `from` belongs to.
|
|
4032
|
+
* @param {Node} [options.skipRoot] - A subtree to leave out entirely, itself
|
|
4033
|
+
* included. A `from` inside it starts right after it.
|
|
4034
|
+
* @param {boolean} [options.skipChildren] - Do not look inside `from`; start
|
|
4035
|
+
* at what follows it.
|
|
4036
|
+
* @returns {Node|null}
|
|
4037
|
+
*/
|
|
3957
4038
|
const findAfter = (
|
|
3958
4039
|
from,
|
|
3959
4040
|
predicate,
|
|
@@ -3970,6 +4051,21 @@ const findAfter = (
|
|
|
3970
4051
|
return null;
|
|
3971
4052
|
};
|
|
3972
4053
|
|
|
4054
|
+
/**
|
|
4055
|
+
* First node before `from` in reverse document order matching `predicate` —
|
|
4056
|
+
* what findAfter is to "next", this is to "previous". A step back lands on
|
|
4057
|
+
* the previous sibling's DEEPEST last node (document order walked backwards),
|
|
4058
|
+
* not on the sibling itself.
|
|
4059
|
+
*
|
|
4060
|
+
* @param {Node} from - The position to search from; not a candidate itself.
|
|
4061
|
+
* @param {(node: Node) => boolean} predicate
|
|
4062
|
+
* @param {object} [options]
|
|
4063
|
+
* @param {Node} [options.root] - Bounds the walk to its subtree; null walks
|
|
4064
|
+
* back to the start of the tree `from` belongs to.
|
|
4065
|
+
* @param {Node} [options.skipRoot] - A subtree to leave out entirely, itself
|
|
4066
|
+
* included. A `from` inside it starts right before it.
|
|
4067
|
+
* @returns {Node|null}
|
|
4068
|
+
*/
|
|
3973
4069
|
const findBefore = (
|
|
3974
4070
|
from,
|
|
3975
4071
|
predicate,
|
|
@@ -4002,6 +4098,15 @@ const getNextNode = (node, rootNode, skipChild = false, skipRoot = null) => {
|
|
|
4002
4098
|
}
|
|
4003
4099
|
}
|
|
4004
4100
|
|
|
4101
|
+
// The traversal is bounded to rootNode's subtree: the root's own siblings
|
|
4102
|
+
// are not part of it. Without this, a rootNode with no children (asking
|
|
4103
|
+
// findDescendant about an <input>, say) steps to its next sibling and walks
|
|
4104
|
+
// the rest of the document from there — the parentNode guard below never
|
|
4105
|
+
// catches it because the walk is already outside the root.
|
|
4106
|
+
if (node === rootNode) {
|
|
4107
|
+
return null;
|
|
4108
|
+
}
|
|
4109
|
+
|
|
4005
4110
|
const nextSibling = node.nextSibling;
|
|
4006
4111
|
if (nextSibling) {
|
|
4007
4112
|
// If next sibling is skipRoot, skip it entirely
|
|
@@ -4410,8 +4515,13 @@ const canInteract = (element) => {
|
|
|
4410
4515
|
if (element.disabled) {
|
|
4411
4516
|
return false;
|
|
4412
4517
|
}
|
|
4413
|
-
|
|
4414
|
-
|
|
4518
|
+
// closest, not hasAttribute: inert is inherited by the whole subtree — the
|
|
4519
|
+
// element itself may carry nothing and still be untouchable because something
|
|
4520
|
+
// above it is inert (a slide waiting off screen, the page behind a modal).
|
|
4521
|
+
// Focusing one of those does nothing at all, silently: the browser refuses and
|
|
4522
|
+
// the focus stays where it was, which reads as "the popup opened on nothing".
|
|
4523
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inert
|
|
4524
|
+
if (element.closest("[inert]")) {
|
|
4415
4525
|
return false;
|
|
4416
4526
|
}
|
|
4417
4527
|
return true;
|
|
@@ -4498,6 +4608,185 @@ const findFocusable = (element, { exclude } = {}) => {
|
|
|
4498
4608
|
return focusableDescendant;
|
|
4499
4609
|
};
|
|
4500
4610
|
|
|
4611
|
+
// note: keep in mind that an element with overflow: 'hidden' is scrollable
|
|
4612
|
+
// it can be scrolled using keyboard arrows or JavaScript properties such as scrollTop, scrollLeft
|
|
4613
|
+
// the only overflow that prevents scroll is "visible"
|
|
4614
|
+
const isScrollable = (element, { includeHidden } = {}) => {
|
|
4615
|
+
if (canHaveVerticalScroll(element, { includeHidden })) {
|
|
4616
|
+
return true;
|
|
4617
|
+
}
|
|
4618
|
+
if (canHaveHorizontalScroll(element, { includeHidden })) {
|
|
4619
|
+
return true;
|
|
4620
|
+
}
|
|
4621
|
+
return false;
|
|
4622
|
+
};
|
|
4623
|
+
|
|
4624
|
+
// Whether this element is what scrolls on that axis: it says it may (overflow)
|
|
4625
|
+
// and it has somewhere to go (it overflows). Both are needed — an "auto" box
|
|
4626
|
+
// whose content fits scrolls nothing, and `overflow-x: auto` alone makes the
|
|
4627
|
+
// COMPUTED overflow-y auto too (CSS does not let one axis stay visible next to
|
|
4628
|
+
// a scrolling one), so a box scrolling sideways declares a vertical scroll it
|
|
4629
|
+
// will never do.
|
|
4630
|
+
const canScroll = (element, axis) => {
|
|
4631
|
+
if (!element || element.nodeType !== 1) {
|
|
4632
|
+
return false;
|
|
4633
|
+
}
|
|
4634
|
+
const style = getComputedStyle(element);
|
|
4635
|
+
const overflow = axis === "x" ? style.overflowX : style.overflowY;
|
|
4636
|
+
if (overflow !== "auto" && overflow !== "scroll") {
|
|
4637
|
+
return false;
|
|
4638
|
+
}
|
|
4639
|
+
const scrollSize = axis === "x" ? element.scrollWidth : element.scrollHeight;
|
|
4640
|
+
const clientSize = axis === "x" ? element.clientWidth : element.clientHeight;
|
|
4641
|
+
// A pixel of slack: subpixel content rounds scrollSize up on boxes that have
|
|
4642
|
+
// nowhere to scroll to.
|
|
4643
|
+
return scrollSize - clientSize > 1;
|
|
4644
|
+
};
|
|
4645
|
+
|
|
4646
|
+
const canHaveVerticalScroll = (element, { includeHidden }) => {
|
|
4647
|
+
const verticalOverflow = getStyle(element, "overflow-y");
|
|
4648
|
+
if (verticalOverflow === "visible") {
|
|
4649
|
+
// browser returns "visible" on documentElement even if it is scrollable
|
|
4650
|
+
if (isDocumentElement(element)) {
|
|
4651
|
+
return true;
|
|
4652
|
+
}
|
|
4653
|
+
return false;
|
|
4654
|
+
}
|
|
4655
|
+
if (verticalOverflow === "hidden" || verticalOverflow === "clip") {
|
|
4656
|
+
return includeHidden;
|
|
4657
|
+
}
|
|
4658
|
+
const overflow = getStyle(element, "overflow");
|
|
4659
|
+
if (overflow === "visible") {
|
|
4660
|
+
// browser returns "visible" on documentElement even if it is scrollable
|
|
4661
|
+
if (isDocumentElement(element)) {
|
|
4662
|
+
return true;
|
|
4663
|
+
}
|
|
4664
|
+
return false;
|
|
4665
|
+
}
|
|
4666
|
+
if (overflow === "hidden" || overflow === "clip") {
|
|
4667
|
+
return includeHidden;
|
|
4668
|
+
}
|
|
4669
|
+
return true; // "auto", "scroll"
|
|
4670
|
+
};
|
|
4671
|
+
const canHaveHorizontalScroll = (element, { includeHidden }) => {
|
|
4672
|
+
const horizontalOverflow = getStyle(element, "overflow-x");
|
|
4673
|
+
if (horizontalOverflow === "visible") {
|
|
4674
|
+
// browser returns "visible" on documentElement even if it is scrollable
|
|
4675
|
+
if (isDocumentElement(element)) {
|
|
4676
|
+
return true;
|
|
4677
|
+
}
|
|
4678
|
+
return false;
|
|
4679
|
+
}
|
|
4680
|
+
if (horizontalOverflow === "hidden" || horizontalOverflow === "clip") {
|
|
4681
|
+
return includeHidden;
|
|
4682
|
+
}
|
|
4683
|
+
const overflow = getStyle(element, "overflow");
|
|
4684
|
+
if (overflow === "visible") {
|
|
4685
|
+
if (isDocumentElement(element)) {
|
|
4686
|
+
// browser returns "visible" on documentElement even if it is scrollable
|
|
4687
|
+
return true;
|
|
4688
|
+
}
|
|
4689
|
+
return false;
|
|
4690
|
+
}
|
|
4691
|
+
if (overflow === "hidden" || overflow === "clip") {
|
|
4692
|
+
return includeHidden;
|
|
4693
|
+
}
|
|
4694
|
+
return true; // "auto", "scroll"
|
|
4695
|
+
};
|
|
4696
|
+
|
|
4697
|
+
const getScrollingElement = (document) => {
|
|
4698
|
+
const { scrollingElement } = document;
|
|
4699
|
+
if (scrollingElement) {
|
|
4700
|
+
return scrollingElement;
|
|
4701
|
+
}
|
|
4702
|
+
|
|
4703
|
+
if (isCompliant(document)) {
|
|
4704
|
+
return document.documentElement;
|
|
4705
|
+
}
|
|
4706
|
+
|
|
4707
|
+
const body = document.body;
|
|
4708
|
+
const isFrameset = body && !/body/i.test(body.tagName);
|
|
4709
|
+
const possiblyScrollingElement = isFrameset ? getNextBodyElement(body) : body;
|
|
4710
|
+
|
|
4711
|
+
// If `body` is itself scrollable, it is not the `scrollingElement`.
|
|
4712
|
+
return possiblyScrollingElement && bodyIsScrollable(possiblyScrollingElement)
|
|
4713
|
+
? null
|
|
4714
|
+
: possiblyScrollingElement;
|
|
4715
|
+
};
|
|
4716
|
+
|
|
4717
|
+
const isHidden = (element) => {
|
|
4718
|
+
const display = getStyle(element, "display");
|
|
4719
|
+
if (display === "none") {
|
|
4720
|
+
return false;
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
if (
|
|
4724
|
+
display === "table-row" ||
|
|
4725
|
+
display === "table-group" ||
|
|
4726
|
+
display === "table-column"
|
|
4727
|
+
) {
|
|
4728
|
+
return getStyle(element, "visibility") !== "collapsed";
|
|
4729
|
+
}
|
|
4730
|
+
|
|
4731
|
+
return true;
|
|
4732
|
+
};
|
|
4733
|
+
const isCompliant = (document) => {
|
|
4734
|
+
// Note: document.compatMode can be toggle at runtime by document.write
|
|
4735
|
+
const isStandardsMode = /^CSS1/.test(document.compatMode);
|
|
4736
|
+
if (isStandardsMode) {
|
|
4737
|
+
return testScrollCompliance(document);
|
|
4738
|
+
}
|
|
4739
|
+
return false;
|
|
4740
|
+
};
|
|
4741
|
+
const testScrollCompliance = (document) => {
|
|
4742
|
+
const iframe = document.createElement("iframe");
|
|
4743
|
+
iframe.style.height = "1px";
|
|
4744
|
+
const parentNode = document.body || document.documentElement || document;
|
|
4745
|
+
parentNode.appendChild(iframe);
|
|
4746
|
+
const iframeDocument = iframe.contentWindow.document;
|
|
4747
|
+
iframeDocument.write('<!DOCTYPE html><div style="height:9999em">x</div>');
|
|
4748
|
+
iframeDocument.close();
|
|
4749
|
+
const scrollComplianceResult =
|
|
4750
|
+
iframeDocument.documentElement.scrollHeight >
|
|
4751
|
+
iframeDocument.body.scrollHeight;
|
|
4752
|
+
iframe.parentNode.removeChild(iframe);
|
|
4753
|
+
return scrollComplianceResult;
|
|
4754
|
+
};
|
|
4755
|
+
const getNextBodyElement = (frameset) => {
|
|
4756
|
+
// We use this function to be correct per spec in case `document.body` is
|
|
4757
|
+
// a `frameset` but there exists a later `body`. Since `document.body` is
|
|
4758
|
+
// a `frameset`, we know the root is an `html`, and there was no `body`
|
|
4759
|
+
// before the `frameset`, so we just need to look at siblings after the
|
|
4760
|
+
// `frameset`.
|
|
4761
|
+
let current = frameset;
|
|
4762
|
+
while ((current = current.nextSibling)) {
|
|
4763
|
+
if (current.nodeType === 1 && isBodyElement(current)) {
|
|
4764
|
+
return current;
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
return null;
|
|
4768
|
+
};
|
|
4769
|
+
const isBodyElement = (element) => element.ownerDocument.body === element;
|
|
4770
|
+
const bodyIsScrollable = (body) => {
|
|
4771
|
+
// a body element is scrollable if body and html are scrollable and rendered
|
|
4772
|
+
if (!isScrollable(body)) {
|
|
4773
|
+
return false;
|
|
4774
|
+
}
|
|
4775
|
+
if (isHidden(body)) {
|
|
4776
|
+
return false;
|
|
4777
|
+
}
|
|
4778
|
+
|
|
4779
|
+
const documentElement = body.ownerDocument.documentElement;
|
|
4780
|
+
if (!isScrollable(documentElement)) {
|
|
4781
|
+
return false;
|
|
4782
|
+
}
|
|
4783
|
+
if (isHidden(documentElement)) {
|
|
4784
|
+
return false;
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4787
|
+
return true;
|
|
4788
|
+
};
|
|
4789
|
+
|
|
4501
4790
|
/**
|
|
4502
4791
|
* Returns the browser's default action for a keyboard event on its target element.
|
|
4503
4792
|
*
|
|
@@ -4509,7 +4798,12 @@ const findFocusable = (element, { exclude } = {}) => {
|
|
|
4509
4798
|
* - `"value_change"` — key increments/decrements the field value (range, number, date…)
|
|
4510
4799
|
* - `"cursor_move"` — key moves the text cursor within the field
|
|
4511
4800
|
* - `"type"` — key produces or deletes text content
|
|
4512
|
-
* - `"scroll"` — key
|
|
4801
|
+
* - `"scroll"` — key would scroll the page: nothing on the element itself
|
|
4802
|
+
* claims it, so it is safe to intercept
|
|
4803
|
+
* - `"scroll_self"` — the focused element scrolls ITSELF that way (it really
|
|
4804
|
+
* overflows on that axis): the key is spoken for, and
|
|
4805
|
+
* taking it would leave a scrollable region no way to be
|
|
4806
|
+
* scrolled from the keyboard
|
|
4513
4807
|
* - `""` — no meaningful browser default; safe to intercept freely
|
|
4514
4808
|
*/
|
|
4515
4809
|
const normalizeKeyboardKey = (rawKey) => {
|
|
@@ -4753,6 +5047,26 @@ const DEFAULT_BEHAVIORS = [
|
|
|
4753
5047
|
escape: "dismiss",
|
|
4754
5048
|
},
|
|
4755
5049
|
},
|
|
5050
|
+
{
|
|
5051
|
+
// An element that really scrolls — a slide's own body, a scrollable panel:
|
|
5052
|
+
// the browser gives it the arrows (and Home/End/PageUp/PageDown) so it can
|
|
5053
|
+
// be read from the keyboard, and that is not a key to take. Asked per axis
|
|
5054
|
+
// and per element, not from a class or an attribute: what makes it true is
|
|
5055
|
+
// that it overflows right now.
|
|
5056
|
+
test: (el) => canScroll(el, "y") || canScroll(el, "x"),
|
|
5057
|
+
keys: {
|
|
5058
|
+
arrowup: (e) => (canScroll(e.target, "y") ? "scroll_self" : undefined),
|
|
5059
|
+
arrowdown: (e) => (canScroll(e.target, "y") ? "scroll_self" : undefined),
|
|
5060
|
+
arrowleft: (e) => (canScroll(e.target, "x") ? "scroll_self" : undefined),
|
|
5061
|
+
arrowright: (e) => (canScroll(e.target, "x") ? "scroll_self" : undefined),
|
|
5062
|
+
pageup: (e) => (canScroll(e.target, "y") ? "scroll_self" : undefined),
|
|
5063
|
+
pagedown: (e) => (canScroll(e.target, "y") ? "scroll_self" : undefined),
|
|
5064
|
+
home: (e) => (canScroll(e.target, "y") ? "scroll_self" : undefined),
|
|
5065
|
+
end: (e) => (canScroll(e.target, "y") ? "scroll_self" : undefined),
|
|
5066
|
+
space: (e) => (canScroll(e.target, "y") ? "scroll_self" : undefined),
|
|
5067
|
+
},
|
|
5068
|
+
// no fallback: only these keys are claimed, everything else keeps looking
|
|
5069
|
+
},
|
|
4756
5070
|
{
|
|
4757
5071
|
// Non-interactive elements: browser scrolls on Space and arrow keys
|
|
4758
5072
|
test: () => true,
|
|
@@ -6033,9 +6347,15 @@ const trapFocusInside = (
|
|
|
6033
6347
|
// A backdrop click is detected when the target is a <dialog> element —
|
|
6034
6348
|
// the ::backdrop pseudo-element is not in the DOM, so the event target
|
|
6035
6349
|
// becomes the dialog element itself when its content area is not hit.
|
|
6350
|
+
// Read through getAttribute rather than .className: on an SVG element
|
|
6351
|
+
// className is an SVGAnimatedString, not a string, and asking it for
|
|
6352
|
+
// .includes throws — which is how clicking an icon inside the trap
|
|
6353
|
+
// used to break. Still a substring test, because the real class names
|
|
6354
|
+
// are navi_dialog_backdrop / navi_popover_backdrop / ….
|
|
6355
|
+
const targetClass = event.target.getAttribute?.("class") || "";
|
|
6036
6356
|
const isBackdropClick =
|
|
6037
6357
|
event.target.tagName === "DIALOG" ||
|
|
6038
|
-
|
|
6358
|
+
targetClass.includes("backdrop");
|
|
6039
6359
|
if (!isBackdropClick) {
|
|
6040
6360
|
event.stopImmediatePropagation();
|
|
6041
6361
|
}
|
|
@@ -6203,163 +6523,6 @@ const captureScrollState = (element) => {
|
|
|
6203
6523
|
};
|
|
6204
6524
|
};
|
|
6205
6525
|
|
|
6206
|
-
// note: keep in mind that an element with overflow: 'hidden' is scrollable
|
|
6207
|
-
// it can be scrolled using keyboard arrows or JavaScript properties such as scrollTop, scrollLeft
|
|
6208
|
-
// the only overflow that prevents scroll is "visible"
|
|
6209
|
-
const isScrollable = (element, { includeHidden } = {}) => {
|
|
6210
|
-
if (canHaveVerticalScroll(element, { includeHidden })) {
|
|
6211
|
-
return true;
|
|
6212
|
-
}
|
|
6213
|
-
if (canHaveHorizontalScroll(element, { includeHidden })) {
|
|
6214
|
-
return true;
|
|
6215
|
-
}
|
|
6216
|
-
return false;
|
|
6217
|
-
};
|
|
6218
|
-
|
|
6219
|
-
const canHaveVerticalScroll = (element, { includeHidden }) => {
|
|
6220
|
-
const verticalOverflow = getStyle(element, "overflow-y");
|
|
6221
|
-
if (verticalOverflow === "visible") {
|
|
6222
|
-
// browser returns "visible" on documentElement even if it is scrollable
|
|
6223
|
-
if (isDocumentElement(element)) {
|
|
6224
|
-
return true;
|
|
6225
|
-
}
|
|
6226
|
-
return false;
|
|
6227
|
-
}
|
|
6228
|
-
if (verticalOverflow === "hidden" || verticalOverflow === "clip") {
|
|
6229
|
-
return includeHidden;
|
|
6230
|
-
}
|
|
6231
|
-
const overflow = getStyle(element, "overflow");
|
|
6232
|
-
if (overflow === "visible") {
|
|
6233
|
-
// browser returns "visible" on documentElement even if it is scrollable
|
|
6234
|
-
if (isDocumentElement(element)) {
|
|
6235
|
-
return true;
|
|
6236
|
-
}
|
|
6237
|
-
return false;
|
|
6238
|
-
}
|
|
6239
|
-
if (overflow === "hidden" || overflow === "clip") {
|
|
6240
|
-
return includeHidden;
|
|
6241
|
-
}
|
|
6242
|
-
return true; // "auto", "scroll"
|
|
6243
|
-
};
|
|
6244
|
-
const canHaveHorizontalScroll = (element, { includeHidden }) => {
|
|
6245
|
-
const horizontalOverflow = getStyle(element, "overflow-x");
|
|
6246
|
-
if (horizontalOverflow === "visible") {
|
|
6247
|
-
// browser returns "visible" on documentElement even if it is scrollable
|
|
6248
|
-
if (isDocumentElement(element)) {
|
|
6249
|
-
return true;
|
|
6250
|
-
}
|
|
6251
|
-
return false;
|
|
6252
|
-
}
|
|
6253
|
-
if (horizontalOverflow === "hidden" || horizontalOverflow === "clip") {
|
|
6254
|
-
return includeHidden;
|
|
6255
|
-
}
|
|
6256
|
-
const overflow = getStyle(element, "overflow");
|
|
6257
|
-
if (overflow === "visible") {
|
|
6258
|
-
if (isDocumentElement(element)) {
|
|
6259
|
-
// browser returns "visible" on documentElement even if it is scrollable
|
|
6260
|
-
return true;
|
|
6261
|
-
}
|
|
6262
|
-
return false;
|
|
6263
|
-
}
|
|
6264
|
-
if (overflow === "hidden" || overflow === "clip") {
|
|
6265
|
-
return includeHidden;
|
|
6266
|
-
}
|
|
6267
|
-
return true; // "auto", "scroll"
|
|
6268
|
-
};
|
|
6269
|
-
|
|
6270
|
-
const getScrollingElement = (document) => {
|
|
6271
|
-
const { scrollingElement } = document;
|
|
6272
|
-
if (scrollingElement) {
|
|
6273
|
-
return scrollingElement;
|
|
6274
|
-
}
|
|
6275
|
-
|
|
6276
|
-
if (isCompliant(document)) {
|
|
6277
|
-
return document.documentElement;
|
|
6278
|
-
}
|
|
6279
|
-
|
|
6280
|
-
const body = document.body;
|
|
6281
|
-
const isFrameset = body && !/body/i.test(body.tagName);
|
|
6282
|
-
const possiblyScrollingElement = isFrameset ? getNextBodyElement(body) : body;
|
|
6283
|
-
|
|
6284
|
-
// If `body` is itself scrollable, it is not the `scrollingElement`.
|
|
6285
|
-
return possiblyScrollingElement && bodyIsScrollable(possiblyScrollingElement)
|
|
6286
|
-
? null
|
|
6287
|
-
: possiblyScrollingElement;
|
|
6288
|
-
};
|
|
6289
|
-
|
|
6290
|
-
const isHidden = (element) => {
|
|
6291
|
-
const display = getStyle(element, "display");
|
|
6292
|
-
if (display === "none") {
|
|
6293
|
-
return false;
|
|
6294
|
-
}
|
|
6295
|
-
|
|
6296
|
-
if (
|
|
6297
|
-
display === "table-row" ||
|
|
6298
|
-
display === "table-group" ||
|
|
6299
|
-
display === "table-column"
|
|
6300
|
-
) {
|
|
6301
|
-
return getStyle(element, "visibility") !== "collapsed";
|
|
6302
|
-
}
|
|
6303
|
-
|
|
6304
|
-
return true;
|
|
6305
|
-
};
|
|
6306
|
-
const isCompliant = (document) => {
|
|
6307
|
-
// Note: document.compatMode can be toggle at runtime by document.write
|
|
6308
|
-
const isStandardsMode = /^CSS1/.test(document.compatMode);
|
|
6309
|
-
if (isStandardsMode) {
|
|
6310
|
-
return testScrollCompliance(document);
|
|
6311
|
-
}
|
|
6312
|
-
return false;
|
|
6313
|
-
};
|
|
6314
|
-
const testScrollCompliance = (document) => {
|
|
6315
|
-
const iframe = document.createElement("iframe");
|
|
6316
|
-
iframe.style.height = "1px";
|
|
6317
|
-
const parentNode = document.body || document.documentElement || document;
|
|
6318
|
-
parentNode.appendChild(iframe);
|
|
6319
|
-
const iframeDocument = iframe.contentWindow.document;
|
|
6320
|
-
iframeDocument.write('<!DOCTYPE html><div style="height:9999em">x</div>');
|
|
6321
|
-
iframeDocument.close();
|
|
6322
|
-
const scrollComplianceResult =
|
|
6323
|
-
iframeDocument.documentElement.scrollHeight >
|
|
6324
|
-
iframeDocument.body.scrollHeight;
|
|
6325
|
-
iframe.parentNode.removeChild(iframe);
|
|
6326
|
-
return scrollComplianceResult;
|
|
6327
|
-
};
|
|
6328
|
-
const getNextBodyElement = (frameset) => {
|
|
6329
|
-
// We use this function to be correct per spec in case `document.body` is
|
|
6330
|
-
// a `frameset` but there exists a later `body`. Since `document.body` is
|
|
6331
|
-
// a `frameset`, we know the root is an `html`, and there was no `body`
|
|
6332
|
-
// before the `frameset`, so we just need to look at siblings after the
|
|
6333
|
-
// `frameset`.
|
|
6334
|
-
let current = frameset;
|
|
6335
|
-
while ((current = current.nextSibling)) {
|
|
6336
|
-
if (current.nodeType === 1 && isBodyElement(current)) {
|
|
6337
|
-
return current;
|
|
6338
|
-
}
|
|
6339
|
-
}
|
|
6340
|
-
return null;
|
|
6341
|
-
};
|
|
6342
|
-
const isBodyElement = (element) => element.ownerDocument.body === element;
|
|
6343
|
-
const bodyIsScrollable = (body) => {
|
|
6344
|
-
// a body element is scrollable if body and html are scrollable and rendered
|
|
6345
|
-
if (!isScrollable(body)) {
|
|
6346
|
-
return false;
|
|
6347
|
-
}
|
|
6348
|
-
if (isHidden(body)) {
|
|
6349
|
-
return false;
|
|
6350
|
-
}
|
|
6351
|
-
|
|
6352
|
-
const documentElement = body.ownerDocument.documentElement;
|
|
6353
|
-
if (!isScrollable(documentElement)) {
|
|
6354
|
-
return false;
|
|
6355
|
-
}
|
|
6356
|
-
if (isHidden(documentElement)) {
|
|
6357
|
-
return false;
|
|
6358
|
-
}
|
|
6359
|
-
|
|
6360
|
-
return true;
|
|
6361
|
-
};
|
|
6362
|
-
|
|
6363
6526
|
// https://developer.mozilla.org/en-US/docs/Glossary/Scroll_container
|
|
6364
6527
|
|
|
6365
6528
|
|
|
@@ -7229,9 +7392,14 @@ const getPaddingSizes = (element) => {
|
|
|
7229
7392
|
*
|
|
7230
7393
|
* @param {HTMLElement} element - The overlay element being shown. Its preceding
|
|
7231
7394
|
* siblings and all ancestor scroll containers will be scroll-locked.
|
|
7395
|
+
* @param {Object} [options]
|
|
7396
|
+
* @param {HTMLElement} [options.boundaryElement] - Only lock scroll containers
|
|
7397
|
+
* inside this element (itself included). For an overlay confined to a local
|
|
7398
|
+
* container rather than the viewport: the container's own scroll must stop,
|
|
7399
|
+
* the rest of the page keeps scrolling as usual.
|
|
7232
7400
|
* @returns {() => void} Cleanup function that restores all modified styles.
|
|
7233
7401
|
*/
|
|
7234
|
-
const trapScrollInside = (element) => {
|
|
7402
|
+
const trapScrollInside = (element, { boundaryElement } = {}) => {
|
|
7235
7403
|
const cleanupCallbackSet = new Set();
|
|
7236
7404
|
|
|
7237
7405
|
// Collect every element to lock first (preceding scrollable siblings + all
|
|
@@ -7245,7 +7413,11 @@ const trapScrollInside = (element) => {
|
|
|
7245
7413
|
previous = previous.previousSibling;
|
|
7246
7414
|
}
|
|
7247
7415
|
for (const selfOrAncestorScroll of getSelfAndAncestorScrolls(element)) {
|
|
7248
|
-
|
|
7416
|
+
const { scrollContainer } = selfOrAncestorScroll;
|
|
7417
|
+
if (boundaryElement && !boundaryElement.contains(scrollContainer)) {
|
|
7418
|
+
continue;
|
|
7419
|
+
}
|
|
7420
|
+
elementsToLock.push(scrollContainer);
|
|
7249
7421
|
}
|
|
7250
7422
|
|
|
7251
7423
|
// Phase 1 — MEASURE. Batch every layout/style read (scrollTop, scrollbar
|
|
@@ -10635,47 +10807,127 @@ const moveCSSVars = (vars, fromEl, toEl) => {
|
|
|
10635
10807
|
};
|
|
10636
10808
|
|
|
10637
10809
|
installImportMetaCssBuild(import.meta);const css$1 = /* css */`
|
|
10810
|
+
/* IN THE PAGE, NOT IN THE LIST: the hint lands on the edge of a row, which
|
|
10811
|
+
for the last one is the very bottom of the scroll area — drawn inside it,
|
|
10812
|
+
the line would push the scrollable area a few pixels further and make a
|
|
10813
|
+
scrollbar appear (or hide the hint under it) exactly when one is trying to
|
|
10814
|
+
drop at the end. Placed in the body and positioned in viewport
|
|
10815
|
+
coordinates, it can sit anywhere, overhang the list, and cost nothing to
|
|
10816
|
+
the layout. Fixed, like the clone it accompanies. */
|
|
10638
10817
|
.navi_drop_hint {
|
|
10639
|
-
|
|
10818
|
+
/* A popover, so it lands in the top layer: no z-index to bid against the
|
|
10819
|
+
page, and nothing it can be hidden behind. Shown BEFORE the clone, which
|
|
10820
|
+
is what puts the clone above it — the top layer stacks in the order
|
|
10821
|
+
things are shown, and the item being carried should pass over the line
|
|
10822
|
+
rather than under it. The UA styles for [popover] have to be undone:
|
|
10823
|
+
inset:0, margin:auto, a border and a background of its own. */
|
|
10824
|
+
position: fixed;
|
|
10825
|
+
inset: auto;
|
|
10640
10826
|
top: var(--drop-hint-y);
|
|
10641
10827
|
left: calc(var(--drop-target-left) + var(--drop-hint-margin-x, 0px));
|
|
10642
|
-
z-index: 10;
|
|
10643
10828
|
display: none;
|
|
10829
|
+
box-sizing: border-box;
|
|
10644
10830
|
width: calc(var(--drop-target-width) - 2 * var(--drop-hint-margin-x, 0px));
|
|
10645
10831
|
height: var(--drop-hint-size, 3px);
|
|
10832
|
+
margin: 0;
|
|
10833
|
+
padding: 0;
|
|
10834
|
+
color: inherit;
|
|
10646
10835
|
background: var(--drop-hint-background-color, #4476ff);
|
|
10836
|
+
border: none;
|
|
10647
10837
|
border-radius: var(--drop-hint-border-radius, 2px);
|
|
10648
10838
|
transform: translateY(-50%);
|
|
10649
10839
|
pointer-events: none;
|
|
10840
|
+
overflow: visible;
|
|
10650
10841
|
}
|
|
10651
|
-
[data-drop-edge
|
|
10842
|
+
.navi_drop_hint[data-drop-edge]:popover-open {
|
|
10652
10843
|
display: block;
|
|
10844
|
+
}
|
|
10845
|
+
.navi_drop_hint[data-drop-edge="top"] {
|
|
10653
10846
|
--drop-hint-y: calc(
|
|
10654
10847
|
var(--drop-target-top) - var(--drop-hint-margin-y, 0px)
|
|
10655
10848
|
);
|
|
10656
10849
|
}
|
|
10657
|
-
[data-drop-edge="bottom"]
|
|
10658
|
-
display: block;
|
|
10850
|
+
.navi_drop_hint[data-drop-edge="bottom"] {
|
|
10659
10851
|
--drop-hint-y: calc(
|
|
10660
10852
|
var(--drop-target-bottom) + var(--drop-hint-margin-y, 0px)
|
|
10661
10853
|
);
|
|
10662
10854
|
}
|
|
10855
|
+
/* A chevron at each end, pointing in: the line alone is easy to lose against
|
|
10856
|
+
a list of borders and separators, two arrows read as "here" at a glance
|
|
10857
|
+
(same idea as the table's column drop preview). They overhang the line,
|
|
10858
|
+
which costs nothing now that the hint is out of the scrollable area — and
|
|
10859
|
+
the more they stick out, the easier they are to spot. */
|
|
10860
|
+
.navi_drop_hint_cap {
|
|
10861
|
+
position: absolute;
|
|
10862
|
+
top: 50%;
|
|
10863
|
+
display: flex;
|
|
10864
|
+
color: var(--drop-hint-background-color, #4476ff);
|
|
10865
|
+
translate: 0 -50%;
|
|
10866
|
+
}
|
|
10867
|
+
.navi_drop_hint_cap svg {
|
|
10868
|
+
width: var(--drop-hint-arrow-size, 11px);
|
|
10869
|
+
height: var(--drop-hint-arrow-size, 11px);
|
|
10870
|
+
}
|
|
10871
|
+
.navi_drop_hint_cap[data-side="start"] {
|
|
10872
|
+
left: calc(-1 * var(--drop-hint-arrow-size, 11px));
|
|
10873
|
+
rotate: -90deg;
|
|
10874
|
+
}
|
|
10875
|
+
.navi_drop_hint_cap[data-side="end"] {
|
|
10876
|
+
right: calc(-1 * var(--drop-hint-arrow-size, 11px));
|
|
10877
|
+
rotate: 90deg;
|
|
10878
|
+
}
|
|
10879
|
+
|
|
10880
|
+
/* WHO CAN START A DRAG, said in the cursor.
|
|
10881
|
+
A handle drags on the spot, so it shows the hand. A source only drags once
|
|
10882
|
+
the pointer has travelled a few pixels — a plain click stays a click — but
|
|
10883
|
+
the text inside it can no longer be selected (the gesture takes the
|
|
10884
|
+
pointer), so an I-beam over it would promise something that does not
|
|
10885
|
+
happen: it reads as a plain surface instead. An opted-out area keeps both
|
|
10886
|
+
its cursor and its selection, and never starts a drag (see the check in
|
|
10887
|
+
startDragToReorder).
|
|
10888
|
+
Controls inside a source keep their own cursor: cursor is inherited, and
|
|
10889
|
+
anything setting its own (a button's pointer) wins on itself.
|
|
10890
|
+
Only the resting cursor is set here: what it becomes once a drag is under
|
|
10891
|
+
way belongs to the gesture (see the backdrop in drag_gesture.js), the only
|
|
10892
|
+
thing that knows a drag actually started. */
|
|
10893
|
+
[data-drag-handle] {
|
|
10894
|
+
cursor: grab;
|
|
10895
|
+
}
|
|
10896
|
+
[data-drag-source] {
|
|
10897
|
+
cursor: default;
|
|
10898
|
+
user-select: none;
|
|
10899
|
+
}
|
|
10900
|
+
[data-drag-ignore] {
|
|
10901
|
+
cursor: auto;
|
|
10902
|
+
user-select: auto;
|
|
10903
|
+
}
|
|
10663
10904
|
|
|
10664
10905
|
[navi-drag-clone-source] {
|
|
10665
10906
|
visibility: hidden;
|
|
10666
10907
|
}
|
|
10667
10908
|
|
|
10668
10909
|
[navi-drag-clone-wrapper] {
|
|
10669
|
-
|
|
10910
|
+
/* Also a popover (see .navi_drop_hint): in the top layer it is over the
|
|
10911
|
+
page whatever the page's own stacking is, and the coordinates it is
|
|
10912
|
+
given are viewport ones — which is what the pointer carrying it works
|
|
10913
|
+
in. Same UA-style reset as the hint. */
|
|
10914
|
+
position: fixed;
|
|
10915
|
+
inset: auto;
|
|
10670
10916
|
top: var(--clone-top);
|
|
10671
10917
|
left: var(--clone-left);
|
|
10672
|
-
|
|
10918
|
+
box-sizing: border-box;
|
|
10673
10919
|
width: var(--clone-width);
|
|
10674
10920
|
height: var(--clone-height);
|
|
10921
|
+
margin: 0;
|
|
10922
|
+
padding: 0;
|
|
10923
|
+
color: inherit;
|
|
10924
|
+
background: transparent;
|
|
10925
|
+
border: none;
|
|
10675
10926
|
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.22);
|
|
10676
10927
|
opacity: 0.95;
|
|
10677
10928
|
transition: box-shadow 0.15s ease;
|
|
10678
10929
|
pointer-events: none;
|
|
10930
|
+
overflow: visible;
|
|
10679
10931
|
}
|
|
10680
10932
|
|
|
10681
10933
|
[navi-drag-clone] {
|
|
@@ -10694,7 +10946,10 @@ installImportMetaCssBuild(import.meta);const css$1 = /* css */`
|
|
|
10694
10946
|
}
|
|
10695
10947
|
}
|
|
10696
10948
|
`;
|
|
10697
|
-
|
|
10949
|
+
// At module scope, not inside startDragToReorder: the cursor rules above say who
|
|
10950
|
+
// can start a drag, and they have to be true BEFORE anyone drags anything.
|
|
10951
|
+
import.meta.css = [css$1, "@jsenv/dom/src/interaction/drag/drag_to_reorder.js"];
|
|
10952
|
+
const dragCSSVars = ["--drop-hint-size", "--drop-hint-background-color", "--drop-hint-border-radius", "--drop-hint-margin-x", "--drop-hint-margin-y", "--drop-hint-arrow-size", "--drag-clone-scale"];
|
|
10698
10953
|
|
|
10699
10954
|
/**
|
|
10700
10955
|
* Starts a drag-to-reorder interaction on a list item.
|
|
@@ -10760,7 +11015,11 @@ const startDragToReorder = (event, {
|
|
|
10760
11015
|
},
|
|
10761
11016
|
...options
|
|
10762
11017
|
}) => {
|
|
10763
|
-
|
|
11018
|
+
// An area that opted out of dragging (a text one wants to select, a control
|
|
11019
|
+
// that owns the gesture): the press there is none of our business.
|
|
11020
|
+
if (event.target.closest && event.target.closest("[data-drag-ignore]")) {
|
|
11021
|
+
return undefined;
|
|
11022
|
+
}
|
|
10764
11023
|
event.preventDefault();
|
|
10765
11024
|
return dragAfterThreshold(event, () => {
|
|
10766
11025
|
const cloneWrapper = createDragClone(draggedElement, event);
|
|
@@ -10780,21 +11039,23 @@ const startDragToReorder = (event, {
|
|
|
10780
11039
|
// getDropTargetInfo uses gestureInfo.elementImpacted to compute the dragged rect.
|
|
10781
11040
|
// Point it at the clone so drop detection tracks the clone's current position.
|
|
10782
11041
|
dragGesture.gestureInfo.elementImpacted = cloneWrapper;
|
|
10783
|
-
const
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
11042
|
+
const dropHintEl = createDropHint();
|
|
11043
|
+
document.body.appendChild(dropHintEl);
|
|
11044
|
+
// The hint first, the clone second: that order is what stacks them in the
|
|
11045
|
+
// top layer.
|
|
11046
|
+
dropHintEl.showPopover();
|
|
11047
|
+
cloneWrapper.showPopover();
|
|
10787
11048
|
|
|
10788
11049
|
// currentBeforeElement: element before which the grabbed item will be inserted (null = end)
|
|
10789
11050
|
// currentReleaseElement: the actual hovered drop target — used to snap the clone on release
|
|
10790
11051
|
let currentBeforeElement;
|
|
10791
11052
|
let currentReleaseElement;
|
|
10792
11053
|
const clearDropHintDOM = () => {
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
10796
|
-
|
|
10797
|
-
|
|
11054
|
+
dropHintEl.removeAttribute("data-drop-edge");
|
|
11055
|
+
dropHintEl.style.removeProperty("--drop-target-top");
|
|
11056
|
+
dropHintEl.style.removeProperty("--drop-target-bottom");
|
|
11057
|
+
dropHintEl.style.removeProperty("--drop-target-left");
|
|
11058
|
+
dropHintEl.style.removeProperty("--drop-target-width");
|
|
10798
11059
|
};
|
|
10799
11060
|
const clearDropHint = () => {
|
|
10800
11061
|
currentBeforeElement = undefined;
|
|
@@ -10845,16 +11106,15 @@ const startDragToReorder = (event, {
|
|
|
10845
11106
|
// beforeElement = X → insert before X (hint at top edge of X)
|
|
10846
11107
|
const anchorEl = beforeElement || items[items.length - 1];
|
|
10847
11108
|
const anchorEdge = beforeElement !== null ? "top" : "bottom";
|
|
10848
|
-
|
|
11109
|
+
// Viewport coordinates, straight from the anchor row: the hint is fixed
|
|
11110
|
+
// in the page (see its CSS), so there is no container box to be relative
|
|
11111
|
+
// to and no scroll offset to add back.
|
|
10849
11112
|
const anchorRect = anchorEl.getBoundingClientRect();
|
|
10850
|
-
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
scrollContainer.style.setProperty("--drop-target-bottom", `${anchorRect.bottom - containerRect.top + scrollOffsetTop}px`);
|
|
10856
|
-
scrollContainer.style.setProperty("--drop-target-left", `${anchorRect.left - containerRect.left + scrollOffsetLeft}px`);
|
|
10857
|
-
scrollContainer.style.setProperty("--drop-target-width", `${anchorRect.width}px`);
|
|
11113
|
+
dropHintEl.setAttribute("data-drop-edge", anchorEdge);
|
|
11114
|
+
dropHintEl.style.setProperty("--drop-target-top", `${anchorRect.top}px`);
|
|
11115
|
+
dropHintEl.style.setProperty("--drop-target-bottom", `${anchorRect.bottom}px`);
|
|
11116
|
+
dropHintEl.style.setProperty("--drop-target-left", `${anchorRect.left}px`);
|
|
11117
|
+
dropHintEl.style.setProperty("--drop-target-width", `${anchorRect.width}px`);
|
|
10858
11118
|
});
|
|
10859
11119
|
dragGesture.addReleaseCallback(async gestureInfo => {
|
|
10860
11120
|
clearDropHintDOM();
|
|
@@ -10864,7 +11124,7 @@ const startDragToReorder = (event, {
|
|
|
10864
11124
|
const clone = cloneWrapper.firstElementChild;
|
|
10865
11125
|
// Bake the current visual position (transform included) into the CSS vars
|
|
10866
11126
|
// so the clone stays where the user released it when we clear the transform.
|
|
10867
|
-
|
|
11127
|
+
setCloneViewportRect(cloneWrapper, cloneWrapper);
|
|
10868
11128
|
gestureInfo.cancelPosition();
|
|
10869
11129
|
const fromId = getItemId(draggedElement);
|
|
10870
11130
|
const toId = currentBeforeElement ? getItemId(currentBeforeElement) : null;
|
|
@@ -10873,7 +11133,7 @@ const startDragToReorder = (event, {
|
|
|
10873
11133
|
const syncCloneWithDropTarget = () => {
|
|
10874
11134
|
// Snap the CSS-var position to the drop target rect so the browser
|
|
10875
11135
|
// captures the "new" state at the landing position.
|
|
10876
|
-
|
|
11136
|
+
setCloneViewportRect(cloneWrapper, currentReleaseElement);
|
|
10877
11137
|
// Removing this attr drops the CSS scale(1.15), so the browser
|
|
10878
11138
|
// captures the clone at scale 1 as the "new" state.
|
|
10879
11139
|
clone.removeAttribute("navi-drag-clone");
|
|
@@ -10887,15 +11147,13 @@ const startDragToReorder = (event, {
|
|
|
10887
11147
|
});
|
|
10888
11148
|
};
|
|
10889
11149
|
|
|
10890
|
-
// getBoundingClientRect
|
|
10891
|
-
//
|
|
10892
|
-
//
|
|
10893
|
-
const
|
|
11150
|
+
// Viewport coordinates, as getBoundingClientRect gives them: the clone is a
|
|
11151
|
+
// fixed-position popover, so that is the space it lives in — and the one the
|
|
11152
|
+
// pointer dragging it works in too.
|
|
11153
|
+
const setCloneViewportRect = (cloneWrapper, el) => {
|
|
10894
11154
|
const rect = el.getBoundingClientRect();
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
cloneWrapper.style.setProperty("--clone-top", `${rect.top + scrollTop}px`);
|
|
10898
|
-
cloneWrapper.style.setProperty("--clone-left", `${rect.left + scrollLeft}px`);
|
|
11155
|
+
cloneWrapper.style.setProperty("--clone-top", `${rect.top}px`);
|
|
11156
|
+
cloneWrapper.style.setProperty("--clone-left", `${rect.left}px`);
|
|
10899
11157
|
cloneWrapper.style.setProperty("--clone-width", `${rect.width}px`);
|
|
10900
11158
|
cloneWrapper.style.setProperty("--clone-height", `${rect.height}px`);
|
|
10901
11159
|
};
|
|
@@ -10914,12 +11172,43 @@ const setCloneDocumentRect = (cloneWrapper, el) => {
|
|
|
10914
11172
|
// so the element expands naturally from where the user clicked.
|
|
10915
11173
|
// On release, the `navi-drag-clone` attribute is removed inside
|
|
10916
11174
|
// startViewTransition to drop the scale back to 1 as the "new" state.
|
|
11175
|
+
// The chevron is the one the table's column drop preview uses, rotated by the
|
|
11176
|
+
// CSS above so each cap points into the line.
|
|
11177
|
+
const dropHintTemplate = /* html */`
|
|
11178
|
+
<div
|
|
11179
|
+
class="navi_drop_hint"
|
|
11180
|
+
popover="manual"
|
|
11181
|
+
>
|
|
11182
|
+
<span class="navi_drop_hint_cap" data-side="start">
|
|
11183
|
+
<svg fill="currentColor" viewBox="0 0 30.727 30.727">
|
|
11184
|
+
<path
|
|
11185
|
+
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"
|
|
11186
|
+
/>
|
|
11187
|
+
</svg>
|
|
11188
|
+
</span>
|
|
11189
|
+
<span class="navi_drop_hint_cap" data-side="end">
|
|
11190
|
+
<svg fill="currentColor" viewBox="0 0 30.727 30.727">
|
|
11191
|
+
<path
|
|
11192
|
+
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"
|
|
11193
|
+
/>
|
|
11194
|
+
</svg>
|
|
11195
|
+
</span>
|
|
11196
|
+
</div>
|
|
11197
|
+
`;
|
|
11198
|
+
const createDropHint = () => {
|
|
11199
|
+
const div = document.createElement("div");
|
|
11200
|
+
div.innerHTML = dropHintTemplate.trim();
|
|
11201
|
+
return div.firstElementChild;
|
|
11202
|
+
};
|
|
10917
11203
|
const createDragClone = (element, pointerEvent) => {
|
|
10918
11204
|
const rect = element.getBoundingClientRect();
|
|
10919
11205
|
const wrapper = document.createElement("div");
|
|
10920
11206
|
wrapper.setAttribute("navi-drag-clone-wrapper", "");
|
|
11207
|
+
// Manual: it is opened and closed with the drag, and must survive an Escape
|
|
11208
|
+
// or a click elsewhere (light dismiss would take it away mid-gesture).
|
|
11209
|
+
wrapper.setAttribute("popover", "manual");
|
|
10921
11210
|
wrapper.viewTransitionName = "navi-drag-clone-wrapper";
|
|
10922
|
-
|
|
11211
|
+
setCloneViewportRect(wrapper, element);
|
|
10923
11212
|
// Grab point within the element — used as transform-origin so the
|
|
10924
11213
|
// scale(1.15) expands from where the user clicked, not the element center.
|
|
10925
11214
|
// These offsets are element-relative so viewport coords are correct here.
|
|
@@ -11013,7 +11302,19 @@ const getResizeDirection = (element) => {
|
|
|
11013
11302
|
// directions: hide when a container closes, recheck when it reopens) — the
|
|
11014
11303
|
// selector/open-detection/timing primitives are identical for both, only
|
|
11015
11304
|
// what each does with a transition differs.
|
|
11016
|
-
const
|
|
11305
|
+
const OPENABLE_SELECTOR = "dialog, details, [popover], [aria-expanded]";
|
|
11306
|
+
|
|
11307
|
+
// An element that IS openable is closed in exactly the same way an element
|
|
11308
|
+
// inside one is — which matters for anything positioned against it, e.g. a
|
|
11309
|
+
// callout anchored to a dialog rather than to a field inside it. Same selector
|
|
11310
|
+
// as the walk up: whatever counts as openable above counts as openable here,
|
|
11311
|
+
// custom [aria-expanded] nodes included.
|
|
11312
|
+
const selfOrClosestOpenableAncestor = (element) => {
|
|
11313
|
+
if (element.matches?.(OPENABLE_SELECTOR)) {
|
|
11314
|
+
return element;
|
|
11315
|
+
}
|
|
11316
|
+
return closestOpenableAncestor(element);
|
|
11317
|
+
};
|
|
11017
11318
|
|
|
11018
11319
|
const closestOpenableAncestor = (element) => {
|
|
11019
11320
|
const parentElement = element.parentElement;
|
|
@@ -11023,7 +11324,7 @@ const closestOpenableAncestor = (element) => {
|
|
|
11023
11324
|
if (!parentElement.closest) {
|
|
11024
11325
|
return null;
|
|
11025
11326
|
}
|
|
11026
|
-
return parentElement.closest(
|
|
11327
|
+
return parentElement.closest(OPENABLE_SELECTOR);
|
|
11027
11328
|
};
|
|
11028
11329
|
|
|
11029
11330
|
const isAncestorOpen = (ancestor) => {
|
|
@@ -11575,7 +11876,10 @@ const MIN_CONTENT_VISIBILITY_RATIO = 0.6;
|
|
|
11575
11876
|
*/
|
|
11576
11877
|
// The event type observeSize() reports with — recognized by check() as "the
|
|
11577
11878
|
// change is in another element, not in the tracked rect".
|
|
11578
|
-
|
|
11879
|
+
// Exported: a caller that resized the element itself (a callout whose message
|
|
11880
|
+
// just changed, say) has to re-check with this rather than with nothing — its
|
|
11881
|
+
// own rect may not have moved at all, and the dedup would drop the check.
|
|
11882
|
+
const ELEMENT_SIZE_CHANGE = "observed_element_size_change";
|
|
11579
11883
|
|
|
11580
11884
|
const visibleRectEffect = (
|
|
11581
11885
|
element,
|
|
@@ -11645,7 +11949,12 @@ const visibleRectEffect = (
|
|
|
11645
11949
|
resizeWatchingPaused = false;
|
|
11646
11950
|
publishResizeWatchingPausedChange(false);
|
|
11647
11951
|
};
|
|
11648
|
-
|
|
11952
|
+
// Only so the reads below have something to read: a caller that describes
|
|
11953
|
+
// nothing gets no special treatment, it goes through the same dedup as any
|
|
11954
|
+
// other check. A caller that needs the dedup bypassed says so by passing the
|
|
11955
|
+
// event that means it (ELEMENT_SIZE_CHANGE).
|
|
11956
|
+
const UNSET_EVENT = { type: "unset" };
|
|
11957
|
+
const check = (event = UNSET_EVENT) => {
|
|
11649
11958
|
|
|
11650
11959
|
// visualViewport, not window.innerWidth/Height: the layout viewport
|
|
11651
11960
|
// doesn't shrink when the on-screen keyboard opens (same reasoning as
|
|
@@ -11825,7 +12134,7 @@ const visibleRectEffect = (
|
|
|
11825
12134
|
// defeating the whole point of observeSize (a popover reconsidering its
|
|
11826
12135
|
// placement once its own content shrinks/grows, a callout re-measuring
|
|
11827
12136
|
// against its message body).
|
|
11828
|
-
if (event.type ===
|
|
12137
|
+
if (event.type === ELEMENT_SIZE_CHANGE) {
|
|
11829
12138
|
lastVisibleRect = visibleRect;
|
|
11830
12139
|
lastViewportRect = viewportRect;
|
|
11831
12140
|
notify();
|
|
@@ -12062,7 +12371,12 @@ const visibleRectEffect = (
|
|
|
12062
12371
|
});
|
|
12063
12372
|
}
|
|
12064
12373
|
{
|
|
12065
|
-
|
|
12374
|
+
// Self-inclusive on the first step only: `element` can itself be the
|
|
12375
|
+
// dialog/popover that closes (a callout anchored to a dialog rather than
|
|
12376
|
+
// to a field inside it), and its own close hides it just as much as an
|
|
12377
|
+
// ancestor's would. The walk up below starts from parentElement, so the
|
|
12378
|
+
// chain still advances.
|
|
12379
|
+
let currentOpenableAncestor = selfOrClosestOpenableAncestor(element);
|
|
12066
12380
|
while (currentOpenableAncestor) {
|
|
12067
12381
|
const openableAncestor = currentOpenableAncestor;
|
|
12068
12382
|
if (!isAncestorOpen(openableAncestor)) {
|
|
@@ -12235,7 +12549,7 @@ const visibleRectEffect = (
|
|
|
12235
12549
|
pendingFrame = requestAnimationFrame(() => {
|
|
12236
12550
|
pendingFrame = null;
|
|
12237
12551
|
check(
|
|
12238
|
-
new CustomEvent(
|
|
12552
|
+
new CustomEvent(ELEMENT_SIZE_CHANGE, {
|
|
12239
12553
|
detail: { width, height },
|
|
12240
12554
|
}),
|
|
12241
12555
|
);
|
|
@@ -12497,7 +12811,7 @@ const toContainerAlignedPosition = (value) => {
|
|
|
12497
12811
|
* edges instead of the page viewport's, on both axes (the Y axis otherwise has no such
|
|
12498
12812
|
* clamp at all — see the clamp's own comment) — that part *is* gated on `hasValidAnchor`,
|
|
12499
12813
|
* unlike the coordinate-space conversion itself.
|
|
12500
|
-
* @returns {{ hasValidAnchor, shouldTransition, positionX, positionY, left, top, width, height, anchorLeft, anchorTop, anchorRight, anchorBottom, spaceLeft, spaceRight, spaceAbove, spaceBelow }}
|
|
12814
|
+
* @returns {{ hasValidAnchor, shouldTransition, positionX, positionY, left, top, width, height, anchorLeft, anchorTop, anchorRight, anchorBottom, spaceLeft, spaceRight, spaceAbove, spaceBelow, containerWidthAvailable, containerHeightAvailable }}
|
|
12501
12815
|
*/
|
|
12502
12816
|
const pickPositionRelativeTo = (
|
|
12503
12817
|
element,
|
|
@@ -13010,22 +13324,34 @@ const pickPositionRelativeTo = (
|
|
|
13010
13324
|
// so the usable space includes the anchor dimension.
|
|
13011
13325
|
// marginWithAnchor (gap between anchor and element) and marginWithContainer are subtracted
|
|
13012
13326
|
// so callers get the net usable space directly.
|
|
13013
|
-
const
|
|
13014
|
-
|
|
13015
|
-
|
|
13016
|
-
|
|
13017
|
-
|
|
13018
|
-
|
|
13019
|
-
|
|
13020
|
-
|
|
13021
|
-
const
|
|
13022
|
-
|
|
13023
|
-
(
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
13327
|
+
const containerWidthAvailable = availableWidth - 2 * marginWithContainer;
|
|
13328
|
+
const containerHeightAvailable = availableHeight - 2 * marginWithContainer;
|
|
13329
|
+
// Docked to a container (no real anchor): the element is kept inside the
|
|
13330
|
+
// container's margin on BOTH sides — that is what the !hasValidAnchor clamp
|
|
13331
|
+
// above enforces — so what it has to work with is the container net of both.
|
|
13332
|
+
// The anchor-relative formulas below count the margin once, which is right
|
|
13333
|
+
// when the space really is bounded by the anchor on the other side, and
|
|
13334
|
+
// wrong here: it would let the far edge grow flush against the container.
|
|
13335
|
+
const effectiveSpaceAbove = !hasValidAnchor
|
|
13336
|
+
? containerHeightAvailable
|
|
13337
|
+
: (finalY === "inset-bottom" ? spaceAbove + anchorHeight : spaceAbove) -
|
|
13338
|
+
(finalY === "top" ? marginWithAnchor : 0) -
|
|
13339
|
+
marginWithContainer;
|
|
13340
|
+
const effectiveSpaceBelow = !hasValidAnchor
|
|
13341
|
+
? containerHeightAvailable
|
|
13342
|
+
: (finalY === "inset-top" ? spaceBelow + anchorHeight : spaceBelow) -
|
|
13343
|
+
(finalY === "bottom" ? marginWithAnchor : 0) -
|
|
13344
|
+
marginWithContainer;
|
|
13345
|
+
const effectiveSpaceLeft = !hasValidAnchor
|
|
13346
|
+
? containerWidthAvailable
|
|
13347
|
+
: (finalX === "inset-right" ? spaceLeft + anchorWidth : spaceLeft) -
|
|
13348
|
+
(finalX === "left" ? marginWithAnchor : 0) -
|
|
13349
|
+
marginWithContainer;
|
|
13350
|
+
const effectiveSpaceRight = !hasValidAnchor
|
|
13351
|
+
? containerWidthAvailable
|
|
13352
|
+
: (finalX === "inset-left" ? spaceRight + anchorWidth : spaceRight) -
|
|
13353
|
+
(finalX === "right" ? marginWithAnchor : 0) -
|
|
13354
|
+
marginWithContainer;
|
|
13029
13355
|
|
|
13030
13356
|
return {
|
|
13031
13357
|
// Whether a real anchor actually ended up used — false when there's no
|
|
@@ -13048,6 +13374,12 @@ const pickPositionRelativeTo = (
|
|
|
13048
13374
|
spaceRight: effectiveSpaceRight,
|
|
13049
13375
|
spaceAbove: effectiveSpaceAbove,
|
|
13050
13376
|
spaceBelow: effectiveSpaceBelow,
|
|
13377
|
+
// What a centered axis has to work with: the whole container, net of the
|
|
13378
|
+
// margin kept on both sides. spaceLeft/spaceRight can't answer that — they
|
|
13379
|
+
// are measured from the anchor, which for a container-docked element is
|
|
13380
|
+
// the container itself, so they collapse to -marginWithContainer.
|
|
13381
|
+
containerWidthAvailable,
|
|
13382
|
+
containerHeightAvailable,
|
|
13051
13383
|
};
|
|
13052
13384
|
};
|
|
13053
13385
|
|
|
@@ -13168,8 +13500,16 @@ const applyNewPosition = (
|
|
|
13168
13500
|
spaceRight,
|
|
13169
13501
|
spaceAbove,
|
|
13170
13502
|
spaceBelow,
|
|
13503
|
+
containerWidthAvailable,
|
|
13504
|
+
containerHeightAvailable,
|
|
13171
13505
|
},
|
|
13172
13506
|
) => {
|
|
13507
|
+
// A centered axis is published too, from the container's own extent: leaving
|
|
13508
|
+
// the property unset lets the consumer's size cap fall back to its viewport
|
|
13509
|
+
// default, which overflows any container smaller than the viewport (a
|
|
13510
|
+
// dialog/popover confined to a positioned ancestor). It stays a "remaining
|
|
13511
|
+
// space" either way — docked: what is left on that side, centered: the whole
|
|
13512
|
+
// container minus its margins.
|
|
13173
13513
|
if (positionY === "top" || positionY === "inset-bottom") {
|
|
13174
13514
|
element.style.setProperty(
|
|
13175
13515
|
"--container-position-remaining-height",
|
|
@@ -13180,8 +13520,13 @@ const applyNewPosition = (
|
|
|
13180
13520
|
"--container-position-remaining-height",
|
|
13181
13521
|
`${spaceBelow}px`,
|
|
13182
13522
|
);
|
|
13183
|
-
} else {
|
|
13523
|
+
} else if (containerHeightAvailable === undefined) {
|
|
13184
13524
|
element.style.removeProperty("--container-position-remaining-height");
|
|
13525
|
+
} else {
|
|
13526
|
+
element.style.setProperty(
|
|
13527
|
+
"--container-position-remaining-height",
|
|
13528
|
+
`${containerHeightAvailable}px`,
|
|
13529
|
+
);
|
|
13185
13530
|
}
|
|
13186
13531
|
if (positionX === "left" || positionX === "inset-right") {
|
|
13187
13532
|
element.style.setProperty(
|
|
@@ -13193,8 +13538,13 @@ const applyNewPosition = (
|
|
|
13193
13538
|
"--container-position-remaining-width",
|
|
13194
13539
|
`${spaceRight}px`,
|
|
13195
13540
|
);
|
|
13196
|
-
} else {
|
|
13541
|
+
} else if (containerWidthAvailable === undefined) {
|
|
13197
13542
|
element.style.removeProperty("--container-position-remaining-width");
|
|
13543
|
+
} else {
|
|
13544
|
+
element.style.setProperty(
|
|
13545
|
+
"--container-position-remaining-width",
|
|
13546
|
+
`${containerWidthAvailable}px`,
|
|
13547
|
+
);
|
|
13198
13548
|
}
|
|
13199
13549
|
|
|
13200
13550
|
// A single implicit keyframe turned out not to work here: the WAAPI
|
|
@@ -16484,4 +16834,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
|
|
|
16484
16834
|
};
|
|
16485
16835
|
};
|
|
16486
16836
|
|
|
16487
|
-
export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, applyNewPosition, captureScrollState, chainEvent, closestOpenableAncestor, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createEventGroupLogger, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dispatchCustomEvent, dispatchInternalCustomEvent, dispatchPublicCustomEvent, dragAfterThreshold, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findEvent, findFocusDelegateTarget, findFocusable, findSelfOrAncestorFixedPosition, formatEventSideEffect, getAncestorOpenType, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getKeyboardEventDefaultAction, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPositioningScrollOffset, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isAncestorOpen, isSameColor, isScrollable, measureLongestVisualLineWidth, measureScrollbar, measureWidestChildRow, mergeOneStyle, mergeTwoStyles, normalizeKeyboardKey, normalizeStyle, normalizeStyles, observeAncestorOpenState, onAncestorReopen, parsePositionArea, parseStyle, performTabNavigation, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, setAttribute, setAttributes, setStyles, snapToPixel, startDragToReorder, startDragToResizeGesture, stickyAsRelativeCoords, stringifyStyle, subscribeVisualViewportResizeSettled, subscribeWindowResizeSettled, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
|
|
16837
|
+
export { EASING, ELEMENT_SIZE_CHANGE, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, applyNewPosition, canScroll, 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 };
|