@jsenv/navi 0.29.24 → 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 +296 -168
- package/dist/jsenv_navi.js.map +16 -10
- package/docs/popup_open.md +185 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,8 @@ A capable `Table` component that handles what you'd expect from a spreadsheet-li
|
|
|
47
47
|
|
|
48
48
|
Dialogs, badges, details/collapsible, separators, keyboard shortcuts, popovers, copy-to-clipboard, and other utilities.
|
|
49
49
|
|
|
50
|
+
A `Dialog`/`Popover` owns whether it is open; what varies is the trigger — a button, a gesture, a piece of application state. Which one to use, and what each costs, is in [docs/popup_open.md](./docs/popup_open.md).
|
|
51
|
+
|
|
50
52
|
---
|
|
51
53
|
|
|
52
54
|
Named after Navi, the fairy guide from Zelda — it helps you navigate through the complexities of building modern web applications.
|
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);
|
|
@@ -25760,150 +25952,6 @@ const unfreezeSize = (el) => {
|
|
|
25760
25952
|
el.style.height = "";
|
|
25761
25953
|
};
|
|
25762
25954
|
|
|
25763
|
-
/**
|
|
25764
|
-
* Small, renderer-agnostic helpers shared by Popover and Dialog's own custom
|
|
25765
|
-
* (non-top-layer) renderers — operate on a plain DOM element, no knowledge
|
|
25766
|
-
* of which of the two owns it.
|
|
25767
|
-
*/
|
|
25768
|
-
|
|
25769
|
-
|
|
25770
|
-
/**
|
|
25771
|
-
* Disables pointer-events on `el` until its current CSS transition settles
|
|
25772
|
-
* (via `transitionend`, with a safety `setTimeout` fallback matching the
|
|
25773
|
-
* longest `transition-duration` in case nothing actually transitions or an
|
|
25774
|
-
* event is missed) — avoids the cursor changing/something becoming
|
|
25775
|
-
* clickable while the popup is still visually moving into or out of place.
|
|
25776
|
-
*
|
|
25777
|
-
* Returns a "cancel" function: doesn't restore pointer-events (a fresh call
|
|
25778
|
-
* for the next open/close is about to set its own state) — only prevents
|
|
25779
|
-
* this stale instance's `transitionend` listener/timeout from firing later
|
|
25780
|
-
* and clobbering that fresh state.
|
|
25781
|
-
*/
|
|
25782
|
-
const suppressPointerEventsDuringTransition = (el) => {
|
|
25783
|
-
el.style.pointerEvents = "none";
|
|
25784
|
-
let settled = false;
|
|
25785
|
-
const onTransitionEnd = (transitionEvent) => {
|
|
25786
|
-
if (transitionEvent.target === el) {
|
|
25787
|
-
finish();
|
|
25788
|
-
}
|
|
25789
|
-
};
|
|
25790
|
-
const finish = () => {
|
|
25791
|
-
if (settled) {
|
|
25792
|
-
return;
|
|
25793
|
-
}
|
|
25794
|
-
settled = true;
|
|
25795
|
-
el.style.pointerEvents = "";
|
|
25796
|
-
el.removeEventListener("transitionend", onTransitionEnd);
|
|
25797
|
-
clearTimeout(safetyTimeoutId);
|
|
25798
|
-
};
|
|
25799
|
-
el.addEventListener("transitionend", onTransitionEnd);
|
|
25800
|
-
const durationsInSeconds = getComputedStyle(el)
|
|
25801
|
-
.transitionDuration.split(",")
|
|
25802
|
-
.map((value) => parseFloat(value) || 0);
|
|
25803
|
-
const longestDurationMs = Math.max(0, ...durationsInSeconds) * 1000;
|
|
25804
|
-
const safetyTimeoutId = setTimeout(finish, longestDurationMs + 50);
|
|
25805
|
-
return () => {
|
|
25806
|
-
if (settled) {
|
|
25807
|
-
return;
|
|
25808
|
-
}
|
|
25809
|
-
settled = true;
|
|
25810
|
-
el.removeEventListener("transitionend", onTransitionEnd);
|
|
25811
|
-
clearTimeout(safetyTimeoutId);
|
|
25812
|
-
};
|
|
25813
|
-
};
|
|
25814
|
-
|
|
25815
|
-
/**
|
|
25816
|
-
* Hides the backdrop, deferring until the browser's matching "click" fires
|
|
25817
|
-
* when `closeEvent` was triggered by a mousedown (see popover.jsx's top
|
|
25818
|
-
* comment for why) — same capture-phase-on-document pattern as
|
|
25819
|
-
* armSuppressNextOpenRequest in open_controller.js, which a plain timeout
|
|
25820
|
-
* can't safely replace: mouseup (and the click that follows it) can land an
|
|
25821
|
-
* arbitrarily long time after mousedown (the user is still holding the
|
|
25822
|
-
* button down), so a short timeout can fire first and hide the backdrop
|
|
25823
|
-
* before its own click ever arrives. A capture-phase listener on document
|
|
25824
|
-
* fires for every click regardless of what any bubble-phase handler does
|
|
25825
|
-
* downstream, so no fallback timer is needed.
|
|
25826
|
-
*
|
|
25827
|
-
* `hide` is the caller's own way to actually hide the backdrop
|
|
25828
|
-
* (`hidePopover()` for a top-layer backdrop, a plain `style.display = "none"`
|
|
25829
|
-
* for a plain div) — this helper only owns the mousedown/click timing.
|
|
25830
|
-
*
|
|
25831
|
-
* Returns a disarm function (or undefined if hidden immediately), so a
|
|
25832
|
-
* fresh open can cancel a pending hide it's about to make redundant.
|
|
25833
|
-
*/
|
|
25834
|
-
const armPointerDownOutsideClose = (closeEvent, hide) => {
|
|
25835
|
-
const mousedownEvent = findEvent(closeEvent, "mousedown");
|
|
25836
|
-
if (!mousedownEvent) {
|
|
25837
|
-
hide();
|
|
25838
|
-
return undefined;
|
|
25839
|
-
}
|
|
25840
|
-
const onClick = () => {
|
|
25841
|
-
document.removeEventListener("click", onClick, { capture: true });
|
|
25842
|
-
hide();
|
|
25843
|
-
};
|
|
25844
|
-
document.addEventListener("click", onClick, { capture: true });
|
|
25845
|
-
return () => {
|
|
25846
|
-
document.removeEventListener("click", onClick, { capture: true });
|
|
25847
|
-
};
|
|
25848
|
-
};
|
|
25849
|
-
|
|
25850
|
-
/**
|
|
25851
|
-
* Maps a positionArea y/x pair to a concrete `navi-animation` value (a
|
|
25852
|
-
* `prefix` plus a direction word), or `null` if both axes overlap the anchor
|
|
25853
|
-
* (no direction at all — that's `resolvedAnimationKind === "scaling"`
|
|
25854
|
-
* territory instead, see resolveAutoAnimationKind below).
|
|
25855
|
-
*
|
|
25856
|
-
* `prefix: "slide-from"` (used with no real anchor — Dialog always, Popover
|
|
25857
|
-
* when docked) keeps the word as the compass direction the popup comes
|
|
25858
|
-
* from: placed "top" (a point/corner), it slides in from the top.
|
|
25859
|
-
* `prefix: "expand"` (a real anchor, Popover-only) uses the motion/growth
|
|
25860
|
-
* direction instead, the opposite compass point: placed "top" of the
|
|
25861
|
-
* anchor, it moves/grows up, away from the anchor (which sits below it).
|
|
25862
|
-
*
|
|
25863
|
-
* "inset-*"/"center" contribute no direction on their axis either way.
|
|
25864
|
-
*/
|
|
25865
|
-
const resolveDirectionValue = (y, x, { prefix }) => {
|
|
25866
|
-
const yWord =
|
|
25867
|
-
y === "top"
|
|
25868
|
-
? prefix === "expand"
|
|
25869
|
-
? "up"
|
|
25870
|
-
: "top"
|
|
25871
|
-
: y === "bottom"
|
|
25872
|
-
? prefix === "expand"
|
|
25873
|
-
? "down"
|
|
25874
|
-
: "bottom"
|
|
25875
|
-
: null;
|
|
25876
|
-
const xWord = x === "left" ? "left" : x === "right" ? "right" : null;
|
|
25877
|
-
if (!yWord && !xWord) {
|
|
25878
|
-
return null;
|
|
25879
|
-
}
|
|
25880
|
-
return yWord && xWord
|
|
25881
|
-
? `${prefix}-${yWord}-${xWord}`
|
|
25882
|
-
: `${prefix}-${yWord || xWord}`;
|
|
25883
|
-
};
|
|
25884
|
-
|
|
25885
|
-
/**
|
|
25886
|
-
* Shared `animation="auto"`/`true` resolution: "scaling" reads best overall
|
|
25887
|
-
* — picked for any real anchor, or for a point/corner placed dead-center
|
|
25888
|
-
* (both positionArea axes overlapping — there's no sensible direction to
|
|
25889
|
-
* slide from in that case). "sliding" otherwise. `anchor` is `undefined`
|
|
25890
|
-
* for any no-anchor/docked case (Dialog always, Popover's own custom
|
|
25891
|
-
* renderer when there's no real anchor), so this collapses to "scaling"
|
|
25892
|
-
* there only for the dead-center case, "sliding" otherwise. The two
|
|
25893
|
-
* "overlapping" booleans below describe the *positionArea* itself (a bare
|
|
25894
|
-
* word vs. "inset-"/"center"), not anything about the anchor — they'd
|
|
25895
|
-
* mean exactly the same thing even with no anchor at all, since it's the
|
|
25896
|
-
* position strategy, not the anchor, that decides whether there's a
|
|
25897
|
-
* direction to slide from.
|
|
25898
|
-
*/
|
|
25899
|
-
const resolveAutoAnimationKind = (anchor, parsedPositionArea) => {
|
|
25900
|
-
const yIsOverlapping =
|
|
25901
|
-
parsedPositionArea.y !== "top" && parsedPositionArea.y !== "bottom";
|
|
25902
|
-
const xIsOverlapping =
|
|
25903
|
-
parsedPositionArea.x !== "left" && parsedPositionArea.x !== "right";
|
|
25904
|
-
return anchor || (yIsOverlapping && xIsOverlapping) ? "scaling" : "sliding";
|
|
25905
|
-
};
|
|
25906
|
-
|
|
25907
25955
|
installImportMetaCssBuild(import.meta);/**
|
|
25908
25956
|
* A dialog is a surface, not a control: it holds no value, has no action and
|
|
25909
25957
|
* aggregates nothing. Fields and a submit go in a `<Form>` inside it, exactly
|
|
@@ -26384,6 +26432,11 @@ const css$V = /* css */`
|
|
|
26384
26432
|
* content something depends on while the popup is still closed: a value read
|
|
26385
26433
|
* off it, fields a surrounding form collects on submit, a size measured from
|
|
26386
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.
|
|
26387
26440
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
26388
26441
|
*/
|
|
26389
26442
|
const Dialog = props => {
|
|
@@ -26580,11 +26633,13 @@ const useDialogProps = props => {
|
|
|
26580
26633
|
onKeyDown,
|
|
26581
26634
|
children: childrenProp,
|
|
26582
26635
|
mountWhenClosed,
|
|
26636
|
+
unmountWhenClosed,
|
|
26583
26637
|
...rest
|
|
26584
26638
|
} = props;
|
|
26585
|
-
const children = usePopupContentMount(openController, {
|
|
26639
|
+
const children = usePopupContentMount(openController, props.ref, {
|
|
26586
26640
|
children: childrenProp,
|
|
26587
|
-
mountWhenClosed
|
|
26641
|
+
mountWhenClosed,
|
|
26642
|
+
unmountWhenClosed
|
|
26588
26643
|
});
|
|
26589
26644
|
const isModal = layer === "top";
|
|
26590
26645
|
const ref = props.ref;
|
|
@@ -27632,6 +27687,11 @@ const css$U = /* css */`
|
|
|
27632
27687
|
* content something depends on while the popup is still closed: a value read
|
|
27633
27688
|
* off it, fields a surrounding form collects on submit, a size measured from
|
|
27634
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.
|
|
27635
27695
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
27636
27696
|
*/
|
|
27637
27697
|
const Popover = props => {
|
|
@@ -27821,11 +27881,13 @@ const usePopoverProps = props => {
|
|
|
27821
27881
|
onKeyDown,
|
|
27822
27882
|
children: childrenProp,
|
|
27823
27883
|
mountWhenClosed,
|
|
27884
|
+
unmountWhenClosed,
|
|
27824
27885
|
...rest
|
|
27825
27886
|
} = props;
|
|
27826
|
-
const children = usePopupContentMount(openController, {
|
|
27887
|
+
const children = usePopupContentMount(openController, props.ref, {
|
|
27827
27888
|
children: childrenProp,
|
|
27828
|
-
mountWhenClosed
|
|
27889
|
+
mountWhenClosed,
|
|
27890
|
+
unmountWhenClosed
|
|
27829
27891
|
});
|
|
27830
27892
|
const isTopLayer = layer === "top";
|
|
27831
27893
|
const ref = props.ref;
|
|
@@ -34884,7 +34946,14 @@ const css$R = /* css */`
|
|
|
34884
34946
|
|
|
34885
34947
|
&::view-transition-old(navi-route-travel),
|
|
34886
34948
|
&::view-transition-new(navi-route-travel) {
|
|
34887
|
-
|
|
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;
|
|
34888
34957
|
/* The default cross-fade, dropped: two pages sliding past each other are
|
|
34889
34958
|
two solid things, and seeing through one to the other says they are the
|
|
34890
34959
|
same page changing its mind. */
|
|
@@ -34900,7 +34969,21 @@ const css$R = /* css */`
|
|
|
34900
34969
|
overflow: clip;
|
|
34901
34970
|
}
|
|
34902
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);
|
|
34903
34985
|
animation-duration: var(--navi-route-travel-duration, 300ms);
|
|
34986
|
+
animation-name: none;
|
|
34904
34987
|
}
|
|
34905
34988
|
}
|
|
34906
34989
|
|
|
@@ -35130,18 +35213,26 @@ const RouteTravel = ({
|
|
|
35130
35213
|
document.documentElement.setAttribute(DRAGGED_ATTRIBUTE, "");
|
|
35131
35214
|
}
|
|
35132
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;
|
|
35133
35219
|
// The hold a navigation already took, if this travel is the answer to one:
|
|
35134
35220
|
// taking another would be taking a hold on a page that is holding still.
|
|
35135
35221
|
const releaseRendering = renderingHeldForRouting || holdRendering();
|
|
35136
35222
|
renderingHeldForRouting = null;
|
|
35137
35223
|
// The picture the browser is about to take must be of the page that was
|
|
35138
35224
|
// asked for, and a route matching is not yet a page rendered.
|
|
35139
|
-
const viewTransition = startViewTransition(
|
|
35140
|
-
|
|
35141
|
-
|
|
35142
|
-
|
|
35143
|
-
|
|
35144
|
-
|
|
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
|
+
});
|
|
35145
35236
|
travel.viewTransition = viewTransition;
|
|
35146
35237
|
if (scrub) {
|
|
35147
35238
|
// Said only now: the release has to have something to let go of, and the
|
|
@@ -35435,6 +35526,7 @@ const RouteTravel = ({
|
|
|
35435
35526
|
document.documentElement.removeAttribute(TRAVEL_ATTRIBUTE);
|
|
35436
35527
|
document.documentElement.removeAttribute(DRAGGED_ATTRIBUTE);
|
|
35437
35528
|
document.documentElement.removeAttribute(TURNED_ATTRIBUTE);
|
|
35529
|
+
releaseTravelHeight();
|
|
35438
35530
|
}
|
|
35439
35531
|
};
|
|
35440
35532
|
|
|
@@ -35816,6 +35908,24 @@ const releaseHold = travel => {
|
|
|
35816
35908
|
travelHoldingPictures = null;
|
|
35817
35909
|
document.documentElement.removeAttribute(HOLD_ATTRIBUTE);
|
|
35818
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
|
+
};
|
|
35819
35929
|
|
|
35820
35930
|
// The browser does not take the picture of the page being left when a
|
|
35821
35931
|
// transition is ASKED for — it takes it at the next frame, just before running
|
|
@@ -48329,6 +48439,11 @@ const css$z = /* css */`
|
|
|
48329
48439
|
* content something depends on while the popup is still closed: a value read
|
|
48330
48440
|
* off it, fields a surrounding form collects on submit, a size measured from
|
|
48331
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.
|
|
48332
48447
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
48333
48448
|
*/
|
|
48334
48449
|
const Popup = props => {
|
|
@@ -51323,10 +51438,17 @@ const ListUI = props => {
|
|
|
51323
51438
|
// search fallback), otherwise an empty list is the "empty" state.
|
|
51324
51439
|
const searchFallbackShown = (allNoMatch || searching && itemCount === 0) && !searchFallbackDisabled;
|
|
51325
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);
|
|
51326
51447
|
// Hide the whole list — border included — when there is genuinely nothing to
|
|
51327
|
-
// show: no visible items AND no fallback message. Never while loading
|
|
51328
|
-
//
|
|
51329
|
-
|
|
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;
|
|
51330
51452
|
|
|
51331
51453
|
// Placeholder content replaces the real children: an error message when the
|
|
51332
51454
|
// load failed (takes precedence), otherwise — while loading — whatever
|
|
@@ -51421,7 +51543,7 @@ const ListUI = props => {
|
|
|
51421
51543
|
fallbackShown: emptyFallbackShown,
|
|
51422
51544
|
searchFallback: searchFallback,
|
|
51423
51545
|
searchFallbackShown: searchFallbackShown,
|
|
51424
|
-
|
|
51546
|
+
loadingPlaceholderShown: loadingPlaceholderShown,
|
|
51425
51547
|
error: error,
|
|
51426
51548
|
searchNoMatchMode: searchNoMatchMode,
|
|
51427
51549
|
separator: separator,
|
|
@@ -51521,6 +51643,10 @@ const ListFirstResolver = props => {
|
|
|
51521
51643
|
* displays nothing. A list that knows how many rows it will have has no use
|
|
51522
51644
|
* for this — see `<List.Items count>`, whose not-yet-loaded rows are drawn
|
|
51523
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.
|
|
51524
51650
|
* @param {"start"|"end"|number|{id: string, offset?: number}} [props.defaultScrolled="start"]
|
|
51525
51651
|
* Where the list opens, after which the user owns the scroll. `"end"` is a
|
|
51526
51652
|
* thread read backwards — the last rows are the ones to show, and the ones
|
|
@@ -51581,7 +51707,7 @@ const ListContent = ({
|
|
|
51581
51707
|
fallbackShown,
|
|
51582
51708
|
searchFallback,
|
|
51583
51709
|
searchFallbackShown,
|
|
51584
|
-
|
|
51710
|
+
loadingPlaceholderShown,
|
|
51585
51711
|
error,
|
|
51586
51712
|
searchNoMatchMode,
|
|
51587
51713
|
separator,
|
|
@@ -51605,7 +51731,7 @@ const ListContent = ({
|
|
|
51605
51731
|
fallbackShown: fallbackShown,
|
|
51606
51732
|
searchFallback: searchFallback,
|
|
51607
51733
|
searchFallbackShown: searchFallbackShown,
|
|
51608
|
-
|
|
51734
|
+
loadingPlaceholderShown: loadingPlaceholderShown,
|
|
51609
51735
|
error: error,
|
|
51610
51736
|
searchNoMatchMode: searchNoMatchMode,
|
|
51611
51737
|
separator: separator,
|
|
@@ -52893,7 +53019,7 @@ const UnorderedList = ({
|
|
|
52893
53019
|
fallbackShown,
|
|
52894
53020
|
searchFallback,
|
|
52895
53021
|
searchFallbackShown,
|
|
52896
|
-
|
|
53022
|
+
loadingPlaceholderShown,
|
|
52897
53023
|
error,
|
|
52898
53024
|
searchNoMatchMode,
|
|
52899
53025
|
separator,
|
|
@@ -52904,9 +53030,11 @@ const UnorderedList = ({
|
|
|
52904
53030
|
children,
|
|
52905
53031
|
...rest
|
|
52906
53032
|
}) => {
|
|
52907
|
-
// No empty/no-match message while loading or
|
|
52908
|
-
//
|
|
52909
|
-
|
|
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);
|
|
52910
53038
|
return jsxs(Box, {
|
|
52911
53039
|
as: "ul",
|
|
52912
53040
|
flex: columns ? undefined : horizontal ? "x" : "y",
|
|
@@ -66817,5 +66945,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
66817
66945
|
})
|
|
66818
66946
|
});
|
|
66819
66947
|
|
|
66820
|
-
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 };
|
|
66821
66949
|
//# sourceMappingURL=jsenv_navi.js.map
|