@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.
@@ -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
 
@@ -58750,6 +58782,16 @@ const css$E = /* css */`
58750
58782
  --backdrop-background: transparent;
58751
58783
  --backdrop-filter: none;
58752
58784
  }
58785
+ /* A wall-less top-layer dialog reaches the top layer through the Popover
58786
+ API, and the browser generates a ::backdrop for a popover exactly as it
58787
+ does for a modal. Nothing is meant to be between this dialog and the
58788
+ page, so that pseudo-element paints nothing — last, after the variant
58789
+ rules it must win over: they answer how a wall is painted, this one
58790
+ says there is no wall to paint. */
58791
+ &[data-backdrop="none"] {
58792
+ --backdrop-background: transparent;
58793
+ --backdrop-filter: none;
58794
+ }
58753
58795
  &::backdrop {
58754
58796
  background: var(--backdrop-background);
58755
58797
  backdrop-filter: var(--backdrop-filter);
@@ -58941,6 +58983,73 @@ const css$E = /* css */`
58941
58983
  &::view-transition-new(navi-popup-grow) {
58942
58984
  animation-duration: var(--navi-popup-grow-duration, 0.25s);
58943
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
+ }
58944
59053
  }
58945
59054
 
58946
59055
  ${surfaceTextCss}
@@ -59077,6 +59186,15 @@ const css$E = /* css */`
59077
59186
  * prop) and grows into whatever inside the dialog carries `data-grow`, the
59078
59187
  * dialog itself when nothing does. `"auto"` never picks it: only the caller
59079
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.
59080
59198
  * @param {string} [props.animationDuration] - Maps to
59081
59199
  * `--popup-animation-duration`.
59082
59200
  * @param {Element|{current: Element}|string} [props.anchor] - Never used for
@@ -59419,6 +59537,7 @@ const useDialogProps = props => {
59419
59537
  // once, held at that size while open. See this prop's own JSDoc above.
59420
59538
  sizing = "auto",
59421
59539
  animation,
59540
+ grow = "box",
59422
59541
  // Inert unless sizeFromAnchor below (see this file's top comment) —
59423
59542
  // Dialog's own positioning is never relative to it.
59424
59543
  anchor,
@@ -59642,7 +59761,8 @@ const useDialogProps = props => {
59642
59761
  return;
59643
59762
  }
59644
59763
  growPopupFromAnchor(dialogEl, anchorElement, applyChange, {
59645
- opened
59764
+ opened,
59765
+ grow
59646
59766
  });
59647
59767
  } : null;
59648
59768
 
@@ -60121,11 +60241,18 @@ const useDialogProps = props => {
60121
60241
  // (and harmless for a local dialog, whose real backdrop element gets it
60122
60242
  // via backdropProps).
60123
60243
  "data-backdrop-variant": backdropVariant,
60244
+ // That this dialog has no wall, said in the DOM: the browser generates a
60245
+ // ::backdrop for the popover a wall-less top-layer dialog is shown as,
60246
+ // and the CSS above keys off this to leave it unpainted. Also the only
60247
+ // way anything outside can read the absence — a test measuring pixels is
60248
+ // the alternative.
60249
+ "data-backdrop": backdrop ? undefined : "none",
60124
60250
  // Read by the native ::backdrop, which inherits them from here (see this
60125
60251
  // file's CSS) — a local dialog's own backdrop element gets them via
60126
- // backdropProps above.
60127
- backdropColor,
60128
- backdropFilter,
60252
+ // backdropProps above. Dropped without a wall: they say how one is
60253
+ // painted, and inline they would beat the rule that says there is none.
60254
+ "backdropColor": backdrop ? backdropColor : undefined,
60255
+ "backdropFilter": backdrop ? backdropFilter : undefined,
60129
60256
  "styleCSSVars": DIALOG_STYLE_CSS_VARS,
60130
60257
  ...rest,
60131
60258
  ...autoFocusProps,
@@ -61962,6 +62089,7 @@ const css$C = /* css */`@layer navi {
61962
62089
  * popup meant to dock on a phone must not declare itself compact — see
61963
62090
  * `maxWidth` below.
61964
62091
  * @param {boolean} [props.sizeFromAnchor] - **Dialog-only**, same guard.
62092
+ * @param {"box"|"scene"} [props.grow] - **Dialog-only**, same guard.
61965
62093
  * @param {string} [props.positionArea] - Forwarded as-is — `Dialog` and
61966
62094
  * `Popover` have different own defaults (`"center"` vs. `"bottom"`),
61967
62095
  * deliberately not homogenized here (each reads best for its own typical
@@ -62068,6 +62196,7 @@ const Popup = props => {
62068
62196
  // in the dialog branch, so they never reach the popover element.
62069
62197
  dockedOnSmallTouchScreen,
62070
62198
  sizeFromAnchor,
62199
+ grow,
62071
62200
  ...rest
62072
62201
  } = props;
62073
62202
  const [mode] = useResolvedPopupMode(modeProp, maxWidth, {
@@ -62086,6 +62215,7 @@ const Popup = props => {
62086
62215
  ...rest,
62087
62216
  dockedOnSmallTouchScreen: dockedOnSmallTouchScreen,
62088
62217
  sizeFromAnchor: sizeFromAnchor,
62218
+ grow: grow,
62089
62219
  maxWidth: maxWidth,
62090
62220
  pointerInteractionOutsideEffect: pointerInteractionOutsideEffect,
62091
62221
  backdrop: backdrop,
@@ -62545,19 +62675,26 @@ const PickerCustom = props => {
62545
62675
  commitUIStateAsAnswer(controller, closeEvent);
62546
62676
  }
62547
62677
  }
62548
- leaveExpanded({
62549
- isBack: closeEvent.detail.isCancel
62550
- });
62551
- // Reset so the next opening re-evaluates screen size
62552
- resetMode();
62553
62678
  const confirmEvent = confirmEventRef.current;
62554
62679
  confirmEventRef.current = null;
62555
62680
  if (confirmEvent && !closeEvent.detail.isCancel) {
62556
62681
  onConfirm?.(confirmEvent);
62557
62682
  }
62558
- // Last, after the value bookkeeping above: whoever listens reads the
62559
- // 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.
62560
62692
  onClose?.(closeEvent);
62693
+ leaveExpanded({
62694
+ isBack: closeEvent.detail.isCancel
62695
+ });
62696
+ // Reset so the next opening re-evaluates screen size
62697
+ resetMode();
62561
62698
  }
62562
62699
  };
62563
62700
  });
@@ -62951,6 +63088,7 @@ const PickerContentInsidePopup = props => {
62951
63088
  // its own width once lifted. Dialog's own `sizeFromAnchor`.
62952
63089
  dialogSizeFromAnchor,
62953
63090
  animation,
63091
+ grow,
62954
63092
  animationDuration,
62955
63093
  // mode="callout": what the callout says about what it holds, and paints
62956
63094
  // in its border and icon — "none" for a plain tooltip (see the callout
@@ -63029,6 +63167,7 @@ const PickerContentInsidePopup = props => {
63029
63167
  expandY: isPopover ? undefined : dialogExpandY,
63030
63168
  dockedOnSmallTouchScreen: isPopover ? undefined : dockedOnSmallTouchScreen,
63031
63169
  sizeFromAnchor: isPopover ? undefined : dialogSizeFromAnchor,
63170
+ grow: isPopover ? undefined : grow,
63032
63171
  children: jsx(PopupModeContext.Provider, {
63033
63172
  value: mode,
63034
63173
  children: children