@jsenv/navi 0.29.23 → 0.29.25
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/README.md +2 -0
- package/dist/jsenv_navi.js +443 -158
- package/dist/jsenv_navi.js.map +25 -13
- package/docs/popup_open.md +185 -0
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -25120,6 +25120,9 @@ const createOpenController = (
|
|
|
25120
25120
|
openEffectCleanup = null;
|
|
25121
25121
|
closeHandlers?.onClose?.(closeEvent);
|
|
25122
25122
|
closeHandlers = null;
|
|
25123
|
+
// Last: the close effects above are what starts the exit transition the
|
|
25124
|
+
// content must outlive (see popup_content_mount.js).
|
|
25125
|
+
controller.unmountContent?.();
|
|
25123
25126
|
};
|
|
25124
25127
|
const controller = {
|
|
25125
25128
|
opened: false,
|
|
@@ -25128,6 +25131,9 @@ const createOpenController = (
|
|
|
25128
25131
|
// content is still waiting for a first open to be built. Called below,
|
|
25129
25132
|
// before openEffect, so the popup measures and positions the real thing.
|
|
25130
25133
|
mountContent: null,
|
|
25134
|
+
// The counterpart, set only when the popup was told to throw its content
|
|
25135
|
+
// away on close (`unmountWhenClosed`). Called from performClose above.
|
|
25136
|
+
unmountContent: null,
|
|
25131
25137
|
open: (e, detail) => {
|
|
25132
25138
|
if (controller.opened || !controller.openEffect) {
|
|
25133
25139
|
return;
|
|
@@ -25407,7 +25413,166 @@ const flushSyncRendering = (fn) => {
|
|
|
25407
25413
|
};
|
|
25408
25414
|
|
|
25409
25415
|
/**
|
|
25410
|
-
*
|
|
25416
|
+
* Small, renderer-agnostic helpers shared by Popover and Dialog's own custom
|
|
25417
|
+
* (non-top-layer) renderers — operate on a plain DOM element, no knowledge
|
|
25418
|
+
* of which of the two owns it.
|
|
25419
|
+
*/
|
|
25420
|
+
|
|
25421
|
+
|
|
25422
|
+
/**
|
|
25423
|
+
* Calls `onSettled` once `el`'s current CSS transition is over — via
|
|
25424
|
+
* `transitionend`, with a safety `setTimeout` fallback matching the longest
|
|
25425
|
+
* `transition-duration`, in case nothing actually transitions or an event is
|
|
25426
|
+
* missed.
|
|
25427
|
+
*
|
|
25428
|
+
* Returns a "cancel" function, so a caller whose instance has been superseded
|
|
25429
|
+
* (a fresh open/close about to set its own state) can keep this stale one from
|
|
25430
|
+
* firing later. Cancelling only stops `onSettled`: undoing whatever the caller
|
|
25431
|
+
* did up front is that fresh call's business, not this one's.
|
|
25432
|
+
*/
|
|
25433
|
+
const whenTransitionSettles = (el, onSettled) => {
|
|
25434
|
+
let settled = false;
|
|
25435
|
+
const onTransitionEnd = (transitionEvent) => {
|
|
25436
|
+
if (transitionEvent.target === el) {
|
|
25437
|
+
finish();
|
|
25438
|
+
}
|
|
25439
|
+
};
|
|
25440
|
+
const stopWatching = () => {
|
|
25441
|
+
settled = true;
|
|
25442
|
+
el.removeEventListener("transitionend", onTransitionEnd);
|
|
25443
|
+
clearTimeout(safetyTimeoutId);
|
|
25444
|
+
};
|
|
25445
|
+
const finish = () => {
|
|
25446
|
+
if (settled) {
|
|
25447
|
+
return;
|
|
25448
|
+
}
|
|
25449
|
+
stopWatching();
|
|
25450
|
+
onSettled();
|
|
25451
|
+
};
|
|
25452
|
+
el.addEventListener("transitionend", onTransitionEnd);
|
|
25453
|
+
const durationsInSeconds = getComputedStyle(el)
|
|
25454
|
+
.transitionDuration.split(",")
|
|
25455
|
+
.map((value) => parseFloat(value) || 0);
|
|
25456
|
+
const longestDurationMs = Math.max(0, ...durationsInSeconds) * 1000;
|
|
25457
|
+
const safetyTimeoutId = setTimeout(finish, longestDurationMs + 50);
|
|
25458
|
+
return () => {
|
|
25459
|
+
if (settled) {
|
|
25460
|
+
return;
|
|
25461
|
+
}
|
|
25462
|
+
stopWatching();
|
|
25463
|
+
};
|
|
25464
|
+
};
|
|
25465
|
+
|
|
25466
|
+
/**
|
|
25467
|
+
* Disables pointer-events on `el` until its current CSS transition settles —
|
|
25468
|
+
* avoids the cursor changing/something becoming clickable while the popup is
|
|
25469
|
+
* still visually moving into or out of place.
|
|
25470
|
+
*
|
|
25471
|
+
* Returns whenTransitionSettles' own "cancel" function: it doesn't restore
|
|
25472
|
+
* pointer-events, since a fresh call for the next open/close is about to set
|
|
25473
|
+
* its own state.
|
|
25474
|
+
*/
|
|
25475
|
+
const suppressPointerEventsDuringTransition = (el) => {
|
|
25476
|
+
el.style.pointerEvents = "none";
|
|
25477
|
+
return whenTransitionSettles(el, () => {
|
|
25478
|
+
el.style.pointerEvents = "";
|
|
25479
|
+
});
|
|
25480
|
+
};
|
|
25481
|
+
|
|
25482
|
+
/**
|
|
25483
|
+
* Hides the backdrop, deferring until the browser's matching "click" fires
|
|
25484
|
+
* when `closeEvent` was triggered by a mousedown (see popover.jsx's top
|
|
25485
|
+
* comment for why) — same capture-phase-on-document pattern as
|
|
25486
|
+
* armSuppressNextOpenRequest in open_controller.js, which a plain timeout
|
|
25487
|
+
* can't safely replace: mouseup (and the click that follows it) can land an
|
|
25488
|
+
* arbitrarily long time after mousedown (the user is still holding the
|
|
25489
|
+
* button down), so a short timeout can fire first and hide the backdrop
|
|
25490
|
+
* before its own click ever arrives. A capture-phase listener on document
|
|
25491
|
+
* fires for every click regardless of what any bubble-phase handler does
|
|
25492
|
+
* downstream, so no fallback timer is needed.
|
|
25493
|
+
*
|
|
25494
|
+
* `hide` is the caller's own way to actually hide the backdrop
|
|
25495
|
+
* (`hidePopover()` for a top-layer backdrop, a plain `style.display = "none"`
|
|
25496
|
+
* for a plain div) — this helper only owns the mousedown/click timing.
|
|
25497
|
+
*
|
|
25498
|
+
* Returns a disarm function (or undefined if hidden immediately), so a
|
|
25499
|
+
* fresh open can cancel a pending hide it's about to make redundant.
|
|
25500
|
+
*/
|
|
25501
|
+
const armPointerDownOutsideClose = (closeEvent, hide) => {
|
|
25502
|
+
const mousedownEvent = findEvent(closeEvent, "mousedown");
|
|
25503
|
+
if (!mousedownEvent) {
|
|
25504
|
+
hide();
|
|
25505
|
+
return undefined;
|
|
25506
|
+
}
|
|
25507
|
+
const onClick = () => {
|
|
25508
|
+
document.removeEventListener("click", onClick, { capture: true });
|
|
25509
|
+
hide();
|
|
25510
|
+
};
|
|
25511
|
+
document.addEventListener("click", onClick, { capture: true });
|
|
25512
|
+
return () => {
|
|
25513
|
+
document.removeEventListener("click", onClick, { capture: true });
|
|
25514
|
+
};
|
|
25515
|
+
};
|
|
25516
|
+
|
|
25517
|
+
/**
|
|
25518
|
+
* Maps a positionArea y/x pair to a concrete `navi-animation` value (a
|
|
25519
|
+
* `prefix` plus a direction word), or `null` if both axes overlap the anchor
|
|
25520
|
+
* (no direction at all — that's `resolvedAnimationKind === "scaling"`
|
|
25521
|
+
* territory instead, see resolveAutoAnimationKind below).
|
|
25522
|
+
*
|
|
25523
|
+
* `prefix: "slide-from"` (used with no real anchor — Dialog always, Popover
|
|
25524
|
+
* when docked) keeps the word as the compass direction the popup comes
|
|
25525
|
+
* from: placed "top" (a point/corner), it slides in from the top.
|
|
25526
|
+
* `prefix: "expand"` (a real anchor, Popover-only) uses the motion/growth
|
|
25527
|
+
* direction instead, the opposite compass point: placed "top" of the
|
|
25528
|
+
* anchor, it moves/grows up, away from the anchor (which sits below it).
|
|
25529
|
+
*
|
|
25530
|
+
* "inset-*"/"center" contribute no direction on their axis either way.
|
|
25531
|
+
*/
|
|
25532
|
+
const resolveDirectionValue = (y, x, { prefix }) => {
|
|
25533
|
+
const yWord =
|
|
25534
|
+
y === "top"
|
|
25535
|
+
? prefix === "expand"
|
|
25536
|
+
? "up"
|
|
25537
|
+
: "top"
|
|
25538
|
+
: y === "bottom"
|
|
25539
|
+
? prefix === "expand"
|
|
25540
|
+
? "down"
|
|
25541
|
+
: "bottom"
|
|
25542
|
+
: null;
|
|
25543
|
+
const xWord = x === "left" ? "left" : x === "right" ? "right" : null;
|
|
25544
|
+
if (!yWord && !xWord) {
|
|
25545
|
+
return null;
|
|
25546
|
+
}
|
|
25547
|
+
return yWord && xWord
|
|
25548
|
+
? `${prefix}-${yWord}-${xWord}`
|
|
25549
|
+
: `${prefix}-${yWord || xWord}`;
|
|
25550
|
+
};
|
|
25551
|
+
|
|
25552
|
+
/**
|
|
25553
|
+
* Shared `animation="auto"`/`true` resolution: "scaling" reads best overall
|
|
25554
|
+
* — picked for any real anchor, or for a point/corner placed dead-center
|
|
25555
|
+
* (both positionArea axes overlapping — there's no sensible direction to
|
|
25556
|
+
* slide from in that case). "sliding" otherwise. `anchor` is `undefined`
|
|
25557
|
+
* for any no-anchor/docked case (Dialog always, Popover's own custom
|
|
25558
|
+
* renderer when there's no real anchor), so this collapses to "scaling"
|
|
25559
|
+
* there only for the dead-center case, "sliding" otherwise. The two
|
|
25560
|
+
* "overlapping" booleans below describe the *positionArea* itself (a bare
|
|
25561
|
+
* word vs. "inset-"/"center"), not anything about the anchor — they'd
|
|
25562
|
+
* mean exactly the same thing even with no anchor at all, since it's the
|
|
25563
|
+
* position strategy, not the anchor, that decides whether there's a
|
|
25564
|
+
* direction to slide from.
|
|
25565
|
+
*/
|
|
25566
|
+
const resolveAutoAnimationKind = (anchor, parsedPositionArea) => {
|
|
25567
|
+
const yIsOverlapping =
|
|
25568
|
+
parsedPositionArea.y !== "top" && parsedPositionArea.y !== "bottom";
|
|
25569
|
+
const xIsOverlapping =
|
|
25570
|
+
parsedPositionArea.x !== "left" && parsedPositionArea.x !== "right";
|
|
25571
|
+
return anchor || (yIsOverlapping && xIsOverlapping) ? "scaling" : "sliding";
|
|
25572
|
+
};
|
|
25573
|
+
|
|
25574
|
+
/**
|
|
25575
|
+
* When a popup builds what it holds, and when it throws it away.
|
|
25411
25576
|
*
|
|
25412
25577
|
* A closed popup shows nothing, focuses nothing, and answers nothing: what it
|
|
25413
25578
|
* holds is out of reach until it opens. Building that content at mount time
|
|
@@ -25430,12 +25595,18 @@ const flushSyncRendering = (fn) => {
|
|
|
25430
25595
|
* `mountWhenClosed` is for content something else depends on before any of
|
|
25431
25596
|
* this: a value the popup's owner reads off its own children, fields a form
|
|
25432
25597
|
* around it collects on submit, a size measured from outside.
|
|
25598
|
+
*
|
|
25599
|
+
* `unmountWhenClosed` is the opposite end: content that must be rebuilt from
|
|
25600
|
+
* scratch every time, because what it shows is read once at build time and can
|
|
25601
|
+
* change while the popup is closed — an uncontrolled field seeded from a
|
|
25602
|
+
* `defaultValue`, a form whose fresh state is its initial state.
|
|
25433
25603
|
*/
|
|
25434
25604
|
|
|
25435
25605
|
|
|
25436
25606
|
const usePopupContentMount = (
|
|
25437
25607
|
openController,
|
|
25438
|
-
|
|
25608
|
+
ref,
|
|
25609
|
+
{ children, mountWhenClosed, unmountWhenClosed },
|
|
25439
25610
|
) => {
|
|
25440
25611
|
const [contentMounted, setContentMounted] = useState(
|
|
25441
25612
|
() => Boolean(mountWhenClosed) || openController.opened,
|
|
@@ -25447,6 +25618,27 @@ const usePopupContentMount = (
|
|
|
25447
25618
|
setContentMounted(true);
|
|
25448
25619
|
});
|
|
25449
25620
|
};
|
|
25621
|
+
openController.unmountContent =
|
|
25622
|
+
unmountWhenClosed && !mountWhenClosed
|
|
25623
|
+
? () => {
|
|
25624
|
+
const element = ref?.current;
|
|
25625
|
+
if (!element) {
|
|
25626
|
+
setContentMounted(false);
|
|
25627
|
+
return;
|
|
25628
|
+
}
|
|
25629
|
+
// The popup is still on screen while it plays its exit transition;
|
|
25630
|
+
// emptying it right away would show that transition running on a
|
|
25631
|
+
// blank surface.
|
|
25632
|
+
whenTransitionSettles(element, () => {
|
|
25633
|
+
if (openController.opened) {
|
|
25634
|
+
// reopened while it was leaving — the content it holds is the
|
|
25635
|
+
// one that open just asked for
|
|
25636
|
+
return;
|
|
25637
|
+
}
|
|
25638
|
+
setContentMounted(false);
|
|
25639
|
+
});
|
|
25640
|
+
}
|
|
25641
|
+
: null;
|
|
25450
25642
|
useLayoutEffect(() => {
|
|
25451
25643
|
if (mountWhenClosed) {
|
|
25452
25644
|
setContentMounted(true);
|
|
@@ -25728,147 +25920,36 @@ const popupCss = /* css */ `
|
|
|
25728
25920
|
`;
|
|
25729
25921
|
|
|
25730
25922
|
/**
|
|
25731
|
-
*
|
|
25732
|
-
*
|
|
25733
|
-
* of which of the two owns it.
|
|
25734
|
-
*/
|
|
25735
|
-
|
|
25736
|
-
|
|
25737
|
-
/**
|
|
25738
|
-
* Disables pointer-events on `el` until its current CSS transition settles
|
|
25739
|
-
* (via `transitionend`, with a safety `setTimeout` fallback matching the
|
|
25740
|
-
* longest `transition-duration` in case nothing actually transitions or an
|
|
25741
|
-
* event is missed) — avoids the cursor changing/something becoming
|
|
25742
|
-
* clickable while the popup is still visually moving into or out of place.
|
|
25743
|
-
*
|
|
25744
|
-
* Returns a "cancel" function: doesn't restore pointer-events (a fresh call
|
|
25745
|
-
* for the next open/close is about to set its own state) — only prevents
|
|
25746
|
-
* this stale instance's `transitionend` listener/timeout from firing later
|
|
25747
|
-
* and clobbering that fresh state.
|
|
25748
|
-
*/
|
|
25749
|
-
const suppressPointerEventsDuringTransition = (el) => {
|
|
25750
|
-
el.style.pointerEvents = "none";
|
|
25751
|
-
let settled = false;
|
|
25752
|
-
const onTransitionEnd = (transitionEvent) => {
|
|
25753
|
-
if (transitionEvent.target === el) {
|
|
25754
|
-
finish();
|
|
25755
|
-
}
|
|
25756
|
-
};
|
|
25757
|
-
const finish = () => {
|
|
25758
|
-
if (settled) {
|
|
25759
|
-
return;
|
|
25760
|
-
}
|
|
25761
|
-
settled = true;
|
|
25762
|
-
el.style.pointerEvents = "";
|
|
25763
|
-
el.removeEventListener("transitionend", onTransitionEnd);
|
|
25764
|
-
clearTimeout(safetyTimeoutId);
|
|
25765
|
-
};
|
|
25766
|
-
el.addEventListener("transitionend", onTransitionEnd);
|
|
25767
|
-
const durationsInSeconds = getComputedStyle(el)
|
|
25768
|
-
.transitionDuration.split(",")
|
|
25769
|
-
.map((value) => parseFloat(value) || 0);
|
|
25770
|
-
const longestDurationMs = Math.max(0, ...durationsInSeconds) * 1000;
|
|
25771
|
-
const safetyTimeoutId = setTimeout(finish, longestDurationMs + 50);
|
|
25772
|
-
return () => {
|
|
25773
|
-
if (settled) {
|
|
25774
|
-
return;
|
|
25775
|
-
}
|
|
25776
|
-
settled = true;
|
|
25777
|
-
el.removeEventListener("transitionend", onTransitionEnd);
|
|
25778
|
-
clearTimeout(safetyTimeoutId);
|
|
25779
|
-
};
|
|
25780
|
-
};
|
|
25781
|
-
|
|
25782
|
-
/**
|
|
25783
|
-
* Hides the backdrop, deferring until the browser's matching "click" fires
|
|
25784
|
-
* when `closeEvent` was triggered by a mousedown (see popover.jsx's top
|
|
25785
|
-
* comment for why) — same capture-phase-on-document pattern as
|
|
25786
|
-
* armSuppressNextOpenRequest in open_controller.js, which a plain timeout
|
|
25787
|
-
* can't safely replace: mouseup (and the click that follows it) can land an
|
|
25788
|
-
* arbitrarily long time after mousedown (the user is still holding the
|
|
25789
|
-
* button down), so a short timeout can fire first and hide the backdrop
|
|
25790
|
-
* before its own click ever arrives. A capture-phase listener on document
|
|
25791
|
-
* fires for every click regardless of what any bubble-phase handler does
|
|
25792
|
-
* downstream, so no fallback timer is needed.
|
|
25793
|
-
*
|
|
25794
|
-
* `hide` is the caller's own way to actually hide the backdrop
|
|
25795
|
-
* (`hidePopover()` for a top-layer backdrop, a plain `style.display = "none"`
|
|
25796
|
-
* for a plain div) — this helper only owns the mousedown/click timing.
|
|
25923
|
+
* Holding a box at the size it has right now — what the `sizing="frozen"` prop
|
|
25924
|
+
* on Dialog, Popover and SlideContainer is made of.
|
|
25797
25925
|
*
|
|
25798
|
-
*
|
|
25799
|
-
*
|
|
25926
|
+
* The need: a surface being used is a frame that has been put down. What moves
|
|
25927
|
+
* inside it is its content, not the frame — a list one empties by acting on it
|
|
25928
|
+
* (marking as read, archiving) must not resize the box under the finger, or the
|
|
25929
|
+
* next row moves while it is being aimed at.
|
|
25800
25930
|
*/
|
|
25801
|
-
const armPointerDownOutsideClose = (closeEvent, hide) => {
|
|
25802
|
-
const mousedownEvent = findEvent(closeEvent, "mousedown");
|
|
25803
|
-
if (!mousedownEvent) {
|
|
25804
|
-
hide();
|
|
25805
|
-
return undefined;
|
|
25806
|
-
}
|
|
25807
|
-
const onClick = () => {
|
|
25808
|
-
document.removeEventListener("click", onClick, { capture: true });
|
|
25809
|
-
hide();
|
|
25810
|
-
};
|
|
25811
|
-
document.addEventListener("click", onClick, { capture: true });
|
|
25812
|
-
return () => {
|
|
25813
|
-
document.removeEventListener("click", onClick, { capture: true });
|
|
25814
|
-
};
|
|
25815
|
-
};
|
|
25816
25931
|
|
|
25817
25932
|
/**
|
|
25818
|
-
*
|
|
25819
|
-
*
|
|
25820
|
-
*
|
|
25821
|
-
*
|
|
25822
|
-
*
|
|
25823
|
-
*
|
|
25824
|
-
*
|
|
25825
|
-
*
|
|
25826
|
-
*
|
|
25827
|
-
*
|
|
25828
|
-
* anchor, it moves/grows up, away from the anchor (which sits below it).
|
|
25829
|
-
*
|
|
25830
|
-
* "inset-*"/"center" contribute no direction on their axis either way.
|
|
25933
|
+
* Read back through getComputedStyle rather than offsetWidth/offsetHeight: the
|
|
25934
|
+
* used values honour whatever `box-sizing` is in effect, so writing them back
|
|
25935
|
+
* reproduces exactly the box that was measured, to the subpixel. Neither is
|
|
25936
|
+
* affected by a transform, so this is safe to call while an entrance animation
|
|
25937
|
+
* is scaling the box.
|
|
25938
|
+
*
|
|
25939
|
+
* `width`/`height`, never `min-width`/`min-height`: a `max-*` — the caller's
|
|
25940
|
+
* own, or the container ceiling a popup already computes for itself — must keep
|
|
25941
|
+
* winning, so a box frozen at 500px on a phone held upright still fits once it
|
|
25942
|
+
* is turned.
|
|
25831
25943
|
*/
|
|
25832
|
-
const
|
|
25833
|
-
const
|
|
25834
|
-
|
|
25835
|
-
|
|
25836
|
-
? "up"
|
|
25837
|
-
: "top"
|
|
25838
|
-
: y === "bottom"
|
|
25839
|
-
? prefix === "expand"
|
|
25840
|
-
? "down"
|
|
25841
|
-
: "bottom"
|
|
25842
|
-
: null;
|
|
25843
|
-
const xWord = x === "left" ? "left" : x === "right" ? "right" : null;
|
|
25844
|
-
if (!yWord && !xWord) {
|
|
25845
|
-
return null;
|
|
25846
|
-
}
|
|
25847
|
-
return yWord && xWord
|
|
25848
|
-
? `${prefix}-${yWord}-${xWord}`
|
|
25849
|
-
: `${prefix}-${yWord || xWord}`;
|
|
25944
|
+
const freezeSize = (el) => {
|
|
25945
|
+
const { width, height } = getComputedStyle(el);
|
|
25946
|
+
el.style.width = width;
|
|
25947
|
+
el.style.height = height;
|
|
25850
25948
|
};
|
|
25851
25949
|
|
|
25852
|
-
|
|
25853
|
-
|
|
25854
|
-
|
|
25855
|
-
* (both positionArea axes overlapping — there's no sensible direction to
|
|
25856
|
-
* slide from in that case). "sliding" otherwise. `anchor` is `undefined`
|
|
25857
|
-
* for any no-anchor/docked case (Dialog always, Popover's own custom
|
|
25858
|
-
* renderer when there's no real anchor), so this collapses to "scaling"
|
|
25859
|
-
* there only for the dead-center case, "sliding" otherwise. The two
|
|
25860
|
-
* "overlapping" booleans below describe the *positionArea* itself (a bare
|
|
25861
|
-
* word vs. "inset-"/"center"), not anything about the anchor — they'd
|
|
25862
|
-
* mean exactly the same thing even with no anchor at all, since it's the
|
|
25863
|
-
* position strategy, not the anchor, that decides whether there's a
|
|
25864
|
-
* direction to slide from.
|
|
25865
|
-
*/
|
|
25866
|
-
const resolveAutoAnimationKind = (anchor, parsedPositionArea) => {
|
|
25867
|
-
const yIsOverlapping =
|
|
25868
|
-
parsedPositionArea.y !== "top" && parsedPositionArea.y !== "bottom";
|
|
25869
|
-
const xIsOverlapping =
|
|
25870
|
-
parsedPositionArea.x !== "left" && parsedPositionArea.x !== "right";
|
|
25871
|
-
return anchor || (yIsOverlapping && xIsOverlapping) ? "scaling" : "sliding";
|
|
25950
|
+
const unfreezeSize = (el) => {
|
|
25951
|
+
el.style.width = "";
|
|
25952
|
+
el.style.height = "";
|
|
25872
25953
|
};
|
|
25873
25954
|
|
|
25874
25955
|
installImportMetaCssBuild(import.meta);/**
|
|
@@ -26311,6 +26392,19 @@ const css$V = /* css */`
|
|
|
26311
26392
|
* @param {string} [props.minHeight] - Maps to `--dialog-min-height`, same
|
|
26312
26393
|
* clamping as `minWidth`.
|
|
26313
26394
|
* @param {string} [props.maxHeight] - Maps to `--dialog-max-height`.
|
|
26395
|
+
* @param {"auto"|"frozen"} [props.sizing="auto"] - `"auto"`: the dialog follows
|
|
26396
|
+
* its content for as long as it stays open. `"frozen"`: it is measured once
|
|
26397
|
+
* and held at that size until it closes — what no longer fits (or no longer
|
|
26398
|
+
* fills it) is the scroll's business. For a surface acted upon while it is
|
|
26399
|
+
* open: marking a notification as read, emptying a queue, swapping between
|
|
26400
|
+
* two slides of different heights — the row being aimed at must not move
|
|
26401
|
+
* under the finger. The measure is taken at the first render where this says
|
|
26402
|
+
* `"frozen"`, so a dialog opening on skeletons can say
|
|
26403
|
+
* `sizing={loading ? "auto" : "frozen"}` and be measured once the real
|
|
26404
|
+
* content is there. The freeze writes a `height`/`width`, never a `min-*`:
|
|
26405
|
+
* `maxHeight`/`maxWidth` and the container ceiling keep winning, so a frozen
|
|
26406
|
+
* dialog still fits when the phone is turned. Closing releases it — the next
|
|
26407
|
+
* opening measures again.
|
|
26314
26408
|
* @param {number} [props.tabIndex=-1] - Set on the dialog element itself so
|
|
26315
26409
|
* `autoFocus="last-resort"` below has somewhere to land when the dialog has
|
|
26316
26410
|
* no other focusable descendant of its own.
|
|
@@ -26338,6 +26432,11 @@ const css$V = /* css */`
|
|
|
26338
26432
|
* content something depends on while the popup is still closed: a value read
|
|
26339
26433
|
* off it, fields a surrounding form collects on submit, a size measured from
|
|
26340
26434
|
* outside.
|
|
26435
|
+
* @param {boolean} [props.unmountWhenClosed] - Throws `children` away once the
|
|
26436
|
+
* popup has finished closing (see popup_content_mount.js). For content whose
|
|
26437
|
+
* fresh state is its initial state: an uncontrolled field seeded from a
|
|
26438
|
+
* `defaultValue` that changed while the popup was closed. Ignored when
|
|
26439
|
+
* `mountWhenClosed` is set.
|
|
26341
26440
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
26342
26441
|
*/
|
|
26343
26442
|
const Dialog = props => {
|
|
@@ -26515,6 +26614,9 @@ const useDialogProps = props => {
|
|
|
26515
26614
|
// actually makes "capture"/"none" behave the same way here too.
|
|
26516
26615
|
pointerInteractionOutsideEffect = "close",
|
|
26517
26616
|
scrollCapture: scrollCaptureProp,
|
|
26617
|
+
// "auto" (default) → the dialog follows its content. "frozen" → measured
|
|
26618
|
+
// once, held at that size while open. See this prop's own JSDoc above.
|
|
26619
|
+
sizing = "auto",
|
|
26518
26620
|
animation,
|
|
26519
26621
|
// Only ever affects --anchor-width/--anchor-height (see this file's top
|
|
26520
26622
|
// comment) — Dialog's own positioning is never relative to it.
|
|
@@ -26531,11 +26633,13 @@ const useDialogProps = props => {
|
|
|
26531
26633
|
onKeyDown,
|
|
26532
26634
|
children: childrenProp,
|
|
26533
26635
|
mountWhenClosed,
|
|
26636
|
+
unmountWhenClosed,
|
|
26534
26637
|
...rest
|
|
26535
26638
|
} = props;
|
|
26536
|
-
const children = usePopupContentMount(openController, {
|
|
26639
|
+
const children = usePopupContentMount(openController, props.ref, {
|
|
26537
26640
|
children: childrenProp,
|
|
26538
|
-
mountWhenClosed
|
|
26641
|
+
mountWhenClosed,
|
|
26642
|
+
unmountWhenClosed
|
|
26539
26643
|
});
|
|
26540
26644
|
const isModal = layer === "top";
|
|
26541
26645
|
const ref = props.ref;
|
|
@@ -26577,6 +26681,21 @@ const useDialogProps = props => {
|
|
|
26577
26681
|
detail: {}
|
|
26578
26682
|
}));
|
|
26579
26683
|
}, [positionArea, marginWithContainer]);
|
|
26684
|
+
// The freeze is taken where the value changes, not only at open time: a
|
|
26685
|
+
// dialog showing skeletons first says sizing="auto" until its content is
|
|
26686
|
+
// there, and would otherwise be held at the size of the waiting state.
|
|
26687
|
+
// Opening while already "frozen" is openEffect's own case.
|
|
26688
|
+
useEffect(() => {
|
|
26689
|
+
const dialogEl = ref.current;
|
|
26690
|
+
if (!dialogEl || !openController.opened) {
|
|
26691
|
+
return;
|
|
26692
|
+
}
|
|
26693
|
+
if (sizing === "frozen") {
|
|
26694
|
+
freezeSize(dialogEl);
|
|
26695
|
+
} else {
|
|
26696
|
+
unfreezeSize(dialogEl);
|
|
26697
|
+
}
|
|
26698
|
+
}, [sizing]);
|
|
26580
26699
|
const positionAreaParseResult = parsePositionArea(positionArea);
|
|
26581
26700
|
if (!positionAreaParseResult) {
|
|
26582
26701
|
console.warn(`Dialog: invalid positionArea="${positionArea}"`);
|
|
@@ -26808,6 +26927,13 @@ const useDialogProps = props => {
|
|
|
26808
26927
|
// navi_position_change on every call) — nothing to do here.
|
|
26809
26928
|
};
|
|
26810
26929
|
positionDialog();
|
|
26930
|
+
if (sizing === "frozen") {
|
|
26931
|
+
// After positionDialog: the caps it writes
|
|
26932
|
+
// (--container-position-remaining-*) are part of what decides the size
|
|
26933
|
+
// being taken, so measuring before it would freeze a box the dialog
|
|
26934
|
+
// never actually had.
|
|
26935
|
+
freezeSize(dialogEl);
|
|
26936
|
+
}
|
|
26811
26937
|
|
|
26812
26938
|
// Reposition on the same triggers Popover's own visibleRectEffect
|
|
26813
26939
|
// already reacts to generically — window resize/scroll/visual-viewport
|
|
@@ -26939,6 +27065,9 @@ const useDialogProps = props => {
|
|
|
26939
27065
|
// property is actually present — harmless the rest of the time.
|
|
26940
27066
|
dialogEl.setAttribute("navi-hidden", "");
|
|
26941
27067
|
dialogEl.close();
|
|
27068
|
+
// The freeze only ever holds for one opening: the next one has its own
|
|
27069
|
+
// content to be measured against.
|
|
27070
|
+
unfreezeSize(dialogEl);
|
|
26942
27071
|
cancelOpenInteractionSuppression?.();
|
|
26943
27072
|
if (hasCssTransitionAnimation) {
|
|
26944
27073
|
suppressPointerEventsDuringTransition(dialogEl);
|
|
@@ -27516,6 +27645,18 @@ const css$U = /* css */`
|
|
|
27516
27645
|
* @param {string} [props.minHeight] - Maps to `--popover-min-height`, same
|
|
27517
27646
|
* clamping as `minWidth`.
|
|
27518
27647
|
* @param {string} [props.maxHeight] - Maps to `--popover-max-height`.
|
|
27648
|
+
* @param {"auto"|"frozen"} [props.sizing="auto"] - `"auto"`: the popover
|
|
27649
|
+
* follows its content for as long as it stays open. `"frozen"`: it is
|
|
27650
|
+
* measured once and held at that size until it closes — what no longer fits
|
|
27651
|
+
* (or no longer fills it) is the scroll's business. For a surface acted upon
|
|
27652
|
+
* while it is open: emptying a list, swapping between two panels of
|
|
27653
|
+
* different heights — the row being aimed at must not move under the
|
|
27654
|
+
* pointer. The measure is taken at the first render where this says
|
|
27655
|
+
* `"frozen"`, so a popover opening on skeletons can say
|
|
27656
|
+
* `sizing={loading ? "auto" : "frozen"}` and be measured once the real
|
|
27657
|
+
* content is there. The freeze writes a `height`/`width`, never a `min-*`:
|
|
27658
|
+
* `maxHeight`/`maxWidth` and the container ceiling keep winning. Closing
|
|
27659
|
+
* releases it — the next opening measures again.
|
|
27519
27660
|
* @param {number} [props.tabIndex=-1] - Set on the popover element itself
|
|
27520
27661
|
* so `autoFocus="last-resort"` below has somewhere to land when the popover
|
|
27521
27662
|
* has no other focusable descendant of its own.
|
|
@@ -27546,6 +27687,11 @@ const css$U = /* css */`
|
|
|
27546
27687
|
* content something depends on while the popup is still closed: a value read
|
|
27547
27688
|
* off it, fields a surrounding form collects on submit, a size measured from
|
|
27548
27689
|
* outside.
|
|
27690
|
+
* @param {boolean} [props.unmountWhenClosed] - Throws `children` away once the
|
|
27691
|
+
* popup has finished closing (see popup_content_mount.js). For content whose
|
|
27692
|
+
* fresh state is its initial state: an uncontrolled field seeded from a
|
|
27693
|
+
* `defaultValue` that changed while the popup was closed. Ignored when
|
|
27694
|
+
* `mountWhenClosed` is set.
|
|
27549
27695
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
27550
27696
|
*/
|
|
27551
27697
|
const Popover = props => {
|
|
@@ -27716,6 +27862,9 @@ const usePopoverProps = props => {
|
|
|
27716
27862
|
pointerInteractionOutsideEffect = "none",
|
|
27717
27863
|
scrollCapture,
|
|
27718
27864
|
focusCapture,
|
|
27865
|
+
// "auto" (default) → the popover follows its content. "frozen" → measured
|
|
27866
|
+
// once, held at that size while open. See this prop's own JSDoc above.
|
|
27867
|
+
sizing = "auto",
|
|
27719
27868
|
animation,
|
|
27720
27869
|
anchor,
|
|
27721
27870
|
anchorCustomEventDetail = "override",
|
|
@@ -27732,11 +27881,13 @@ const usePopoverProps = props => {
|
|
|
27732
27881
|
onKeyDown,
|
|
27733
27882
|
children: childrenProp,
|
|
27734
27883
|
mountWhenClosed,
|
|
27884
|
+
unmountWhenClosed,
|
|
27735
27885
|
...rest
|
|
27736
27886
|
} = props;
|
|
27737
|
-
const children = usePopupContentMount(openController, {
|
|
27887
|
+
const children = usePopupContentMount(openController, props.ref, {
|
|
27738
27888
|
children: childrenProp,
|
|
27739
|
-
mountWhenClosed
|
|
27889
|
+
mountWhenClosed,
|
|
27890
|
+
unmountWhenClosed
|
|
27740
27891
|
});
|
|
27741
27892
|
const isTopLayer = layer === "top";
|
|
27742
27893
|
const ref = props.ref;
|
|
@@ -27774,6 +27925,21 @@ const usePopoverProps = props => {
|
|
|
27774
27925
|
detail: {}
|
|
27775
27926
|
}));
|
|
27776
27927
|
}, [positionArea, positionAreaFixed, positionAreaWhenAnchorIsInvalid, marginWithAnchor, marginWithContainer]);
|
|
27928
|
+
// The freeze is taken where the value changes, not only at open time: a
|
|
27929
|
+
// popover showing skeletons first says sizing="auto" until its content is
|
|
27930
|
+
// there, and would otherwise be held at the size of the waiting state.
|
|
27931
|
+
// Opening while already "frozen" is openEffect's own case.
|
|
27932
|
+
useEffect(() => {
|
|
27933
|
+
const popoverEl = ref.current;
|
|
27934
|
+
if (!popoverEl || !openController.opened) {
|
|
27935
|
+
return;
|
|
27936
|
+
}
|
|
27937
|
+
if (sizing === "frozen") {
|
|
27938
|
+
freezeSize(popoverEl);
|
|
27939
|
+
} else {
|
|
27940
|
+
unfreezeSize(popoverEl);
|
|
27941
|
+
}
|
|
27942
|
+
}, [sizing]);
|
|
27777
27943
|
// The custom renderer's own starting-hidden state is a stylesheet default
|
|
27778
27944
|
// now (&:not([popover]) { display: none } on .navi_popover/
|
|
27779
27945
|
// .navi_popover_backdrop above) rather than set here imperatively — a
|
|
@@ -28170,6 +28336,14 @@ const usePopoverProps = props => {
|
|
|
28170
28336
|
addCleanup(() => {
|
|
28171
28337
|
rectEffect.disconnect();
|
|
28172
28338
|
});
|
|
28339
|
+
if (sizing === "frozen") {
|
|
28340
|
+
// After rectEffect's own setup, which has already placed the popover:
|
|
28341
|
+
// the caps that placement writes
|
|
28342
|
+
// (--container-position-remaining-*) are part of what decides the size
|
|
28343
|
+
// being taken, so measuring before it would freeze a box the popover
|
|
28344
|
+
// never actually had.
|
|
28345
|
+
freezeSize(popoverEl);
|
|
28346
|
+
}
|
|
28173
28347
|
|
|
28174
28348
|
// "sliding"/"expanding" need a concrete direction (see
|
|
28175
28349
|
// resolveDirectionValue) — resolved here, once, now that rectEffect's
|
|
@@ -28265,6 +28439,9 @@ const usePopoverProps = props => {
|
|
|
28265
28439
|
} else {
|
|
28266
28440
|
openLocalPopoverCount = Math.max(0, openLocalPopoverCount - 1);
|
|
28267
28441
|
}
|
|
28442
|
+
// The freeze only ever holds for one opening: the next one has its own
|
|
28443
|
+
// content to be measured against.
|
|
28444
|
+
unfreezeSize(popoverEl);
|
|
28268
28445
|
// Not interactive while it's leaving either — cancel the open side's
|
|
28269
28446
|
// still-pending suppression first, since a fresh one below fully
|
|
28270
28447
|
// replaces it (nothing ever needs to cancel this one in turn: a
|
|
@@ -34769,7 +34946,14 @@ const css$R = /* css */`
|
|
|
34769
34946
|
|
|
34770
34947
|
&::view-transition-old(navi-route-travel),
|
|
34771
34948
|
&::view-transition-new(navi-route-travel) {
|
|
34772
|
-
|
|
34949
|
+
/* Each picture at the size it was taken at: a page is not resized by the
|
|
34950
|
+
page it crosses. Told to fill a box whose height is being animated, a
|
|
34951
|
+
picture is STRETCHED with it — the page leaving is then seen squashing
|
|
34952
|
+
upwards, or zooming, over the length of the travel, when all it is
|
|
34953
|
+
doing is walking off the edge. */
|
|
34954
|
+
height: auto;
|
|
34955
|
+
object-fit: none;
|
|
34956
|
+
object-position: top left;
|
|
34773
34957
|
/* The default cross-fade, dropped: two pages sliding past each other are
|
|
34774
34958
|
two solid things, and seeing through one to the other says they are the
|
|
34775
34959
|
same page changing its mind. */
|
|
@@ -34785,7 +34969,21 @@ const css$R = /* css */`
|
|
|
34785
34969
|
overflow: clip;
|
|
34786
34970
|
}
|
|
34787
34971
|
&::view-transition-group(navi-route-travel) {
|
|
34972
|
+
/* The window the two pictures are seen through, held still for the whole
|
|
34973
|
+
travel at the taller of the two boxes (see holdTravelHeight): the group
|
|
34974
|
+
is what CLIPS, and the browser animates its height from the box being
|
|
34975
|
+
left to the box arriving — so the window shrinks under the pictures and
|
|
34976
|
+
cuts the page leaving from the bottom, progressively. The box does end
|
|
34977
|
+
up at the arriving page's height, and that is right; what must not
|
|
34978
|
+
happen is the user watching it get there.
|
|
34979
|
+
|
|
34980
|
+
The height is held by dropping the group's animation rather than by
|
|
34981
|
+
winning against it with !important — which also drops its position
|
|
34982
|
+
animation, fine while a travel box stands in the same place from one
|
|
34983
|
+
route to the next. */
|
|
34984
|
+
height: var(--navi-route-travel-height);
|
|
34788
34985
|
animation-duration: var(--navi-route-travel-duration, 300ms);
|
|
34986
|
+
animation-name: none;
|
|
34789
34987
|
}
|
|
34790
34988
|
}
|
|
34791
34989
|
|
|
@@ -35015,18 +35213,26 @@ const RouteTravel = ({
|
|
|
35015
35213
|
document.documentElement.setAttribute(DRAGGED_ATTRIBUTE, "");
|
|
35016
35214
|
}
|
|
35017
35215
|
routeAskedForRef.current = route;
|
|
35216
|
+
// The box as it stands before anything moves: rendering is held, so this is
|
|
35217
|
+
// still the page being left (see holdTravelHeight).
|
|
35218
|
+
const heightBefore = elementRef.current.getBoundingClientRect().height;
|
|
35018
35219
|
// The hold a navigation already took, if this travel is the answer to one:
|
|
35019
35220
|
// taking another would be taking a hold on a page that is holding still.
|
|
35020
35221
|
const releaseRendering = renderingHeldForRouting || holdRendering();
|
|
35021
35222
|
renderingHeldForRouting = null;
|
|
35022
35223
|
// The picture the browser is about to take must be of the page that was
|
|
35023
35224
|
// asked for, and a route matching is not yet a page rendered.
|
|
35024
|
-
const viewTransition = startViewTransition(
|
|
35025
|
-
|
|
35026
|
-
|
|
35027
|
-
|
|
35028
|
-
|
|
35029
|
-
|
|
35225
|
+
const viewTransition = startViewTransition(async () => {
|
|
35226
|
+
await whileRouteRenders(route, async () => {
|
|
35227
|
+
releaseRendering();
|
|
35228
|
+
if (change) {
|
|
35229
|
+
await change();
|
|
35230
|
+
}
|
|
35231
|
+
});
|
|
35232
|
+
// The page arriving is in the DOM and the transition has not started
|
|
35233
|
+
// playing: the one moment both boxes can be known.
|
|
35234
|
+
holdTravelHeight(elementRef.current, heightBefore);
|
|
35235
|
+
});
|
|
35030
35236
|
travel.viewTransition = viewTransition;
|
|
35031
35237
|
if (scrub) {
|
|
35032
35238
|
// Said only now: the release has to have something to let go of, and the
|
|
@@ -35320,6 +35526,7 @@ const RouteTravel = ({
|
|
|
35320
35526
|
document.documentElement.removeAttribute(TRAVEL_ATTRIBUTE);
|
|
35321
35527
|
document.documentElement.removeAttribute(DRAGGED_ATTRIBUTE);
|
|
35322
35528
|
document.documentElement.removeAttribute(TURNED_ATTRIBUTE);
|
|
35529
|
+
releaseTravelHeight();
|
|
35323
35530
|
}
|
|
35324
35531
|
};
|
|
35325
35532
|
|
|
@@ -35701,6 +35908,24 @@ const releaseHold = travel => {
|
|
|
35701
35908
|
travelHoldingPictures = null;
|
|
35702
35909
|
document.documentElement.removeAttribute(HOLD_ATTRIBUTE);
|
|
35703
35910
|
};
|
|
35911
|
+
const TRAVEL_HEIGHT_PROPERTY = "--navi-route-travel-height";
|
|
35912
|
+
// The height the group is held at for the whole travel: the taller of the two
|
|
35913
|
+
// boxes, so neither picture is ever cut. It cannot be said in CSS — neither box
|
|
35914
|
+
// is knowable there — and it cannot be measured from one side alone: a page
|
|
35915
|
+
// arriving shorter than the one it replaces would cut the one leaving, a page
|
|
35916
|
+
// arriving taller would be cut itself.
|
|
35917
|
+
const holdTravelHeight = (element, heightBefore) => {
|
|
35918
|
+
const heightAfter = element.getBoundingClientRect().height;
|
|
35919
|
+
const height = heightBefore > heightAfter ? heightBefore : heightAfter;
|
|
35920
|
+
document.documentElement.style.setProperty(TRAVEL_HEIGHT_PROPERTY, `${height}px`);
|
|
35921
|
+
};
|
|
35922
|
+
// The live layout takes the box back. A discontinuity by construction — the
|
|
35923
|
+
// group stands at the held height, the box is at the new one — and an invisible
|
|
35924
|
+
// one: the page arriving is fully in place, and the strip below it that the
|
|
35925
|
+
// group still covers shows the page leaving only while it is still on screen.
|
|
35926
|
+
const releaseTravelHeight = () => {
|
|
35927
|
+
document.documentElement.style.removeProperty(TRAVEL_HEIGHT_PROPERTY);
|
|
35928
|
+
};
|
|
35704
35929
|
|
|
35705
35930
|
// The browser does not take the picture of the page being left when a
|
|
35706
35931
|
// transition is ASKED for — it takes it at the next frame, just before running
|
|
@@ -45593,6 +45818,18 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
|
|
|
45593
45818
|
* scrolling the document — so a map that travels vertically has to be told
|
|
45594
45819
|
* (`travelByScroll` / `"y"` / `"xy"`) before a wheel moves it.
|
|
45595
45820
|
* @param {string} [props.duration="300ms"] - how long a slide change takes.
|
|
45821
|
+
* @param {"largest"|"frozen"} [props.sizing="largest"] - what decides the size
|
|
45822
|
+
* of the box. "largest": the largest slide, at every moment — the box follows
|
|
45823
|
+
* whatever the slides do. "frozen": the box is measured once and kept at that
|
|
45824
|
+
* size, and what no longer fits (or no longer fills it) is the scroll's
|
|
45825
|
+
* business. For slides one ACTS on rather than merely reads: a row marked as
|
|
45826
|
+
* read leaves one panel for the other, the tallest slide is not the same one
|
|
45827
|
+
* anymore, and with "largest" the box resizes under the finger aiming at the
|
|
45828
|
+
* next row. The measure is taken at the first render where this says
|
|
45829
|
+
* "frozen", so a container opening on skeletons says
|
|
45830
|
+
* `sizing={loading ? "largest" : "frozen"}` and is measured once the real
|
|
45831
|
+
* content is there. The freeze holds against what happens INSIDE the box, not
|
|
45832
|
+
* against the room it is given: a window resize measures again.
|
|
45596
45833
|
*/
|
|
45597
45834
|
const SlideContainer = ({
|
|
45598
45835
|
layout = "row",
|
|
@@ -45606,6 +45843,7 @@ const SlideContainer = ({
|
|
|
45606
45843
|
travelByDrag = true,
|
|
45607
45844
|
travelByScroll = "x",
|
|
45608
45845
|
duration = "300ms",
|
|
45846
|
+
sizing = "largest",
|
|
45609
45847
|
children,
|
|
45610
45848
|
...rest
|
|
45611
45849
|
}) => {
|
|
@@ -45740,6 +45978,31 @@ const SlideContainer = ({
|
|
|
45740
45978
|
};
|
|
45741
45979
|
}, [noTravel]);
|
|
45742
45980
|
|
|
45981
|
+
// The box, held at the size it has right now (sizing="frozen"): what the
|
|
45982
|
+
// slides do afterwards moves their own content and not this box. Taken where
|
|
45983
|
+
// the value changes rather than at mount alone, so a container opening on
|
|
45984
|
+
// skeletons is measured on the real thing (see the prop's own doc).
|
|
45985
|
+
useLayoutEffect(() => {
|
|
45986
|
+
if (sizing !== "frozen") {
|
|
45987
|
+
return undefined;
|
|
45988
|
+
}
|
|
45989
|
+
const containerEl = containerRef.current;
|
|
45990
|
+
freezeSize(containerEl);
|
|
45991
|
+
// The freeze is about the content, never about the room: a box frozen on a
|
|
45992
|
+
// phone held upright has to fit once it is turned, and one frozen in a
|
|
45993
|
+
// window has to follow that window. So the measure is taken again whenever
|
|
45994
|
+
// the room changes — the only moment the box is allowed to resize.
|
|
45995
|
+
const onWindowResize = () => {
|
|
45996
|
+
unfreezeSize(containerEl);
|
|
45997
|
+
freezeSize(containerEl);
|
|
45998
|
+
};
|
|
45999
|
+
window.addEventListener("resize", onWindowResize);
|
|
46000
|
+
return () => {
|
|
46001
|
+
window.removeEventListener("resize", onWindowResize);
|
|
46002
|
+
unfreezeSize(containerEl);
|
|
46003
|
+
};
|
|
46004
|
+
}, [sizing]);
|
|
46005
|
+
|
|
45743
46006
|
// What the user was doing on each slide, so coming back comes back to it. The
|
|
45744
46007
|
// ways out are left out on purpose: pressing one is how one LEAVES a slide,
|
|
45745
46008
|
// and remembering it would mean coming back to the exit rather than to the
|
|
@@ -48151,6 +48414,10 @@ const css$z = /* css */`
|
|
|
48151
48414
|
* @param {string} [props.minWidth] - Forwarded as-is.
|
|
48152
48415
|
* @param {string} [props.minHeight] - Forwarded as-is.
|
|
48153
48416
|
* @param {string} [props.maxHeight] - Forwarded as-is.
|
|
48417
|
+
* @param {"auto"|"frozen"} [props.sizing] - Forwarded as-is to both, which
|
|
48418
|
+
* understand it identically: `"frozen"` holds the surface at the size it was
|
|
48419
|
+
* measured at while it stays open, so acting on what it contains moves the
|
|
48420
|
+
* content and not the surface. See either component's own doc.
|
|
48154
48421
|
* @param {boolean} [props.expand] - Dialog-mode only: shorthand for both
|
|
48155
48422
|
* `expandX`/`expandY` below. No effect in popover mode.
|
|
48156
48423
|
* @param {boolean} [props.expandX] - Dialog-mode only: stretches the dialog
|
|
@@ -48172,6 +48439,11 @@ const css$z = /* css */`
|
|
|
48172
48439
|
* content something depends on while the popup is still closed: a value read
|
|
48173
48440
|
* off it, fields a surrounding form collects on submit, a size measured from
|
|
48174
48441
|
* outside.
|
|
48442
|
+
* @param {boolean} [props.unmountWhenClosed] - Throws `children` away once the
|
|
48443
|
+
* popup has finished closing (see popup_content_mount.js). For content whose
|
|
48444
|
+
* fresh state is its initial state: an uncontrolled field seeded from a
|
|
48445
|
+
* `defaultValue` that changed while the popup was closed. Ignored when
|
|
48446
|
+
* `mountWhenClosed` is set.
|
|
48175
48447
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
48176
48448
|
*/
|
|
48177
48449
|
const Popup = props => {
|
|
@@ -51166,10 +51438,17 @@ const ListUI = props => {
|
|
|
51166
51438
|
// search fallback), otherwise an empty list is the "empty" state.
|
|
51167
51439
|
const searchFallbackShown = (allNoMatch || searching && itemCount === 0) && !searchFallbackDisabled;
|
|
51168
51440
|
const emptyFallbackShown = !searching && itemCount === 0 && !fallbackDisabled;
|
|
51441
|
+
// A loading state only holds the list on screen when it has something to
|
|
51442
|
+
// draw. A count of 0 (or no loadingFallback at all) says the list is known to
|
|
51443
|
+
// be empty before the response arrives, so the empty state can already be
|
|
51444
|
+
// shown — nothing jumps when the response lands, exactly as three skeletons
|
|
51445
|
+
// become three rows.
|
|
51446
|
+
const loadingPlaceholderShown = Boolean(loading) && Boolean(loadingFallback) && (loadingFallback !== "skeleton" || loadingSkeletonCount > 0);
|
|
51169
51447
|
// Hide the whole list — border included — when there is genuinely nothing to
|
|
51170
|
-
// show: no visible items AND no fallback message. Never while loading
|
|
51171
|
-
//
|
|
51172
|
-
|
|
51448
|
+
// show: no visible items AND no fallback message. Never while a loading
|
|
51449
|
+
// placeholder or an error message is on screen (they ARE the content to
|
|
51450
|
+
// display).
|
|
51451
|
+
const nothingToDisplay = !loadingPlaceholderShown && !error && noVisibleItems && !searchFallbackShown && !emptyFallbackShown;
|
|
51173
51452
|
|
|
51174
51453
|
// Placeholder content replaces the real children: an error message when the
|
|
51175
51454
|
// load failed (takes precedence), otherwise — while loading — whatever
|
|
@@ -51264,7 +51543,7 @@ const ListUI = props => {
|
|
|
51264
51543
|
fallbackShown: emptyFallbackShown,
|
|
51265
51544
|
searchFallback: searchFallback,
|
|
51266
51545
|
searchFallbackShown: searchFallbackShown,
|
|
51267
|
-
|
|
51546
|
+
loadingPlaceholderShown: loadingPlaceholderShown,
|
|
51268
51547
|
error: error,
|
|
51269
51548
|
searchNoMatchMode: searchNoMatchMode,
|
|
51270
51549
|
separator: separator,
|
|
@@ -51364,6 +51643,10 @@ const ListFirstResolver = props => {
|
|
|
51364
51643
|
* displays nothing. A list that knows how many rows it will have has no use
|
|
51365
51644
|
* for this — see `<List.Items count>`, whose not-yet-loaded rows are drawn
|
|
51366
51645
|
* as skeletons in place, one per row, virtualized like the rest.
|
|
51646
|
+
* @param {number} [props.loadingSkeletonCount=3]
|
|
51647
|
+
* How many placeholder rows `loadingFallback="skeleton"` draws. `0` says the
|
|
51648
|
+
* list is already known to be empty: the empty `fallback` shows right away
|
|
51649
|
+
* rather than an empty frame, so nothing moves when the response arrives.
|
|
51367
51650
|
* @param {"start"|"end"|number|{id: string, offset?: number}} [props.defaultScrolled="start"]
|
|
51368
51651
|
* Where the list opens, after which the user owns the scroll. `"end"` is a
|
|
51369
51652
|
* thread read backwards — the last rows are the ones to show, and the ones
|
|
@@ -51424,7 +51707,7 @@ const ListContent = ({
|
|
|
51424
51707
|
fallbackShown,
|
|
51425
51708
|
searchFallback,
|
|
51426
51709
|
searchFallbackShown,
|
|
51427
|
-
|
|
51710
|
+
loadingPlaceholderShown,
|
|
51428
51711
|
error,
|
|
51429
51712
|
searchNoMatchMode,
|
|
51430
51713
|
separator,
|
|
@@ -51448,7 +51731,7 @@ const ListContent = ({
|
|
|
51448
51731
|
fallbackShown: fallbackShown,
|
|
51449
51732
|
searchFallback: searchFallback,
|
|
51450
51733
|
searchFallbackShown: searchFallbackShown,
|
|
51451
|
-
|
|
51734
|
+
loadingPlaceholderShown: loadingPlaceholderShown,
|
|
51452
51735
|
error: error,
|
|
51453
51736
|
searchNoMatchMode: searchNoMatchMode,
|
|
51454
51737
|
separator: separator,
|
|
@@ -52736,7 +53019,7 @@ const UnorderedList = ({
|
|
|
52736
53019
|
fallbackShown,
|
|
52737
53020
|
searchFallback,
|
|
52738
53021
|
searchFallbackShown,
|
|
52739
|
-
|
|
53022
|
+
loadingPlaceholderShown,
|
|
52740
53023
|
error,
|
|
52741
53024
|
searchNoMatchMode,
|
|
52742
53025
|
separator,
|
|
@@ -52747,9 +53030,11 @@ const UnorderedList = ({
|
|
|
52747
53030
|
children,
|
|
52748
53031
|
...rest
|
|
52749
53032
|
}) => {
|
|
52750
|
-
// No empty/no-match message while loading or
|
|
52751
|
-
//
|
|
52752
|
-
|
|
53033
|
+
// No empty/no-match message while a loading placeholder or an error message
|
|
53034
|
+
// is on screen — that IS the content, even though no items are tracked yet.
|
|
53035
|
+
// A loading state drawing nothing keeps the message: an announced count of 0
|
|
53036
|
+
// already tells us the list is empty (see ListUI's loadingPlaceholderShown).
|
|
53037
|
+
const suppressFallback = loadingPlaceholderShown || Boolean(error);
|
|
52753
53038
|
return jsxs(Box, {
|
|
52754
53039
|
as: "ul",
|
|
52755
53040
|
flex: columns ? undefined : horizontal ? "x" : "y",
|
|
@@ -66660,5 +66945,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
66660
66945
|
})
|
|
66661
66946
|
});
|
|
66662
66947
|
|
|
66663
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
|
|
66948
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
|
|
66664
66949
|
//# sourceMappingURL=jsenv_navi.js.map
|