@jsenv/dom 0.15.0 → 0.17.0
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 +778 -173
- package/package.json +3 -3
package/dist/jsenv_dom.js
CHANGED
|
@@ -5550,7 +5550,22 @@ const performTabNavigation = (
|
|
|
5550
5550
|
if (hasNegativeTabIndex(element)) {
|
|
5551
5551
|
return false;
|
|
5552
5552
|
}
|
|
5553
|
-
|
|
5553
|
+
if (!elementIsFocusable(element, { excludeAriaHidden })) {
|
|
5554
|
+
return false;
|
|
5555
|
+
}
|
|
5556
|
+
// Native radio-group semantics: within a named radio group only ONE radio
|
|
5557
|
+
// is a Tab stop — the checked one, or the first focusable one when none is
|
|
5558
|
+
// checked. The rest are reachable with arrow keys, not Tab. Without this,
|
|
5559
|
+
// tabbing into a group would land on its first radio instead of its checked
|
|
5560
|
+
// value (e.g. tabbing between two wheels of a WheelGroup).
|
|
5561
|
+
if (
|
|
5562
|
+
element.matches?.('input[type="radio"]') &&
|
|
5563
|
+
element.name &&
|
|
5564
|
+
!radioIsGroupTabStop(element)
|
|
5565
|
+
) {
|
|
5566
|
+
return false;
|
|
5567
|
+
}
|
|
5568
|
+
return true;
|
|
5554
5569
|
};
|
|
5555
5570
|
|
|
5556
5571
|
// A focus group "owns" the activeElement when activeElement is inside it.
|
|
@@ -5701,6 +5716,43 @@ const performTabNavigation = (
|
|
|
5701
5716
|
}
|
|
5702
5717
|
};
|
|
5703
5718
|
|
|
5719
|
+
// Whether a radio is the single Tab stop of its native radio group (checked, or
|
|
5720
|
+
// the first enabled radio when none is checked). Mirrors how the browser puts
|
|
5721
|
+
// only one radio of a group in the Tab order.
|
|
5722
|
+
const radioIsGroupTabStop = (radio) => {
|
|
5723
|
+
const scope = radio.form || radio.getRootNode();
|
|
5724
|
+
if (!scope || !scope.querySelectorAll) {
|
|
5725
|
+
return true;
|
|
5726
|
+
}
|
|
5727
|
+
const sameName = scope.querySelectorAll(
|
|
5728
|
+
`input[type="radio"][name="${CSS.escape(radio.name)}"]`,
|
|
5729
|
+
);
|
|
5730
|
+
const radioForm = radio.form || null;
|
|
5731
|
+
let checked = null;
|
|
5732
|
+
let firstEnabled = null;
|
|
5733
|
+
let groupSize = 0;
|
|
5734
|
+
for (const candidate of sameName) {
|
|
5735
|
+
// Radios only form one group when they share the same form owner.
|
|
5736
|
+
if ((candidate.form || null) !== radioForm) {
|
|
5737
|
+
continue;
|
|
5738
|
+
}
|
|
5739
|
+
groupSize++;
|
|
5740
|
+
if (candidate.disabled) {
|
|
5741
|
+
continue;
|
|
5742
|
+
}
|
|
5743
|
+
if (!firstEnabled) {
|
|
5744
|
+
firstEnabled = candidate;
|
|
5745
|
+
}
|
|
5746
|
+
if (candidate.checked && !checked) {
|
|
5747
|
+
checked = candidate;
|
|
5748
|
+
}
|
|
5749
|
+
}
|
|
5750
|
+
if (groupSize <= 1) {
|
|
5751
|
+
return true;
|
|
5752
|
+
}
|
|
5753
|
+
return radio === (checked || firstEnabled);
|
|
5754
|
+
};
|
|
5755
|
+
|
|
5704
5756
|
const isTabEvent$1 = (event) => event.key === "Tab" || event.keyCode === 9;
|
|
5705
5757
|
|
|
5706
5758
|
const hasNegativeTabIndex = (element) => {
|
|
@@ -6323,7 +6375,10 @@ const getScrollContainer = (arg, { includeHidden } = {}) => {
|
|
|
6323
6375
|
}
|
|
6324
6376
|
return null;
|
|
6325
6377
|
}
|
|
6326
|
-
if (element.hasAttribute("popover")
|
|
6378
|
+
if (element.hasAttribute("popover")) {
|
|
6379
|
+
return getScrollingElement(element.ownerDocument);
|
|
6380
|
+
}
|
|
6381
|
+
if (element.tagName === "DIALOG" && element.matches(":modal")) {
|
|
6327
6382
|
return getScrollingElement(element.ownerDocument);
|
|
6328
6383
|
}
|
|
6329
6384
|
const position = getStyle(element, "position");
|
|
@@ -7173,49 +7228,61 @@ const getPaddingSizes = (element) => {
|
|
|
7173
7228
|
*/
|
|
7174
7229
|
const trapScrollInside = (element) => {
|
|
7175
7230
|
const cleanupCallbackSet = new Set();
|
|
7176
|
-
|
|
7231
|
+
|
|
7232
|
+
// Collect every element to lock first (preceding scrollable siblings + all
|
|
7233
|
+
// ancestor scroll containers).
|
|
7234
|
+
const elementsToLock = [];
|
|
7235
|
+
let previous = element.previousSibling;
|
|
7236
|
+
while (previous) {
|
|
7237
|
+
if (previous.nodeType === 1 && isScrollable(previous)) {
|
|
7238
|
+
elementsToLock.push(previous);
|
|
7239
|
+
}
|
|
7240
|
+
previous = previous.previousSibling;
|
|
7241
|
+
}
|
|
7242
|
+
for (const selfOrAncestorScroll of getSelfAndAncestorScrolls(element)) {
|
|
7243
|
+
elementsToLock.push(selfOrAncestorScroll.scrollContainer);
|
|
7244
|
+
}
|
|
7245
|
+
|
|
7246
|
+
// Phase 1 — MEASURE. Batch every layout/style read (scrollTop, scrollbar
|
|
7247
|
+
// size, padding) before any style write, so the layout that showModal
|
|
7248
|
+
// invalidated is recomputed once rather than thrashing between each write and
|
|
7249
|
+
// the next read. (measureScrollbar still forces its own reflow per element via
|
|
7250
|
+
// its probe node — that one is inherent.)
|
|
7251
|
+
const plans = elementsToLock.map((el) => {
|
|
7177
7252
|
const savedScrollTop = el.scrollTop;
|
|
7178
7253
|
const savedScrollLeft = el.scrollLeft;
|
|
7179
7254
|
const scrollbarGutter = getStyle(el, "scrollbar-gutter");
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
el.scrollLeft = savedScrollLeft;
|
|
7189
|
-
});
|
|
7190
|
-
return;
|
|
7255
|
+
if (scrollbarGutter && scrollbarGutter !== "auto") {
|
|
7256
|
+
// The element manages its own gutter — just hide overflow, no padding.
|
|
7257
|
+
return {
|
|
7258
|
+
el,
|
|
7259
|
+
savedScrollTop,
|
|
7260
|
+
savedScrollLeft,
|
|
7261
|
+
styles: { overflow: "hidden" },
|
|
7262
|
+
};
|
|
7191
7263
|
}
|
|
7192
7264
|
const [scrollbarWidth, scrollbarHeight] = measureScrollbar(el);
|
|
7193
7265
|
const { right, bottom } = getPaddingSizes(el);
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7266
|
+
return {
|
|
7267
|
+
el,
|
|
7268
|
+
savedScrollTop,
|
|
7269
|
+
savedScrollLeft,
|
|
7270
|
+
styles: {
|
|
7271
|
+
"padding-right": `${right + scrollbarWidth}px`,
|
|
7272
|
+
"padding-bottom": `${bottom + scrollbarHeight}px`,
|
|
7273
|
+
"overflow": "hidden",
|
|
7274
|
+
},
|
|
7275
|
+
};
|
|
7276
|
+
});
|
|
7277
|
+
|
|
7278
|
+
// Phase 2 — MUTATE. All style writes together.
|
|
7279
|
+
for (const { el, savedScrollTop, savedScrollLeft, styles } of plans) {
|
|
7280
|
+
const removeScrollLockStyles = setStyles(el, styles);
|
|
7199
7281
|
cleanupCallbackSet.add(() => {
|
|
7200
7282
|
removeScrollLockStyles();
|
|
7201
7283
|
el.scrollTop = savedScrollTop;
|
|
7202
7284
|
el.scrollLeft = savedScrollLeft;
|
|
7203
7285
|
});
|
|
7204
|
-
};
|
|
7205
|
-
let previous = element.previousSibling;
|
|
7206
|
-
while (previous) {
|
|
7207
|
-
if (previous.nodeType === 1) {
|
|
7208
|
-
if (isScrollable(previous)) {
|
|
7209
|
-
lockScroll(previous);
|
|
7210
|
-
}
|
|
7211
|
-
}
|
|
7212
|
-
previous = previous.previousSibling;
|
|
7213
|
-
}
|
|
7214
|
-
|
|
7215
|
-
const selfAndAncestorScrolls = getSelfAndAncestorScrolls(element);
|
|
7216
|
-
for (const selfOrAncestorScroll of selfAndAncestorScrolls) {
|
|
7217
|
-
const elementToScrollLock = selfOrAncestorScroll.scrollContainer;
|
|
7218
|
-
lockScroll(elementToScrollLock);
|
|
7219
7286
|
}
|
|
7220
7287
|
|
|
7221
7288
|
return () => {
|
|
@@ -7363,6 +7430,74 @@ const applyWheelScrollThrough = (element, wheelEvent) => {
|
|
|
7363
7430
|
});
|
|
7364
7431
|
};
|
|
7365
7432
|
|
|
7433
|
+
/**
|
|
7434
|
+
* The element `element` is genuinely `position: absolute`/`fixed` relative
|
|
7435
|
+
* to: its own nearest positioned ancestor (walking up the DOM tree), or
|
|
7436
|
+
* `document.documentElement` (the viewport) if none is found.
|
|
7437
|
+
*
|
|
7438
|
+
* Also aware of `element` itself being promoted to the top layer: a
|
|
7439
|
+
* `<dialog>` actually shown modally (`showModal()`, matches `:modal` — a
|
|
7440
|
+
* `.show()`'d, non-modal dialog does NOT match and is positioned like any
|
|
7441
|
+
* other in-flow element instead, walked up normally below), or *any*
|
|
7442
|
+
* `[popover]` element, always uses the initial containing block (the
|
|
7443
|
+
* viewport) regardless of its own `position` or DOM ancestry — walking up
|
|
7444
|
+
* its own parent chain (what the rest of this function does) would give
|
|
7445
|
+
* the wrong answer for these two specifically, since their real DOM
|
|
7446
|
+
* position becomes irrelevant to their own containing block the moment
|
|
7447
|
+
* they're actually promoted. Checked via the `popover` attribute itself,
|
|
7448
|
+
* not the live `:popover-open` state — unlike `<dialog>`, a `[popover]`
|
|
7449
|
+
* element has no "local" mode: it's always top-layer-bound once shown,
|
|
7450
|
+
* regardless of whether it happens to be open right this moment, so the
|
|
7451
|
+
* static attribute alone is enough (and correct even when called just
|
|
7452
|
+
* before `showPopover()` actually runs, when `:popover-open` isn't true
|
|
7453
|
+
* yet).
|
|
7454
|
+
*
|
|
7455
|
+
* `document.documentElement` (not `document.body`, not `null`) is this
|
|
7456
|
+
* function's own "no real container — use the viewport" sentinel:
|
|
7457
|
+
* `documentElement` is the actual initial containing block, so the walk
|
|
7458
|
+
* below stops there without testing its own `position` (there's nothing
|
|
7459
|
+
* beyond it to fall back to anyway) — unlike the previous version of this
|
|
7460
|
+
* function, which stopped one level too early, at `document.body`, without
|
|
7461
|
+
* ever testing *its* `position` either (a `position: relative` body, for
|
|
7462
|
+
* instance, would have been silently skipped). Returning `documentElement`
|
|
7463
|
+
* instead of `null` also means no special-casing is needed by callers that
|
|
7464
|
+
* already compare a resolved container against `document.documentElement`
|
|
7465
|
+
* (see e.g. visible_rect.js's own `hasRealContainer` check).
|
|
7466
|
+
*/
|
|
7467
|
+
const getPositionedParent = (element) => {
|
|
7468
|
+
const isPromotedToTopLayer =
|
|
7469
|
+
(element.tagName === "DIALOG" && element.matches(":modal")) ||
|
|
7470
|
+
element.hasAttribute("popover");
|
|
7471
|
+
if (isPromotedToTopLayer) {
|
|
7472
|
+
return document.documentElement;
|
|
7473
|
+
}
|
|
7474
|
+
let parent = element.parentElement;
|
|
7475
|
+
while (parent && parent !== document.documentElement) {
|
|
7476
|
+
const position = window.getComputedStyle(parent).position;
|
|
7477
|
+
if (
|
|
7478
|
+
position === "relative" ||
|
|
7479
|
+
position === "absolute" ||
|
|
7480
|
+
position === "fixed"
|
|
7481
|
+
) {
|
|
7482
|
+
return parent;
|
|
7483
|
+
}
|
|
7484
|
+
parent = parent.parentElement;
|
|
7485
|
+
}
|
|
7486
|
+
return document.documentElement;
|
|
7487
|
+
};
|
|
7488
|
+
|
|
7489
|
+
/**
|
|
7490
|
+
* Walks `element` and its ancestors (stopping at, but not including,
|
|
7491
|
+
* `document.documentElement`) looking for the first one whose *computed*
|
|
7492
|
+
* `position` is `fixed` — i.e. pinned to the viewport, ignoring document
|
|
7493
|
+
* scroll, regardless of what `element` itself is positioned relative to.
|
|
7494
|
+
*
|
|
7495
|
+
* @param {Element} element
|
|
7496
|
+
* @returns {[left: number, top: number] | null} The fixed ancestor's own
|
|
7497
|
+
* viewport-relative `getBoundingClientRect()` origin, or `null` if neither
|
|
7498
|
+
* `element` nor any ancestor is fixed (i.e. `element` genuinely scrolls
|
|
7499
|
+
* with the document).
|
|
7500
|
+
*/
|
|
7366
7501
|
const findSelfOrAncestorFixedPosition = (element) => {
|
|
7367
7502
|
let current = element;
|
|
7368
7503
|
while (true) {
|
|
@@ -7460,14 +7595,18 @@ const createDragElementPositioner = (
|
|
|
7460
7595
|
let scrollableTop;
|
|
7461
7596
|
let convertScrollablePosition;
|
|
7462
7597
|
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7598
|
+
// getPositionedParent, not raw .offsetParent — offsetParent is null for a
|
|
7599
|
+
// position: fixed element, and also for one promoted to the top layer
|
|
7600
|
+
// (e.g. a <dialog>/[popover] being dragged by its own handle), which
|
|
7601
|
+
// crashes the fixed-position lookup below (findSelfOrAncestorFixedPosition
|
|
7602
|
+
// assumes a real starting element, not null). getPositionedParent never
|
|
7603
|
+
// returns null (document.documentElement instead — see its own doc).
|
|
7604
|
+
const positionedParent = getPositionedParent(elementToMove || element);
|
|
7466
7605
|
const scrollContainer = getScrollContainer(element);
|
|
7467
7606
|
const [getPositionOffsets, getScrollOffsets] = createGetOffsets({
|
|
7468
7607
|
positionedParent,
|
|
7469
7608
|
referencePositionedParent: referenceElement
|
|
7470
|
-
? referenceElement
|
|
7609
|
+
? getPositionedParent(referenceElement)
|
|
7471
7610
|
: positionedParent,
|
|
7472
7611
|
scrollContainer,
|
|
7473
7612
|
referenceScrollContainer: referenceElement
|
|
@@ -7721,7 +7860,7 @@ const isOverlayOf = (element, potentialTarget) => {
|
|
|
7721
7860
|
if (overlayTarget === potentialTarget) {
|
|
7722
7861
|
return true;
|
|
7723
7862
|
}
|
|
7724
|
-
const overlayTargetPositionedParent = overlayTarget
|
|
7863
|
+
const overlayTargetPositionedParent = getPositionedParent(overlayTarget);
|
|
7725
7864
|
if (overlayTargetPositionedParent === potentialTarget) {
|
|
7726
7865
|
return true;
|
|
7727
7866
|
}
|
|
@@ -10864,42 +11003,160 @@ const getResizeDirection = (element) => {
|
|
|
10864
11003
|
return { x, y };
|
|
10865
11004
|
};
|
|
10866
11005
|
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
parent = parent.parentElement;
|
|
11006
|
+
// Shared by navi's own use_displayed_layout_effect.js (rich "navi_displayed"
|
|
11007
|
+
// CustomEvent, open transitions only) and visible_rect.js (needs both
|
|
11008
|
+
// directions: hide when a container closes, recheck when it reopens) — the
|
|
11009
|
+
// selector/open-detection/timing primitives are identical for both, only
|
|
11010
|
+
// what each does with a transition differs.
|
|
11011
|
+
const ANCESTOR_OPEN_SELECTOR = "dialog, details, [popover], [aria-expanded]";
|
|
11012
|
+
|
|
11013
|
+
const closestOpenableAncestor = (element) => {
|
|
11014
|
+
const parentElement = element.parentElement;
|
|
11015
|
+
if (!parentElement) {
|
|
11016
|
+
return null;
|
|
10879
11017
|
}
|
|
10880
|
-
|
|
11018
|
+
if (!parentElement.closest) {
|
|
11019
|
+
return null;
|
|
11020
|
+
}
|
|
11021
|
+
return parentElement.closest(ANCESTOR_OPEN_SELECTOR);
|
|
11022
|
+
};
|
|
11023
|
+
|
|
11024
|
+
const isAncestorOpen = (ancestor) => {
|
|
11025
|
+
if (ancestor.tagName === "DIALOG" || ancestor.hasAttribute("popover")) {
|
|
11026
|
+
return ancestor.matches(":popover-open, [open]");
|
|
11027
|
+
}
|
|
11028
|
+
if (ancestor.tagName === "DETAILS") {
|
|
11029
|
+
return ancestor.open;
|
|
11030
|
+
}
|
|
11031
|
+
if (ancestor.hasAttribute("aria-expanded")) {
|
|
11032
|
+
return ancestor.getAttribute("aria-expanded") === "true";
|
|
11033
|
+
}
|
|
11034
|
+
return true;
|
|
11035
|
+
};
|
|
11036
|
+
|
|
11037
|
+
const getAncestorOpenType = (ancestor) => {
|
|
11038
|
+
if (ancestor === document) {
|
|
11039
|
+
return "document";
|
|
11040
|
+
}
|
|
11041
|
+
if (ancestor.tagName === "DIALOG") {
|
|
11042
|
+
return "dialog";
|
|
11043
|
+
}
|
|
11044
|
+
if (ancestor.hasAttribute("popover")) {
|
|
11045
|
+
return "popover";
|
|
11046
|
+
}
|
|
11047
|
+
if (ancestor.tagName === "DETAILS") {
|
|
11048
|
+
return "details";
|
|
11049
|
+
}
|
|
11050
|
+
if (ancestor.hasAttribute("aria-expanded")) {
|
|
11051
|
+
return `${ancestor.tagName}[aria-expanded]`;
|
|
11052
|
+
}
|
|
11053
|
+
return `${ancestor.tagName}`;
|
|
10881
11054
|
};
|
|
10882
11055
|
|
|
10883
11056
|
/**
|
|
10884
|
-
*
|
|
10885
|
-
*
|
|
10886
|
-
*
|
|
10887
|
-
*
|
|
10888
|
-
*
|
|
10889
|
-
*
|
|
10890
|
-
*
|
|
10891
|
-
*
|
|
10892
|
-
*
|
|
10893
|
-
*
|
|
10894
|
-
*
|
|
10895
|
-
*
|
|
10896
|
-
*
|
|
11057
|
+
* Notifies `callback({ isOpen, ancestor, ancestorType, toggleEvent })` the
|
|
11058
|
+
* moment `ancestor`'s open state changes, in either direction — timed to
|
|
11059
|
+
* land strictly before the browser's next paint, so a caller reacting to it
|
|
11060
|
+
* (measurement, visibility tracking, layout) never flashes the stale state
|
|
11061
|
+
* first. Plain object, not a CustomEvent — there's no real DOM event behind
|
|
11062
|
+
* most of these transitions (see `toggleEvent` below), so wrapping the info
|
|
11063
|
+
* in one would mostly be manufacturing a fake event for no benefit.
|
|
11064
|
+
*
|
|
11065
|
+
* We deliberately do NOT use the native `toggle` event as the primary
|
|
11066
|
+
* signal, even though every <dialog>/<details>/[popover] fires one: per the
|
|
11067
|
+
* WHATWG spec it's dispatched via a *queued task* ("queue a popover toggle
|
|
11068
|
+
* event task"), not synchronously and not as a microtask. The element's
|
|
11069
|
+
* shown state itself (showPopover()/showModal()) still flips synchronously,
|
|
11070
|
+
* so the browser can — and does — paint it in its default, uncorrected
|
|
11071
|
+
* state before that queued task ever runs. Relying on `toggle` alone means
|
|
11072
|
+
* a reaction to it always arrives one paint late.
|
|
11073
|
+
*
|
|
11074
|
+
* Instead we watch `open`/`aria-expanded` via MutationObserver:
|
|
11075
|
+
* - <dialog>/<details> reflect `open` themselves, natively, synchronously.
|
|
11076
|
+
* - navi's own Popover.jsx sets `aria-expanded` synchronously in the same
|
|
11077
|
+
* call stack as showPopover() (see popover.jsx's own aria-expanded
|
|
11078
|
+
* comments) — not part of any web standard, just that library's own
|
|
11079
|
+
* convention, but reliable for anything built through it.
|
|
11080
|
+
* MutationObserver callbacks run as a microtask, strictly before paint —
|
|
11081
|
+
* exactly the timing needed, no ambiguity. `toggleEvent` is `undefined` on
|
|
11082
|
+
* this path (there's no native event to report — a mutation record isn't
|
|
11083
|
+
* one).
|
|
11084
|
+
*
|
|
11085
|
+
* The `toggle` listener is kept as a fallback, attached ONLY where the
|
|
11086
|
+
* MutationObserver above has no chance of ever firing: a bare [popover]
|
|
11087
|
+
* element with no `aria-expanded` of its own — i.e. one not built through
|
|
11088
|
+
* navi's own Popover.jsx (the only thing that reliably sets it). That's the
|
|
11089
|
+
* one case with no other synchronously-observable signal at all. It still
|
|
11090
|
+
* arrives a paint late, but a late correction beats none. `toggleEvent` is
|
|
11091
|
+
* the real `toggle` event on this path.
|
|
11092
|
+
*
|
|
11093
|
+
* @param {Element} ancestor
|
|
11094
|
+
* @param {(info: { isOpen: boolean, ancestor: Element, ancestorType: string, toggleEvent: Event | undefined }) => void} callback
|
|
11095
|
+
* @returns {() => void} cleanup — removes the observer/listener
|
|
10897
11096
|
*/
|
|
10898
|
-
const
|
|
10899
|
-
|
|
10900
|
-
|
|
11097
|
+
const observeAncestorOpenState = (ancestor, callback) => {
|
|
11098
|
+
const ancestorType = getAncestorOpenType(ancestor);
|
|
11099
|
+
const needsToggleFallback =
|
|
11100
|
+
ancestor.hasAttribute("popover") && !ancestor.hasAttribute("aria-expanded");
|
|
11101
|
+
if (needsToggleFallback) {
|
|
11102
|
+
const onToggle = (toggleEvent) => {
|
|
11103
|
+
callback({
|
|
11104
|
+
isOpen: isAncestorOpen(ancestor),
|
|
11105
|
+
ancestor,
|
|
11106
|
+
ancestorType,
|
|
11107
|
+
toggleEvent,
|
|
11108
|
+
});
|
|
11109
|
+
};
|
|
11110
|
+
ancestor.addEventListener("toggle", onToggle);
|
|
11111
|
+
return () => {
|
|
11112
|
+
ancestor.removeEventListener("toggle", onToggle);
|
|
11113
|
+
};
|
|
10901
11114
|
}
|
|
10902
|
-
|
|
11115
|
+
|
|
11116
|
+
// Edge-triggered on purpose: some consumers (e.g. Popover.jsx) set
|
|
11117
|
+
// aria-expanded both imperatively (in their own openEffect, for precise
|
|
11118
|
+
// ordering relative to forced reflows/transitions) AND declaratively via a
|
|
11119
|
+
// JSX prop derived from the same open state — the latter is a deliberate
|
|
11120
|
+
// "always reflect current truth" prop, but Preact diffs against its own
|
|
11121
|
+
// previous *rendered* value, not the live DOM, so any later re-render that
|
|
11122
|
+
// happens to occur while already open re-applies the same "true" value as
|
|
11123
|
+
// a genuinely new attribute mutation. Tracking wasOpen here collapses that
|
|
11124
|
+
// redundant open→open (or close→close) mutation instead of notifying
|
|
11125
|
+
// callback a second time for the same state.
|
|
11126
|
+
let wasOpen = isAncestorOpen(ancestor);
|
|
11127
|
+
const observer = new MutationObserver(() => {
|
|
11128
|
+
const isOpen = isAncestorOpen(ancestor);
|
|
11129
|
+
if (isOpen === wasOpen) {
|
|
11130
|
+
return;
|
|
11131
|
+
}
|
|
11132
|
+
wasOpen = isOpen;
|
|
11133
|
+
callback({
|
|
11134
|
+
isOpen,
|
|
11135
|
+
ancestor,
|
|
11136
|
+
ancestorType,
|
|
11137
|
+
toggleEvent: undefined,
|
|
11138
|
+
});
|
|
11139
|
+
});
|
|
11140
|
+
observer.observe(ancestor, {
|
|
11141
|
+
attributes: true,
|
|
11142
|
+
attributeFilter: ["open", "aria-expanded"],
|
|
11143
|
+
});
|
|
11144
|
+
return () => {
|
|
11145
|
+
observer.disconnect();
|
|
11146
|
+
};
|
|
11147
|
+
};
|
|
11148
|
+
|
|
11149
|
+
const onAncestorReopen = (el, callback) => {
|
|
11150
|
+
const nearestOpenableAncestor = closestOpenableAncestor(el);
|
|
11151
|
+
if (!nearestOpenableAncestor) {
|
|
11152
|
+
return () => {};
|
|
11153
|
+
}
|
|
11154
|
+
return observeAncestorOpenState(nearestOpenableAncestor, ({ isOpen }) => {
|
|
11155
|
+
if (!isOpen) {
|
|
11156
|
+
return;
|
|
11157
|
+
}
|
|
11158
|
+
callback();
|
|
11159
|
+
});
|
|
10903
11160
|
};
|
|
10904
11161
|
|
|
10905
11162
|
const getHeight = (element) => {
|
|
@@ -11296,10 +11553,10 @@ const MIN_CONTENT_VISIBILITY_RATIO = 0.6;
|
|
|
11296
11553
|
* Is 0 when ancestorClosed is true.
|
|
11297
11554
|
*
|
|
11298
11555
|
* @typedef {Object} VisibleRectInfo
|
|
11299
|
-
* @property {Event} event
|
|
11300
|
-
* @property {number} width
|
|
11301
|
-
* @property {number} height
|
|
11302
|
-
* @property {boolean} ancestorClosed
|
|
11556
|
+
* @property {Event} event - The DOM event (or CustomEvent) that triggered the check.
|
|
11557
|
+
* @property {number} width - Raw getBoundingClientRect() width of the element.
|
|
11558
|
+
* @property {number} height - Raw getBoundingClientRect() height of the element.
|
|
11559
|
+
* @property {boolean} ancestorClosed - True when a popover, dialog, or details ancestor is
|
|
11303
11560
|
* currently closed so the element is not rendered. All visibleRect values are 0 in that case.
|
|
11304
11561
|
* update() is called immediately on ancestor close and again (with false) on reopen.
|
|
11305
11562
|
*
|
|
@@ -11307,6 +11564,7 @@ const MIN_CONTENT_VISIBILITY_RATIO = 0.6;
|
|
|
11307
11564
|
* - Once synchronously on initialization (event.type = "initialization")
|
|
11308
11565
|
* - On document/container scroll, window resize, element resize, intersection changes, touch move
|
|
11309
11566
|
* - Immediately when an ancestor popover/dialog/details opens or closes
|
|
11567
|
+
* - Immediately when an ancestor popover/dialog starts or stops repositioning itself
|
|
11310
11568
|
*
|
|
11311
11569
|
* A bit like https://tetherjs.dev/ but different
|
|
11312
11570
|
*/
|
|
@@ -11332,8 +11590,72 @@ const visibleRectEffect = (
|
|
|
11332
11590
|
let lastMeasuredWidth;
|
|
11333
11591
|
let lastMeasuredHeight;
|
|
11334
11592
|
let ancestorClosedCount = 0;
|
|
11593
|
+
// Every ResizeObserver this effect owns (its own element-resize watcher
|
|
11594
|
+
// below, plus one per observeSize() call) unobserves itself the moment an
|
|
11595
|
+
// ancestor closes, reobserving once it reopens (see on_ancestor_events) —
|
|
11596
|
+
// closing a dialog/popover containing several watched elements can make
|
|
11597
|
+
// them all collapse to zero size in the same reflow, which is what trips
|
|
11598
|
+
// the browser's "ResizeObserver loop completed with undelivered
|
|
11599
|
+
// notifications" warning. Proactively unobserving avoids generating those
|
|
11600
|
+
// notifications instead of just reacting differently to them.
|
|
11601
|
+
// Set while an ancestor is itself mid-repositioning — on_ancestor_events'
|
|
11602
|
+
// own onNaviPositionTransition already drives this element's position
|
|
11603
|
+
// every frame for that duration, more accurately than the direct
|
|
11604
|
+
// window/visualViewport resize reaction below (on_resize) could. Gates
|
|
11605
|
+
// that reaction so it doesn't also fire mid-transition and animate its
|
|
11606
|
+
// own competing move toward a target computed from the anchor's still
|
|
11607
|
+
// mid-flight rect, racing the frame-by-frame follow loop.
|
|
11608
|
+
let ancestorRepositioningCount = 0;
|
|
11609
|
+
// check() runs on every scroll/resize/frame of an ancestor's own
|
|
11610
|
+
// transition, but plenty of those land on an identical result — skip
|
|
11611
|
+
// calling update() again when neither snapshot changed. Two snapshots,
|
|
11612
|
+
// not one, because pickPositionRelativeTo depends on both separately:
|
|
11613
|
+
// - lastVisibleRect: left/top/width/height, plus visibilityRatio (which
|
|
11614
|
+
// can change on its own — see its own ratio formula further down —
|
|
11615
|
+
// without any of the other four moving).
|
|
11616
|
+
// - lastViewportRect: not part of visibleRect at all, but an on-screen
|
|
11617
|
+
// keyboard opening/closing can shrink the viewport without moving
|
|
11618
|
+
// this element's own visibleRect by a single pixel, and
|
|
11619
|
+
// pickPositionRelativeTo's available space depends on it too.
|
|
11620
|
+
let lastVisibleRect = null;
|
|
11621
|
+
let lastViewportRect = null;
|
|
11622
|
+
let resizeWatchingPaused = false;
|
|
11623
|
+
const [publishResizeWatchingPausedChange, onResizeWatchingPausedChange] =
|
|
11624
|
+
createPubSub();
|
|
11625
|
+
const pauseResizeWatching = () => {
|
|
11626
|
+
if (resizeWatchingPaused) {
|
|
11627
|
+
return;
|
|
11628
|
+
}
|
|
11629
|
+
resizeWatchingPaused = true;
|
|
11630
|
+
publishResizeWatchingPausedChange(true);
|
|
11631
|
+
};
|
|
11632
|
+
const resumeResizeWatching = () => {
|
|
11633
|
+
if (!resizeWatchingPaused) {
|
|
11634
|
+
return;
|
|
11635
|
+
}
|
|
11636
|
+
resizeWatchingPaused = false;
|
|
11637
|
+
publishResizeWatchingPausedChange(false);
|
|
11638
|
+
};
|
|
11335
11639
|
const check = (event) => {
|
|
11336
11640
|
|
|
11641
|
+
// visualViewport, not window.innerWidth/Height: the layout viewport
|
|
11642
|
+
// doesn't shrink when the on-screen keyboard opens (same reasoning as
|
|
11643
|
+
// pickPositionRelativeTo's own identical choice). offsetLeft/Top matter
|
|
11644
|
+
// too, for pinch-zoom/pan. Computed here regardless of scroll container
|
|
11645
|
+
// (not just where the non-document branch below needs it) because a
|
|
11646
|
+
// keyboard opening can change pickPositionRelativeTo's available space
|
|
11647
|
+
// without moving this element's own visibleRect at all — see
|
|
11648
|
+
// viewportRectChanged further down.
|
|
11649
|
+
const visualViewport = window.visualViewport;
|
|
11650
|
+
const viewportWidth = visualViewport
|
|
11651
|
+
? visualViewport.width
|
|
11652
|
+
: window.innerWidth;
|
|
11653
|
+
const viewportHeight = visualViewport
|
|
11654
|
+
? visualViewport.height
|
|
11655
|
+
: window.innerHeight;
|
|
11656
|
+
const viewportOffsetLeft = visualViewport ? visualViewport.offsetLeft : 0;
|
|
11657
|
+
const viewportOffsetTop = visualViewport ? visualViewport.offsetTop : 0;
|
|
11658
|
+
|
|
11337
11659
|
// 1. Calculate element position relative to scrollable parent
|
|
11338
11660
|
const { scrollLeft, scrollTop } = scrollContainer;
|
|
11339
11661
|
const visibleAreaLeft = scrollLeft;
|
|
@@ -11434,22 +11756,26 @@ const visibleRectEffect = (
|
|
|
11434
11756
|
if (scrollContainerIsDocument) {
|
|
11435
11757
|
visibilityRatio = (widthVisible * heightVisible) / (width * height);
|
|
11436
11758
|
} else {
|
|
11437
|
-
// widthVisible/heightVisible are already clipped to the scroll
|
|
11438
|
-
// Now clip their viewport-relative counterparts against
|
|
11439
|
-
|
|
11440
|
-
|
|
11759
|
+
// widthVisible/heightVisible are already clipped to the scroll
|
|
11760
|
+
// container. Now clip their viewport-relative counterparts against
|
|
11761
|
+
// the viewport (viewportWidth/Height/OffsetLeft/OffsetTop computed
|
|
11762
|
+
// once, at the top of check() — see their own comment there).
|
|
11441
11763
|
// Container-clipped visible rect in viewport coordinates
|
|
11442
11764
|
const visibleLeft = overlayLeft;
|
|
11443
11765
|
const visibleTop = overlayTop;
|
|
11444
11766
|
const visibleRight = overlayLeft + widthVisible;
|
|
11445
11767
|
const visibleBottom = overlayTop + heightVisible;
|
|
11446
11768
|
// Intersect with viewport
|
|
11447
|
-
const clippedLeft =
|
|
11448
|
-
|
|
11769
|
+
const clippedLeft =
|
|
11770
|
+
visibleLeft < viewportOffsetLeft ? viewportOffsetLeft : visibleLeft;
|
|
11771
|
+
const clippedTop =
|
|
11772
|
+
visibleTop < viewportOffsetTop ? viewportOffsetTop : visibleTop;
|
|
11773
|
+
const viewportRight = viewportOffsetLeft + viewportWidth;
|
|
11774
|
+
const viewportBottom = viewportOffsetTop + viewportHeight;
|
|
11449
11775
|
const clippedRight =
|
|
11450
|
-
visibleRight >
|
|
11776
|
+
visibleRight > viewportRight ? viewportRight : visibleRight;
|
|
11451
11777
|
const clippedBottom =
|
|
11452
|
-
visibleBottom >
|
|
11778
|
+
visibleBottom > viewportBottom ? viewportBottom : visibleBottom;
|
|
11453
11779
|
const clippedWidth =
|
|
11454
11780
|
clippedRight > clippedLeft ? clippedRight - clippedLeft : 0;
|
|
11455
11781
|
const clippedHeight =
|
|
@@ -11466,12 +11792,49 @@ const visibleRectEffect = (
|
|
|
11466
11792
|
height: heightVisible,
|
|
11467
11793
|
visibilityRatio,
|
|
11468
11794
|
};
|
|
11469
|
-
|
|
11470
|
-
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11795
|
+
// Not part of visibleRect itself, tracked only so viewportRectChanged
|
|
11796
|
+
// below can catch a keyboard opening/closing even when it doesn't move
|
|
11797
|
+
// this element's own visibleRect.
|
|
11798
|
+
const viewportRect = {
|
|
11799
|
+
viewportWidth,
|
|
11800
|
+
viewportHeight,
|
|
11801
|
+
viewportOffsetLeft,
|
|
11802
|
+
viewportOffsetTop,
|
|
11803
|
+
};
|
|
11804
|
+
const notify = (reason) => {
|
|
11805
|
+
update(visibleRect, {
|
|
11806
|
+
event,
|
|
11807
|
+
width,
|
|
11808
|
+
height,
|
|
11809
|
+
ancestorClosed: ancestorClosedCount > 0,
|
|
11810
|
+
});
|
|
11811
|
+
};
|
|
11812
|
+
|
|
11813
|
+
const visibleRectChanged =
|
|
11814
|
+
!lastVisibleRect ||
|
|
11815
|
+
lastVisibleRect.left !== visibleRect.left ||
|
|
11816
|
+
lastVisibleRect.top !== visibleRect.top ||
|
|
11817
|
+
lastVisibleRect.width !== visibleRect.width ||
|
|
11818
|
+
lastVisibleRect.height !== visibleRect.height ||
|
|
11819
|
+
lastVisibleRect.visibilityRatio !== visibleRect.visibilityRatio;
|
|
11820
|
+
if (visibleRectChanged) {
|
|
11821
|
+
lastVisibleRect = visibleRect;
|
|
11822
|
+
lastViewportRect = viewportRect;
|
|
11823
|
+
notify();
|
|
11824
|
+
return;
|
|
11825
|
+
}
|
|
11826
|
+
const viewportRectChanged =
|
|
11827
|
+
!lastViewportRect ||
|
|
11828
|
+
lastViewportRect.viewportWidth !== viewportRect.viewportWidth ||
|
|
11829
|
+
lastViewportRect.viewportHeight !== viewportRect.viewportHeight ||
|
|
11830
|
+
lastViewportRect.viewportOffsetLeft !== viewportRect.viewportOffsetLeft ||
|
|
11831
|
+
lastViewportRect.viewportOffsetTop !== viewportRect.viewportOffsetTop;
|
|
11832
|
+
if (viewportRectChanged) {
|
|
11833
|
+
lastVisibleRect = visibleRect;
|
|
11834
|
+
lastViewportRect = viewportRect;
|
|
11835
|
+
notify();
|
|
11836
|
+
return;
|
|
11837
|
+
}
|
|
11475
11838
|
};
|
|
11476
11839
|
|
|
11477
11840
|
check(initialEvent);
|
|
@@ -11549,8 +11912,24 @@ const visibleRectEffect = (
|
|
|
11549
11912
|
{
|
|
11550
11913
|
// See window_size.js's own module comment for why both of these go
|
|
11551
11914
|
// through their shared debounce instead of each keeping its own timer.
|
|
11552
|
-
|
|
11553
|
-
|
|
11915
|
+
const onWindowOrViewportResize = (event) => {
|
|
11916
|
+
// An ancestor's own navi_position_transition follow loop (see
|
|
11917
|
+
// on_ancestor_events below) is already re-checking this element's
|
|
11918
|
+
// position every frame, tracking the ancestor's live in-flight
|
|
11919
|
+
// position — more accurately than this debounced, ~100ms-after-the-
|
|
11920
|
+
// fact check could. Reacting here too would race it: a real
|
|
11921
|
+
// "resize" event makes shouldTransition true, so this would animate
|
|
11922
|
+
// its own competing move toward a target computed from the
|
|
11923
|
+
// anchor's current (still mid-flight) rect.
|
|
11924
|
+
if (ancestorRepositioningCount > 0) {
|
|
11925
|
+
return;
|
|
11926
|
+
}
|
|
11927
|
+
autoCheck(event);
|
|
11928
|
+
};
|
|
11929
|
+
addTeardown(
|
|
11930
|
+
subscribeVisualViewportResizeSettled(onWindowOrViewportResize),
|
|
11931
|
+
);
|
|
11932
|
+
addTeardown(subscribeWindowResizeSettled(onWindowOrViewportResize));
|
|
11554
11933
|
}
|
|
11555
11934
|
on_element_resize: {
|
|
11556
11935
|
if (skipElementResize) {
|
|
@@ -11585,16 +11964,30 @@ const visibleRectEffect = (
|
|
|
11585
11964
|
handlingResize = false;
|
|
11586
11965
|
});
|
|
11587
11966
|
resizeObserver.observe(element);
|
|
11967
|
+
const unsubscribeResizeWatchingPausedChange =
|
|
11968
|
+
onResizeWatchingPausedChange((paused) => {
|
|
11969
|
+
if (paused) {
|
|
11970
|
+
resizeObserver.unobserve(element);
|
|
11971
|
+
} else {
|
|
11972
|
+
resizeObserver.observe(element);
|
|
11973
|
+
}
|
|
11974
|
+
});
|
|
11588
11975
|
// Temporarily disconnect ResizeObserver to prevent feedback loops eventually caused by update function
|
|
11589
11976
|
onBeforeAutoCheck(() => {
|
|
11590
11977
|
resizeObserver.unobserve(element);
|
|
11591
11978
|
return () => {
|
|
11592
|
-
//
|
|
11593
|
-
//
|
|
11594
|
-
|
|
11979
|
+
// Not reobserved at all while an ancestor is closed (see
|
|
11980
|
+
// pauseResizeWatching/resumeResizeWatching above) — resumeResizeWatching's
|
|
11981
|
+
// own publish is what reobserves once it reopens instead.
|
|
11982
|
+
if (!resizeWatchingPaused) {
|
|
11983
|
+
// This triggers a new call to the resive observer that will be ignored thanks to
|
|
11984
|
+
// the widthDiff/heightDiff early return
|
|
11985
|
+
resizeObserver.observe(element);
|
|
11986
|
+
}
|
|
11595
11987
|
};
|
|
11596
11988
|
});
|
|
11597
11989
|
addTeardown(() => {
|
|
11990
|
+
unsubscribeResizeWatchingPausedChange();
|
|
11598
11991
|
resizeObserver.disconnect();
|
|
11599
11992
|
});
|
|
11600
11993
|
}
|
|
@@ -11648,29 +12041,27 @@ const visibleRectEffect = (
|
|
|
11648
12041
|
});
|
|
11649
12042
|
}
|
|
11650
12043
|
{
|
|
11651
|
-
let
|
|
11652
|
-
while (
|
|
11653
|
-
|
|
11654
|
-
|
|
11655
|
-
|
|
11656
|
-
|
|
11657
|
-
|
|
11658
|
-
|
|
11659
|
-
|
|
11660
|
-
ancestor.tagName === "DIALOG" || ancestor.tagName === "DETAILS"
|
|
11661
|
-
? !ancestor.open
|
|
11662
|
-
: !ancestor.matches(":popover-open");
|
|
11663
|
-
if (isInitiallyClosed) {
|
|
11664
|
-
ancestorClosedCount++;
|
|
11665
|
-
}
|
|
12044
|
+
let currentOpenableAncestor = closestOpenableAncestor(element);
|
|
12045
|
+
while (currentOpenableAncestor) {
|
|
12046
|
+
const openableAncestor = currentOpenableAncestor;
|
|
12047
|
+
if (!isAncestorOpen(openableAncestor)) {
|
|
12048
|
+
ancestorClosedCount++;
|
|
12049
|
+
pauseResizeWatching();
|
|
12050
|
+
}
|
|
12051
|
+
const removeOpenStateObserver = observeAncestorOpenState(
|
|
12052
|
+
openableAncestor,
|
|
11666
12053
|
// eslint-disable-next-line no-loop-func
|
|
11667
|
-
|
|
11668
|
-
|
|
11669
|
-
ancestor.tagName === "DETAILS"
|
|
11670
|
-
? !ancestor.open
|
|
11671
|
-
: e.newState === "closed";
|
|
11672
|
-
if (isClosed) {
|
|
12054
|
+
({ isOpen, toggleEvent }) => {
|
|
12055
|
+
if (!isOpen) {
|
|
11673
12056
|
ancestorClosedCount++;
|
|
12057
|
+
pauseResizeWatching();
|
|
12058
|
+
// Invalidates check()'s own "did anything actually change"
|
|
12059
|
+
// caches — without this, reopening onto the exact same
|
|
12060
|
+
// geometry/viewport as before closing would look unchanged to
|
|
12061
|
+
// check() and it would skip calling update() again, leaving a
|
|
12062
|
+
// consumer stuck showing this closed/zeroed state.
|
|
12063
|
+
lastVisibleRect = null;
|
|
12064
|
+
lastViewportRect = null;
|
|
11674
12065
|
update(
|
|
11675
12066
|
{
|
|
11676
12067
|
left: 0,
|
|
@@ -11681,35 +12072,91 @@ const visibleRectEffect = (
|
|
|
11681
12072
|
height: 0,
|
|
11682
12073
|
visibilityRatio: 0,
|
|
11683
12074
|
},
|
|
11684
|
-
{
|
|
12075
|
+
{
|
|
12076
|
+
event: toggleEvent ?? new CustomEvent("ancestor_close"),
|
|
12077
|
+
width: 0,
|
|
12078
|
+
height: 0,
|
|
12079
|
+
ancestorClosed: true,
|
|
12080
|
+
},
|
|
11685
12081
|
);
|
|
11686
|
-
|
|
11687
|
-
if (ancestorClosedCount > 0) {
|
|
11688
|
-
ancestorClosedCount--;
|
|
11689
|
-
}
|
|
11690
|
-
if (ancestorClosedCount === 0) {
|
|
11691
|
-
check(e);
|
|
11692
|
-
}
|
|
12082
|
+
return;
|
|
11693
12083
|
}
|
|
11694
|
-
|
|
11695
|
-
|
|
12084
|
+
if (ancestorClosedCount > 0) {
|
|
12085
|
+
ancestorClosedCount--;
|
|
12086
|
+
}
|
|
12087
|
+
if (ancestorClosedCount === 0) {
|
|
12088
|
+
resumeResizeWatching();
|
|
12089
|
+
check(toggleEvent ?? new CustomEvent("ancestor_open"));
|
|
12090
|
+
}
|
|
12091
|
+
},
|
|
12092
|
+
);
|
|
11696
12093
|
|
|
11697
|
-
|
|
12094
|
+
const onNaviPositionChange = (e) => {
|
|
12095
|
+
autoCheck(e);
|
|
12096
|
+
};
|
|
12097
|
+
openableAncestor.addEventListener(
|
|
12098
|
+
"navi_position_change",
|
|
12099
|
+
onNaviPositionChange,
|
|
12100
|
+
);
|
|
12101
|
+
// Dispatched by applyNewPosition's own notifyPositionTransition
|
|
12102
|
+
// around this ancestor's own left/top animation (distinct from
|
|
12103
|
+
// navi_position_change, fired once with the final target, not per
|
|
12104
|
+
// frame). The anchor this element is positioned against may live
|
|
12105
|
+
// inside that ancestor and be moving right now — rather than hiding
|
|
12106
|
+
// for the duration (an opacity flicker once it settles reads worse
|
|
12107
|
+
// than a slightly-behind position), autoCheck() every frame for as
|
|
12108
|
+
// long as the animation runs, so this element stays in lockstep.
|
|
12109
|
+
// autoCheck, not check directly, so this element's own
|
|
12110
|
+
// ResizeObserver(s) stay unobserved for the loop's duration too —
|
|
12111
|
+
// repositioning every frame can itself cause reflows. e.detail.onEnd
|
|
12112
|
+
// stops the loop and settles on one final check once it ends.
|
|
12113
|
+
let positionTransitionRafId = null;
|
|
12114
|
+
let isTrackingPositionTransition = false;
|
|
12115
|
+
// ancestorRepositioningCount is intentionally shared across every
|
|
12116
|
+
// ancestor level (declared once, outside this loop) — it's a
|
|
12117
|
+
// single "is this element's position currently being driven by
|
|
12118
|
+
// some ancestor's transition" flag for the element itself, not
|
|
12119
|
+
// per-ancestor state.
|
|
12120
|
+
// eslint-disable-next-line no-loop-func
|
|
12121
|
+
const onNaviPositionTransition = (e) => {
|
|
12122
|
+
cancelAnimationFrame(positionTransitionRafId);
|
|
12123
|
+
if (!isTrackingPositionTransition) {
|
|
12124
|
+
isTrackingPositionTransition = true;
|
|
12125
|
+
ancestorRepositioningCount++;
|
|
12126
|
+
}
|
|
12127
|
+
const loop = () => {
|
|
11698
12128
|
autoCheck(e);
|
|
12129
|
+
positionTransitionRafId = requestAnimationFrame(loop);
|
|
11699
12130
|
};
|
|
11700
|
-
|
|
12131
|
+
loop();
|
|
12132
|
+
e.detail.onEnd(() => {
|
|
12133
|
+
cancelAnimationFrame(positionTransitionRafId);
|
|
12134
|
+
if (isTrackingPositionTransition) {
|
|
12135
|
+
isTrackingPositionTransition = false;
|
|
12136
|
+
ancestorRepositioningCount--;
|
|
12137
|
+
}
|
|
12138
|
+
autoCheck(e);
|
|
12139
|
+
});
|
|
12140
|
+
};
|
|
12141
|
+
openableAncestor.addEventListener(
|
|
12142
|
+
"navi_position_transition",
|
|
12143
|
+
onNaviPositionTransition,
|
|
12144
|
+
);
|
|
12145
|
+
addTeardown(() => {
|
|
12146
|
+
removeOpenStateObserver();
|
|
12147
|
+
cancelAnimationFrame(positionTransitionRafId);
|
|
12148
|
+
openableAncestor.removeEventListener(
|
|
11701
12149
|
"navi_position_change",
|
|
11702
12150
|
onNaviPositionChange,
|
|
11703
12151
|
);
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
current = current.parentElement;
|
|
12152
|
+
openableAncestor.removeEventListener(
|
|
12153
|
+
"navi_position_transition",
|
|
12154
|
+
onNaviPositionTransition,
|
|
12155
|
+
);
|
|
12156
|
+
});
|
|
12157
|
+
currentOpenableAncestor = closestOpenableAncestor(
|
|
12158
|
+
currentOpenableAncestor,
|
|
12159
|
+
);
|
|
11713
12160
|
}
|
|
11714
12161
|
}
|
|
11715
12162
|
}
|
|
@@ -11774,20 +12221,45 @@ const visibleRectEffect = (
|
|
|
11774
12221
|
});
|
|
11775
12222
|
});
|
|
11776
12223
|
resizeObserver.observe(elementToObserve);
|
|
12224
|
+
// An ancestor may already be closed by the time a consumer calls
|
|
12225
|
+
// observeSize (e.g. Callout's own observeSize(calloutMessageElement)
|
|
12226
|
+
// call happens after visibleRectEffect itself returns) — keep this new
|
|
12227
|
+
// observer consistent with that already-paused state instead of
|
|
12228
|
+
// observing it only to immediately generate a closed-container
|
|
12229
|
+
// notification.
|
|
12230
|
+
if (resizeWatchingPaused) {
|
|
12231
|
+
resizeObserver.unobserve(elementToObserve);
|
|
12232
|
+
}
|
|
12233
|
+
const unsubscribeResizeWatchingPausedChange = onResizeWatchingPausedChange(
|
|
12234
|
+
(paused) => {
|
|
12235
|
+
if (paused) {
|
|
12236
|
+
resizeObserver.unobserve(elementToObserve);
|
|
12237
|
+
} else {
|
|
12238
|
+
resizeObserver.observe(elementToObserve);
|
|
12239
|
+
}
|
|
12240
|
+
},
|
|
12241
|
+
);
|
|
11777
12242
|
const cleanupAutoCheck = onBeforeAutoCheck(() => {
|
|
11778
12243
|
resizeObserver.unobserve(elementToObserve);
|
|
11779
12244
|
return () => {
|
|
11780
|
-
|
|
12245
|
+
// Not reobserved at all while an ancestor is closed (see
|
|
12246
|
+
// pauseResizeWatching/resumeResizeWatching) — resumeResizeWatching's
|
|
12247
|
+
// own publish is what reobserves once it reopens instead.
|
|
12248
|
+
if (!resizeWatchingPaused) {
|
|
12249
|
+
resizeObserver.observe(elementToObserve);
|
|
12250
|
+
}
|
|
11781
12251
|
};
|
|
11782
12252
|
});
|
|
11783
12253
|
addTeardown(() => {
|
|
11784
12254
|
if (pendingFrame !== null) {
|
|
11785
12255
|
cancelAnimationFrame(pendingFrame);
|
|
11786
12256
|
}
|
|
12257
|
+
unsubscribeResizeWatchingPausedChange();
|
|
11787
12258
|
resizeObserver.disconnect();
|
|
11788
12259
|
});
|
|
11789
12260
|
return () => {
|
|
11790
12261
|
cleanupAutoCheck();
|
|
12262
|
+
unsubscribeResizeWatchingPausedChange();
|
|
11791
12263
|
if (pendingFrame !== null) {
|
|
11792
12264
|
cancelAnimationFrame(pendingFrame);
|
|
11793
12265
|
}
|
|
@@ -11984,17 +12456,18 @@ const toContainerAlignedPosition = (value) => {
|
|
|
11984
12456
|
* there's a real `anchor`, since `element` can be container-relative either way (e.g. the
|
|
11985
12457
|
* custom renderer in popover.jsx, always relative to its own positioned ancestor whether
|
|
11986
12458
|
* or not it also has a real anchor). Whenever not explicitly given, this is always
|
|
11987
|
-
* resolved automatically via `
|
|
12459
|
+
* resolved automatically via `getPositionedParent(element)` instead — regardless of
|
|
11988
12460
|
* `hasValidAnchor` — so a caller that never thinks about `container` at all still gets the
|
|
11989
|
-
* right behavior on its own: `
|
|
11990
|
-
* `
|
|
11991
|
-
*
|
|
11992
|
-
*
|
|
11993
|
-
*
|
|
11994
|
-
*
|
|
11995
|
-
*
|
|
11996
|
-
*
|
|
11997
|
-
*
|
|
12461
|
+
* right behavior on its own: `document.documentElement` from `getPositionedParent` (an
|
|
12462
|
+
* `element` promoted to the top layer — a `[popover]` while shown, or a `<dialog>` while
|
|
12463
|
+
* actually modal — or one with no positioned ancestor at all, e.g. Callout's own element)
|
|
12464
|
+
* falls back to the traditional document-relative path below, exactly as if `container`
|
|
12465
|
+
* genuinely didn't apply; anything else `getPositionedParent` finds (a real positioned
|
|
12466
|
+
* ancestor) is used the same way an explicit `container` would be. A container that
|
|
12467
|
+
* resolves to `document.documentElement` (the viewport) produces identical output to the
|
|
12468
|
+
* plain document-relative path either way, since the document's own scroll and the
|
|
12469
|
+
* viewport's own origin already coincide with what this generically computes for any other
|
|
12470
|
+
* container element. When there's a real container (explicit or resolved) either way: the final
|
|
11998
12471
|
* `left`/`top` (and the returned `anchorLeft/Top/Right/Bottom`) are expressed relative to
|
|
11999
12472
|
* its own padding-box origin plus its own scroll, instead of the document's — `element`'s
|
|
12000
12473
|
* own computed `position` is *not* consulted in that case, unlike the traditional path.
|
|
@@ -12040,9 +12513,11 @@ const pickPositionRelativeTo = (
|
|
|
12040
12513
|
// never gets offered more room (anchor-too-big check, flip decisions,
|
|
12041
12514
|
// clamp) than its own container — resolvedContainer's own padding-box
|
|
12042
12515
|
// edges when there is one — actually has.
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
|
|
12516
|
+
// Always a real element now (never null/undefined) — getPositionedParent
|
|
12517
|
+
// itself never returns anything falsy, document.documentElement (the
|
|
12518
|
+
// viewport) included.
|
|
12519
|
+
const resolvedContainer = container ?? getPositionedParent(element);
|
|
12520
|
+
const hasRealContainer = resolvedContainer !== document.documentElement;
|
|
12046
12521
|
const containerRect = hasRealContainer
|
|
12047
12522
|
? resolvedContainer.getBoundingClientRect()
|
|
12048
12523
|
: null;
|
|
@@ -12118,14 +12593,13 @@ const pickPositionRelativeTo = (
|
|
|
12118
12593
|
positionXFixed = positionX;
|
|
12119
12594
|
positionYFixed = positionY;
|
|
12120
12595
|
}
|
|
12121
|
-
// resolvedContainer was already resolved above.
|
|
12122
|
-
//
|
|
12123
|
-
//
|
|
12124
|
-
//
|
|
12125
|
-
//
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
: resolvedContainer || document.documentElement;
|
|
12596
|
+
// resolvedContainer was already resolved above. document.documentElement
|
|
12597
|
+
// from getPositionedParent (a popover/dialog element, e.g. Callout's own,
|
|
12598
|
+
// or one with no positioned ancestor at all) falls through to the
|
|
12599
|
+
// traditional document-relative path below all the same, so an existing
|
|
12600
|
+
// caller that never thinks about `container` at all keeps behaving
|
|
12601
|
+
// exactly as before.
|
|
12602
|
+
const effectiveAnchor = hasValidAnchor ? anchor : resolvedContainer;
|
|
12129
12603
|
// document.documentElement is used as a sentinel "the viewport" value: an
|
|
12130
12604
|
// anchorless popup should center/place itself against the visual
|
|
12131
12605
|
// viewport, not against <html>'s own box — which, unlike the viewport,
|
|
@@ -12317,7 +12791,14 @@ const pickPositionRelativeTo = (
|
|
|
12317
12791
|
if (currentFitsEnough) {
|
|
12318
12792
|
finalX = activeX;
|
|
12319
12793
|
} else {
|
|
12320
|
-
|
|
12794
|
+
// Only flip if the opposite side has more space — avoids oscillation
|
|
12795
|
+
// when neither side has enough room (both fail the ratio). Mirrors
|
|
12796
|
+
// the Y-axis branch above; missing here was the actual cause of a
|
|
12797
|
+
// real left/right flicker on a narrow viewport (neither side ever
|
|
12798
|
+
// "fits enough", so this branch ran on every reposition).
|
|
12799
|
+
const opposite = oppositeX[activeX];
|
|
12800
|
+
const oppositeHasMoreSpace = spaceFor(opposite) > spaceFor(activeX);
|
|
12801
|
+
finalX = oppositeHasMoreSpace ? opposite : activeX;
|
|
12321
12802
|
}
|
|
12322
12803
|
}
|
|
12323
12804
|
}
|
|
@@ -12549,14 +13030,110 @@ const pickPositionRelativeTo = (
|
|
|
12549
13030
|
};
|
|
12550
13031
|
};
|
|
12551
13032
|
|
|
13033
|
+
// Per-element bookkeeping for the currently in-flight, self-driven position
|
|
13034
|
+
// transition, if any — see notifyPositionTransition's own doc for why this
|
|
13035
|
+
// is animation-driven rather than listening for the browser's own
|
|
13036
|
+
// transitionrun/transitionend: element -> { animation, endCallbacks }.
|
|
13037
|
+
const pendingPositionTransitions = new WeakMap();
|
|
13038
|
+
|
|
13039
|
+
// Reads `cssVarName` off `element` (getComputedStyle, so it's whatever the
|
|
13040
|
+
// cascade resolves to — a consumer can set it inline, in its own CSS rule,
|
|
13041
|
+
// or not at all) and converts it to milliseconds: "0.25s" -> 250, "250ms" ->
|
|
13042
|
+
// 250. Falls back to `fallbackMs` when unset/empty/unparsable, so a caller
|
|
13043
|
+
// never has to declare the CSS var itself just to get a sane default
|
|
13044
|
+
// duration — it only needs to when it actually wants to override it.
|
|
13045
|
+
const parseTransitionDurationMs = (element, cssVarName, fallbackMs) => {
|
|
13046
|
+
const trimmed = getStyle(element, cssVarName).trim();
|
|
13047
|
+
if (!trimmed) {
|
|
13048
|
+
return fallbackMs;
|
|
13049
|
+
}
|
|
13050
|
+
if (trimmed.endsWith("ms")) {
|
|
13051
|
+
return parseFloat(trimmed);
|
|
13052
|
+
}
|
|
13053
|
+
if (trimmed.endsWith("s")) {
|
|
13054
|
+
return parseFloat(trimmed) * 1000;
|
|
13055
|
+
}
|
|
13056
|
+
const parsed = parseFloat(trimmed);
|
|
13057
|
+
return Number.isNaN(parsed) ? fallbackMs : parsed;
|
|
13058
|
+
};
|
|
13059
|
+
|
|
12552
13060
|
/**
|
|
12553
|
-
*
|
|
12554
|
-
*
|
|
12555
|
-
*
|
|
12556
|
-
*
|
|
12557
|
-
*
|
|
12558
|
-
*
|
|
12559
|
-
*
|
|
13061
|
+
* Dispatches a single "navi_position_transition" event on `element`,
|
|
13062
|
+
* self-driven rather than confirmed by the browser's own `transitionrun` —
|
|
13063
|
+
* `applyNewPosition` calls this exactly when it knows it just started a
|
|
13064
|
+
* left/top `animation`, so there's nothing to wait for. transitionrun was
|
|
13065
|
+
* tried first and dropped: it reacts to *any* transition sharing the
|
|
13066
|
+
* element (a scale/opacity entrance would wrongly hide a descendant too),
|
|
13067
|
+
* and filtering by `propertyName` is unreliable (observed firing for "top"
|
|
13068
|
+
* instead of "left" in practice, despite the transition-property order).
|
|
13069
|
+
* A dedicated `Animation` sidesteps both.
|
|
13070
|
+
*
|
|
13071
|
+
* A descendant anchored inside `element` (see on_ancestor_events)
|
|
13072
|
+
* re-checks its own position every frame for as long as this animation
|
|
13073
|
+
* runs, instead of showing a stale position. `event.detail.onEnd(callback)`
|
|
13074
|
+
* is how it learns when the animation actually ends.
|
|
13075
|
+
*
|
|
13076
|
+
* A second reposition landing mid-animation cancels the pending one and
|
|
13077
|
+
* flushes its own registered callbacks immediately (same spirit as a real
|
|
13078
|
+
* `transitioncancel`), so nothing is left waiting on a superseded `onEnd`.
|
|
13079
|
+
*
|
|
13080
|
+
* `commitStyles()` below isn't what makes the final position correct —
|
|
13081
|
+
* `applyNewPosition` already sets the specified `left`/`top` before this
|
|
13082
|
+
* animation starts, so it takes back over once the active duration elapses
|
|
13083
|
+
* regardless. It just makes that explicit instead of relying on `fill:
|
|
13084
|
+
* "none"` timing, and drops the finished Animation instead of leaving it.
|
|
13085
|
+
*/
|
|
13086
|
+
const notifyPositionTransition = (element, animation) => {
|
|
13087
|
+
const pending = pendingPositionTransitions.get(element);
|
|
13088
|
+
if (pending) {
|
|
13089
|
+
pending.animation.cancel();
|
|
13090
|
+
for (const callback of pending.endCallbacks) {
|
|
13091
|
+
callback();
|
|
13092
|
+
}
|
|
13093
|
+
}
|
|
13094
|
+
const endCallbacks = [];
|
|
13095
|
+
dispatchCustomEvent(element, "navi_position_transition", {
|
|
13096
|
+
onEnd: (callback) => {
|
|
13097
|
+
endCallbacks.push(callback);
|
|
13098
|
+
},
|
|
13099
|
+
});
|
|
13100
|
+
const current = { animation, endCallbacks };
|
|
13101
|
+
pendingPositionTransitions.set(element, current);
|
|
13102
|
+
animation.finished
|
|
13103
|
+
.then(() => {
|
|
13104
|
+
if (pendingPositionTransitions.get(element) === current) {
|
|
13105
|
+
pendingPositionTransitions.delete(element);
|
|
13106
|
+
}
|
|
13107
|
+
try {
|
|
13108
|
+
animation.commitStyles();
|
|
13109
|
+
} catch {
|
|
13110
|
+
// Element no longer rendered (removed/hidden mid-animation) —
|
|
13111
|
+
// nothing to commit to, and left/top were already final anyway.
|
|
13112
|
+
}
|
|
13113
|
+
animation.cancel();
|
|
13114
|
+
for (const callback of endCallbacks) {
|
|
13115
|
+
callback();
|
|
13116
|
+
}
|
|
13117
|
+
})
|
|
13118
|
+
.catch(() => {
|
|
13119
|
+
// Cancelled by a subsequent reposition — already flushed above.
|
|
13120
|
+
});
|
|
13121
|
+
};
|
|
13122
|
+
|
|
13123
|
+
/**
|
|
13124
|
+
* Applies a `pickPositionRelativeTo` result to `element`. `left`/`top` are
|
|
13125
|
+
* set instantly (a scroll-triggered reposition should never lag its
|
|
13126
|
+
* target); when `shouldTransition` is set (a resize-triggered reposition),
|
|
13127
|
+
* the visual move is played out via `element.animate()` instead — kept
|
|
13128
|
+
* independent of Popover/Dialog/Callout's own opacity/scale/display CSS
|
|
13129
|
+
* transition on the same element, so neither can clobber the other (see
|
|
13130
|
+
* notifyPositionTransition's own doc for why a dedicated Animation over a
|
|
13131
|
+
* CSS one). Duration comes from `--popup-position-transition-duration`
|
|
13132
|
+
* (parseTransitionDurationMs), falling back to 180ms unset.
|
|
13133
|
+
* Dispatches navi_position_transition when it starts such an animation, and
|
|
13134
|
+
* navi_position_change unconditionally — every caller (Dialog, Popover,
|
|
13135
|
+
* Callout) wants both, so a descendant anchored inside `element` can always
|
|
13136
|
+
* recheck its own position whenever `element` moves.
|
|
12560
13137
|
*/
|
|
12561
13138
|
const applyNewPosition = (
|
|
12562
13139
|
element,
|
|
@@ -12571,15 +13148,7 @@ const applyNewPosition = (
|
|
|
12571
13148
|
spaceAbove,
|
|
12572
13149
|
spaceBelow,
|
|
12573
13150
|
},
|
|
12574
|
-
{ transitionDuration = "0.25s" } = {},
|
|
12575
13151
|
) => {
|
|
12576
|
-
element.style.setProperty(
|
|
12577
|
-
"--popup-position-transition-duration",
|
|
12578
|
-
shouldTransition ? transitionDuration : "0s",
|
|
12579
|
-
);
|
|
12580
|
-
element.style.left = `${left}px`;
|
|
12581
|
-
element.style.top = `${top}px`;
|
|
12582
|
-
|
|
12583
13152
|
if (positionY === "top" || positionY === "inset-bottom") {
|
|
12584
13153
|
element.style.setProperty(
|
|
12585
13154
|
"--container-position-remaining-height",
|
|
@@ -12606,6 +13175,42 @@ const applyNewPosition = (
|
|
|
12606
13175
|
} else {
|
|
12607
13176
|
element.style.removeProperty("--container-position-remaining-width");
|
|
12608
13177
|
}
|
|
13178
|
+
|
|
13179
|
+
// A single implicit keyframe turned out not to work here: the WAAPI
|
|
13180
|
+
// "neutral" start keyframe isn't frozen at `animate()` call time, it's
|
|
13181
|
+
// resolved from the underlying value when the animation is first
|
|
13182
|
+
// *sampled* (the next frame) — by then `element.style.left`/`top` below
|
|
13183
|
+
// has already been overwritten with the new target, so start === end and
|
|
13184
|
+
// nothing visibly moves (observed as the dialog just jumping). Reading
|
|
13185
|
+
// the previous value ourselves, before overwriting it, and passing both
|
|
13186
|
+
// keyframes explicitly sidesteps that entirely.
|
|
13187
|
+
const previousLeft = parseFloat(element.style.left) || left;
|
|
13188
|
+
const previousTop = parseFloat(element.style.top) || top;
|
|
13189
|
+
if (shouldTransition) {
|
|
13190
|
+
const animation = element.animate(
|
|
13191
|
+
[
|
|
13192
|
+
{ left: `${previousLeft}px`, top: `${previousTop}px` },
|
|
13193
|
+
{ left: `${left}px`, top: `${top}px` },
|
|
13194
|
+
],
|
|
13195
|
+
{
|
|
13196
|
+
duration: parseTransitionDurationMs(
|
|
13197
|
+
element,
|
|
13198
|
+
"--popup-position-transition-duration",
|
|
13199
|
+
250,
|
|
13200
|
+
),
|
|
13201
|
+
easing: "ease",
|
|
13202
|
+
},
|
|
13203
|
+
);
|
|
13204
|
+
notifyPositionTransition(element, animation);
|
|
13205
|
+
}
|
|
13206
|
+
// The specified `left`/`top` are set to their final target right away,
|
|
13207
|
+
// regardless of `shouldTransition` — the animation above only plays the
|
|
13208
|
+
// visual move from the old position, it never becomes the actual
|
|
13209
|
+
// specified style (see notifyPositionTransition's own commitStyles for
|
|
13210
|
+
// why that matters once it ends).
|
|
13211
|
+
element.style.left = `${left}px`;
|
|
13212
|
+
element.style.top = `${top}px`;
|
|
13213
|
+
dispatchCustomEvent(element, "navi_position_change");
|
|
12609
13214
|
};
|
|
12610
13215
|
|
|
12611
13216
|
const [publishDebugger, subscribeDebugger] = createPubSub();
|
|
@@ -15858,4 +16463,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
|
|
|
15858
16463
|
};
|
|
15859
16464
|
};
|
|
15860
16465
|
|
|
15861
|
-
export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, applyNewPosition, captureScrollState, chainEvent, 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, formatEventSideEffect, 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,
|
|
16466
|
+
export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, applyNewPosition, captureScrollState, chainEvent, closestOpenableAncestor, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createEventGroupLogger, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dispatchCustomEvent, dispatchInternalCustomEvent, dispatchPublicCustomEvent, dragAfterThreshold, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findEvent, findFocusDelegateTarget, findFocusable, findSelfOrAncestorFixedPosition, formatEventSideEffect, getAncestorOpenType, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getKeyboardEventDefaultAction, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPositioningScrollOffset, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isAncestorOpen, isSameColor, isScrollable, measureLongestVisualLineWidth, measureScrollbar, measureWidestChildRow, mergeOneStyle, mergeTwoStyles, normalizeKeyboardKey, normalizeStyle, normalizeStyles, observeAncestorOpenState, onAncestorReopen, parsePositionArea, parseStyle, performTabNavigation, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, setAttribute, setAttributes, setStyles, snapToPixel, startDragToReorder, startDragToResizeGesture, stickyAsRelativeCoords, stringifyStyle, subscribeVisualViewportResizeSettled, subscribeWindowResizeSettled, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/dom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DOM utilities for writing frontend code",
|
|
6
6
|
"repository": {
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"@jsenv/core": "../../../",
|
|
41
41
|
"@jsenv/navi": "../navi",
|
|
42
42
|
"@jsenv/snapshot": "../../tooling/snapshot",
|
|
43
|
-
"@preact/signals": "2.9.
|
|
44
|
-
"preact": "11.0.0-beta.
|
|
43
|
+
"@preact/signals": "2.9.4",
|
|
44
|
+
"preact": "11.0.0-beta.2"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|
|
47
47
|
"access": "public"
|