@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.
@@ -37704,6 +37704,15 @@ const useInteractiveProps = (props, {
37704
37704
  const debugAction = useDebugAction();
37705
37705
  const debugInteraction = useDebugInteraction();
37706
37706
  const debugFocus = useDebugFocus();
37707
+
37708
+ // The very instance a run of this control is using, held in a SIGNAL because
37709
+ // the busy state below follows it: it is captured at navi_action_start and
37710
+ // released when the run settles (see onnavi_action_start).
37711
+ const runningActionSignalRef = useRef(null);
37712
+ if (runningActionSignalRef.current === null) {
37713
+ runningActionSignalRef.current = signal(null);
37714
+ }
37715
+ uiStateController.runningActionSignal = runningActionSignalRef.current;
37707
37716
  {
37708
37717
  const {
37709
37718
  autoFocus,
@@ -37742,7 +37751,15 @@ const useInteractiveProps = (props, {
37742
37751
  const isActionRequesterComputed = useMemo(() => parentActionRequesterSignal ? computed(() => parentActionRequesterSignal.value === ref.current) : null, [parentActionRequesterSignal]);
37743
37752
  const parallelGuard = useContext(ParallelGuardContext);
37744
37753
  const parentAction = useContext(ActionContext);
37745
- const actionStatus = useActionStatus(boundAction);
37754
+ // The instance the run in flight is using when there is one, and only
37755
+ // then boundAction: boundAction can be a proxy following the UI state
37756
+ // signal, and that state moves while a run is out — a popup content
37757
+ // unmounting takes the group's value with it. The proxy would resolve to
37758
+ // the instance for the new value, which is idle, and the control would
37759
+ // stop waiting on a run that is still going. What it waits on is the run
37760
+ // it started, from navi_action_start to its settlement.
37761
+ const runningAction = uiStateController.runningActionSignal.value;
37762
+ const actionStatus = useActionStatus(runningAction || boundAction);
37746
37763
  const networkPolicyReason = useNetworkPolicyReason();
37747
37764
  const {
37748
37765
  disabled,
@@ -38103,7 +38120,7 @@ const useInteractiveProps = (props, {
38103
38120
  // state has already moved to the new value by now — the proxy would
38104
38121
  // resolve to the instance for that new value, which is not the one
38105
38122
  // running.
38106
- uiStateController.runningAction?.abort(`superseded by a newer request on this control`);
38123
+ uiStateController.runningActionSignal.peek()?.abort(`superseded by a newer request on this control`);
38107
38124
  return;
38108
38125
  }
38109
38126
  debugAction(e, `executing action ${e.detail.action.callSource}`);
@@ -38125,14 +38142,14 @@ const useInteractiveProps = (props, {
38125
38142
  // this run (see the optimistic queue above), the state — and the
38126
38143
  // proxy's resolution — will have moved on.
38127
38144
  const runAction = e.detail.action;
38128
- uiStateController.runningAction = runAction.getCurrentAction?.() ?? runAction;
38145
+ uiStateController.runningActionSignal.value = runAction.getCurrentAction?.() ?? runAction;
38129
38146
  // Fires when the run's underlying work has settled — even for an
38130
38147
  // aborted run, whose promise is awaited to completion (see
38131
38148
  // performRun in actions.js) — which is exactly what "the server is
38132
38149
  // done with it" means, and therefore when the queued request may go.
38133
38150
  e.detail.addSideEffect(outcome => {
38134
38151
  uiStateController.actionInFlight = false;
38135
- uiStateController.runningAction = null;
38152
+ uiStateController.runningActionSignal.value = null;
38136
38153
  uiStateController.parallelGuard?.release(uiStateController);
38137
38154
  const queuedEvent = uiStateController.queuedActionAllowedEvent;
38138
38155
  if (!queuedEvent) {
@@ -58332,14 +58349,22 @@ const clipOf = (side, distance) => {
58332
58349
  // before the next one takes it (see releaseGrowInProgress).
58333
58350
  const NAME = "navi-popup-grow";
58334
58351
  const NAME_PROPERTY = "view-transition-name";
58335
- // Worn by the root for the length of the movement — what the CSS keys the
58336
- // page's own opt-out on, and the movement's only trace in the document.
58352
+ // Worn by the root for the length of the movement, saying which way it goes
58353
+ // ("opening" | "closing") — what the CSS keys the page's own opt-out and the
58354
+ // clip near the anchor's end on, and the movement's only trace in the document.
58337
58355
  const ROOT_ATTRIBUTE = "data-navi-popup-grow";
58356
+ // What the two boxes are to each other (Dialog's `grow`: "box" | "scene"),
58357
+ // worn by the root too — it decides how each picture sits in the moving box.
58358
+ const KIND_ATTRIBUTE = "data-navi-popup-grow-kind";
58338
58359
  // Inside the popup, the one node that IS the anchor once it has grown.
58339
58360
  const TARGET_SELECTOR = "[data-grow]";
58340
58361
  // The popup's own animation duration, published on the root because the
58341
58362
  // ::view-transition tree hangs off it and inherits from nowhere else.
58342
58363
  const DURATION_PROPERTY = "--navi-popup-grow-duration";
58364
+ // The corners of the box being left, published the same way: the pictures are
58365
+ // clipped to the moving box (dialog.jsx), and a card with rounded corners must
58366
+ // not travel with square ones.
58367
+ const BORDER_RADIUS_PROPERTY = "--navi-popup-grow-border-radius";
58343
58368
 
58344
58369
  let releaseGrowInProgress = null;
58345
58370
 
@@ -58350,13 +58375,13 @@ let releaseGrowInProgress = null;
58350
58375
  * `opened` says which way: the box being left is the anchor when the popup is
58351
58376
  * opening and the popup when it is closing, and the arriving one is only known
58352
58377
  * once the change has been made (the content a popup grows into is built by
58353
- * that very change).
58378
+ * that very change). `grow` is Dialog's own prop of that name.
58354
58379
  */
58355
58380
  const growPopupFromAnchor = (
58356
58381
  popupEl,
58357
58382
  anchorElement,
58358
58383
  applyChange,
58359
- { opened },
58384
+ { opened, grow },
58360
58385
  ) => {
58361
58386
  const startViewTransition = ensureDocumentStartViewTransition();
58362
58387
  // A movement still wearing the name would make the name two elements wide,
@@ -58367,7 +58392,8 @@ const growPopupFromAnchor = (
58367
58392
  const elementLeaving = opened ? anchorElement : resolveGrowTarget(popupEl);
58368
58393
  const giveBackNameLeaving = wearGrowName(elementLeaving);
58369
58394
  const root = document.documentElement;
58370
- root.setAttribute(ROOT_ATTRIBUTE, "");
58395
+ root.setAttribute(ROOT_ATTRIBUTE, opened ? "opening" : "closing");
58396
+ root.setAttribute(KIND_ATTRIBUTE, grow);
58371
58397
  const duration = getComputedStyle(popupEl)
58372
58398
  .getPropertyValue("--popup-animation-duration")
58373
58399
  .trim();
@@ -58377,6 +58403,10 @@ const growPopupFromAnchor = (
58377
58403
  // browser's own pace.
58378
58404
  root.style.setProperty(DURATION_PROPERTY, duration);
58379
58405
  }
58406
+ const borderRadius = getComputedStyle(elementLeaving).borderRadius;
58407
+ if (borderRadius) {
58408
+ root.style.setProperty(BORDER_RADIUS_PROPERTY, borderRadius);
58409
+ }
58380
58410
 
58381
58411
  let giveBackNameArriving = null;
58382
58412
  const release = () => {
@@ -58387,7 +58417,9 @@ const growPopupFromAnchor = (
58387
58417
  giveBackNameLeaving();
58388
58418
  giveBackNameArriving?.();
58389
58419
  root.removeAttribute(ROOT_ATTRIBUTE);
58420
+ root.removeAttribute(KIND_ATTRIBUTE);
58390
58421
  root.style.removeProperty(DURATION_PROPERTY);
58422
+ root.style.removeProperty(BORDER_RADIUS_PROPERTY);
58391
58423
  };
58392
58424
  releaseGrowInProgress = release;
58393
58425
 
@@ -58951,6 +58983,73 @@ const css$E = /* css */`
58951
58983
  &::view-transition-new(navi-popup-grow) {
58952
58984
  animation-duration: var(--navi-popup-grow-duration, 0.25s);
58953
58985
  }
58986
+
58987
+ /* Each picture is drawn as wide as the box and as tall as it is, so a box
58988
+ that grows in height alone (a card keeping its width) would show the
58989
+ whole taller picture from the first frame, and the movement would read
58990
+ as a fade. Clipped to the box, the picture is uncovered as the box
58991
+ grows and covered back as it shrinks — behind the corners the box
58992
+ has in the page, published by popup_grow.js. */
58993
+ &::view-transition-image-pair(navi-popup-grow) {
58994
+ border-radius: var(--navi-popup-grow-border-radius, 0);
58995
+ overflow: clip;
58996
+ }
58997
+
58998
+ /* One scene through two frames (Dialog's grow="scene"): each picture
58999
+ covers the box, cropped around its centre, so what both frames show
59000
+ lands on itself. */
59001
+ &[data-navi-popup-grow-kind="scene"] {
59002
+ &::view-transition-old(navi-popup-grow),
59003
+ &::view-transition-new(navi-popup-grow) {
59004
+ height: 100%;
59005
+ object-fit: cover;
59006
+ }
59007
+ }
59008
+
59009
+ /* The anchor lives in the page, under the fixed bars; the popup lives in
59010
+ the top layer, over them. The pictures are painted above everything,
59011
+ bars included, so near the anchor's end of the movement the whole tree
59012
+ is clipped to the room between the bars — a thumbnail half under the
59013
+ top bar leaves from under it and comes back under it — and freed near
59014
+ the popup's end, where a tall dialog may stand over them. Clipped for
59015
+ the 65% of the time nearest the anchor: with the group's own ease that
59016
+ leaves the last 35% of the time to cover more than half of the trip,
59017
+ so the picture is out of the bars before the clip closes on it. */
59018
+ &::view-transition {
59019
+ animation: navi-popup-grow-clip var(--navi-popup-grow-duration, 0.25s)
59020
+ ease both;
59021
+ }
59022
+ &[data-navi-popup-grow="closing"]::view-transition {
59023
+ animation-direction: reverse;
59024
+ }
59025
+ }
59026
+ /* The bars sit at the app's own inset (fixed_bar.jsx), so the room they
59027
+ take starts there; both tokens are 0px where nothing takes any. */
59028
+ @keyframes navi-popup-grow-clip {
59029
+ 0%,
59030
+ 65% {
59031
+ clip-path: inset(
59032
+ calc(
59033
+ var(--navi-app-inset-top, 0px) +
59034
+ var(--navi-fixed-bar-space-top, 0px)
59035
+ )
59036
+ calc(
59037
+ var(--navi-app-inset-right, 0px) +
59038
+ var(--navi-fixed-bar-space-right, 0px)
59039
+ )
59040
+ calc(
59041
+ var(--navi-app-inset-bottom, 0px) +
59042
+ var(--navi-fixed-bar-space-bottom, 0px)
59043
+ )
59044
+ calc(
59045
+ var(--navi-app-inset-left, 0px) +
59046
+ var(--navi-fixed-bar-space-left, 0px)
59047
+ )
59048
+ );
59049
+ }
59050
+ 100% {
59051
+ clip-path: inset(0);
59052
+ }
58954
59053
  }
58955
59054
 
58956
59055
  ${surfaceTextCss}
@@ -59087,6 +59186,15 @@ const css$E = /* css */`
59087
59186
  * prop) and grows into whatever inside the dialog carries `data-grow`, the
59088
59187
  * dialog itself when nothing does. `"auto"` never picks it: only the caller
59089
59188
  * knows the two boxes are one object. See `popup_grow.js`.
59189
+ * @param {"box"|"scene"} [props.grow="box"] - Under `animation="growing"`,
59190
+ * what the anchor and what it grows into are to each other, which decides
59191
+ * how their pictures sit in the box moving between them. `"box"`: one
59192
+ * object at two sizes — a card gaining fields. Each picture is drawn at the
59193
+ * box's width from its top edge, and a box growing in height uncovers more
59194
+ * of it: the header stays where it is, the rest extends. `"scene"`: one
59195
+ * scene through two frames — a thumbnail and the map it is cut from. Each
59196
+ * picture covers the box, cropped around its centre and never distorted, so
59197
+ * what both frames show lands on itself.
59090
59198
  * @param {string} [props.animationDuration] - Maps to
59091
59199
  * `--popup-animation-duration`.
59092
59200
  * @param {Element|{current: Element}|string} [props.anchor] - Never used for
@@ -59429,6 +59537,7 @@ const useDialogProps = props => {
59429
59537
  // once, held at that size while open. See this prop's own JSDoc above.
59430
59538
  sizing = "auto",
59431
59539
  animation,
59540
+ grow = "box",
59432
59541
  // Inert unless sizeFromAnchor below (see this file's top comment) —
59433
59542
  // Dialog's own positioning is never relative to it.
59434
59543
  anchor,
@@ -59652,7 +59761,8 @@ const useDialogProps = props => {
59652
59761
  return;
59653
59762
  }
59654
59763
  growPopupFromAnchor(dialogEl, anchorElement, applyChange, {
59655
- opened
59764
+ opened,
59765
+ grow
59656
59766
  });
59657
59767
  } : null;
59658
59768
 
@@ -61979,6 +62089,7 @@ const css$C = /* css */`@layer navi {
61979
62089
  * popup meant to dock on a phone must not declare itself compact — see
61980
62090
  * `maxWidth` below.
61981
62091
  * @param {boolean} [props.sizeFromAnchor] - **Dialog-only**, same guard.
62092
+ * @param {"box"|"scene"} [props.grow] - **Dialog-only**, same guard.
61982
62093
  * @param {string} [props.positionArea] - Forwarded as-is — `Dialog` and
61983
62094
  * `Popover` have different own defaults (`"center"` vs. `"bottom"`),
61984
62095
  * deliberately not homogenized here (each reads best for its own typical
@@ -62085,6 +62196,7 @@ const Popup = props => {
62085
62196
  // in the dialog branch, so they never reach the popover element.
62086
62197
  dockedOnSmallTouchScreen,
62087
62198
  sizeFromAnchor,
62199
+ grow,
62088
62200
  ...rest
62089
62201
  } = props;
62090
62202
  const [mode] = useResolvedPopupMode(modeProp, maxWidth, {
@@ -62103,6 +62215,7 @@ const Popup = props => {
62103
62215
  ...rest,
62104
62216
  dockedOnSmallTouchScreen: dockedOnSmallTouchScreen,
62105
62217
  sizeFromAnchor: sizeFromAnchor,
62218
+ grow: grow,
62106
62219
  maxWidth: maxWidth,
62107
62220
  pointerInteractionOutsideEffect: pointerInteractionOutsideEffect,
62108
62221
  backdrop: backdrop,
@@ -62562,19 +62675,26 @@ const PickerCustom = props => {
62562
62675
  commitUIStateAsAnswer(controller, closeEvent);
62563
62676
  }
62564
62677
  }
62565
- leaveExpanded({
62566
- isBack: closeEvent.detail.isCancel
62567
- });
62568
- // Reset so the next opening re-evaluates screen size
62569
- resetMode();
62570
62678
  const confirmEvent = confirmEventRef.current;
62571
62679
  confirmEventRef.current = null;
62572
62680
  if (confirmEvent && !closeEvent.detail.isCancel) {
62573
62681
  onConfirm?.(confirmEvent);
62574
62682
  }
62575
- // Last, after the value bookkeeping above: whoever listens reads the
62576
- // picker's value as it ends up — committed, or restored on a cancel.
62683
+ // After the value bookkeeping above — whoever listens reads the
62684
+ // picker's value as it ends up, committed or restored on a cancel —
62685
+ // and before leaveExpanded below, which is where the caller's
62686
+ // reaction runs for a Dialog too. A close that keeps goes back onto
62687
+ // the entry the popup was opened from while KEEPING the url as it
62688
+ // stands at that moment (see useNavState's leave()), so what onClose
62689
+ // spells into the address — a route param naming what was being
62690
+ // edited, cleared now that nothing is — has to be written before,
62691
+ // or the landing puts it back.
62577
62692
  onClose?.(closeEvent);
62693
+ leaveExpanded({
62694
+ isBack: closeEvent.detail.isCancel
62695
+ });
62696
+ // Reset so the next opening re-evaluates screen size
62697
+ resetMode();
62578
62698
  }
62579
62699
  };
62580
62700
  });
@@ -62968,6 +63088,7 @@ const PickerContentInsidePopup = props => {
62968
63088
  // its own width once lifted. Dialog's own `sizeFromAnchor`.
62969
63089
  dialogSizeFromAnchor,
62970
63090
  animation,
63091
+ grow,
62971
63092
  animationDuration,
62972
63093
  // mode="callout": what the callout says about what it holds, and paints
62973
63094
  // in its border and icon — "none" for a plain tooltip (see the callout
@@ -63046,6 +63167,7 @@ const PickerContentInsidePopup = props => {
63046
63167
  expandY: isPopover ? undefined : dialogExpandY,
63047
63168
  dockedOnSmallTouchScreen: isPopover ? undefined : dockedOnSmallTouchScreen,
63048
63169
  sizeFromAnchor: isPopover ? undefined : dialogSizeFromAnchor,
63170
+ grow: isPopover ? undefined : grow,
63049
63171
  children: jsx(PopupModeContext.Provider, {
63050
63172
  value: mode,
63051
63173
  children: children