@jsenv/navi 0.29.304 → 0.29.305

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.
@@ -38366,6 +38366,15 @@ const useInteractiveProps = (props, {
38366
38366
  const debugAction = useDebugAction();
38367
38367
  const debugInteraction = useDebugInteraction();
38368
38368
  const debugFocus = useDebugFocus();
38369
+
38370
+ // The very instance a run of this control is using, held in a SIGNAL because
38371
+ // the busy state below follows it: it is captured at navi_action_start and
38372
+ // released when the run settles (see onnavi_action_start).
38373
+ const runningActionSignalRef = useRef(null);
38374
+ if (runningActionSignalRef.current === null) {
38375
+ runningActionSignalRef.current = signal(null);
38376
+ }
38377
+ uiStateController.runningActionSignal = runningActionSignalRef.current;
38369
38378
  {
38370
38379
  const {
38371
38380
  autoFocus,
@@ -38404,7 +38413,15 @@ const useInteractiveProps = (props, {
38404
38413
  const isActionRequesterComputed = useMemo(() => parentActionRequesterSignal ? computed(() => parentActionRequesterSignal.value === ref.current) : null, [parentActionRequesterSignal]);
38405
38414
  const parallelGuard = useContext(ParallelGuardContext);
38406
38415
  const parentAction = useContext(ActionContext);
38407
- const actionStatus = useActionStatus(boundAction);
38416
+ // The instance the run in flight is using when there is one, and only
38417
+ // then boundAction: boundAction can be a proxy following the UI state
38418
+ // signal, and that state moves while a run is out — a popup content
38419
+ // unmounting takes the group's value with it. The proxy would resolve to
38420
+ // the instance for the new value, which is idle, and the control would
38421
+ // stop waiting on a run that is still going. What it waits on is the run
38422
+ // it started, from navi_action_start to its settlement.
38423
+ const runningAction = uiStateController.runningActionSignal.value;
38424
+ const actionStatus = useActionStatus(runningAction || boundAction);
38408
38425
  const networkPolicyReason = useNetworkPolicyReason();
38409
38426
  const {
38410
38427
  disabled,
@@ -38765,7 +38782,7 @@ const useInteractiveProps = (props, {
38765
38782
  // state has already moved to the new value by now — the proxy would
38766
38783
  // resolve to the instance for that new value, which is not the one
38767
38784
  // running.
38768
- uiStateController.runningAction?.abort(`superseded by a newer request on this control`);
38785
+ uiStateController.runningActionSignal.peek()?.abort(`superseded by a newer request on this control`);
38769
38786
  return;
38770
38787
  }
38771
38788
  debugAction(e, `executing action ${e.detail.action.callSource}`);
@@ -38787,14 +38804,14 @@ const useInteractiveProps = (props, {
38787
38804
  // this run (see the optimistic queue above), the state — and the
38788
38805
  // proxy's resolution — will have moved on.
38789
38806
  const runAction = e.detail.action;
38790
- uiStateController.runningAction = runAction.getCurrentAction?.() ?? runAction;
38807
+ uiStateController.runningActionSignal.value = runAction.getCurrentAction?.() ?? runAction;
38791
38808
  // Fires when the run's underlying work has settled — even for an
38792
38809
  // aborted run, whose promise is awaited to completion (see
38793
38810
  // performRun in actions.js) — which is exactly what "the server is
38794
38811
  // done with it" means, and therefore when the queued request may go.
38795
38812
  e.detail.addSideEffect(outcome => {
38796
38813
  uiStateController.actionInFlight = false;
38797
- uiStateController.runningAction = null;
38814
+ uiStateController.runningActionSignal.value = null;
38798
38815
  uiStateController.parallelGuard?.release(uiStateController);
38799
38816
  const queuedEvent = uiStateController.queuedActionAllowedEvent;
38800
38817
  if (!queuedEvent) {
@@ -59115,14 +59132,22 @@ const clipOf = (side, distance) => {
59115
59132
  // before the next one takes it (see releaseGrowInProgress).
59116
59133
  const NAME = "navi-popup-grow";
59117
59134
  const NAME_PROPERTY = "view-transition-name";
59118
- // Worn by the root for the length of the movement — what the CSS keys the
59119
- // page's own opt-out on, and the movement's only trace in the document.
59135
+ // Worn by the root for the length of the movement, saying which way it goes
59136
+ // ("opening" | "closing") — what the CSS keys the page's own opt-out and the
59137
+ // clip near the anchor's end on, and the movement's only trace in the document.
59120
59138
  const ROOT_ATTRIBUTE = "data-navi-popup-grow";
59139
+ // What the two boxes are to each other (Dialog's `grow`: "box" | "scene"),
59140
+ // worn by the root too — it decides how each picture sits in the moving box.
59141
+ const KIND_ATTRIBUTE = "data-navi-popup-grow-kind";
59121
59142
  // Inside the popup, the one node that IS the anchor once it has grown.
59122
59143
  const TARGET_SELECTOR = "[data-grow]";
59123
59144
  // The popup's own animation duration, published on the root because the
59124
59145
  // ::view-transition tree hangs off it and inherits from nowhere else.
59125
59146
  const DURATION_PROPERTY = "--navi-popup-grow-duration";
59147
+ // The corners of the box being left, published the same way: the pictures are
59148
+ // clipped to the moving box (dialog.jsx), and a card with rounded corners must
59149
+ // not travel with square ones.
59150
+ const BORDER_RADIUS_PROPERTY = "--navi-popup-grow-border-radius";
59126
59151
 
59127
59152
  let releaseGrowInProgress = null;
59128
59153
 
@@ -59133,13 +59158,13 @@ let releaseGrowInProgress = null;
59133
59158
  * `opened` says which way: the box being left is the anchor when the popup is
59134
59159
  * opening and the popup when it is closing, and the arriving one is only known
59135
59160
  * once the change has been made (the content a popup grows into is built by
59136
- * that very change).
59161
+ * that very change). `grow` is Dialog's own prop of that name.
59137
59162
  */
59138
59163
  const growPopupFromAnchor = (
59139
59164
  popupEl,
59140
59165
  anchorElement,
59141
59166
  applyChange,
59142
- { opened },
59167
+ { opened, grow },
59143
59168
  ) => {
59144
59169
  const startViewTransition = ensureDocumentStartViewTransition();
59145
59170
  // A movement still wearing the name would make the name two elements wide,
@@ -59150,7 +59175,8 @@ const growPopupFromAnchor = (
59150
59175
  const elementLeaving = opened ? anchorElement : resolveGrowTarget(popupEl);
59151
59176
  const giveBackNameLeaving = wearGrowName(elementLeaving);
59152
59177
  const root = document.documentElement;
59153
- root.setAttribute(ROOT_ATTRIBUTE, "");
59178
+ root.setAttribute(ROOT_ATTRIBUTE, opened ? "opening" : "closing");
59179
+ root.setAttribute(KIND_ATTRIBUTE, grow);
59154
59180
  const duration = getComputedStyle(popupEl)
59155
59181
  .getPropertyValue("--popup-animation-duration")
59156
59182
  .trim();
@@ -59160,6 +59186,10 @@ const growPopupFromAnchor = (
59160
59186
  // browser's own pace.
59161
59187
  root.style.setProperty(DURATION_PROPERTY, duration);
59162
59188
  }
59189
+ const borderRadius = getComputedStyle(elementLeaving).borderRadius;
59190
+ if (borderRadius) {
59191
+ root.style.setProperty(BORDER_RADIUS_PROPERTY, borderRadius);
59192
+ }
59163
59193
 
59164
59194
  let giveBackNameArriving = null;
59165
59195
  const release = () => {
@@ -59170,7 +59200,9 @@ const growPopupFromAnchor = (
59170
59200
  giveBackNameLeaving();
59171
59201
  giveBackNameArriving?.();
59172
59202
  root.removeAttribute(ROOT_ATTRIBUTE);
59203
+ root.removeAttribute(KIND_ATTRIBUTE);
59173
59204
  root.style.removeProperty(DURATION_PROPERTY);
59205
+ root.style.removeProperty(BORDER_RADIUS_PROPERTY);
59174
59206
  };
59175
59207
  releaseGrowInProgress = release;
59176
59208
 
@@ -59734,6 +59766,73 @@ const css$E = /* css */`
59734
59766
  &::view-transition-new(navi-popup-grow) {
59735
59767
  animation-duration: var(--navi-popup-grow-duration, 0.25s);
59736
59768
  }
59769
+
59770
+ /* Each picture is drawn as wide as the box and as tall as it is, so a box
59771
+ that grows in height alone (a card keeping its width) would show the
59772
+ whole taller picture from the first frame, and the movement would read
59773
+ as a fade. Clipped to the box, the picture is uncovered as the box
59774
+ grows and covered back as it shrinks — behind the corners the box
59775
+ has in the page, published by popup_grow.js. */
59776
+ &::view-transition-image-pair(navi-popup-grow) {
59777
+ border-radius: var(--navi-popup-grow-border-radius, 0);
59778
+ overflow: clip;
59779
+ }
59780
+
59781
+ /* One scene through two frames (Dialog's grow="scene"): each picture
59782
+ covers the box, cropped around its centre, so what both frames show
59783
+ lands on itself. */
59784
+ &[data-navi-popup-grow-kind="scene"] {
59785
+ &::view-transition-old(navi-popup-grow),
59786
+ &::view-transition-new(navi-popup-grow) {
59787
+ height: 100%;
59788
+ object-fit: cover;
59789
+ }
59790
+ }
59791
+
59792
+ /* The anchor lives in the page, under the fixed bars; the popup lives in
59793
+ the top layer, over them. The pictures are painted above everything,
59794
+ bars included, so near the anchor's end of the movement the whole tree
59795
+ is clipped to the room between the bars — a thumbnail half under the
59796
+ top bar leaves from under it and comes back under it — and freed near
59797
+ the popup's end, where a tall dialog may stand over them. Clipped for
59798
+ the 65% of the time nearest the anchor: with the group's own ease that
59799
+ leaves the last 35% of the time to cover more than half of the trip,
59800
+ so the picture is out of the bars before the clip closes on it. */
59801
+ &::view-transition {
59802
+ animation: navi-popup-grow-clip var(--navi-popup-grow-duration, 0.25s)
59803
+ ease both;
59804
+ }
59805
+ &[data-navi-popup-grow="closing"]::view-transition {
59806
+ animation-direction: reverse;
59807
+ }
59808
+ }
59809
+ /* The bars sit at the app's own inset (fixed_bar.jsx), so the room they
59810
+ take starts there; both tokens are 0px where nothing takes any. */
59811
+ @keyframes navi-popup-grow-clip {
59812
+ 0%,
59813
+ 65% {
59814
+ clip-path: inset(
59815
+ calc(
59816
+ var(--navi-app-inset-top, 0px) +
59817
+ var(--navi-fixed-bar-space-top, 0px)
59818
+ )
59819
+ calc(
59820
+ var(--navi-app-inset-right, 0px) +
59821
+ var(--navi-fixed-bar-space-right, 0px)
59822
+ )
59823
+ calc(
59824
+ var(--navi-app-inset-bottom, 0px) +
59825
+ var(--navi-fixed-bar-space-bottom, 0px)
59826
+ )
59827
+ calc(
59828
+ var(--navi-app-inset-left, 0px) +
59829
+ var(--navi-fixed-bar-space-left, 0px)
59830
+ )
59831
+ );
59832
+ }
59833
+ 100% {
59834
+ clip-path: inset(0);
59835
+ }
59737
59836
  }
59738
59837
 
59739
59838
  ${surfaceTextCss}
@@ -59870,6 +59969,15 @@ const css$E = /* css */`
59870
59969
  * prop) and grows into whatever inside the dialog carries `data-grow`, the
59871
59970
  * dialog itself when nothing does. `"auto"` never picks it: only the caller
59872
59971
  * knows the two boxes are one object. See `popup_grow.js`.
59972
+ * @param {"box"|"scene"} [props.grow="box"] - Under `animation="growing"`,
59973
+ * what the anchor and what it grows into are to each other, which decides
59974
+ * how their pictures sit in the box moving between them. `"box"`: one
59975
+ * object at two sizes — a card gaining fields. Each picture is drawn at the
59976
+ * box's width from its top edge, and a box growing in height uncovers more
59977
+ * of it: the header stays where it is, the rest extends. `"scene"`: one
59978
+ * scene through two frames — a thumbnail and the map it is cut from. Each
59979
+ * picture covers the box, cropped around its centre and never distorted, so
59980
+ * what both frames show lands on itself.
59873
59981
  * @param {string} [props.animationDuration] - Maps to
59874
59982
  * `--popup-animation-duration`.
59875
59983
  * @param {Element|{current: Element}|string} [props.anchor] - Never used for
@@ -60212,6 +60320,7 @@ const useDialogProps = props => {
60212
60320
  // once, held at that size while open. See this prop's own JSDoc above.
60213
60321
  sizing = "auto",
60214
60322
  animation,
60323
+ grow = "box",
60215
60324
  // Inert unless sizeFromAnchor below (see this file's top comment) —
60216
60325
  // Dialog's own positioning is never relative to it.
60217
60326
  anchor,
@@ -60438,7 +60547,8 @@ const useDialogProps = props => {
60438
60547
  return;
60439
60548
  }
60440
60549
  growPopupFromAnchor(dialogEl, anchorElement, applyChange, {
60441
- opened
60550
+ opened,
60551
+ grow
60442
60552
  });
60443
60553
  } : null;
60444
60554
 
@@ -62777,6 +62887,7 @@ const css$C = /* css */`@layer navi {
62777
62887
  * popup meant to dock on a phone must not declare itself compact — see
62778
62888
  * `maxWidth` below.
62779
62889
  * @param {boolean} [props.sizeFromAnchor] - **Dialog-only**, same guard.
62890
+ * @param {"box"|"scene"} [props.grow] - **Dialog-only**, same guard.
62780
62891
  * @param {string} [props.positionArea] - Forwarded as-is — `Dialog` and
62781
62892
  * `Popover` have different own defaults (`"center"` vs. `"bottom"`),
62782
62893
  * deliberately not homogenized here (each reads best for its own typical
@@ -62883,6 +62994,7 @@ const Popup = props => {
62883
62994
  // in the dialog branch, so they never reach the popover element.
62884
62995
  dockedOnSmallTouchScreen,
62885
62996
  sizeFromAnchor,
62997
+ grow,
62886
62998
  ...rest
62887
62999
  } = props;
62888
63000
  const [mode] = useResolvedPopupMode(modeProp, maxWidth, {
@@ -62901,6 +63013,7 @@ const Popup = props => {
62901
63013
  ...rest,
62902
63014
  dockedOnSmallTouchScreen: dockedOnSmallTouchScreen,
62903
63015
  sizeFromAnchor: sizeFromAnchor,
63016
+ grow: grow,
62904
63017
  maxWidth: maxWidth,
62905
63018
  pointerInteractionOutsideEffect: pointerInteractionOutsideEffect,
62906
63019
  backdrop: backdrop,
@@ -63360,19 +63473,26 @@ const PickerCustom = props => {
63360
63473
  commitUIStateAsAnswer(controller, closeEvent);
63361
63474
  }
63362
63475
  }
63363
- leaveExpanded({
63364
- isBack: closeEvent.detail.isCancel
63365
- });
63366
- // Reset so the next opening re-evaluates screen size
63367
- resetMode();
63368
63476
  const confirmEvent = confirmEventRef.current;
63369
63477
  confirmEventRef.current = null;
63370
63478
  if (confirmEvent && !closeEvent.detail.isCancel) {
63371
63479
  onConfirm?.(confirmEvent);
63372
63480
  }
63373
- // Last, after the value bookkeeping above: whoever listens reads the
63374
- // picker's value as it ends up — committed, or restored on a cancel.
63481
+ // After the value bookkeeping above — whoever listens reads the
63482
+ // picker's value as it ends up, committed or restored on a cancel —
63483
+ // and before leaveExpanded below, which is where the caller's
63484
+ // reaction runs for a Dialog too. A close that keeps goes back onto
63485
+ // the entry the popup was opened from while KEEPING the url as it
63486
+ // stands at that moment (see useNavState's leave()), so what onClose
63487
+ // spells into the address — a route param naming what was being
63488
+ // edited, cleared now that nothing is — has to be written before,
63489
+ // or the landing puts it back.
63375
63490
  onClose?.(closeEvent);
63491
+ leaveExpanded({
63492
+ isBack: closeEvent.detail.isCancel
63493
+ });
63494
+ // Reset so the next opening re-evaluates screen size
63495
+ resetMode();
63376
63496
  }
63377
63497
  };
63378
63498
  });
@@ -63766,6 +63886,7 @@ const PickerContentInsidePopup = props => {
63766
63886
  // its own width once lifted. Dialog's own `sizeFromAnchor`.
63767
63887
  dialogSizeFromAnchor,
63768
63888
  animation,
63889
+ grow,
63769
63890
  animationDuration,
63770
63891
  // mode="callout": what the callout says about what it holds, and paints
63771
63892
  // in its border and icon — "none" for a plain tooltip (see the callout
@@ -63844,6 +63965,7 @@ const PickerContentInsidePopup = props => {
63844
63965
  expandY: isPopover ? undefined : dialogExpandY,
63845
63966
  dockedOnSmallTouchScreen: isPopover ? undefined : dockedOnSmallTouchScreen,
63846
63967
  sizeFromAnchor: isPopover ? undefined : dialogSizeFromAnchor,
63968
+ grow: isPopover ? undefined : grow,
63847
63969
  children: jsx(PopupModeContext.Provider, {
63848
63970
  value: mode,
63849
63971
  children: children
@@ -72845,6 +72967,7 @@ const PickerFirstResolver = props => {
72845
72967
  * mode?: "popover" | "dialog" | "callout",
72846
72968
  * openOn?: "press" | "longpress" | "contextmenu" | string | string[],
72847
72969
  * animation?: boolean | "auto" | "fading" | "scaling" | "sliding" | "growing" | `slide-from-${string}`,
72970
+ * grow?: "box" | "scene",
72848
72971
  * animationDuration?: string,
72849
72972
  * calloutStatus?: "info" | "warning" | "error" | "success" | "none",
72850
72973
  * calloutIcon?: boolean,
@@ -73135,7 +73258,9 @@ const PickerFirstResolver = props => {
73135
73258
  * the user validated: that is `action`. `closeEvent.detail.isCancel` tells a
73136
73259
  * cancel (Escape, the back gesture) from a close that keeps. Fires after the
73137
73260
  * picker's value has settled — committed, or restored on a cancel — so the
73138
- * value read in here is the one the picker ends up holding.
73261
+ * value read in here is the one the picker ends up holding, and before the
73262
+ * popup leaves the history entry it pushed, so a route param cleared in here
73263
+ * is part of the url a close that keeps carries back.
73139
73264
  * @param {number|string} [rightSlotIconSize="inherit"] How big what sits in the
73140
73265
  * right slot is drawn — the chevron, a `rightSlotIcon`, or the clear button's
73141
73266
  * cross. "inherit" takes the picker's own font size.
@@ -73182,6 +73307,10 @@ const PickerFirstResolver = props => {
73182
73307
  * the anchor, so nothing has to be named for it — `dialogSizeFromAnchor`
73183
73308
  * for a card that must keep its width on the way, left out for a drawing
73184
73309
  * that opens precisely to get bigger.
73310
+ * @param {"box"|"scene"} [grow="box"] Dialog's own, under `animation="growing"`:
73311
+ * `"box"` for a card that extends (its top stays, the box uncovers the rest),
73312
+ * `"scene"` for a thumbnail and the bigger view it is cut from (each picture
73313
+ * covers the box, cropped around its centre).
73185
73314
  * @param {string} [animationDuration] The popup's own (`--popup-animation-duration`).
73186
73315
  * @param {boolean} [dialogSizeFromAnchor] Dialog mode: the dialog is as wide
73187
73316
  * as the trigger — its box as a floor (Dialog's own `sizeFromAnchor`) and