@jsenv/navi 0.29.303 → 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
 
@@ -59533,6 +59565,16 @@ const css$E = /* css */`
59533
59565
  --backdrop-background: transparent;
59534
59566
  --backdrop-filter: none;
59535
59567
  }
59568
+ /* A wall-less top-layer dialog reaches the top layer through the Popover
59569
+ API, and the browser generates a ::backdrop for a popover exactly as it
59570
+ does for a modal. Nothing is meant to be between this dialog and the
59571
+ page, so that pseudo-element paints nothing — last, after the variant
59572
+ rules it must win over: they answer how a wall is painted, this one
59573
+ says there is no wall to paint. */
59574
+ &[data-backdrop="none"] {
59575
+ --backdrop-background: transparent;
59576
+ --backdrop-filter: none;
59577
+ }
59536
59578
  &::backdrop {
59537
59579
  background: var(--backdrop-background);
59538
59580
  backdrop-filter: var(--backdrop-filter);
@@ -59724,6 +59766,73 @@ const css$E = /* css */`
59724
59766
  &::view-transition-new(navi-popup-grow) {
59725
59767
  animation-duration: var(--navi-popup-grow-duration, 0.25s);
59726
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
+ }
59727
59836
  }
59728
59837
 
59729
59838
  ${surfaceTextCss}
@@ -59860,6 +59969,15 @@ const css$E = /* css */`
59860
59969
  * prop) and grows into whatever inside the dialog carries `data-grow`, the
59861
59970
  * dialog itself when nothing does. `"auto"` never picks it: only the caller
59862
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.
59863
59981
  * @param {string} [props.animationDuration] - Maps to
59864
59982
  * `--popup-animation-duration`.
59865
59983
  * @param {Element|{current: Element}|string} [props.anchor] - Never used for
@@ -60202,6 +60320,7 @@ const useDialogProps = props => {
60202
60320
  // once, held at that size while open. See this prop's own JSDoc above.
60203
60321
  sizing = "auto",
60204
60322
  animation,
60323
+ grow = "box",
60205
60324
  // Inert unless sizeFromAnchor below (see this file's top comment) —
60206
60325
  // Dialog's own positioning is never relative to it.
60207
60326
  anchor,
@@ -60428,7 +60547,8 @@ const useDialogProps = props => {
60428
60547
  return;
60429
60548
  }
60430
60549
  growPopupFromAnchor(dialogEl, anchorElement, applyChange, {
60431
- opened
60550
+ opened,
60551
+ grow
60432
60552
  });
60433
60553
  } : null;
60434
60554
 
@@ -60913,11 +61033,18 @@ const useDialogProps = props => {
60913
61033
  // (and harmless for a local dialog, whose real backdrop element gets it
60914
61034
  // via backdropProps).
60915
61035
  "data-backdrop-variant": backdropVariant,
61036
+ // That this dialog has no wall, said in the DOM: the browser generates a
61037
+ // ::backdrop for the popover a wall-less top-layer dialog is shown as,
61038
+ // and the CSS above keys off this to leave it unpainted. Also the only
61039
+ // way anything outside can read the absence — a test measuring pixels is
61040
+ // the alternative.
61041
+ "data-backdrop": backdrop ? undefined : "none",
60916
61042
  // Read by the native ::backdrop, which inherits them from here (see this
60917
61043
  // file's CSS) — a local dialog's own backdrop element gets them via
60918
- // backdropProps above.
60919
- backdropColor,
60920
- backdropFilter,
61044
+ // backdropProps above. Dropped without a wall: they say how one is
61045
+ // painted, and inline they would beat the rule that says there is none.
61046
+ "backdropColor": backdrop ? backdropColor : undefined,
61047
+ "backdropFilter": backdrop ? backdropFilter : undefined,
60921
61048
  "styleCSSVars": DIALOG_STYLE_CSS_VARS,
60922
61049
  ...rest,
60923
61050
  ...autoFocusProps,
@@ -62760,6 +62887,7 @@ const css$C = /* css */`@layer navi {
62760
62887
  * popup meant to dock on a phone must not declare itself compact — see
62761
62888
  * `maxWidth` below.
62762
62889
  * @param {boolean} [props.sizeFromAnchor] - **Dialog-only**, same guard.
62890
+ * @param {"box"|"scene"} [props.grow] - **Dialog-only**, same guard.
62763
62891
  * @param {string} [props.positionArea] - Forwarded as-is — `Dialog` and
62764
62892
  * `Popover` have different own defaults (`"center"` vs. `"bottom"`),
62765
62893
  * deliberately not homogenized here (each reads best for its own typical
@@ -62866,6 +62994,7 @@ const Popup = props => {
62866
62994
  // in the dialog branch, so they never reach the popover element.
62867
62995
  dockedOnSmallTouchScreen,
62868
62996
  sizeFromAnchor,
62997
+ grow,
62869
62998
  ...rest
62870
62999
  } = props;
62871
63000
  const [mode] = useResolvedPopupMode(modeProp, maxWidth, {
@@ -62884,6 +63013,7 @@ const Popup = props => {
62884
63013
  ...rest,
62885
63014
  dockedOnSmallTouchScreen: dockedOnSmallTouchScreen,
62886
63015
  sizeFromAnchor: sizeFromAnchor,
63016
+ grow: grow,
62887
63017
  maxWidth: maxWidth,
62888
63018
  pointerInteractionOutsideEffect: pointerInteractionOutsideEffect,
62889
63019
  backdrop: backdrop,
@@ -63343,19 +63473,26 @@ const PickerCustom = props => {
63343
63473
  commitUIStateAsAnswer(controller, closeEvent);
63344
63474
  }
63345
63475
  }
63346
- leaveExpanded({
63347
- isBack: closeEvent.detail.isCancel
63348
- });
63349
- // Reset so the next opening re-evaluates screen size
63350
- resetMode();
63351
63476
  const confirmEvent = confirmEventRef.current;
63352
63477
  confirmEventRef.current = null;
63353
63478
  if (confirmEvent && !closeEvent.detail.isCancel) {
63354
63479
  onConfirm?.(confirmEvent);
63355
63480
  }
63356
- // Last, after the value bookkeeping above: whoever listens reads the
63357
- // 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.
63358
63490
  onClose?.(closeEvent);
63491
+ leaveExpanded({
63492
+ isBack: closeEvent.detail.isCancel
63493
+ });
63494
+ // Reset so the next opening re-evaluates screen size
63495
+ resetMode();
63359
63496
  }
63360
63497
  };
63361
63498
  });
@@ -63749,6 +63886,7 @@ const PickerContentInsidePopup = props => {
63749
63886
  // its own width once lifted. Dialog's own `sizeFromAnchor`.
63750
63887
  dialogSizeFromAnchor,
63751
63888
  animation,
63889
+ grow,
63752
63890
  animationDuration,
63753
63891
  // mode="callout": what the callout says about what it holds, and paints
63754
63892
  // in its border and icon — "none" for a plain tooltip (see the callout
@@ -63827,6 +63965,7 @@ const PickerContentInsidePopup = props => {
63827
63965
  expandY: isPopover ? undefined : dialogExpandY,
63828
63966
  dockedOnSmallTouchScreen: isPopover ? undefined : dockedOnSmallTouchScreen,
63829
63967
  sizeFromAnchor: isPopover ? undefined : dialogSizeFromAnchor,
63968
+ grow: isPopover ? undefined : grow,
63830
63969
  children: jsx(PopupModeContext.Provider, {
63831
63970
  value: mode,
63832
63971
  children: children
@@ -72828,6 +72967,7 @@ const PickerFirstResolver = props => {
72828
72967
  * mode?: "popover" | "dialog" | "callout",
72829
72968
  * openOn?: "press" | "longpress" | "contextmenu" | string | string[],
72830
72969
  * animation?: boolean | "auto" | "fading" | "scaling" | "sliding" | "growing" | `slide-from-${string}`,
72970
+ * grow?: "box" | "scene",
72831
72971
  * animationDuration?: string,
72832
72972
  * calloutStatus?: "info" | "warning" | "error" | "success" | "none",
72833
72973
  * calloutIcon?: boolean,
@@ -73118,7 +73258,9 @@ const PickerFirstResolver = props => {
73118
73258
  * the user validated: that is `action`. `closeEvent.detail.isCancel` tells a
73119
73259
  * cancel (Escape, the back gesture) from a close that keeps. Fires after the
73120
73260
  * picker's value has settled — committed, or restored on a cancel — so the
73121
- * 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.
73122
73264
  * @param {number|string} [rightSlotIconSize="inherit"] How big what sits in the
73123
73265
  * right slot is drawn — the chevron, a `rightSlotIcon`, or the clear button's
73124
73266
  * cross. "inherit" takes the picker's own font size.
@@ -73165,6 +73307,10 @@ const PickerFirstResolver = props => {
73165
73307
  * the anchor, so nothing has to be named for it — `dialogSizeFromAnchor`
73166
73308
  * for a card that must keep its width on the way, left out for a drawing
73167
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).
73168
73314
  * @param {string} [animationDuration] The popup's own (`--popup-animation-duration`).
73169
73315
  * @param {boolean} [dialogSizeFromAnchor] Dialog mode: the dialog is as wide
73170
73316
  * as the trigger — its box as a floor (Dialog's own `sizeFromAnchor`) and