@jsenv/dom 0.9.5 → 0.10.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 +657 -228
- package/package.json +2 -2
package/dist/jsenv_dom.js
CHANGED
|
@@ -117,13 +117,16 @@ const getElementSignature = (element) => {
|
|
|
117
117
|
return "<html>";
|
|
118
118
|
}
|
|
119
119
|
const elementId = element.id;
|
|
120
|
-
|
|
120
|
+
const className = element.className;
|
|
121
|
+
if (elementId && !looksLikeGeneratedId(elementId)) {
|
|
121
122
|
return `${tagName}#${elementId}`;
|
|
122
123
|
}
|
|
123
|
-
const className = element.className;
|
|
124
124
|
if (className) {
|
|
125
125
|
return `${tagName}.${className.split(" ").join(".")}`;
|
|
126
126
|
}
|
|
127
|
+
if (elementId) {
|
|
128
|
+
return `${tagName}#${elementId}`;
|
|
129
|
+
}
|
|
127
130
|
|
|
128
131
|
const parentSignature = getElementSignature(element.parentElement);
|
|
129
132
|
return `${parentSignature} > ${tagName}`;
|
|
@@ -131,6 +134,13 @@ const getElementSignature = (element) => {
|
|
|
131
134
|
return String(element);
|
|
132
135
|
};
|
|
133
136
|
|
|
137
|
+
// Generated ids from frameworks (Preact useId, React useId, etc.) look like
|
|
138
|
+
// "P0-0", ":r0:", "P1-3" — short alphanumeric tokens with dashes or colons.
|
|
139
|
+
// If an id matches this pattern we prefer className over it.
|
|
140
|
+
const looksLikeGeneratedId = (id) => {
|
|
141
|
+
return /^[A-Z][0-9]+-[0-9]+$|^:[a-z][0-9]*:$/.test(id);
|
|
142
|
+
};
|
|
143
|
+
|
|
134
144
|
const createIterableWeakSet = () => {
|
|
135
145
|
const objectWeakRefSet = new Set();
|
|
136
146
|
|
|
@@ -309,6 +319,7 @@ const elementIsWindow = (a) => a.window === a;
|
|
|
309
319
|
const elementIsDocument = (a) => a.nodeType === 9;
|
|
310
320
|
const elementIsDetails = ({ nodeName }) => nodeName === "DETAILS";
|
|
311
321
|
const elementIsSummary = ({ nodeName }) => nodeName === "SUMMARY";
|
|
322
|
+
const elementIsDialog = ({ nodeName }) => nodeName === "DIALOG";
|
|
312
323
|
|
|
313
324
|
// should be used ONLY when an element is related to other elements that are not descendants of this element
|
|
314
325
|
const getAssociatedElements = (element) => {
|
|
@@ -2073,6 +2084,9 @@ const normalizeStyle = (
|
|
|
2073
2084
|
if (propertyName === "transform") {
|
|
2074
2085
|
if (context === "js") {
|
|
2075
2086
|
if (typeof value === "string") {
|
|
2087
|
+
if (isCSSKeyword(value)) {
|
|
2088
|
+
return value;
|
|
2089
|
+
}
|
|
2076
2090
|
// For js context, prefer objects
|
|
2077
2091
|
return parseCSSTransform(value, normalizeStyle);
|
|
2078
2092
|
}
|
|
@@ -2110,6 +2124,9 @@ const normalizeStyle = (
|
|
|
2110
2124
|
if (propertyName === "background") {
|
|
2111
2125
|
if (context === "js") {
|
|
2112
2126
|
if (typeof value === "string") {
|
|
2127
|
+
if (isCSSKeyword(value)) {
|
|
2128
|
+
return value;
|
|
2129
|
+
}
|
|
2113
2130
|
// For js context, prefer objects
|
|
2114
2131
|
return parseCSSBackground(value, {
|
|
2115
2132
|
parseStyle,
|
|
@@ -2147,6 +2164,9 @@ const normalizeStyle = (
|
|
|
2147
2164
|
if (propertyName === "border") {
|
|
2148
2165
|
if (context === "js") {
|
|
2149
2166
|
if (typeof value === "string") {
|
|
2167
|
+
if (isCSSKeyword(value)) {
|
|
2168
|
+
return value;
|
|
2169
|
+
}
|
|
2150
2170
|
// For js context, prefer objects
|
|
2151
2171
|
return parseCSSBorder(value, element);
|
|
2152
2172
|
}
|
|
@@ -2274,10 +2294,20 @@ const normalizeStyle = (
|
|
|
2274
2294
|
}
|
|
2275
2295
|
|
|
2276
2296
|
if (colorPropertySet.has(propertyName)) {
|
|
2277
|
-
if (typeof value === "string"
|
|
2278
|
-
|
|
2297
|
+
if (typeof value === "string") {
|
|
2298
|
+
if (isCSSKeyword(value)) {
|
|
2299
|
+
return value;
|
|
2300
|
+
}
|
|
2301
|
+
if (isCSSFunction(value)) {
|
|
2302
|
+
return value;
|
|
2303
|
+
}
|
|
2279
2304
|
}
|
|
2280
2305
|
const rgba = parseCSSColor(value, element);
|
|
2306
|
+
if (rgba === null) {
|
|
2307
|
+
// parseCSSColor could not parse the value (e.g. a CSS variable or unknown keyword)
|
|
2308
|
+
// return as-is so the original string reaches the DOM unchanged
|
|
2309
|
+
return value;
|
|
2310
|
+
}
|
|
2281
2311
|
if (context === "js") {
|
|
2282
2312
|
return rgba;
|
|
2283
2313
|
}
|
|
@@ -2296,6 +2326,9 @@ const stringifyStyle = (value, propertyName, element) => {
|
|
|
2296
2326
|
const isCSSFunction = (value) => {
|
|
2297
2327
|
return /^[a-z-]+\(/.test(value);
|
|
2298
2328
|
};
|
|
2329
|
+
const isCSSKeyword = (value) => {
|
|
2330
|
+
return globalCSSKeywordSet.has(value);
|
|
2331
|
+
};
|
|
2299
2332
|
const normalizeNumber = (value, { unit, propertyName, preferedType }) => {
|
|
2300
2333
|
if (typeof value === "string") {
|
|
2301
2334
|
// CSS variables and CSS functions like calc() must be passed through as-is
|
|
@@ -3395,74 +3428,101 @@ const isSameColor = (color1, color2) => {
|
|
|
3395
3428
|
|
|
3396
3429
|
/**
|
|
3397
3430
|
* Returns `"white"` or `"black"`, whichever provides better contrast against
|
|
3398
|
-
* the given background color
|
|
3431
|
+
* the given background color, using OKLCH lightness (perceptually uniform).
|
|
3399
3432
|
*
|
|
3400
|
-
*
|
|
3433
|
+
* Uses a threshold of 0.5 on the OKLCH L axis (0–1 scale).
|
|
3434
|
+
* Colors with L > threshold are considered light → return "black".
|
|
3435
|
+
* Colors with L ≤ threshold are considered dark → return "white".
|
|
3401
3436
|
*
|
|
3402
3437
|
* @param {string} backgroundColor - CSS color value (hex, rgb, hsl, CSS variable, …)
|
|
3403
3438
|
* @param {Element} [element] - DOM element used to resolve CSS variables / computed styles
|
|
3439
|
+
* @param {number} [lightnessThreshold=0.5] - OKLCH L threshold (0–1). Below → "white", above → "black".
|
|
3404
3440
|
* @returns {"white"|"black"}
|
|
3405
3441
|
* @example
|
|
3406
|
-
* contrastColor("#1a202c")
|
|
3407
|
-
* contrastColor("#f5f5f5")
|
|
3408
|
-
* contrastColor("
|
|
3442
|
+
* contrastColor("#1a202c") // "white" (dark background)
|
|
3443
|
+
* contrastColor("#f5f5f5") // "black" (light background)
|
|
3444
|
+
* contrastColor("#e91e8c") // "white" (vivid pink, perceptually dark)
|
|
3409
3445
|
*/
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3446
|
+
const contrastColor = (
|
|
3447
|
+
backgroundColor,
|
|
3448
|
+
element,
|
|
3449
|
+
lightnessThreshold = 0.5,
|
|
3450
|
+
) => {
|
|
3413
3451
|
const resolvedBgColor = parseCSSColor(backgroundColor, element);
|
|
3414
3452
|
if (!resolvedBgColor) {
|
|
3415
3453
|
return "white";
|
|
3416
3454
|
}
|
|
3417
|
-
|
|
3418
|
-
// Composite against white when the background has transparency so the
|
|
3419
|
-
// luminance reflects what the user actually sees.
|
|
3420
3455
|
const [r, g, b] =
|
|
3421
3456
|
resolvedBgColor[3] === 1
|
|
3422
3457
|
? resolvedBgColor
|
|
3423
3458
|
: compositeColor(resolvedBgColor, WHITE_RGBA);
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
// One luminance comparison replaces two full contrast-ratio computations.
|
|
3428
|
-
// White wins (or ties) when bgLuminance <= the crossover point where both
|
|
3429
|
-
// colors yield identical ratios:
|
|
3430
|
-
// contrastWithWhite = contrastWithBlack
|
|
3431
|
-
// 1.05 / (L + 0.05) = (L + 0.05) / 0.05
|
|
3432
|
-
// L = √(1.05 × 0.05) − 0.05 ≈ 0.179
|
|
3433
|
-
return bgLuminance <= EQUAL_CONTRAST_LUMINANCE ? "white" : "black";
|
|
3459
|
+
const L = rgbToOklchL(r, g, b);
|
|
3460
|
+
return L <= lightnessThreshold ? "white" : "black";
|
|
3434
3461
|
};
|
|
3435
3462
|
|
|
3436
|
-
// Luminance threshold at which white and black yield the same contrast ratio
|
|
3437
|
-
// against a background. Below → white wins or ties; above → black wins.
|
|
3438
|
-
const EQUAL_CONTRAST_LUMINANCE = Math.sqrt(1.05 * 0.05) - 0.05;
|
|
3439
|
-
const WHITE_RGBA = [255, 255, 255, 1];
|
|
3440
|
-
|
|
3441
3463
|
/**
|
|
3442
|
-
* Resolves the
|
|
3464
|
+
* Resolves the OKLCH lightness of a CSS color (perceptually uniform, 0–1 scale).
|
|
3465
|
+
*
|
|
3443
3466
|
* @param {string} color - CSS color value (hex, rgb, hsl, CSS variable, etc.)
|
|
3444
3467
|
* @param {Element} [element] - DOM element to resolve CSS variables against
|
|
3445
|
-
* @returns {number|
|
|
3468
|
+
* @returns {number|null} OKLCH L value (0–1), or null if color cannot be resolved
|
|
3446
3469
|
* @example
|
|
3447
|
-
* //
|
|
3448
|
-
*
|
|
3449
|
-
*
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3470
|
+
* resolveOklchLightness("#e91e8c") // ~0.56 (vivid pink feels medium-bright)
|
|
3471
|
+
* resolveOklchLightness("#4476ff") // ~0.53 (blue)
|
|
3472
|
+
* resolveOklchLightness("#1a202c") // ~0.22 (dark background)
|
|
3473
|
+
*/
|
|
3474
|
+
const resolveOklchLightness = (color, element) => {
|
|
3475
|
+
const rgba = parseCSSColor(color, element);
|
|
3476
|
+
if (!rgba) {
|
|
3477
|
+
return null;
|
|
3478
|
+
}
|
|
3479
|
+
const [r, g, b] = rgba;
|
|
3480
|
+
return rgbToOklchL(r, g, b);
|
|
3481
|
+
};
|
|
3482
|
+
|
|
3483
|
+
/**
|
|
3484
|
+
* Resolves the WCAG relative luminance of a CSS color (kept for backwards compatibility).
|
|
3485
|
+
* @deprecated Prefer resolveOklchLightness for perceptually uniform results.
|
|
3456
3486
|
*/
|
|
3457
3487
|
const resolveColorLuminance = (color, element) => {
|
|
3458
3488
|
const rgba = parseCSSColor(color, element);
|
|
3459
3489
|
if (!rgba) {
|
|
3460
|
-
return
|
|
3490
|
+
return null;
|
|
3461
3491
|
}
|
|
3462
3492
|
const [r, g, b] = rgba;
|
|
3463
3493
|
return getLuminance(r, g, b);
|
|
3464
3494
|
};
|
|
3465
3495
|
|
|
3496
|
+
const WHITE_RGBA = [255, 255, 255, 1];
|
|
3497
|
+
|
|
3498
|
+
/**
|
|
3499
|
+
* Converts sRGB (0–255 each) to OKLCH lightness L (0–1).
|
|
3500
|
+
* Implements the sRGB → Linear sRGB → XYZ D65 → OKLab → L pipeline.
|
|
3501
|
+
*/
|
|
3502
|
+
const rgbToOklchL = (r, g, b) => {
|
|
3503
|
+
// sRGB → linear
|
|
3504
|
+
const toLinear = (c) => {
|
|
3505
|
+
c = c / 255;
|
|
3506
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
3507
|
+
};
|
|
3508
|
+
const lr = toLinear(r);
|
|
3509
|
+
const lg = toLinear(g);
|
|
3510
|
+
const lb = toLinear(b);
|
|
3511
|
+
|
|
3512
|
+
// Linear sRGB → LMS (Oklab M1 matrix)
|
|
3513
|
+
const l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb;
|
|
3514
|
+
const m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb;
|
|
3515
|
+
const s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb;
|
|
3516
|
+
|
|
3517
|
+
// Cube root
|
|
3518
|
+
const l_ = Math.cbrt(l);
|
|
3519
|
+
const m_ = Math.cbrt(m);
|
|
3520
|
+
const s_ = Math.cbrt(s);
|
|
3521
|
+
|
|
3522
|
+
// LMS → OKLab L
|
|
3523
|
+
return 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_;
|
|
3524
|
+
};
|
|
3525
|
+
|
|
3466
3526
|
/**
|
|
3467
3527
|
* Calculates the contrast ratio between two RGBA colors
|
|
3468
3528
|
* Based on WCAG 2.1 specification
|
|
@@ -3849,6 +3909,16 @@ const getFocusVisibilityInfo = (node) => {
|
|
|
3849
3909
|
}
|
|
3850
3910
|
// Continue checking ancestors
|
|
3851
3911
|
}
|
|
3912
|
+
if (elementIsDialog(nodeOrAncestor) && !nodeOrAncestor.open) {
|
|
3913
|
+
return { visible: false, reason: "inside closed dialog element" };
|
|
3914
|
+
}
|
|
3915
|
+
if (
|
|
3916
|
+
nodeOrAncestor.popover !== null &&
|
|
3917
|
+
nodeOrAncestor.popover !== undefined &&
|
|
3918
|
+
!nodeOrAncestor.matches(":popover-open")
|
|
3919
|
+
) {
|
|
3920
|
+
return { visible: false, reason: "inside closed popover element" };
|
|
3921
|
+
}
|
|
3852
3922
|
nodeOrAncestor = nodeOrAncestor.parentNode;
|
|
3853
3923
|
}
|
|
3854
3924
|
return { visible: true, reason: "no reason to be hidden" };
|
|
@@ -4661,7 +4731,11 @@ const getNextTablePosition = (
|
|
|
4661
4731
|
|
|
4662
4732
|
const performTabNavigation = (
|
|
4663
4733
|
event,
|
|
4664
|
-
{
|
|
4734
|
+
{
|
|
4735
|
+
rootElement = document.body,
|
|
4736
|
+
outsideOfElement = null,
|
|
4737
|
+
debug = () => {},
|
|
4738
|
+
} = {},
|
|
4665
4739
|
) => {
|
|
4666
4740
|
if (!isTabEvent$1(event)) {
|
|
4667
4741
|
return false;
|
|
@@ -4673,29 +4747,20 @@ const performTabNavigation = (
|
|
|
4673
4747
|
}
|
|
4674
4748
|
const isForward = !event.shiftKey;
|
|
4675
4749
|
const onTargetToFocus = (targetToFocus) => {
|
|
4676
|
-
|
|
4750
|
+
debug(
|
|
4677
4751
|
`Tab navigation: ${isForward ? "forward" : "backward"} from`,
|
|
4678
|
-
activeElement,
|
|
4752
|
+
getElementSignature(activeElement),
|
|
4679
4753
|
"to",
|
|
4680
|
-
targetToFocus,
|
|
4754
|
+
getElementSignature(targetToFocus),
|
|
4681
4755
|
);
|
|
4682
4756
|
event.preventDefault();
|
|
4683
4757
|
markFocusNav(event);
|
|
4684
4758
|
targetToFocus.focus();
|
|
4685
4759
|
};
|
|
4686
4760
|
|
|
4687
|
-
{
|
|
4688
|
-
console.debug(
|
|
4689
|
-
`Tab navigation: ${isForward ? "forward" : "backward"} from,`,
|
|
4690
|
-
activeElement,
|
|
4691
|
-
);
|
|
4692
|
-
}
|
|
4693
|
-
|
|
4694
4761
|
const predicate = (candidate) => {
|
|
4695
4762
|
const canBeFocusedByTab = isFocusableByTab(candidate);
|
|
4696
|
-
{
|
|
4697
|
-
console.debug(`Testing`, candidate, `${canBeFocusedByTab ? "✓" : "✗"}`);
|
|
4698
|
-
}
|
|
4763
|
+
// debug(`Testing`, candidate, `${canBeFocusedByTab ? "✓" : "✗"}`);
|
|
4699
4764
|
return canBeFocusedByTab;
|
|
4700
4765
|
};
|
|
4701
4766
|
|
|
@@ -4720,7 +4785,8 @@ const performTabNavigation = (
|
|
|
4720
4785
|
if (nextFocusableElement) {
|
|
4721
4786
|
return onTargetToFocus(nextFocusableElement);
|
|
4722
4787
|
}
|
|
4723
|
-
|
|
4788
|
+
// Wrap around: go back to the first focusable element in root.
|
|
4789
|
+
const firstFocusableElement = findDescendant(rootElement, predicate, {
|
|
4724
4790
|
skipRoot: outsideOfElement,
|
|
4725
4791
|
});
|
|
4726
4792
|
if (firstFocusableElement) {
|
|
@@ -4751,7 +4817,8 @@ const performTabNavigation = (
|
|
|
4751
4817
|
if (previousFocusableElement) {
|
|
4752
4818
|
return onTargetToFocus(previousFocusableElement);
|
|
4753
4819
|
}
|
|
4754
|
-
|
|
4820
|
+
// Wrap around: go back to the last focusable element in root.
|
|
4821
|
+
const lastFocusableElement = findLastDescendant(rootElement, predicate, {
|
|
4755
4822
|
skipRoot: outsideOfElement,
|
|
4756
4823
|
});
|
|
4757
4824
|
if (lastFocusableElement) {
|
|
@@ -4876,7 +4943,35 @@ const preventFocusNavViaKeyboard = (keyboardEvent) => {
|
|
|
4876
4943
|
return false;
|
|
4877
4944
|
};
|
|
4878
4945
|
|
|
4879
|
-
|
|
4946
|
+
/**
|
|
4947
|
+
* Traps keyboard focus and mouse clicks inside `element`.
|
|
4948
|
+
*
|
|
4949
|
+
* Once active:
|
|
4950
|
+
* - **Tab / Shift+Tab** cycle through focusable descendants of `element`,
|
|
4951
|
+
* wrapping from last → first and first → last. If no focusable element
|
|
4952
|
+
* exists, the default browser Tab action is suppressed so focus cannot
|
|
4953
|
+
* escape.
|
|
4954
|
+
* - **Mouse clicks** outside `element` are only blocked when `pointerTrap`
|
|
4955
|
+
* is `true`. Backdrop clicks (on `<dialog>` elements) still propagate even
|
|
4956
|
+
* then, so the dialog can close itself.
|
|
4957
|
+
*
|
|
4958
|
+
* Multiple traps can be stacked. When a new trap is activated the previous
|
|
4959
|
+
* one is paused; when the new trap is released the previous one resumes.
|
|
4960
|
+
* Traps must be released in LIFO order (the reverse of activation order).
|
|
4961
|
+
*
|
|
4962
|
+
* @param {HTMLElement} element - The root element to trap focus inside.
|
|
4963
|
+
* @param {object} [options]
|
|
4964
|
+
* @param {boolean} [options.pointerTrap=false] - When true, mouse clicks outside `element`
|
|
4965
|
+
* are cancelled so the user cannot move focus away by clicking the backdrop.
|
|
4966
|
+
* Backdrop clicks (target is a `<dialog>` element) only receive `preventDefault`
|
|
4967
|
+
* and still propagate, allowing the dialog to react to them (e.g. close itself).
|
|
4968
|
+
* @param {Function} [options.debug] - Optional debug logger passed to tab navigation.
|
|
4969
|
+
* @returns {() => void} Cleanup function — call it to release the trap.
|
|
4970
|
+
*/
|
|
4971
|
+
const trapFocusInside = (
|
|
4972
|
+
element,
|
|
4973
|
+
{ debug, pointerTrap = false } = {},
|
|
4974
|
+
) => {
|
|
4880
4975
|
if (element.nodeType === 3) {
|
|
4881
4976
|
console.warn("cannot trap focus inside a text node");
|
|
4882
4977
|
return () => {};
|
|
@@ -4891,39 +4986,67 @@ const trapFocusInside = (element) => {
|
|
|
4891
4986
|
}
|
|
4892
4987
|
|
|
4893
4988
|
const isEventOutside = (event) => {
|
|
4894
|
-
if (event.target === element)
|
|
4895
|
-
|
|
4989
|
+
if (event.target === element) {
|
|
4990
|
+
return false;
|
|
4991
|
+
}
|
|
4992
|
+
if (element.contains(event.target)) {
|
|
4993
|
+
return false;
|
|
4994
|
+
}
|
|
4896
4995
|
return true;
|
|
4897
4996
|
};
|
|
4898
4997
|
|
|
4899
4998
|
const lock = () => {
|
|
4900
|
-
const onmousedown =
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4999
|
+
const onmousedown = pointerTrap
|
|
5000
|
+
? (event) => {
|
|
5001
|
+
if (!isEventOutside(event)) {
|
|
5002
|
+
return;
|
|
5003
|
+
}
|
|
5004
|
+
event.preventDefault();
|
|
5005
|
+
// Backdrop clicks (e.g. clicking a <dialog>'s ::backdrop) must still
|
|
5006
|
+
// propagate so the dialog/popover can react to them (e.g. close itself).
|
|
5007
|
+
// A backdrop click is detected when the target is a <dialog> element —
|
|
5008
|
+
// the ::backdrop pseudo-element is not in the DOM, so the event target
|
|
5009
|
+
// becomes the dialog element itself when its content area is not hit.
|
|
5010
|
+
const isBackdropClick =
|
|
5011
|
+
event.target.tagName === "DIALOG" ||
|
|
5012
|
+
event.target.className.includes("backdrop");
|
|
5013
|
+
if (!isBackdropClick) {
|
|
5014
|
+
event.stopImmediatePropagation();
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
5017
|
+
: null;
|
|
4906
5018
|
|
|
4907
5019
|
const onkeydown = (event) => {
|
|
4908
5020
|
if (isTabEvent(event)) {
|
|
4909
|
-
performTabNavigation(event, {
|
|
5021
|
+
const handled = performTabNavigation(event, {
|
|
5022
|
+
rootElement: element,
|
|
5023
|
+
debug,
|
|
5024
|
+
});
|
|
5025
|
+
if (!handled) {
|
|
5026
|
+
// No focusable target found — prevent the browser from moving focus outside the trap.
|
|
5027
|
+
event.preventDefault();
|
|
5028
|
+
}
|
|
4910
5029
|
}
|
|
4911
5030
|
};
|
|
4912
5031
|
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
5032
|
+
if (onmousedown) {
|
|
5033
|
+
document.addEventListener("mousedown", onmousedown, {
|
|
5034
|
+
capture: true,
|
|
5035
|
+
passive: false,
|
|
5036
|
+
});
|
|
5037
|
+
}
|
|
4917
5038
|
document.addEventListener("keydown", onkeydown, {
|
|
4918
5039
|
capture: true,
|
|
4919
5040
|
passive: false,
|
|
4920
5041
|
});
|
|
4921
5042
|
|
|
4922
5043
|
return () => {
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
5044
|
+
if (onmousedown) {
|
|
5045
|
+
document.removeEventListener("mousedown", onmousedown, {
|
|
5046
|
+
capture: true,
|
|
5047
|
+
passive: false,
|
|
5048
|
+
});
|
|
5049
|
+
}
|
|
4927
5050
|
document.removeEventListener("keydown", onkeydown, {
|
|
4928
5051
|
capture: true,
|
|
4929
5052
|
passive: false,
|
|
@@ -5785,11 +5908,228 @@ const getScrollbarState = (
|
|
|
5785
5908
|
return { x, y, availableWidth, availableHeight };
|
|
5786
5909
|
};
|
|
5787
5910
|
|
|
5911
|
+
/**
|
|
5912
|
+
* Scrolls el into view within a specific container only — does NOT scroll
|
|
5913
|
+
* any ancestor beyond that container (document, popover backdrop, etc.).
|
|
5914
|
+
*
|
|
5915
|
+
* Why not just use scrollIntoView({ container: "nearest" })?
|
|
5916
|
+
* It finds the nearest scrollable ancestor and stops there ONLY IF that
|
|
5917
|
+
* ancestor has visible scrollbar, otherwise browser walks further up,
|
|
5918
|
+
* potentially scrolling the document.
|
|
5919
|
+
* This is exactly the wrong behavior inside a popover or fixed panel.
|
|
5920
|
+
* scrollIntoViewScoped avoids this by targeting one container explicitly.
|
|
5921
|
+
*
|
|
5922
|
+
* Uses scrollTo() so CSS scroll-behavior:smooth on the container is respected.
|
|
5923
|
+
* Respects scroll-margin-* on the element.
|
|
5924
|
+
*
|
|
5925
|
+
* @param {Element} el - The element to scroll into view.
|
|
5926
|
+
* @param {object} options
|
|
5927
|
+
* @param {Element} [options.container] - The scroll container to scroll. Defaults to getScrollContainer(el).
|
|
5928
|
+
* @param {"start"|"center"|"end"|"nearest"} [options.block="nearest"] - Vertical alignment.
|
|
5929
|
+
* @param {"start"|"center"|"end"|"nearest"} [options.inline="nearest"] - Horizontal alignment.
|
|
5930
|
+
*/
|
|
5931
|
+
const scrollIntoViewScoped = (
|
|
5932
|
+
el,
|
|
5933
|
+
{
|
|
5934
|
+
container = getScrollContainer(el),
|
|
5935
|
+
block = "nearest",
|
|
5936
|
+
inline = "nearest",
|
|
5937
|
+
} = {},
|
|
5938
|
+
) => {
|
|
5939
|
+
if (!container) {
|
|
5940
|
+
return;
|
|
5941
|
+
}
|
|
5942
|
+
|
|
5943
|
+
const containerRect = container.getBoundingClientRect();
|
|
5944
|
+
const elRect = el.getBoundingClientRect();
|
|
5945
|
+
const style = getComputedStyle(el);
|
|
5946
|
+
|
|
5947
|
+
const scrollMarginTop = parseFloat(style.scrollMarginTop) || 0;
|
|
5948
|
+
const scrollMarginBottom = parseFloat(style.scrollMarginBottom) || 0;
|
|
5949
|
+
const scrollMarginLeft = parseFloat(style.scrollMarginLeft) || 0;
|
|
5950
|
+
const scrollMarginRight = parseFloat(style.scrollMarginRight) || 0;
|
|
5951
|
+
|
|
5952
|
+
const currentScrollTop = container.scrollTop;
|
|
5953
|
+
const currentScrollLeft = container.scrollLeft;
|
|
5954
|
+
const containerHeight = containerRect.height;
|
|
5955
|
+
const containerWidth = containerRect.width;
|
|
5956
|
+
|
|
5957
|
+
// Element position relative to the container's scroll origin.
|
|
5958
|
+
const elTop =
|
|
5959
|
+
elRect.top - containerRect.top + currentScrollTop - scrollMarginTop;
|
|
5960
|
+
const elBottom = elTop + elRect.height + scrollMarginTop + scrollMarginBottom;
|
|
5961
|
+
const elLeft =
|
|
5962
|
+
elRect.left - containerRect.left + currentScrollLeft - scrollMarginLeft;
|
|
5963
|
+
const elRight = elLeft + elRect.width + scrollMarginLeft + scrollMarginRight;
|
|
5964
|
+
|
|
5965
|
+
let newScrollTop = currentScrollTop;
|
|
5966
|
+
if (block === "start") {
|
|
5967
|
+
newScrollTop = elTop;
|
|
5968
|
+
} else if (block === "end") {
|
|
5969
|
+
newScrollTop = elBottom - containerHeight;
|
|
5970
|
+
} else if (block === "center") {
|
|
5971
|
+
newScrollTop = elTop + (elRect.height - containerHeight) / 2;
|
|
5972
|
+
} else {
|
|
5973
|
+
// nearest: scroll only if partially or fully out of view.
|
|
5974
|
+
// When the element is taller than the container, only scroll if it is
|
|
5975
|
+
// completely out of view — otherwise it is already as visible as possible.
|
|
5976
|
+
const scrollBottom = currentScrollTop + containerHeight;
|
|
5977
|
+
const elHeight = elBottom - elTop;
|
|
5978
|
+
if (elHeight <= containerHeight) {
|
|
5979
|
+
if (elTop < currentScrollTop) {
|
|
5980
|
+
newScrollTop = elTop;
|
|
5981
|
+
} else if (elBottom > scrollBottom) {
|
|
5982
|
+
newScrollTop = elBottom - containerHeight;
|
|
5983
|
+
}
|
|
5984
|
+
} else if (elBottom < currentScrollTop) {
|
|
5985
|
+
newScrollTop = elBottom - containerHeight;
|
|
5986
|
+
} else if (elTop > scrollBottom) {
|
|
5987
|
+
newScrollTop = elTop;
|
|
5988
|
+
}
|
|
5989
|
+
}
|
|
5990
|
+
|
|
5991
|
+
let newScrollLeft = currentScrollLeft;
|
|
5992
|
+
if (inline === "start") {
|
|
5993
|
+
newScrollLeft = elLeft;
|
|
5994
|
+
} else if (inline === "end") {
|
|
5995
|
+
newScrollLeft = elRight - containerWidth;
|
|
5996
|
+
} else if (inline === "center") {
|
|
5997
|
+
newScrollLeft = elLeft + (elRect.width - containerWidth) / 2;
|
|
5998
|
+
} else {
|
|
5999
|
+
// nearest: scroll only if partially or fully out of view.
|
|
6000
|
+
// When the element is wider than the container, only scroll if it is
|
|
6001
|
+
// completely out of view — otherwise it is already as visible as possible.
|
|
6002
|
+
const scrollRight = currentScrollLeft + containerWidth;
|
|
6003
|
+
const elWidth = elRight - elLeft;
|
|
6004
|
+
if (elWidth <= containerWidth) {
|
|
6005
|
+
if (elLeft < currentScrollLeft) {
|
|
6006
|
+
newScrollLeft = elLeft;
|
|
6007
|
+
} else if (elRight > scrollRight) {
|
|
6008
|
+
newScrollLeft = elRight - containerWidth;
|
|
6009
|
+
}
|
|
6010
|
+
} else if (elRight < currentScrollLeft) {
|
|
6011
|
+
newScrollLeft = elRight - containerWidth;
|
|
6012
|
+
} else if (elLeft > scrollRight) {
|
|
6013
|
+
newScrollLeft = elLeft;
|
|
6014
|
+
}
|
|
6015
|
+
}
|
|
6016
|
+
|
|
6017
|
+
container.scrollTo({
|
|
6018
|
+
left: newScrollLeft,
|
|
6019
|
+
top: newScrollTop,
|
|
6020
|
+
});
|
|
6021
|
+
};
|
|
6022
|
+
|
|
6023
|
+
/**
|
|
6024
|
+
* DON'T USE THIS, use scroll-padding-top/bottom in CSS instead
|
|
6025
|
+
* better in every aspect
|
|
6026
|
+
*/
|
|
6027
|
+
|
|
6028
|
+
|
|
6029
|
+
/**
|
|
6030
|
+
* Scrolls el into view (using the native "nearest" block behavior) and then
|
|
6031
|
+
* corrects for any sticky element that visually covers el inside its scroll
|
|
6032
|
+
* container.
|
|
6033
|
+
*
|
|
6034
|
+
* After the native scroll, this function iterates the siblings of el (children
|
|
6035
|
+
* of el's parent) and checks whether any of them uses `position: sticky` and
|
|
6036
|
+
* overlaps el. The largest overlap on each side is used to nudge scrollTop:
|
|
6037
|
+
* - sticky-top (top !== auto): subtract overlap so el appears below the header
|
|
6038
|
+
* - sticky-bottom (bottom !== auto): add overlap so el appears above the footer
|
|
6039
|
+
*
|
|
6040
|
+
* If el happens to be covered on both sides at once (extremely unlikely) the
|
|
6041
|
+
* correction picks whichever side was covered — the result may not be perfect
|
|
6042
|
+
* but avoids an infinite correction loop.
|
|
6043
|
+
*
|
|
6044
|
+
* @param {Element} el - The element to scroll into view.
|
|
6045
|
+
*/
|
|
6046
|
+
const scrollIntoViewWithStickyAwareness = (
|
|
6047
|
+
el,
|
|
6048
|
+
{ behavior, block = "nearest", inline, container } = {},
|
|
6049
|
+
) => {
|
|
6050
|
+
el.scrollIntoView({ behavior, block, inline, container });
|
|
6051
|
+
const scrollContainer = getScrollContainer(el);
|
|
6052
|
+
if (!scrollContainer) {
|
|
6053
|
+
return;
|
|
6054
|
+
}
|
|
6055
|
+
const elRect = el.getBoundingClientRect();
|
|
6056
|
+
let topCover = 0;
|
|
6057
|
+
let bottomCover = 0;
|
|
6058
|
+
for (const sibling of el.parentNode.children) {
|
|
6059
|
+
const style = getComputedStyle(sibling);
|
|
6060
|
+
if (style.position !== "sticky") {
|
|
6061
|
+
continue;
|
|
6062
|
+
}
|
|
6063
|
+
const rect = sibling.getBoundingClientRect();
|
|
6064
|
+
if (style.top !== "auto") {
|
|
6065
|
+
// Sticky-top: covers el from above — track the largest overlap.
|
|
6066
|
+
const overlap = rect.bottom - elRect.top;
|
|
6067
|
+
if (overlap > topCover) {
|
|
6068
|
+
topCover = overlap;
|
|
6069
|
+
}
|
|
6070
|
+
} else if (style.bottom !== "auto") {
|
|
6071
|
+
// Sticky-bottom: covers el from below — track the largest overlap.
|
|
6072
|
+
// Only checked when top is "auto" so each element is attributed to one
|
|
6073
|
+
// side only; both sides are still accumulated across all children.
|
|
6074
|
+
const overlap = elRect.bottom - rect.top;
|
|
6075
|
+
if (overlap > bottomCover) {
|
|
6076
|
+
bottomCover = overlap;
|
|
6077
|
+
}
|
|
6078
|
+
}
|
|
6079
|
+
if (topCover > 0 && bottomCover > 0) {
|
|
6080
|
+
// Both sides already have coverage — no point checking further children.
|
|
6081
|
+
break;
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
if (topCover > 0) {
|
|
6085
|
+
// For block="center" the element is visually centered in the full viewport.
|
|
6086
|
+
// A sticky header of height H shifts the available center upward by H/2,
|
|
6087
|
+
// so we only need to correct by half the overlap to keep the element
|
|
6088
|
+
// centered in the visible (uncovered) area.
|
|
6089
|
+
scrollContainer.scrollTop -= block === "center" ? topCover / 2 : topCover;
|
|
6090
|
+
}
|
|
6091
|
+
if (bottomCover > 0) {
|
|
6092
|
+
scrollContainer.scrollTop +=
|
|
6093
|
+
block === "center" ? bottomCover / 2 : bottomCover;
|
|
6094
|
+
}
|
|
6095
|
+
};
|
|
6096
|
+
|
|
6097
|
+
/**
|
|
6098
|
+
* Prevents scrolling on all scrollable containers that are ancestors of (or
|
|
6099
|
+
* siblings preceding) `element`. Used when an overlay (popover, dialog) is
|
|
6100
|
+
* open and background scroll should be disabled.
|
|
6101
|
+
*
|
|
6102
|
+
* **Why padding instead of scrollbar-gutter?**
|
|
6103
|
+
* `scrollbar-gutter: stable` would be the modern, CSS-native way to reserve
|
|
6104
|
+
* the scrollbar lane before hiding overflow so the layout doesn't shift.
|
|
6105
|
+
* However it only works well when the element's design already accounts for
|
|
6106
|
+
* that reserved space. On arbitrary containers we can't assume that, so we
|
|
6107
|
+
* measure the actual scrollbar size and compensate with padding — a technique
|
|
6108
|
+
* that works regardless of how the element is styled.
|
|
6109
|
+
*
|
|
6110
|
+
* **What if the element already uses scrollbar-gutter?**
|
|
6111
|
+
* A non-"auto" `scrollbar-gutter` value signals that the element has its own
|
|
6112
|
+
* scrollbar-gutter strategy in place. In that case we skip the padding
|
|
6113
|
+
* compensation and rely on that strategy instead — adding padding on top of an
|
|
6114
|
+
* already-reserved gutter would double-count the space.
|
|
6115
|
+
*
|
|
6116
|
+
* @param {HTMLElement} element - The overlay element being shown. Its preceding
|
|
6117
|
+
* siblings and all ancestor scroll containers will be scroll-locked.
|
|
6118
|
+
* @returns {() => void} Cleanup function that restores all modified styles.
|
|
6119
|
+
*/
|
|
5788
6120
|
const trapScrollInside = (element) => {
|
|
5789
6121
|
const cleanupCallbackSet = new Set();
|
|
5790
6122
|
const lockScroll = (el) => {
|
|
6123
|
+
const scrollbarGutter = getStyle(el, "scrollbar-gutter");
|
|
6124
|
+
const hasScrollbarGutterStrategy =
|
|
6125
|
+
scrollbarGutter && scrollbarGutter !== "auto";
|
|
6126
|
+
if (hasScrollbarGutterStrategy) {
|
|
6127
|
+
// The element manages its own gutter — just hide overflow, no padding needed.
|
|
6128
|
+
const removeScrollLockStyles = setStyles(el, { overflow: "hidden" });
|
|
6129
|
+
cleanupCallbackSet.add(removeScrollLockStyles);
|
|
6130
|
+
return;
|
|
6131
|
+
}
|
|
5791
6132
|
const [scrollbarWidth, scrollbarHeight] = measureScrollbar(el);
|
|
5792
|
-
// scrollbar-gutter would work but would display an empty blank space
|
|
5793
6133
|
const paddingRight = parseInt(getStyle(el, "padding-right"), 0);
|
|
5794
6134
|
const paddingTop = parseInt(getStyle(el, "padding-top"), 0);
|
|
5795
6135
|
const removeScrollLockStyles = setStyles(el, {
|
|
@@ -5797,9 +6137,7 @@ const trapScrollInside = (element) => {
|
|
|
5797
6137
|
"padding-top": `${paddingTop + scrollbarHeight}px`,
|
|
5798
6138
|
"overflow": "hidden",
|
|
5799
6139
|
});
|
|
5800
|
-
cleanupCallbackSet.add(
|
|
5801
|
-
removeScrollLockStyles();
|
|
5802
|
-
});
|
|
6140
|
+
cleanupCallbackSet.add(removeScrollLockStyles);
|
|
5803
6141
|
};
|
|
5804
6142
|
let previous = element.previousSibling;
|
|
5805
6143
|
while (previous) {
|
|
@@ -9237,24 +9575,27 @@ const stickyAsRelativeCoords = (
|
|
|
9237
9575
|
return [leftPosition, topPosition];
|
|
9238
9576
|
};
|
|
9239
9577
|
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9578
|
+
/**
|
|
9579
|
+
* Tracks how much of an element is visible within its scrollable parent and within the
|
|
9580
|
+
* document viewport. Calls update() on initialization and whenever visibility changes
|
|
9581
|
+
* (scroll, resize, intersection changes).
|
|
9582
|
+
*
|
|
9583
|
+
* The update callback receives a visibleRect object with:
|
|
9584
|
+
* - left, top, right, bottom, width, height: the visible portion of the element,
|
|
9585
|
+
* clipped to its scroll container's bounds and expressed in overlay coordinates
|
|
9586
|
+
* - visibilityRatio: fraction of the element's area that is truly visible on screen (0–1).
|
|
9587
|
+
* For document scroll containers this is the viewport-clipped fraction.
|
|
9588
|
+
* For custom containers this is the fraction clipped by both the container AND the viewport
|
|
9589
|
+
* (so an element scrolled out of its container correctly reports 0, not 1).
|
|
9590
|
+
*
|
|
9591
|
+
* A bit like https://tetherjs.dev/ but different
|
|
9592
|
+
*/
|
|
9252
9593
|
const visibleRectEffect = (element, update) => {
|
|
9253
9594
|
const [teardown, addTeardown] = createPubSub();
|
|
9254
9595
|
const scrollContainer = getScrollContainer(element);
|
|
9255
9596
|
const scrollContainerIsDocument =
|
|
9256
9597
|
scrollContainer === document.documentElement;
|
|
9257
|
-
const check = (
|
|
9598
|
+
const check = (event) => {
|
|
9258
9599
|
|
|
9259
9600
|
// 1. Calculate element position relative to scrollable parent
|
|
9260
9601
|
const { scrollLeft, scrollTop } = scrollContainer;
|
|
@@ -9346,27 +9687,35 @@ const visibleRectEffect = (element, update) => {
|
|
|
9346
9687
|
}
|
|
9347
9688
|
}
|
|
9348
9689
|
|
|
9349
|
-
// Calculate
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
//
|
|
9353
|
-
let
|
|
9690
|
+
// Calculate visibilityRatio: fraction of element area truly visible on screen.
|
|
9691
|
+
// For custom containers we intersect the container-clipped visible size (widthVisible x
|
|
9692
|
+
// heightVisible) with the viewport bounds, so an element scrolled out of its container
|
|
9693
|
+
// correctly reports 0 rather than the raw viewport intersection of its bounding rect.
|
|
9694
|
+
let visibilityRatio;
|
|
9354
9695
|
if (scrollContainerIsDocument) {
|
|
9355
|
-
|
|
9696
|
+
visibilityRatio = (widthVisible * heightVisible) / (width * height);
|
|
9356
9697
|
} else {
|
|
9357
|
-
//
|
|
9358
|
-
|
|
9698
|
+
// widthVisible/heightVisible are already clipped to the scroll container.
|
|
9699
|
+
// Now clip their viewport-relative counterparts against the viewport.
|
|
9359
9700
|
const viewportWidth = window.innerWidth;
|
|
9360
9701
|
const viewportHeight = window.innerHeight;
|
|
9361
|
-
//
|
|
9362
|
-
const
|
|
9363
|
-
const
|
|
9364
|
-
const
|
|
9365
|
-
const
|
|
9366
|
-
|
|
9367
|
-
const
|
|
9368
|
-
|
|
9369
|
-
|
|
9702
|
+
// Container-clipped visible rect in viewport coordinates
|
|
9703
|
+
const visibleLeft = overlayLeft;
|
|
9704
|
+
const visibleTop = overlayTop;
|
|
9705
|
+
const visibleRight = overlayLeft + widthVisible;
|
|
9706
|
+
const visibleBottom = overlayTop + heightVisible;
|
|
9707
|
+
// Intersect with viewport
|
|
9708
|
+
const clippedLeft = visibleLeft < 0 ? 0 : visibleLeft;
|
|
9709
|
+
const clippedTop = visibleTop < 0 ? 0 : visibleTop;
|
|
9710
|
+
const clippedRight =
|
|
9711
|
+
visibleRight > viewportWidth ? viewportWidth : visibleRight;
|
|
9712
|
+
const clippedBottom =
|
|
9713
|
+
visibleBottom > viewportHeight ? viewportHeight : visibleBottom;
|
|
9714
|
+
const clippedWidth =
|
|
9715
|
+
clippedRight > clippedLeft ? clippedRight - clippedLeft : 0;
|
|
9716
|
+
const clippedHeight =
|
|
9717
|
+
clippedBottom > clippedTop ? clippedBottom - clippedTop : 0;
|
|
9718
|
+
visibilityRatio = (clippedWidth * clippedHeight) / (width * height);
|
|
9370
9719
|
}
|
|
9371
9720
|
|
|
9372
9721
|
const visibleRect = {
|
|
@@ -9376,22 +9725,22 @@ const visibleRectEffect = (element, update) => {
|
|
|
9376
9725
|
bottom: overlayTop + heightVisible,
|
|
9377
9726
|
width: widthVisible,
|
|
9378
9727
|
height: heightVisible,
|
|
9379
|
-
visibilityRatio
|
|
9380
|
-
scrollVisibilityRatio,
|
|
9728
|
+
visibilityRatio,
|
|
9381
9729
|
};
|
|
9382
9730
|
update(visibleRect, {
|
|
9731
|
+
event,
|
|
9383
9732
|
width,
|
|
9384
9733
|
height,
|
|
9385
9734
|
});
|
|
9386
9735
|
};
|
|
9387
9736
|
|
|
9388
|
-
check();
|
|
9737
|
+
check(new CustomEvent("initialization"));
|
|
9389
9738
|
|
|
9390
9739
|
const [publishBeforeAutoCheck, onBeforeAutoCheck] = createPubSub();
|
|
9391
9740
|
{
|
|
9392
|
-
const autoCheck = (
|
|
9393
|
-
const beforeCheckResults = publishBeforeAutoCheck(
|
|
9394
|
-
check();
|
|
9741
|
+
const autoCheck = (event) => {
|
|
9742
|
+
const beforeCheckResults = publishBeforeAutoCheck(event);
|
|
9743
|
+
check(event);
|
|
9395
9744
|
for (const beforeCheckResult of beforeCheckResults) {
|
|
9396
9745
|
if (typeof beforeCheckResult === "function") {
|
|
9397
9746
|
beforeCheckResult();
|
|
@@ -9412,8 +9761,8 @@ const visibleRectEffect = (element, update) => {
|
|
|
9412
9761
|
{
|
|
9413
9762
|
// If scrollable parent is not document, also listen to document scroll
|
|
9414
9763
|
// to update UI position when the scrollable parent moves in viewport
|
|
9415
|
-
const onDocumentScroll = () => {
|
|
9416
|
-
autoCheck(
|
|
9764
|
+
const onDocumentScroll = (e) => {
|
|
9765
|
+
autoCheck(e);
|
|
9417
9766
|
};
|
|
9418
9767
|
document.addEventListener("scroll", onDocumentScroll, {
|
|
9419
9768
|
passive: true,
|
|
@@ -9424,8 +9773,8 @@ const visibleRectEffect = (element, update) => {
|
|
|
9424
9773
|
});
|
|
9425
9774
|
});
|
|
9426
9775
|
if (!scrollContainerIsDocument) {
|
|
9427
|
-
const onScroll = () => {
|
|
9428
|
-
autoCheck(
|
|
9776
|
+
const onScroll = (e) => {
|
|
9777
|
+
autoCheck(e);
|
|
9429
9778
|
};
|
|
9430
9779
|
scrollContainer.addEventListener("scroll", onScroll, {
|
|
9431
9780
|
passive: true,
|
|
@@ -9438,8 +9787,8 @@ const visibleRectEffect = (element, update) => {
|
|
|
9438
9787
|
}
|
|
9439
9788
|
}
|
|
9440
9789
|
{
|
|
9441
|
-
const onWindowResize = () => {
|
|
9442
|
-
autoCheck(
|
|
9790
|
+
const onWindowResize = (e) => {
|
|
9791
|
+
autoCheck(e);
|
|
9443
9792
|
};
|
|
9444
9793
|
window.addEventListener("resize", onWindowResize);
|
|
9445
9794
|
addTeardown(() => {
|
|
@@ -9469,7 +9818,9 @@ const visibleRectEffect = (element, update) => {
|
|
|
9469
9818
|
{
|
|
9470
9819
|
const documentIntersectionObserver = new IntersectionObserver(
|
|
9471
9820
|
() => {
|
|
9472
|
-
autoCheck(
|
|
9821
|
+
autoCheck(
|
|
9822
|
+
new CustomEvent("element_intersection_with_document_change"),
|
|
9823
|
+
);
|
|
9473
9824
|
},
|
|
9474
9825
|
{
|
|
9475
9826
|
root: null,
|
|
@@ -9484,7 +9835,9 @@ const visibleRectEffect = (element, update) => {
|
|
|
9484
9835
|
if (!scrollContainerIsDocument) {
|
|
9485
9836
|
const scrollIntersectionObserver = new IntersectionObserver(
|
|
9486
9837
|
() => {
|
|
9487
|
-
autoCheck(
|
|
9838
|
+
autoCheck(
|
|
9839
|
+
new CustomEvent("element_intersection_with_scroll_change"),
|
|
9840
|
+
);
|
|
9488
9841
|
},
|
|
9489
9842
|
{
|
|
9490
9843
|
root: scrollContainer,
|
|
@@ -9499,8 +9852,8 @@ const visibleRectEffect = (element, update) => {
|
|
|
9499
9852
|
}
|
|
9500
9853
|
}
|
|
9501
9854
|
{
|
|
9502
|
-
const onWindowTouchMove = () => {
|
|
9503
|
-
autoCheck(
|
|
9855
|
+
const onWindowTouchMove = (e) => {
|
|
9856
|
+
autoCheck(e);
|
|
9504
9857
|
};
|
|
9505
9858
|
window.addEventListener("touchmove", onWindowTouchMove, {
|
|
9506
9859
|
passive: true,
|
|
@@ -9522,14 +9875,50 @@ const visibleRectEffect = (element, update) => {
|
|
|
9522
9875
|
};
|
|
9523
9876
|
};
|
|
9524
9877
|
|
|
9878
|
+
/**
|
|
9879
|
+
* Places element adjacent to anchor using one of 9 compass positions.
|
|
9880
|
+
*
|
|
9881
|
+
* ```
|
|
9882
|
+
* top-left | top | top-right
|
|
9883
|
+
* ----------+---------+----------
|
|
9884
|
+
* left | center | right
|
|
9885
|
+
* ----------+---------+----------
|
|
9886
|
+
* bottom-left| bottom |bottom-right
|
|
9887
|
+
* ```
|
|
9888
|
+
*
|
|
9889
|
+
* All positions except "center" place element outside the anchor:
|
|
9890
|
+
* - "top" → element.bottom = anchor.top, horizontally centered
|
|
9891
|
+
* - "bottom" → element.top = anchor.bottom, horizontally centered (default)
|
|
9892
|
+
* - "left" → element.right = anchor.left, vertically centered
|
|
9893
|
+
* - "right" → element.left = anchor.right, vertically centered
|
|
9894
|
+
* - "top-left" → element.bottom = anchor.top, element.right = anchor.left
|
|
9895
|
+
* - "top-right" → element.bottom = anchor.top, element.left = anchor.right
|
|
9896
|
+
* - "bottom-left" → element.top = anchor.bottom, element.right = anchor.left
|
|
9897
|
+
* - "bottom-right" → element.top = anchor.bottom, element.left = anchor.right
|
|
9898
|
+
* - "center" → element centered on anchor (overlapping)
|
|
9899
|
+
*
|
|
9900
|
+
* @param {HTMLElement} element - The element to position (must be document-relative)
|
|
9901
|
+
* @param {HTMLElement} anchor - The anchor element to position against
|
|
9902
|
+
* @param {object} [options]
|
|
9903
|
+
* @param {string} [options.positionTry="bottom"] - Preferred position. Mimics CSS position-try.
|
|
9904
|
+
* If it does not fit, the logical opposite is tried automatically:
|
|
9905
|
+
* top↔bottom, left↔right, top-left↔bottom-right, top-right↔bottom-left.
|
|
9906
|
+
* The element's data-position-try attribute takes precedence over this param;
|
|
9907
|
+
* the last resolved position is persisted as data-position-current to avoid flickering.
|
|
9908
|
+
* @param {string} [options.position] - Force a specific position, skipping the fit-check.
|
|
9909
|
+
* @param {number} [options.alignToViewportEdgeWhenAnchorNearEdge=0] - Snap to viewport left
|
|
9910
|
+
* edge when anchor is within this many px of the left edge and element is wider than anchor.
|
|
9911
|
+
* @param {number} [options.minLeft=0] - Minimum left coordinate (document-relative).
|
|
9912
|
+
* @returns {{ position, left, top, width, height, anchorLeft, anchorTop, anchorRight, anchorBottom, spaceAbove, spaceBelow }}
|
|
9913
|
+
*/
|
|
9525
9914
|
const pickPositionRelativeTo = (
|
|
9526
9915
|
element,
|
|
9527
|
-
|
|
9916
|
+
anchor,
|
|
9528
9917
|
{
|
|
9529
|
-
|
|
9918
|
+
positionTry = "bottom",
|
|
9919
|
+
position,
|
|
9920
|
+
alignToViewportEdgeWhenAnchorNearEdge = 0,
|
|
9530
9921
|
minLeft = 0,
|
|
9531
|
-
positionPreference,
|
|
9532
|
-
forcePosition,
|
|
9533
9922
|
} = {},
|
|
9534
9923
|
) => {
|
|
9535
9924
|
|
|
@@ -9537,7 +9926,7 @@ const pickPositionRelativeTo = (
|
|
|
9537
9926
|
const viewportHeight = document.documentElement.clientHeight;
|
|
9538
9927
|
// Get viewport-relative positions
|
|
9539
9928
|
const elementRect = element.getBoundingClientRect();
|
|
9540
|
-
const
|
|
9929
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
9541
9930
|
const {
|
|
9542
9931
|
left: elementLeft,
|
|
9543
9932
|
right: elementRight,
|
|
@@ -9545,50 +9934,97 @@ const pickPositionRelativeTo = (
|
|
|
9545
9934
|
bottom: elementBottom,
|
|
9546
9935
|
} = elementRect;
|
|
9547
9936
|
const {
|
|
9548
|
-
left:
|
|
9549
|
-
right:
|
|
9550
|
-
top:
|
|
9551
|
-
bottom:
|
|
9552
|
-
} =
|
|
9937
|
+
left: anchorLeft,
|
|
9938
|
+
right: anchorRight,
|
|
9939
|
+
top: anchorTop,
|
|
9940
|
+
bottom: anchorBottom,
|
|
9941
|
+
} = anchorRect;
|
|
9553
9942
|
const elementWidth = elementRight - elementLeft;
|
|
9554
9943
|
const elementHeight = elementBottom - elementTop;
|
|
9555
|
-
const
|
|
9944
|
+
const anchorWidth = anchorRight - anchorLeft;
|
|
9945
|
+
const anchorHeight = anchorBottom - anchorTop;
|
|
9946
|
+
|
|
9947
|
+
// Determine the active position: position wins, then data-position-current (last resolved),
|
|
9948
|
+
// then data-position-try attribute (user preference), then positionTry param
|
|
9949
|
+
let activePosition;
|
|
9950
|
+
if (position) {
|
|
9951
|
+
activePosition = position;
|
|
9952
|
+
} else {
|
|
9953
|
+
const positionCurrentFromAttribute = element.getAttribute(
|
|
9954
|
+
"data-position-current",
|
|
9955
|
+
);
|
|
9956
|
+
const positionTryFromAttribute = element.getAttribute("data-position-try");
|
|
9957
|
+
activePosition =
|
|
9958
|
+
positionCurrentFromAttribute || positionTryFromAttribute || positionTry;
|
|
9959
|
+
}
|
|
9960
|
+
|
|
9961
|
+
const spaceAbove = anchorTop;
|
|
9962
|
+
const spaceBelow = viewportHeight - anchorBottom;
|
|
9963
|
+
|
|
9964
|
+
// Resolve vertical axis, falling back to opposite if the tried position does not fit
|
|
9965
|
+
const { isTop, isBottom, isLeft, isRight, isCenter } =
|
|
9966
|
+
decomposePosition(activePosition);
|
|
9967
|
+
const isCenterX = !isLeft && !isRight; // top / bottom / center
|
|
9968
|
+
const isCenterY = !isTop && !isBottom; // left / right / center
|
|
9969
|
+
|
|
9970
|
+
let resolvedVertical; // "top" | "bottom" | "center-y"
|
|
9971
|
+
if (isCenter || isCenterY) {
|
|
9972
|
+
resolvedVertical = "center-y";
|
|
9973
|
+
} else if (position) {
|
|
9974
|
+
resolvedVertical = isTop ? "top" : "bottom";
|
|
9975
|
+
} else if (isTop) {
|
|
9976
|
+
const minContentVisibilityRatio = 0.6;
|
|
9977
|
+
const fitsAbove = spaceAbove / elementHeight >= minContentVisibilityRatio;
|
|
9978
|
+
if (fitsAbove) {
|
|
9979
|
+
resolvedVertical = "top";
|
|
9980
|
+
} else {
|
|
9981
|
+
resolvedVertical = "bottom"; // opposite of top
|
|
9982
|
+
}
|
|
9983
|
+
} else {
|
|
9984
|
+
// isBottom
|
|
9985
|
+
const elementFitsBelow = spaceBelow >= elementHeight;
|
|
9986
|
+
if (elementFitsBelow) {
|
|
9987
|
+
resolvedVertical = "bottom";
|
|
9988
|
+
} else {
|
|
9989
|
+
resolvedVertical = "top"; // opposite of bottom
|
|
9990
|
+
}
|
|
9991
|
+
}
|
|
9556
9992
|
|
|
9557
9993
|
// Calculate horizontal position (viewport-relative)
|
|
9558
9994
|
let elementPositionLeft;
|
|
9559
9995
|
{
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
if (
|
|
9563
|
-
|
|
9564
|
-
const targetRightIsVisible = targetRight <= viewportWidth;
|
|
9565
|
-
|
|
9566
|
-
if (!targetLeftIsVisible && targetRightIsVisible) {
|
|
9567
|
-
// Target extends beyond left edge but right side is visible
|
|
9568
|
-
const viewportCenter = viewportWidth / 2;
|
|
9569
|
-
const distanceFromRightEdge = viewportWidth - targetRight;
|
|
9570
|
-
elementPositionLeft =
|
|
9571
|
-
viewportCenter - distanceFromRightEdge / 2 - elementWidth / 2;
|
|
9572
|
-
} else if (targetLeftIsVisible && !targetRightIsVisible) {
|
|
9573
|
-
// Target extends beyond right edge but left side is visible
|
|
9574
|
-
const viewportCenter = viewportWidth / 2;
|
|
9575
|
-
const distanceFromLeftEdge = -targetLeft;
|
|
9576
|
-
elementPositionLeft =
|
|
9577
|
-
viewportCenter - distanceFromLeftEdge / 2 - elementWidth / 2;
|
|
9578
|
-
} else {
|
|
9579
|
-
// Target extends beyond both edges or is fully visible (center in viewport)
|
|
9580
|
-
elementPositionLeft = viewportWidth / 2 - elementWidth / 2;
|
|
9581
|
-
}
|
|
9996
|
+
if (isLeft) {
|
|
9997
|
+
elementPositionLeft = anchorLeft - elementWidth;
|
|
9998
|
+
} else if (isRight) {
|
|
9999
|
+
elementPositionLeft = anchorRight;
|
|
9582
10000
|
} else {
|
|
9583
|
-
//
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
const
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
elementPositionLeft =
|
|
10001
|
+
// centered horizontally on anchor
|
|
10002
|
+
const anchorIsWiderThanViewport = anchorWidth > viewportWidth;
|
|
10003
|
+
if (anchorIsWiderThanViewport) {
|
|
10004
|
+
const anchorLeftIsVisible = anchorLeft >= 0;
|
|
10005
|
+
const anchorRightIsVisible = anchorRight <= viewportWidth;
|
|
10006
|
+
if (!anchorLeftIsVisible && anchorRightIsVisible) {
|
|
10007
|
+
const viewportCenter = viewportWidth / 2;
|
|
10008
|
+
const distanceFromRightEdge = viewportWidth - anchorRight;
|
|
10009
|
+
elementPositionLeft =
|
|
10010
|
+
viewportCenter - distanceFromRightEdge / 2 - elementWidth / 2;
|
|
10011
|
+
} else if (anchorLeftIsVisible && !anchorRightIsVisible) {
|
|
10012
|
+
const viewportCenter = viewportWidth / 2;
|
|
10013
|
+
const distanceFromLeftEdge = -anchorLeft;
|
|
10014
|
+
elementPositionLeft =
|
|
10015
|
+
viewportCenter - distanceFromLeftEdge / 2 - elementWidth / 2;
|
|
10016
|
+
} else {
|
|
10017
|
+
elementPositionLeft = viewportWidth / 2 - elementWidth / 2;
|
|
10018
|
+
}
|
|
10019
|
+
} else {
|
|
10020
|
+
elementPositionLeft = anchorLeft + anchorWidth / 2 - elementWidth / 2;
|
|
10021
|
+
if (alignToViewportEdgeWhenAnchorNearEdge) {
|
|
10022
|
+
const elementIsWiderThanAnchor = elementWidth > anchorWidth;
|
|
10023
|
+
const anchorIsNearLeftEdge =
|
|
10024
|
+
anchorLeft < alignToViewportEdgeWhenAnchorNearEdge;
|
|
10025
|
+
if (elementIsWiderThanAnchor && anchorIsNearLeftEdge) {
|
|
10026
|
+
elementPositionLeft = minLeft;
|
|
10027
|
+
}
|
|
9592
10028
|
}
|
|
9593
10029
|
}
|
|
9594
10030
|
}
|
|
@@ -9601,83 +10037,76 @@ const pickPositionRelativeTo = (
|
|
|
9601
10037
|
}
|
|
9602
10038
|
|
|
9603
10039
|
// Calculate vertical position (viewport-relative)
|
|
9604
|
-
let
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
if (
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
const preferredPosition = positionPreference || elementPreferredPosition;
|
|
9617
|
-
|
|
9618
|
-
if (preferredPosition) {
|
|
9619
|
-
// Element has a preferred position - try to keep it unless we really struggle
|
|
9620
|
-
const visibleRatio =
|
|
9621
|
-
preferredPosition === "above"
|
|
9622
|
-
? spaceAboveTarget / elementHeight
|
|
9623
|
-
: spaceBelowTarget / elementHeight;
|
|
9624
|
-
const canShowMinimumContent = visibleRatio >= minContentVisibilityRatio;
|
|
9625
|
-
if (canShowMinimumContent) {
|
|
9626
|
-
position = preferredPosition;
|
|
9627
|
-
break determine_position;
|
|
9628
|
-
}
|
|
9629
|
-
}
|
|
9630
|
-
// No preferred position - use original logic (prefer below, fallback to above if more space)
|
|
9631
|
-
const elementFitsBelow = spaceBelowTarget >= elementHeight;
|
|
9632
|
-
if (elementFitsBelow) {
|
|
9633
|
-
position = "below";
|
|
9634
|
-
break determine_position;
|
|
10040
|
+
let elementPositionTop;
|
|
10041
|
+
{
|
|
10042
|
+
if (resolvedVertical === "center-y") {
|
|
10043
|
+
elementPositionTop = anchorTop + anchorHeight / 2 - elementHeight / 2;
|
|
10044
|
+
} else if (resolvedVertical === "bottom") {
|
|
10045
|
+
const idealTop = anchorBottom;
|
|
10046
|
+
elementPositionTop =
|
|
10047
|
+
idealTop % 1 === 0 ? idealTop : Math.floor(idealTop) + 1;
|
|
10048
|
+
} else {
|
|
10049
|
+
// "top"
|
|
10050
|
+
const idealTop = anchorTop - elementHeight;
|
|
10051
|
+
elementPositionTop = idealTop < 0 ? 0 : idealTop;
|
|
9635
10052
|
}
|
|
9636
|
-
const hasMoreSpaceBelow = spaceBelowTarget >= spaceAboveTarget;
|
|
9637
|
-
position = hasMoreSpaceBelow ? "below" : "above";
|
|
9638
10053
|
}
|
|
9639
10054
|
|
|
9640
|
-
let
|
|
10055
|
+
let finalPosition;
|
|
9641
10056
|
{
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
10057
|
+
const vertPart = resolvedVertical === "center-y" ? "" : resolvedVertical;
|
|
10058
|
+
const horzPart = isCenterX ? "" : isLeft ? "left" : "right";
|
|
10059
|
+
if (vertPart && horzPart) {
|
|
10060
|
+
finalPosition = `${vertPart}-${horzPart}`;
|
|
10061
|
+
} else if (vertPart) {
|
|
10062
|
+
finalPosition = vertPart;
|
|
10063
|
+
} else if (horzPart) {
|
|
10064
|
+
finalPosition = horzPart;
|
|
9649
10065
|
} else {
|
|
9650
|
-
|
|
9651
|
-
const idealTopWhenAbove = targetTop - elementHeight;
|
|
9652
|
-
const minimumTopInViewport = 0;
|
|
9653
|
-
elementPositionTop =
|
|
9654
|
-
idealTopWhenAbove < minimumTopInViewport
|
|
9655
|
-
? minimumTopInViewport
|
|
9656
|
-
: idealTopWhenAbove;
|
|
10066
|
+
finalPosition = "center";
|
|
9657
10067
|
}
|
|
9658
10068
|
}
|
|
9659
10069
|
|
|
10070
|
+
// Persist the resolved position on the element so subsequent calls start from it
|
|
10071
|
+
// (avoids flickering between positions when the element is near the threshold).
|
|
10072
|
+
// position is not persisted — it is always explicit.
|
|
10073
|
+
|
|
10074
|
+
if (!position) {
|
|
10075
|
+
element.setAttribute("data-position-current", finalPosition);
|
|
10076
|
+
}
|
|
10077
|
+
|
|
9660
10078
|
// Get document scroll for final coordinate conversion
|
|
9661
10079
|
const { scrollLeft, scrollTop } = document.documentElement;
|
|
9662
10080
|
const elementDocumentLeft = elementPositionLeft + scrollLeft;
|
|
9663
10081
|
const elementDocumentTop = elementPositionTop + scrollTop;
|
|
9664
|
-
const
|
|
9665
|
-
const
|
|
9666
|
-
const
|
|
9667
|
-
const
|
|
10082
|
+
const anchorDocumentLeft = anchorLeft + scrollLeft;
|
|
10083
|
+
const anchorDocumentTop = anchorTop + scrollTop;
|
|
10084
|
+
const anchorDocumentRight = anchorRight + scrollLeft;
|
|
10085
|
+
const anchorDocumentBottom = anchorBottom + scrollTop;
|
|
9668
10086
|
|
|
9669
10087
|
return {
|
|
9670
|
-
position,
|
|
10088
|
+
position: finalPosition,
|
|
9671
10089
|
left: elementDocumentLeft,
|
|
9672
10090
|
top: elementDocumentTop,
|
|
9673
10091
|
width: elementWidth,
|
|
9674
10092
|
height: elementHeight,
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
10093
|
+
anchorLeft: anchorDocumentLeft,
|
|
10094
|
+
anchorTop: anchorDocumentTop,
|
|
10095
|
+
anchorRight: anchorDocumentRight,
|
|
10096
|
+
anchorBottom: anchorDocumentBottom,
|
|
10097
|
+
spaceAbove,
|
|
10098
|
+
spaceBelow,
|
|
10099
|
+
};
|
|
10100
|
+
};
|
|
10101
|
+
// Decompose position flags
|
|
10102
|
+
const decomposePosition = (pos) => {
|
|
10103
|
+
return {
|
|
10104
|
+
isTop: pos === "top" || pos === "top-left" || pos === "top-right",
|
|
10105
|
+
isBottom:
|
|
10106
|
+
pos === "bottom" || pos === "bottom-left" || pos === "bottom-right",
|
|
10107
|
+
isLeft: pos === "left" || pos === "top-left" || pos === "bottom-left",
|
|
10108
|
+
isRight: pos === "right" || pos === "top-right" || pos === "bottom-right",
|
|
10109
|
+
isCenter: pos === "center",
|
|
9681
10110
|
};
|
|
9682
10111
|
};
|
|
9683
10112
|
|
|
@@ -12804,4 +13233,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
|
|
|
12804
13233
|
};
|
|
12805
13234
|
};
|
|
12806
13235
|
|
|
12807
|
-
export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, canInterceptKeys, captureScrollState, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dragAfterThreshold, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findFocusable, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isSameColor, isScrollable, measureScrollbar, mergeOneStyle, mergeTwoStyles, normalizeStyles, parseStyle, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, resolveCSSColor, resolveCSSSize, resolveColorLuminance, setAttribute, setAttributes, setStyles, startDragToResizeGesture, stickyAsRelativeCoords, stringifyStyle, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
|
|
13236
|
+
export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, canInterceptKeys, captureScrollState, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dragAfterThreshold, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findFocusable, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isSameColor, isScrollable, measureScrollbar, mergeOneStyle, mergeTwoStyles, normalizeStyles, parseStyle, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, setAttribute, setAttributes, setStyles, startDragToResizeGesture, stickyAsRelativeCoords, stringifyStyle, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/dom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DOM utilities for writing frontend code",
|
|
6
6
|
"repository": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@jsenv/navi": "../navi",
|
|
41
41
|
"@jsenv/snapshot": "../../tooling/snapshot",
|
|
42
42
|
"@preact/signals": "2.9.0",
|
|
43
|
-
"preact": "11.0.0-beta.
|
|
43
|
+
"preact": "11.0.0-beta.1"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|