@jsenv/navi 0.29.364 → 0.29.366

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.
@@ -28872,6 +28872,8 @@ const visitedUrlsSignal = browserIntegration.visitedUrlsSignal;
28872
28872
  browserIntegration.handleActionTask;
28873
28873
 
28874
28874
  const idUsageMap = new Map();
28875
+ // Keys found in the entry with nobody to claim them, already reported.
28876
+ const orphanKeysReported = new Set();
28875
28877
  const useNavStateWithWarnings = (id, options) => {
28876
28878
  const idRef = useRef(undefined);
28877
28879
  if (idRef.current !== id) {
@@ -28895,6 +28897,15 @@ Consider using unique IDs for each component instance.`,
28895
28897
  }
28896
28898
 
28897
28899
  useEffect(() => {
28900
+ // Registered here as well as in the render above: preact/compat's
28901
+ // Suspense parks a subtree by running every hook cleanup in it, and the
28902
+ // render resuming it carries the same id.
28903
+ if (!idUsageMap.has(id)) {
28904
+ idUsageMap.set(id, {
28905
+ stackTrace: new Error().stack,
28906
+ });
28907
+ }
28908
+ warnAboutOrphanGeneratedKeys();
28898
28909
  return () => {
28899
28910
  idUsageMap.delete(id);
28900
28911
  };
@@ -28903,6 +28914,31 @@ Consider using unique IDs for each component instance.`,
28903
28914
  return useNavStateBasic(id, options);
28904
28915
  };
28905
28916
 
28917
+ // A generated id (preact's useId()) names one mount. State written under one
28918
+ // outlives that mount in the history entry — a page left with a popup open —
28919
+ // and the mount coming back to the entry generates another id: the state is
28920
+ // there, and nothing reads it. Reported when a component mounting on the entry
28921
+ // finds such a key, the moment someone expected the state back.
28922
+ const warnAboutOrphanGeneratedKeys = () => {
28923
+ const state = browserIntegration.getDocumentState();
28924
+ if (!state) {
28925
+ return;
28926
+ }
28927
+ for (const key of Object.keys(state)) {
28928
+ if (
28929
+ !isLikelyPreactGeneratedId(key) ||
28930
+ idUsageMap.has(key) ||
28931
+ orphanKeysReported.has(key)
28932
+ ) {
28933
+ continue;
28934
+ }
28935
+ orphanKeysReported.add(key);
28936
+ console.warn(
28937
+ `useNavState: this history entry holds "${key}", written by a component whose id was generated (preact's useId()) and that is not mounted anymore — a Picker without an id, a popup with navState and no id. A generated id names one mount, so nothing will read that state again: a popup open when this screen was left comes back closed. Give the component a stable id if it was meant to be found as it was.`,
28938
+ );
28939
+ }
28940
+ };
28941
+
28906
28942
  const NO_OP = () => {};
28907
28943
  const NO_ID_GIVEN = [undefined, NO_OP, NO_OP];
28908
28944
  // What the computed below answers for a key the document state does not hold:
@@ -60567,6 +60603,13 @@ const clipOf = (side, distance) => {
60567
60603
  * (TARGET_WAIT_MS), on the half-strength frame where the anchor is still
60568
60604
  * readable, and lifts the moment it is there.
60569
60605
  *
60606
+ * A closing may land where no opening took off: a dialog opened the plain way
60607
+ * (`animation={{ open, close: "lifting" }}` in dialog.jsx), closing into a box the close
60608
+ * itself brings — the state `onClose` writes renders the place the lifted node
60609
+ * belongs to. So the box a closing comes back to is read once the change is
60610
+ * made, inside the transition, and when the caller named it (`liftAnchor`) it
60611
+ * is waited for (LANDING_WAIT_MS): the new picture is taken once it is there.
60612
+ *
60570
60613
  * One name serves the whole movement, because only one of the two boxes is on
60571
60614
  * screen at a time: it names the anchor while the popup is closed, and the
60572
60615
  * lifted node while it is open.
@@ -60609,6 +60652,12 @@ const ARRIVING_ATTRIBUTE = "data-navi-popup-lift-arriving";
60609
60652
  // stands, without a movement, so a target that never comes cannot keep it
60610
60653
  // unpainted.
60611
60654
  const TARGET_WAIT_MS = 1000;
60655
+ // How long a closing waits for the box it comes back to, when that box is
60656
+ // brought by the close (see this file's top comment). The page is frozen on
60657
+ // the picture of the open popup meanwhile, so the wait is short: what it
60658
+ // covers is a render, not a fetch. Past it the popup's picture plays out on
60659
+ // its own.
60660
+ const LANDING_WAIT_MS = 300;
60612
60661
  // The popup's own animation duration, published on the root because the
60613
60662
  // ::view-transition tree hangs off it and inherits from nowhere else.
60614
60663
  const DURATION_PROPERTY = "--navi-popup-lift-duration";
@@ -60649,14 +60698,16 @@ let releaseScrollHold = null;
60649
60698
  * a view transition morphing the anchor's box into the lifted node's, or back.
60650
60699
  *
60651
60700
  * `opened` says which way: the box being left is the anchor when the popup is
60652
- * opening and the lifted node when it is closing. `lift` is Dialog's own prop
60653
- * of that name.
60701
+ * opening and the lifted node when it is closing. `resolveAnchor` is read on
60702
+ * the spot for an opening, and once the change is made for a closing;
60703
+ * `waitForAnchor` has a closing wait for it when it is not there yet. `lift`
60704
+ * is Dialog's own prop of that name.
60654
60705
  */
60655
60706
  const liftPopupFromAnchor = (
60656
60707
  popupEl,
60657
- anchorElement,
60708
+ resolveAnchor,
60658
60709
  applyChange,
60659
- { opened, lift },
60710
+ { opened, lift, waitForAnchor },
60660
60711
  ) => {
60661
60712
  const startViewTransition = ensureDocumentStartViewTransition();
60662
60713
  // A movement still wearing the name would make the name two elements wide,
@@ -60664,7 +60715,7 @@ const liftPopupFromAnchor = (
60664
60715
  // document.
60665
60716
  releaseLiftInProgress?.();
60666
60717
 
60667
- const elementLeaving = opened ? anchorElement : resolveLiftTarget(popupEl);
60718
+ const elementLeaving = opened ? resolveAnchor() : resolveLiftTarget(popupEl);
60668
60719
  // Read before the first write: the read brings the style up to date, and a
60669
60720
  // write before it would make it bring it up to date once more.
60670
60721
  const duration = getComputedStyle(popupEl)
@@ -60688,12 +60739,14 @@ const liftPopupFromAnchor = (
60688
60739
 
60689
60740
  let giveBackNameArriving = null;
60690
60741
  let stopWaitingForTarget = null;
60742
+ let stopWaitingForAnchor = null;
60691
60743
  const release = () => {
60692
60744
  if (releaseLiftInProgress !== release) {
60693
60745
  return;
60694
60746
  }
60695
60747
  releaseLiftInProgress = null;
60696
60748
  stopWaitingForTarget?.();
60749
+ stopWaitingForAnchor?.();
60697
60750
  boxAnimationInProgress?.cancel();
60698
60751
  boxAnimationInProgress = null;
60699
60752
  releaseScrollHold?.();
@@ -60719,13 +60772,18 @@ const liftPopupFromAnchor = (
60719
60772
  const boxLeaving = room ? elementLeaving.getBoundingClientRect() : null;
60720
60773
  let boxArriving = null;
60721
60774
  let cornersArriving = null;
60722
- const viewTransition = startViewTransition(() => {
60775
+ const viewTransition = startViewTransition(async () => {
60723
60776
  // The name is the arriving box's from here on: worn by both, it is worn
60724
60777
  // by neither. Written rather than removed, so a name the element also
60725
60778
  // has from a stylesheet cannot resurface for the length of the movement.
60726
60779
  elementLeaving.style.setProperty(NAME_PROPERTY, "none");
60727
60780
  change();
60728
- const elementArriving = resolveElementArriving();
60781
+ const elementArriving = await resolveElementArriving();
60782
+ // Replaced while waiting for its landing: the movement replacing it
60783
+ // holds the name now.
60784
+ if (releaseLiftInProgress !== release) {
60785
+ return;
60786
+ }
60729
60787
  if (elementArriving) {
60730
60788
  giveBackNameArriving = wearLiftName(elementArriving);
60731
60789
  cornersArriving = readCorners(elementArriving);
@@ -60750,12 +60808,33 @@ const liftPopupFromAnchor = (
60750
60808
  };
60751
60809
 
60752
60810
  if (!opened) {
60753
- startMovement(applyChange, () =>
60811
+ startMovement(applyChange, () => {
60812
+ const anchorElement = resolveAnchor();
60813
+ if (anchorElement?.isConnected) {
60814
+ return anchorElement;
60815
+ }
60754
60816
  // Gone from the document while the popup was open (the row it stood in
60755
60817
  // was removed): nothing to arrive at, and the browser plays the popup's
60756
60818
  // picture out on its own.
60757
- anchorElement.isConnected ? anchorElement : null,
60758
- );
60819
+ if (!waitForAnchor) {
60820
+ return null;
60821
+ }
60822
+ return new Promise((resolve) => {
60823
+ stopWaitingForAnchor = whenAnchorAppears(resolveAnchor, (element) => {
60824
+ stopWaitingForAnchor = null;
60825
+ // Not when stopped by a movement replacing this one.
60826
+ if (
60827
+ !element &&
60828
+ releaseLiftInProgress === release
60829
+ ) {
60830
+ console.warn(
60831
+ `[navi] Dialog's "liftAnchor" named nothing on screen within ${LANDING_WAIT_MS}ms of the close, so the dialog closes without landing. The element it names is where the box comes back to: in the document at the close, or rendered by what the close changes (onClose).`,
60832
+ );
60833
+ }
60834
+ resolve(element);
60835
+ });
60836
+ });
60837
+ });
60759
60838
  return;
60760
60839
  }
60761
60840
 
@@ -60915,6 +60994,40 @@ const whenLiftTargetAppears = (popupEl, callback) => {
60915
60994
  return stop;
60916
60995
  };
60917
60996
 
60997
+ // Calls `callback` with the anchor once `resolveAnchor` finds it in the
60998
+ // document, or with null past LANDING_WAIT_MS — or when stopped, since the
60999
+ // transition's update is waiting on it. Returns how to stop.
61000
+ const whenAnchorAppears = (resolveAnchor, callback) => {
61001
+ const observer = new MutationObserver(() => {
61002
+ const anchorElement = resolveAnchor();
61003
+ if (anchorElement?.isConnected) {
61004
+ stop(anchorElement);
61005
+ }
61006
+ });
61007
+ observer.observe(document.documentElement, {
61008
+ childList: true,
61009
+ subtree: true,
61010
+ attributes: true,
61011
+ attributeFilter: ["id"],
61012
+ });
61013
+ const timeout = setTimeout(() => {
61014
+ stop(null);
61015
+ }, LANDING_WAIT_MS);
61016
+ let stopped = false;
61017
+ const stop = (anchorElement = null) => {
61018
+ if (stopped) {
61019
+ return;
61020
+ }
61021
+ stopped = true;
61022
+ observer.disconnect();
61023
+ clearTimeout(timeout);
61024
+ callback(anchorElement);
61025
+ };
61026
+ return () => {
61027
+ stop(null);
61028
+ };
61029
+ };
61030
+
60918
61031
  const ignore = () => {};
60919
61032
 
60920
61033
  // The room the fixed bars leave, in viewport coordinates; null without bars.
@@ -61580,6 +61693,18 @@ const css$E = /* css */`
61580
61693
  opacity: 0;
61581
61694
  }
61582
61695
 
61696
+ /* A closing lift is the dialog leaving as a picture: whatever exit its
61697
+ opening animation arms (animation={{ open, close: "lifting" }}, see
61698
+ popup_css.js) would
61699
+ keep it rendered into the picture of the state it closes into. */
61700
+ :root[data-navi-popup-lift="closing"] {
61701
+ .navi_dialog,
61702
+ .navi_dialog::backdrop,
61703
+ .navi_dialog_backdrop {
61704
+ transition: none;
61705
+ }
61706
+ }
61707
+
61583
61708
  /* While a dialog is lifting out of the element that opened it
61584
61709
  (popup_lift.js). The page around IS taken as a picture, the browser's own
61585
61710
  default, and on purpose: the wall and what the dialog holds around the
@@ -61822,7 +61947,7 @@ const css$E = /* css */`
61822
61947
  * scroll while open (its backdrop only covers the scrollport, so scrolling
61823
61948
  * there would reveal uncovered content); this prop extends the lock to the
61824
61949
  * whole page. Defaults to `true` for a dialog docked by `dockedOnSmallTouchScreen`.
61825
- * @param {boolean|"auto"|"fading"|"scaling"|"sliding"|"lifting"|`slide-from-${string}`} [props.animation]
61950
+ * @param {boolean|"auto"|"fading"|"scaling"|"sliding"|"lifting"|`slide-from-${string}`|{open: boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`, close: "lifting"}} [props.animation]
61826
61951
  * - `true`/`"auto"` resolves to `"scaling"` for a centered `positionArea`,
61827
61952
  * or a concrete `"slide-from-*"` direction otherwise. Any other explicit
61828
61953
  * value is used as-is. `"lifting"` is the odd one out: every other kind
@@ -61841,6 +61966,15 @@ const css$E = /* css */`
61841
61966
  * being what the movement leaves rather than a context to keep readable;
61842
61967
  * `backdropVariant="discrete"` asks for the light wash back. See
61843
61968
  * `popup_lift.js`.
61969
+ * - `{ open, close: "lifting" }`: the close alone lifts. The dialog opens
61970
+ * with `open` (any value above but `"lifting"`), and its `data-lift` node
61971
+ * travels into `liftAnchor` on close — for a dialog that did not come out
61972
+ * of what it lands in (a banner opens a full-screen reveal, closing it puts
61973
+ * the crest in its place on the plate that replaces the banner). The box it
61974
+ * lands in may be rendered by the close itself: `liftAnchor` is read once
61975
+ * `onClose` has run, and waited for a moment when it is not there yet.
61976
+ * `"lifting"` is the only `close` that differs from the opening: every
61977
+ * other kind closes by playing its opening backwards.
61844
61978
  * @param {"box"|"scene"} [props.lift="box"] - Under `animation="lifting"`,
61845
61979
  * what the anchor and what it becomes are to each other, which decides
61846
61980
  * how their pictures sit in the box moving between them. `"box"`: one
@@ -61865,14 +61999,15 @@ const css$E = /* css */`
61865
61999
  * `document.getElementById` when the dialog opens — see popover.jsx's own
61866
62000
  * `anchor` doc for why (mainly `defaultOpen`).
61867
62001
  * @param {Element|{current: Element}|string} [props.liftAnchor] - Under
61868
- * `animation="lifting"`, where the closing brings the box back to, when that
61869
- * is no longer where it came from: a popup one walks through (a row of cards
61870
- * shown one at a time) has something else in front by the time it closes,
61871
- * and the box would otherwise fly back to the card the press opened on. Same
61872
- * grammar as `anchor` (element, ref or id), resolved at the close, so
61873
- * whatever names the card currently in frontan id built from the signal
61874
- * the walk is bound to, a ref moved with it is read then and not at the
61875
- * opening. Left out, the box comes back to the anchor it came out of.
62002
+ * `animation="lifting"` or `animation={{ open, close: "lifting" }}`, where the closing
62003
+ * brings the box back to, when that is not where it came from: a popup one
62004
+ * walks through (a row of cards shown one at a time) has something else in
62005
+ * front by the time it closes, and the box would otherwise fly back to the
62006
+ * card the press opened on. Same grammar as `anchor` (element, ref or id),
62007
+ * resolved once the close is made after `onClose` so whatever names the
62008
+ * card currently in front, or the box the close itself renders, is read
62009
+ * then and not at the opening. Left out, the box comes back to the anchor
62010
+ * it came out of.
61876
62011
  * @param {boolean} [props.sizeFromAnchor=false] - Whether the dialog takes the
61877
62012
  * anchor's width/height as a min-width/min-height floor
61878
62013
  * (`--anchor-width`/`--anchor-height`). Off by default: unlike a popover,
@@ -62360,7 +62495,18 @@ const useDialogProps = props => {
62360
62495
  flushEdges.left = expandX || x === "left" || x === "inset-left";
62361
62496
  flushEdges.right = expandX || x === "right" || x === "inset-right";
62362
62497
  }
62363
- const isAutoAnimation = animation === true || animation === "auto";
62498
+
62499
+ // `{ open, close }` says each way on its own; a single value says both.
62500
+ const {
62501
+ open: openAnimation,
62502
+ close: closeAnimation = openAnimation
62503
+ } = animation !== null && typeof animation === "object" ? animation : {
62504
+ open: animation
62505
+ };
62506
+ if (closeAnimation !== openAnimation && closeAnimation !== "lifting") {
62507
+ console.warn(`[navi] Dialog animation={{ close: ${JSON.stringify(closeAnimation)} }}: "lifting" is the only close that differs from the opening; every other kind closes by playing the opening backwards.`);
62508
+ }
62509
+ const isAutoAnimation = openAnimation === true || openAnimation === "auto";
62364
62510
  // The dialog and the anchor are one box, and what plays between them is the
62365
62511
  // browser's own morph (popup_lift.js) — nothing this dialog does to its own
62366
62512
  // box. So it arms no CSS transition of its own, which is not merely useless
@@ -62368,11 +62514,12 @@ const useDialogProps = props => {
62368
62514
  // with allow-discrete, and a dialog kept rendered for the length of its exit
62369
62515
  // is exactly what the picture taken of the state it closes into must not
62370
62516
  // show.
62371
- const lifting = animation === "lifting";
62517
+ const lifting = openAnimation === "lifting";
62518
+ const liftsOnClose = closeAnimation === "lifting";
62372
62519
  // Dialog never has a real anchor to POSITION against (see this file's top
62373
62520
  // comment), so this is always the "no anchor" path — the same one Popover's
62374
62521
  // own custom renderer falls into when it has no real anchor either.
62375
- const resolvedAnimationKind = isAutoAnimation ? resolveAutoAnimationKind(undefined, parsedPositionArea) : animation;
62522
+ const resolvedAnimationKind = isAutoAnimation ? resolveAutoAnimationKind(undefined, parsedPositionArea) : openAnimation;
62376
62523
  // Not gated on isAutoAnimation — an explicit animation="sliding" needs a
62377
62524
  // concrete direction just as much as an auto-resolved one does (same as
62378
62525
  // Popover's own "sliding"/"expanding" resolution step in openEffect).
@@ -62421,13 +62568,11 @@ const useDialogProps = props => {
62421
62568
  // is in front NOW, which only the caller knows. Resolved at the close for
62422
62569
  // that reason: the element it names changes while the popup is open, so
62423
62570
  // anything read at the opening would be the walk's starting point again.
62571
+ // Silent when it names nothing: the close may be what renders it, and
62572
+ // popup_lift.js asks again until it is there (and warns past that).
62424
62573
  const resolveLiftAnchorElement = () => {
62425
62574
  if (typeof liftAnchor === "string") {
62426
- const liftAnchorElementById = document.getElementById(liftAnchor);
62427
- if (!liftAnchorElementById) {
62428
- console.warn(`Dialog: liftAnchor="${liftAnchor}" did not match any element`);
62429
- }
62430
- return liftAnchorElementById;
62575
+ return document.getElementById(liftAnchor);
62431
62576
  }
62432
62577
  // A ref is unwrapped even when it holds nothing, the same way `anchor` is:
62433
62578
  // the ref object itself has no box to come back to.
@@ -62439,7 +62584,7 @@ const useDialogProps = props => {
62439
62584
  // provided the change happens between its two pictures, which is what
62440
62585
  // handing it to the controller buys (see popup_lift.js and
62441
62586
  // open_controller.js's own transitionChange).
62442
- openController.transitionChange = lifting ? (applyChange, {
62587
+ openController.transitionChange = liftsOnClose ? (applyChange, {
62443
62588
  opened,
62444
62589
  event
62445
62590
  }) => {
@@ -62447,29 +62592,29 @@ const useDialogProps = props => {
62447
62592
  // A mount-time opening was never seen closed (see openEffect's own
62448
62593
  // `silent`): there is no box it comes from, because nothing was shown
62449
62594
  // before it.
62450
- if (!dialogEl || opened && event.detail.silent) {
62595
+ if (!dialogEl || opened && (!lifting || event.detail.silent)) {
62451
62596
  applyChange();
62452
62597
  return;
62453
62598
  }
62454
- let anchorElement;
62455
- if (opened) {
62456
- anchorElement = resolveAnchorElement(event);
62457
- } else if (liftAnchor) {
62458
- anchorElement = resolveLiftAnchorElement();
62459
- } else {
62460
- anchorElement = anchorElementRef.current;
62599
+ if (!opened) {
62600
+ // Read once the close is made (popup_lift.js): the box it lands in
62601
+ // may be one the close itself renders.
62602
+ liftPopupFromAnchor(dialogEl, liftAnchor ? resolveLiftAnchorElement : () => anchorElementRef.current, applyChange, {
62603
+ opened,
62604
+ lift,
62605
+ waitForAnchor: Boolean(liftAnchor)
62606
+ });
62607
+ return;
62461
62608
  }
62609
+ const anchorElement = resolveAnchorElement(event);
62462
62610
  if (!anchorElement) {
62463
- if (opened) {
62611
+ {
62464
62612
  console.warn(`[navi] Dialog has animation="lifting" and no anchor to lift out of, so it simply appears. The anchor is whatever opened it — a <Button command="--navi-open">, the "source" given to triggerNaviCommand — or the "anchor" prop.`);
62465
62613
  }
62466
- if (!opened && liftAnchor) {
62467
- console.warn(`[navi] Dialog has animation="lifting" and a "liftAnchor" naming nothing on screen, so it simply closes. The element it names is where the box comes back to, and it has to be in the document at the close.`);
62468
- }
62469
62614
  applyChange();
62470
62615
  return;
62471
62616
  }
62472
- liftPopupFromAnchor(dialogEl, anchorElement, applyChange, {
62617
+ liftPopupFromAnchor(dialogEl, () => anchorElement, applyChange, {
62473
62618
  opened,
62474
62619
  lift
62475
62620
  });
@@ -65284,8 +65429,8 @@ const PickerCustom = props => {
65284
65429
  // before computing popupId below, so two Pickers without an explicit id never collide.
65285
65430
  // Captured before the fallback chain below overwrites props.id — needed to
65286
65431
  // know whether the id actually came from the caller (stable) or from
65287
- // useId()/ControlIdContext (not guaranteed stable across a reload), see
65288
- // pickerNavType below.
65432
+ // useId()/ControlIdContext (a generated id names one mount: a reload, or a
65433
+ // return to this page, generates another), see pickerNavType below.
65289
65434
  const hasExplicitId = Boolean(props.id);
65290
65435
  const idDefault = useId();
65291
65436
  const controlId = useContext(ControlIdContext);
@@ -65362,10 +65507,12 @@ const PickerCustom = props => {
65362
65507
  // pushes a history entry so the back button closes it. Every other case
65363
65508
  // (popover mode, or a dialog whose id was auto-generated via useId()/
65364
65509
  // ControlIdContext) replaces the current history state instead — a
65365
- // generated id isn't stable across a reload, so pushing it would either
65366
- // silently drop the entry or, worse, collide with a different
65367
- // component's own generated id (see useNavState's own fallback for the
65368
- // same concern, applied here proactively for the id we control).
65510
+ // generated id names one mount, so pushing it would either leave an entry
65511
+ // nothing reads or, worse, collide with a different component's own
65512
+ // generated id (see useNavState's own fallback for the same concern,
65513
+ // applied here proactively for the id we control). What a generated id
65514
+ // costs either way: the state is written, and the mount coming back to
65515
+ // the page (or a reload) finds it under a key it does not have.
65369
65516
  const pickerNavType = mode === "dialog" && hasExplicitId ? "push" : "replace";
65370
65517
  const [expanded, enterExpanded, leaveExpanded] = useNavState(popupId, {
65371
65518
  type: pickerNavType,
@@ -65904,6 +66051,7 @@ const PickerContentInsidePopup = props => {
65904
66051
  popupWidthFitContent,
65905
66052
  animation,
65906
66053
  lift,
66054
+ liftAnchor,
65907
66055
  animationDuration,
65908
66056
  // mode="callout": what the callout says about what it holds, and paints
65909
66057
  // in its border and icon — "none" for a plain tooltip (see the callout
@@ -66000,6 +66148,7 @@ const PickerContentInsidePopup = props => {
66000
66148
  dockedOnSmallTouchScreen: isPopover ? undefined : dockedOnSmallTouchScreen,
66001
66149
  sizeFromAnchor: isPopover ? undefined : dialogSizeFromAnchor,
66002
66150
  lift: isPopover ? undefined : lift,
66151
+ liftAnchor: isPopover ? undefined : liftAnchor,
66003
66152
  children: jsx(PopupModeContext.Provider, {
66004
66153
  value: mode,
66005
66154
  children: children
@@ -75471,8 +75620,9 @@ const PickerFirstResolver = props => {
75471
75620
  * children?: import("ignore:preact").ComponentChildren,
75472
75621
  * mode?: "popover" | "dialog" | "callout",
75473
75622
  * openOn?: "press" | "longpress" | "contextmenu" | string | string[],
75474
- * animation?: boolean | "auto" | "fading" | "scaling" | "sliding" | "lifting" | `slide-from-${string}`,
75623
+ * animation?: boolean | "auto" | "fading" | "scaling" | "sliding" | "lifting" | `slide-from-${string}` | { open: boolean | "auto" | "fading" | "scaling" | "sliding" | `slide-from-${string}`, close: "lifting" },
75475
75624
  * lift?: "box" | "scene",
75625
+ * liftAnchor?: Element | { current: Element } | string,
75476
75626
  * animationDuration?: string,
75477
75627
  * calloutStatus?: "info" | "warning" | "error" | "success" | "none",
75478
75628
  * calloutIcon?: boolean,
@@ -75631,6 +75781,12 @@ const PickerFirstResolver = props => {
75631
75781
  * content failed to load, its value could not be resolved…). Shown as a
75632
75782
  * callout on the trigger, open or closed — the caller has nothing to place.
75633
75783
  * Dismissing it discards that error; a new `error` value raises another one.
75784
+ * @param {string} [id] What the popup's open state is kept under in the
75785
+ * history entry: a screen left and come back to finds the picker open, and
75786
+ * in dialog mode the opening is an entry of its own, closed by the back
75787
+ * button before the screen is left. Left out, the key is a generated id,
75788
+ * which names one mount: the state survives neither leaving the screen nor a
75789
+ * reload. A picker whose popup leads somewhere (a link inside it) has one.
75634
75790
  * @param {"popover"|"dialog"|"callout"} [mode] Which popup the children open
75635
75791
  * in. Left out, a popover on a large screen and a dialog on a narrow one.
75636
75792
  * `"callout"` shows them in the picker's own callout — the speech bubble its
@@ -75843,6 +75999,11 @@ const PickerFirstResolver = props => {
75843
75999
  * that opens precisely to get bigger. It brings an opaque, blurred backdrop
75844
76000
  * with it (`--navi-backdrop-lift-*`); `backdropVariant="discrete"` asks for
75845
76001
  * the light wash back.
76002
+ * @param {Element|{current: Element}|string} [liftAnchor] Dialog mode,
76003
+ * Dialog's own: where a lifting close lands when that is not the trigger
76004
+ * (`animation="lifting"`, or `animation={{ open, close: "lifting" }}` for a
76005
+ * popup that opens the plain way and only lands on close),
76006
+ * read once the close is made (after `onClose`).
75846
76007
  * @param {"box"|"scene"} [lift="box"] Dialog's own, under `animation="lifting"`:
75847
76008
  * `"box"` for a card that extends (its top stays, the box uncovers the rest),
75848
76009
  * `"scene"` for a thumbnail that is a band cut from the middle of the bigger