@jsenv/navi 0.29.419 → 0.29.420

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.
@@ -399,6 +399,22 @@ const whenRenderingResumes = (callback) => {
399
399
  renderingHold.waiting.push(callback);
400
400
  };
401
401
 
402
+ // After the render the change being applied has queued, for what depends on
403
+ // the page that change puts on screen — an offset given back to a page is
404
+ // clamped to whatever page the document holds when it is written. Under a hold
405
+ // that render runs when the hold is released, and this right after it.
406
+ // Otherwise the render is Preact's own, queued in a microtask, and every
407
+ // microtask has run by the next frame — before it paints.
408
+ const whenPageRendered = (callback) => {
409
+ if (renderingHold) {
410
+ renderingHold.waiting.push(callback);
411
+ return;
412
+ }
413
+ requestAnimationFrame(() => {
414
+ callback();
415
+ });
416
+ };
417
+
402
418
  // The hold a navigation takes on its way in — from before its first write,
403
419
  // because by the time a route announces that it matches, Preact has already
404
420
  // been told and the render is queued; a hold taken then is a hold taken too
@@ -773,7 +789,10 @@ const writeTarget = (element) => {
773
789
  silencedElement.id = addressTargetId;
774
790
  }
775
791
  window.history.replaceState(historyState, null, address);
776
- if (entry && navigation.currentEntry) {
792
+ // Only a state the entry had is put back: an entry nobody wrote one on
793
+ // (via_history.js never does) has nothing to lose, and updateCurrentEntry
794
+ // cannot say "none" — `state` is a required member, and undefined throws.
795
+ if (entry && navigation.currentEntry && entryState !== undefined) {
777
796
  navigation.updateCurrentEntry({ state: entryState });
778
797
  }
779
798
  targetWriteInProgress = false;
@@ -3527,6 +3546,8 @@ const actionWeakMap = new WeakMap();
3527
3546
  * than a first load (data_states.md). Called at the start of every run whose
3528
3547
  * value is undefined; the answer replaces it as usual, and `undefined` means
3529
3548
  * "nothing known". A resource `GET` uses it to draw the row its store holds.
3549
+ * An action declaring it is asked for it by a binding moving to new params,
3550
+ * rather than handed the previous params' answer (see inheritData).
3530
3551
  * @param {{ ms?: number, max?: number }} [rootOptions.keep] - how long an
3531
3552
  * answer stays good once it has landed. Without it an answer lives exactly as
3532
3553
  * long as something references it: the screen that asked goes away, and
@@ -4184,7 +4205,14 @@ const createAction = (callback, rootOptions = {}) => {
4184
4205
  if (rejected) {
4185
4206
  return failRun(rejectedValue);
4186
4207
  }
4187
- return onRunEnd();
4208
+ // Turning the answer into a value can throw as well — a resource
4209
+ // write answering nothing to upsert. That fails the run, as the
4210
+ // catch below does when the callback answered synchronously.
4211
+ try {
4212
+ return onRunEnd();
4213
+ } catch (e) {
4214
+ return failRun(e);
4215
+ }
4188
4216
  });
4189
4217
  } catch (e) {
4190
4218
  return failRun(e);
@@ -4224,6 +4252,7 @@ const createAction = (callback, rootOptions = {}) => {
4224
4252
 
4225
4253
  const privateProperties = {
4226
4254
  valueInitial,
4255
+ provisionalValue,
4227
4256
 
4228
4257
  performRun,
4229
4258
  performReset,
@@ -4255,7 +4284,8 @@ const createAction = (callback, rootOptions = {}) => {
4255
4284
  * This enables live updates - for example, performing an HTTP GET request every time
4256
4285
  * a list of filters changes, providing real-time results without user interaction.
4257
4286
  * @param {boolean} options.inheritData - When false, each new target action starts fresh with no inherited state.
4258
- * By default (true), the proxy carries over the previous target's value and error into the new action.
4287
+ * By default (true), the proxy carries over the previous target's value and error into the new action —
4288
+ * unless the action declares a `provisionalValue`, which then says what the new one holds.
4259
4289
  * This keeps the facade in sync with the latest known data: `action.dataSignal.value` only changes when a
4260
4290
  * new action completes, not when it starts loading. Code that needs to distinguish loading state can still
4261
4291
  * check `action.runningState`, while code that just reads `action.data` always sees the most recent
@@ -4302,8 +4332,14 @@ const createActionProxyFromSignal = (
4302
4332
  let currentActionPrivateProperties = getActionPrivateProperties(action);
4303
4333
  let actionTargetPreviousWeakRef = null;
4304
4334
 
4335
+ // An action saying for itself what it holds for params it has not answered
4336
+ // (provisionalValue: a resource GET draws the row its store holds) is not
4337
+ // handed the previous params' answer — an answer about another row. What it
4338
+ // does not know stays unknown: a skeleton, never a different row.
4339
+ const inheritsAnswer =
4340
+ inheritData && !getActionPrivateProperties(action).provisionalValue;
4305
4341
  const createTarget = (params) => {
4306
- if (inheritData) {
4342
+ if (inheritsAnswer) {
4307
4343
  const previousActionTarget = actionTargetPreviousWeakRef?.deref();
4308
4344
  const previousTarget = previousActionTarget || action;
4309
4345
  return action.bindParams(params, {
@@ -4416,6 +4452,12 @@ const createActionProxyFromSignal = (
4416
4452
  return signalProxy;
4417
4453
  };
4418
4454
 
4455
+ // Params bound onto this binding make the action bound to both — these over
4456
+ // the ones it follows, as an instance's go over its action's — following the
4457
+ // signals of both: what a control does with the action it is given, and
4458
+ // actionRunEffect. Kept per params like the action's own bindings, since a
4459
+ // control binds its action again at every render.
4460
+ const bindingOnProxyWeakMap = createJsValueWeakMap();
4419
4461
  Object.assign(actionProxy, {
4420
4462
  isAction: true,
4421
4463
  isProxy: true,
@@ -4440,14 +4482,27 @@ const createActionProxyFromSignal = (
4440
4482
  });
4441
4483
  return currentAction;
4442
4484
  },
4443
- bindParams: () => {
4444
- throw new Error(
4445
- `bindParams() is not supported on action proxies, use the underlying action instead`,
4485
+ bindParams: (newParamsOrSignal, options) => {
4486
+ const existingBinding = bindingOnProxyWeakMap.get(newParamsOrSignal);
4487
+ if (existingBinding) {
4488
+ return existingBinding;
4489
+ }
4490
+ const binding = action.bindParams(
4491
+ computed(() =>
4492
+ mergeActionParams(
4493
+ paramsSignal.value,
4494
+ readParamsHandedToBind(newParamsOrSignal),
4495
+ ),
4496
+ ),
4497
+ options,
4446
4498
  );
4499
+ bindingOnProxyWeakMap.set(newParamsOrSignal, binding);
4500
+ return binding;
4447
4501
  },
4448
4502
  replaceParams: null, // Will be set below
4449
4503
  toString: () => callSourceSignal.peek(), // peek: see the action's toString
4450
4504
  meta: action.meta,
4505
+ debug: action.debug,
4451
4506
 
4452
4507
  paramsSignal: proxyParamsSignal,
4453
4508
  paramsSettlingSignal,
@@ -4590,6 +4645,29 @@ const generateActionCallSource = (name, params) => {
4590
4645
  return `${name}${argsString}`;
4591
4646
  };
4592
4647
 
4648
+ // What params handed to bindParams say at this moment, read as a binding reads
4649
+ // them (see _bindParams): a signal is its value, and so is each signal a plain
4650
+ // object holds at its first level. Read inside a computed, it follows them.
4651
+ const readParamsHandedToBind = (newParamsOrSignal) => {
4652
+ if (isSignal(newParamsOrSignal)) {
4653
+ return newParamsOrSignal.value;
4654
+ }
4655
+ if (!isPlainObject(newParamsOrSignal)) {
4656
+ return newParamsOrSignal;
4657
+ }
4658
+ const params = {};
4659
+ for (const key of Object.keys(newParamsOrSignal)) {
4660
+ const value = newParamsOrSignal[key];
4661
+ const valueSignal = isSignal(value)
4662
+ ? value
4663
+ : value
4664
+ ? value[SYMBOL_OBJECT_SIGNAL]
4665
+ : undefined;
4666
+ params[key] = valueSignal ? valueSignal.value : value;
4667
+ }
4668
+ return params;
4669
+ };
4670
+
4593
4671
  const isPlainObject = (obj) => {
4594
4672
  if (typeof obj !== "object" || obj === null) {
4595
4673
  return false;
@@ -4702,9 +4780,16 @@ const actionRunEffect = (
4702
4780
  }
4703
4781
  return params;
4704
4782
  });
4783
+ // Whether the getter asks for a run is read off what it returned, never off
4784
+ // the params of the instance it leads to: an action handed here holding
4785
+ // params of its own (an instance, a binding) keeps them when the getter
4786
+ // returns nothing — and nothing must still run nothing.
4787
+ let asking = Boolean(actionParamsSignal.peek());
4705
4788
  const actionRunnedByThisEffect = action.bindParams(actionParamsSignal, {
4706
4789
  debounce,
4707
4790
  onChange: (actionTarget, actionTargetPrevious, { explicitRunIntent }) => {
4791
+ const askingBefore = asking;
4792
+ asking = Boolean(actionParamsSignal.peek());
4708
4793
  if (explicitRunIntent) {
4709
4794
  // The caller already issued an explicit run/rerun/prerun/reset/abort —
4710
4795
  // don't attempt to also auto-run from the params change to avoid double-runs.
@@ -4715,7 +4800,7 @@ const actionRunEffect = (
4715
4800
  }
4716
4801
  if (!actionTargetPrevious && actionTarget) {
4717
4802
  // first run
4718
- if (!actionTarget.params) {
4803
+ if (!asking) {
4719
4804
  // falsy params, don't run
4720
4805
  return;
4721
4806
  }
@@ -4731,12 +4816,12 @@ const actionRunEffect = (
4731
4816
  actionTarget
4732
4817
  ) {
4733
4818
  // params changed
4734
- if (!actionTarget.params) {
4819
+ if (!asking) {
4735
4820
  // falsy params, don't run
4736
4821
  actionTargetPrevious.abort("abortOnFalsyParams");
4737
4822
  return;
4738
4823
  }
4739
- if (!actionTargetPrevious.params) {
4824
+ if (!askingBefore) {
4740
4825
  // coming from falsy-params state: action may already be cached, avoid unnecessary rerun
4741
4826
  runUnwatched(() =>
4742
4827
  actionTarget.run({ reason: "params restored from falsy state" }),
@@ -4748,7 +4833,7 @@ const actionRunEffect = (
4748
4833
  },
4749
4834
  ...options,
4750
4835
  });
4751
- if (actionParamsSignal.peek()) {
4836
+ if (asking) {
4752
4837
  runUnwatched(() =>
4753
4838
  actionRunnedByThisEffect.run({ reason: "initial truthy params" }),
4754
4839
  );
@@ -6624,6 +6709,28 @@ let navDepthMax = 0;
6624
6709
 
6625
6710
  const getNavDepth = () => navDepth;
6626
6711
 
6712
+ // Where an entry's state says it stands, undefined when it says nothing.
6713
+ const readNavDepthInState = (state) => {
6714
+ if (state && typeof state[NAV_DEPTH_STATE_KEY] === "number") {
6715
+ return state[NAV_DEPTH_STATE_KEY];
6716
+ }
6717
+ return undefined;
6718
+ };
6719
+
6720
+ // The state of the entry a document is loaded into, carrying its depth. One
6721
+ // that says a depth was written by this document (a reload in the middle of
6722
+ // the stack) and keeps it. One that says none was not: a url opened cold, the
6723
+ // first of the stack — or, rarely, an entry the browser made for a fragment,
6724
+ // reloaded, which 0 under-reports as above. Written into the entry by the
6725
+ // integrations, because a back that returns there reads where it stands from
6726
+ // the entry alone.
6727
+ const stateWithNavDepthOnLoad = (state) => {
6728
+ if (readNavDepthInState(state) !== undefined) {
6729
+ return state;
6730
+ }
6731
+ return { ...state, [NAV_DEPTH_STATE_KEY]: 0 };
6732
+ };
6733
+
6627
6734
  const applyNavigationToNavDepth = (navigationType, state) => {
6628
6735
  if (navigationType === "push") {
6629
6736
  navDepth++;
@@ -6631,10 +6738,7 @@ const applyNavigationToNavDepth = (navigationType, state) => {
6631
6738
  navDepthMax = navDepth;
6632
6739
  } else if (navigationType === "replace") ; else {
6633
6740
  // load, reload, traverse: the entry itself says where it stands.
6634
- const depthInState =
6635
- state && typeof state[NAV_DEPTH_STATE_KEY] === "number"
6636
- ? state[NAV_DEPTH_STATE_KEY]
6637
- : undefined;
6741
+ const depthInState = readNavDepthInState(state);
6638
6742
  if (depthInState !== undefined) {
6639
6743
  navDepth = depthInState;
6640
6744
  if (navDepth > navDepthMax) {
@@ -10115,6 +10219,18 @@ const preloadUrl = (url) => {
10115
10219
  }
10116
10220
  };
10117
10221
 
10222
+ // What a reload asks of each action bound to a route (see routeAction): the
10223
+ // action to run again, or nothing when its params ask nothing.
10224
+ const routeReloadMap = new WeakMap();
10225
+ const registerRouteReload = (route, reload) => {
10226
+ let reloadSet = routeReloadMap.get(route);
10227
+ if (!reloadSet) {
10228
+ reloadSet = new Set();
10229
+ routeReloadMap.set(route, reloadSet);
10230
+ }
10231
+ reloadSet.add(reload);
10232
+ };
10233
+
10118
10234
  const routeStatePreloadMap = new WeakMap();
10119
10235
  const registerRouteStatePreload = (route, preload) => {
10120
10236
  let preloadSet = routeStatePreloadMap.get(route);
@@ -10204,6 +10320,7 @@ This prevents cross-test pollution and ensures clean state.`,
10204
10320
  const updateRoutes = (
10205
10321
  url,
10206
10322
  {
10323
+ navigationType,
10207
10324
  isVisited = () => false,
10208
10325
  // state
10209
10326
  } = {},
@@ -10503,6 +10620,27 @@ This prevents cross-test pollution and ensures clean state.`,
10503
10620
  }
10504
10621
  }
10505
10622
 
10623
+ // A route's actions run again when their params change (see routeAction),
10624
+ // and a reload changes none: it asks again for the page one is on. So the
10625
+ // actions of every matching route are handed back, to be rerun — except
10626
+ // those whose params ask nothing.
10627
+ if (navigationType === "reload") {
10628
+ const reloadSet = new Set();
10629
+ for (const route of returnValue.matchingRouteSet) {
10630
+ const routeReloadSet = routeReloadMap.get(route);
10631
+ if (!routeReloadSet) {
10632
+ continue;
10633
+ }
10634
+ for (const reload of routeReloadSet) {
10635
+ const actionToRerun = reload();
10636
+ if (actionToRerun) {
10637
+ reloadSet.add(actionToRerun);
10638
+ }
10639
+ }
10640
+ }
10641
+ returnValue.reloadSet = reloadSet;
10642
+ }
10643
+
10506
10644
  return returnValue;
10507
10645
  };
10508
10646
 
@@ -10890,11 +11028,18 @@ const installScrollRestoration = () => {
10890
11028
  }
10891
11029
  };
10892
11030
 
11031
+ // Where the document lands is the last arrival's to say. A return waits for its
11032
+ // page (see restoreScrollPositionOnReturn), and an arrival decided meanwhile — a
11033
+ // page that sends the reader elsewhere as it mounts — must not be scrolled to
11034
+ // where the return was going.
11035
+ let arrivalCount = 0;
11036
+
10893
11037
  // Nothing to put back is not the same as putting back the top: a page arrived
10894
11038
  // at for the first time is startAtTop's business, and this must not step on it.
10895
11039
  // Whether there was anything, for a caller who has an answer of its own for the
10896
11040
  // page that has never been read.
10897
11041
  const restoreScrollPosition = (url) => {
11042
+ arrivalCount++;
10898
11043
  const position = positionByUrl.get(new URL(url, window.location.href).href);
10899
11044
  if (!position) {
10900
11045
  return false;
@@ -10908,6 +11053,28 @@ const restoreScrollPosition = (url) => {
10908
11053
  return true;
10909
11054
  };
10910
11055
 
11056
+ // A traversal back to `url`. The offset is written once the page returned to is
11057
+ // rendered: written sooner, the document still holds the page being left and
11058
+ // clamps it to that page's height. It is read at once all the same — a page
11059
+ // left taller than the one arriving is clamped by the render itself, and that
11060
+ // clamp is a scroll recorded under the url already returned to.
11061
+ const restoreScrollPositionOnReturn = (url) => {
11062
+ const arrival = ++arrivalCount;
11063
+ const position = positionByUrl.get(new URL(url, window.location.href).href);
11064
+ if (!position) {
11065
+ return;
11066
+ }
11067
+ whenPageRendered(() => {
11068
+ if (arrival !== arrivalCount) {
11069
+ return;
11070
+ }
11071
+ if (documentHoldCount) {
11072
+ return;
11073
+ }
11074
+ scrollTo(position);
11075
+ });
11076
+ };
11077
+
10911
11078
  // A page one arrives at for the first time starts at its top. Only a document
10912
11079
  // navigation does that on its own: a pushState creates its entry with whatever
10913
11080
  // scroll happened to be there, so without this the page opens at the offset of
@@ -10933,6 +11100,7 @@ const startAtTop = (url, { from } = {}) => {
10933
11100
  if (!isArrival(url, { from })) {
10934
11101
  return;
10935
11102
  }
11103
+ arrivalCount++;
10936
11104
  window.scrollTo({ top: 0, left: 0, behavior: "instant" });
10937
11105
  };
10938
11106
  const isArrival = (url, { from }) => {
@@ -11403,12 +11571,12 @@ const setupBrowserIntegrationViaHistory = ({
11403
11571
  isVisited,
11404
11572
  state,
11405
11573
  });
11406
- // Where the document lands, said by what kind of arrival this is. Both are
11407
- // waited for, and for the same two reasons: the page has to be there to be
11408
- // scrolled, and a picture taken before it would be of a page at its top
11409
- // (see rendering_hold.js, which is where the waiting happens). After the
11410
- // history has been written too, so the entry being left keeps the offset
11411
- // it is at.
11574
+ // Where the document lands, said by what kind of arrival this is. Never
11575
+ // before the picture of a transition is taken, which would be of a page
11576
+ // at its top (see rendering_hold.js), and after the history has been
11577
+ // written, so the entry being left keeps the offset it is at. A return
11578
+ // also waits for the page it returns to: an offset is clamped to whatever
11579
+ // page the document holds when it is written.
11412
11580
  //
11413
11581
  // A replace gets neither: it is the same place said differently — a param
11414
11582
  // settling, a state written — and moving the reader for it would throw
@@ -11419,7 +11587,7 @@ const setupBrowserIntegrationViaHistory = ({
11419
11587
  if (navigationType === "push") {
11420
11588
  whenRenderingResumes(() => startAtTop(url, { from: urlLeft }));
11421
11589
  } else if (navigationType === "traverse") {
11422
- whenRenderingResumes(() => restoreScrollPosition(url));
11590
+ restoreScrollPositionOnReturn(url);
11423
11591
  }
11424
11592
  executeWithCleanup(
11425
11593
  () => allResult,
@@ -11654,11 +11822,12 @@ const setupBrowserIntegrationViaHistory = ({
11654
11822
  const init = () => {
11655
11823
  const url = window.location.href;
11656
11824
  const stateOnEntry = window.history.state;
11657
- const state = dropGeneratedIdKeys(stateOnEntry);
11825
+ const state = stateWithNavDepthOnLoad(dropGeneratedIdKeys(stateOnEntry));
11658
11826
  if (state !== stateOnEntry) {
11659
- // The entry itself has to lose them too, not just the document state:
11660
- // getDocumentState() reads the entry back, and every state write copies
11661
- // what it finds there onto the next one.
11827
+ // The entry itself has to carry it, not just the document state:
11828
+ // getDocumentState() reads the entry back, every state write copies what
11829
+ // it finds there onto the next one, and a back returning here reads its
11830
+ // depth from it.
11662
11831
  writeHistoryEntry("replace", state, url);
11663
11832
  }
11664
11833
  handleRoutingTask(url, {
@@ -11775,7 +11944,6 @@ const applyRouting = (
11775
11944
  url,
11776
11945
  {
11777
11946
  globalAbortSignal,
11778
- abortSignal,
11779
11947
  // state
11780
11948
  navigationType,
11781
11949
  isVisited,
@@ -11787,45 +11955,36 @@ const applyRouting = (
11787
11955
  // likely because code does not uses routing at all
11788
11956
  return {};
11789
11957
  }
11790
- const {
11791
- loadSet,
11792
- reloadSet,
11793
- abortSignalMap,
11794
- routeLoadRequestedMap,
11795
- activeRouteSet,
11796
- } = updateRoutes(url, {
11958
+ // The routes' actions follow the address by themselves (see routeAction):
11959
+ // what a navigation leaves to the routing is a reload, the one that asks
11960
+ // them again with the address unchanged.
11961
+ const { reloadSet } = updateRoutes(url, {
11797
11962
  navigationType,
11798
11963
  isVisited,
11799
11964
  // state,
11800
11965
  });
11801
- if (
11802
- (!loadSet || loadSet.size === 0) &&
11803
- (!reloadSet || reloadSet.size === 0)
11804
- ) {
11966
+ if (!reloadSet || reloadSet.size === 0) {
11805
11967
  return {
11806
11968
  allResult: undefined,
11807
11969
  requestedResult: undefined,
11808
- activeRouteSet: new Set(),
11809
11970
  };
11810
11971
  }
11811
11972
  const updateActionsResult = updateActions({
11812
11973
  globalAbortSignal,
11813
- abortSignal,
11814
- runSet: loadSet,
11974
+ // Not the navigation's own signal: the next navigation aborts that one, and
11975
+ // an action whose route still matches there would be left aborted — its
11976
+ // params do not change, so nothing would run it again.
11977
+ abortSignal: new AbortController().signal,
11815
11978
  rerunSet: reloadSet,
11816
- abortSignalMap,
11817
11979
  reason,
11818
- isReplace: navigationType === "replace",
11819
11980
  });
11820
11981
  const { allResult, runningActionSet } = updateActionsResult;
11821
11982
  const pendingTaskNameArray = [];
11822
- for (const [route, routeAction] of routeLoadRequestedMap) {
11823
- if (runningActionSet.has(routeAction)) {
11824
- pendingTaskNameArray.push(`${route.relativeUrl} -> ${routeAction.name}`);
11825
- }
11983
+ for (const runningAction of runningActionSet) {
11984
+ pendingTaskNameArray.push(runningAction.name);
11826
11985
  }
11827
11986
  routingWhile(() => allResult, pendingTaskNameArray);
11828
- return { ...updateActionsResult, activeRouteSet };
11987
+ return updateActionsResult;
11829
11988
  };
11830
11989
 
11831
11990
  const setupBrowserIntegration =
@@ -11907,6 +12066,12 @@ const stopLoad = (reason = "stopLoad() called") => {
11907
12066
  browserIntegration.stop(reason);
11908
12067
  }
11909
12068
  };
12069
+ /**
12070
+ * Asks again for the page one is on, without reloading the document: the
12071
+ * actions of the routes matching the url run again (see routeAction), one that
12072
+ * failed included — what a "Retry" under a page that could not load calls.
12073
+ * The address, the history and the scroll stay as they are.
12074
+ */
11910
12075
  const reload = browserIntegration.reload;
11911
12076
  /**
11912
12077
  * Go back to the screen this document came from.
@@ -11929,11 +12094,13 @@ const reload = browserIntegration.reload;
11929
12094
  * way a screen closed over a url keeps what was written to the url while it
11930
12095
  * was open (see useNavState's leave()).
11931
12096
  * @returns {Promise<boolean>|undefined}
11932
- * When there is something to go back to: a promise resolved once the back
11933
- * has landed and been applied (`true` — the document url and state say
11934
- * where it landed), or `false` when the traversal was preempted and landed
11935
- * nowhere. `undefined` otherwise — nothing happened, or the fallback took
11936
- * the current entry's place.
12097
+ * When there is something to go back to: a promise resolved with `true`
12098
+ * once the back has landed and been applied — the document url and state
12099
+ * then say where it landed. A back preempted by another navigation, which
12100
+ * lands nowhere, resolves `false` only under the Navigation API integration
12101
+ * (see USE_NAVIGATION_API, off today): the History API does not tell it
12102
+ * apart. `undefined` otherwise — nothing happened, or the fallback took the
12103
+ * current entry's place.
11937
12104
  */
11938
12105
  const navBack = browserIntegration.navBack;
11939
12106
  const navForward = browserIntegration.navForward;
@@ -12870,10 +13037,11 @@ const isKeyboardModality = () => keyboardNavigationUsed;
12870
13037
  {
12871
13038
  // We implement :focus and :focus-visible with enriched semantics:
12872
13039
  // an element is considered focused not only when it natively has focus, but also
12873
- // when a "focus proxy" element has focus (e.g. a read-only range input delegates
12874
- // focus to a sibling span) or when a controlling element has focus (e.g. a combobox
12875
- // input with aria-controls pointing to a listbox — the listbox should appear focused
12876
- // while the input is focused).
13040
+ // when it is a control proxy whose real control has focus (the proxy is what is
13041
+ // seen, the focus is dealt to the real control — see control_proxy.js) or when
13042
+ // a controlling element has focus (e.g. a combobox input with aria-controls
13043
+ // pointing to a listbox — the listbox should appear focused while the input is
13044
+ // focused).
12877
13045
  //
12878
13046
  // We intentionally reuse the native :focus / :focus-visible names rather than
12879
13047
  // introducing new navi-specific pseudo-classes (e.g. :-navi-focus). This is a
@@ -12965,7 +13133,7 @@ const isKeyboardModality = () => keyboardNavigationUsed;
12965
13133
 
12966
13134
  // Returns true when el holds focus indirectly — either because a controlling
12967
13135
  // element (aria-controls) has focus, or because el is a proxy whose target
12968
- // is itself controlled by a focused element.
13136
+ // has focus or is itself controlled by a focused element.
12969
13137
  const hasIndirectFocus = (el, { requireFocusVisible = false } = {}) => {
12970
13138
  // No ring inheritance without a keyboard: an editable target draws its own
12971
13139
  // ring on any focus (see isMatchingFocusVisible), but propagating that ring
@@ -21488,10 +21656,11 @@ const swipeTypeOf = (axis, pulled) => {
21488
21656
  * insists.
21489
21657
  *
21490
21658
  * So the interaction stays declared and says "refuse" in place of what it does.
21491
- * The threshold is the same one — a mouse travelling, a finger holding still, the
21492
- * first pixel inside a `data-drag-on-contact` — and at the instant the grab would
21493
- * have been acquired there is none: nothing translates, no copy is made, no
21494
- * release is answered. `refuse` is that instant.
21659
+ * The threshold is the same one — a mouse travelling a few pixels, a finger
21660
+ * holding still, or travelling those same few pixels inside a
21661
+ * `data-drag-on-contact` — and at the instant the grab would have been
21662
+ * acquired there is none: nothing translates, no copy is made, no release is
21663
+ * answered. `refuse` is that instant.
21495
21664
  *
21496
21665
  * It walks no axis, so it takes none: a box that travels and a surface that pans
21497
21666
  * both step over an element that is refusing, and a gesture starting on a locked
@@ -25102,8 +25271,9 @@ const createResourceLifecycleManager = () => {
25102
25271
  }
25103
25272
 
25104
25273
  // scopedMany auto-dependency: only rerun parent singular GET on child POST,
25105
- // and only when the parent GET previously returned the sub-resource embedded
25106
- // inside its response (detected via action._resultProperties).
25274
+ // only the GET of the owner the child was posted to, and only when that
25275
+ // GET previously returned the sub-resource embedded inside its response
25276
+ // (detected via action._resultProperties).
25107
25277
  // GET_MANY is excluded — a list of parents is not stale just because one
25108
25278
  // child item was added to one of them.
25109
25279
  scoped_many_effect: {
@@ -25124,6 +25294,15 @@ const createResourceLifecycleManager = () => {
25124
25294
  if (parentResource !== resourceScope) {
25125
25295
  continue;
25126
25296
  }
25297
+ if (
25298
+ !isOwnerReadBy(
25299
+ triggerResourceScope,
25300
+ triggeringAction,
25301
+ actionCandidate,
25302
+ )
25303
+ ) {
25304
+ break scoped_many_effect;
25305
+ }
25127
25306
  // Only rerun if the last GET response included the embedded sub-resource.
25128
25307
  if (
25129
25308
  !getActionResultProperties(actionCandidate)?.has(propertyName)
@@ -25240,6 +25419,27 @@ const isParamSubset = (parentParams, childParams) => {
25240
25419
  return true;
25241
25420
  };
25242
25421
 
25422
+ // Whether a GET of the owner read the item a scoped child's action was answered
25423
+ // for. The child's value names its owner first ([ownerKey, …], see scopedMany);
25424
+ // the GET's value is the id of the item it read. The key may be a unique key
25425
+ // rather than that id: both lead to the owner's one store of children. Values
25426
+ // are read off the signals: the child's action is still completing, and its
25427
+ // `value` property is only written once that batch ends.
25428
+ const isOwnerReadBy = (childResource, childAction, ownerGetAction) => {
25429
+ const childValue = childAction.valueSignal.peek();
25430
+ if (!Array.isArray(childValue)) {
25431
+ return false;
25432
+ }
25433
+ const [ownerKey] = childValue;
25434
+ const ownerId = ownerGetAction.valueSignal.peek();
25435
+ const { getChildStore } = childResource;
25436
+ if (!getChildStore) {
25437
+ return ownerKey === ownerId;
25438
+ }
25439
+ const childStore = getChildStore(ownerKey);
25440
+ return childStore !== undefined && childStore === getChildStore(ownerId);
25441
+ };
25442
+
25243
25443
  const paramScopeWeakSet = createIterableWeakSet();
25244
25444
  let paramScopeIdCounter = 0;
25245
25445
  const getParamScope = (params) => {
@@ -33741,11 +33941,13 @@ const bandTheCutMayBeRelaxedTo = (bandBefore, bandAfter, rectAfter) => {
33741
33941
  * where they are going, at the movement's own pace, each animation at the rate
33742
33942
  * that gets it there in that time.
33743
33943
  *
33744
- * Where they visibly stand is computed from the clock THROUGH the easing curve,
33745
- * never read off the pseudo-elements: getComputedStyle on them answers with the
33746
- * un-animated value — the animated one lives on the compositor, where no
33747
- * reading from here reaches. It is the same trap as the playbackRate setter,
33748
- * which is why every rate below is handed over with updatePlaybackRate.
33944
+ * Where they visibly stand is computed from the clock THROUGH the easing curve:
33945
+ * a fraction of the way, whatever property carries it — getComputedStyle on
33946
+ * the pseudo-elements gives the animated value of each property, with nothing
33947
+ * saying how far along its keyframes that value is. The rates are handed over
33948
+ * with updatePlaybackRate: in Chromium, the playbackRate setter given a
33949
+ * negative rate puts the pictures at their start at once, and leaves them
33950
+ * there while the clock counts down.
33749
33951
  */
33750
33952
 
33751
33953
  const viewTransitionAnimations = () => {
@@ -34330,11 +34532,11 @@ const css$12 = /* css */`@property --navi-route-transition-leave {
34330
34532
  *
34331
34533
  * It is a real box, and it must be: what is photographed and clipped IS its
34332
34534
  * rectangle. So `display: contents` cannot be used on it — an element with no
34333
- * box is never captured, the movement plays on nothing and the browser aborts
34334
- * the transition. Give it the layout the pages need instead — it is a `Box`,
34335
- * so `flex`, `className`, `style` and the rest are there for that. An
34336
- * application that already has an element holding its pages can mark that one
34337
- * with `data-navi-route-transition-area` rather than nesting another.
34535
+ * box is never captured, and the movement then plays on nothing (navi warns
34536
+ * once the pictures are taken). Give it the layout the pages need instead —
34537
+ * it is a `Box`, so `flex`, `className`, `style` and the rest are there for
34538
+ * that. An application that already has an element holding its pages can mark
34539
+ * that one with `data-navi-route-transition-area` rather than nesting another.
34338
34540
  *
34339
34541
  * @type {import("ignore:preact").FunctionComponent<{ children?: any, [key: string]: any }>}
34340
34542
  */
@@ -34395,10 +34597,14 @@ const RouteTransitionArea = ({
34395
34597
  * length of the transition the root carries
34396
34598
  * `data-navi-route-transition-type="<type>"` next to
34397
34599
  * `data-navi-route-transition="forward"|"back"`, and the application's CSS
34398
- * defines the movement against the view transition pseudo-elements:
34600
+ * defines the movement against the view transition pseudo-elements, for
34601
+ * both groups: `root` when the document travels, `navi-route-transition`
34602
+ * when an area is marked — the root is then unnamed, and a rule written for
34603
+ * `(root)` alone never applies:
34399
34604
  *
34400
34605
  * :root[data-navi-route-transition-type="spin"][data-navi-route-transition="forward"] {
34401
- * &::view-transition-new(root) {
34606
+ * &::view-transition-new(root),
34607
+ * &::view-transition-new(navi-route-transition) {
34402
34608
  * animation-name: my-spin-in;
34403
34609
  * }
34404
34610
  * }
@@ -35056,7 +35262,7 @@ const beginTransition = ({
35056
35262
  // and the movement then plays on the document itself.
35057
35263
  const areaElements = document.querySelectorAll(`[${TRANSITION_AREA_ATTRIBUTE}]`);
35058
35264
  if (areaElements.length > 1) {
35059
- warnOnce("several-areas", `${areaElements.length} elements carry ${TRANSITION_AREA_ATTRIBUTE}. They all take the same view-transition-name, and a name belongs to one element at a time: the browser refuses EVERY view transition of the document while this holds. Mark the one element the pages live in.`);
35265
+ warnOnce("several-areas", `${areaElements.length} elements carry ${TRANSITION_AREA_ATTRIBUTE}. They all take the same view-transition-name, and a name belongs to one element at a time: the browser refuses EVERY route transition while this holds. Mark the one element the pages live in.`);
35060
35266
  }
35061
35267
  const areaElement = areaElements.length > 0 ? areaElements[0] : null;
35062
35268
  // Everything about to be photographed goes deaf to the pointer; the controls
@@ -35800,8 +36006,9 @@ const css$11 = /* css */`.navi_route_travel {
35800
36006
  * - how to go to a tab. The default REPLACES the current history entry
35801
36007
  * rather than pushing one: a swipe is how one browses a page, not a place one
35802
36008
  * aimed at, and three swipes back and forth must not bury the way out of the
35803
- * page under six entries. A tab pressed is the other case and pushes, which
35804
- * is what its <Link> already does.
36009
+ * page under six entries. A tab pressed goes the way its <Link> says —
36010
+ * `<Link replace>` for a row of tabs, so a press and a swipe towards the
36011
+ * same neighbour write the same history.
35805
36012
  *
35806
36013
  * Every other prop lands on the box itself (`id`, `data-testid`, `className`,
35807
36014
  * …), which is how one of these is named: a page can hold several — a row of
@@ -35816,10 +36023,11 @@ const css$11 = /* css */`.navi_route_travel {
35816
36023
  * gesture applies its change on release instead of dragging a picture that does
35817
36024
  * not exist.
35818
36025
  *
35819
- * While a travel plays, the rest of the page is taken as a picture too — this
35820
- * box asks for `view-transition-name: root` back for that time, so an
35821
- * application that opts the document out for its own transitions gets its rule
35822
- * back the moment the travel is over.
36026
+ * While a travel plays, the rest of the page is NOT taken as a picture: the
36027
+ * root's name is dropped for that time (`view-transition-name: none`), so what
36028
+ * stands around the box — a tab row beside it — stays live and answers a press
36029
+ * mid-travel. The application's own rule for the root applies again the moment
36030
+ * the travel is over.
35823
36031
  */
35824
36032
  const RouteTravel = ({
35825
36033
  routes: routesProp,
@@ -37008,9 +37216,9 @@ const writeCover = (edge, element, size) => {
37008
37216
 
37009
37217
  /**
37010
37218
  * Binds an action to a route: it runs when the route matches, with the params
37011
- * the effect reads off the address, and is aborted when the route is left.
37012
- * Its data, its wait and its failure are read by the page through
37013
- * `useAsyncData`.
37219
+ * the effect reads off the address, runs again on a `reload()`, and is aborted
37220
+ * when the route is left. Its data, its wait and its failure are read by the
37221
+ * page through `useAsyncData`.
37014
37222
  *
37015
37223
  * A page's CODE is declared the same way — the import is one more thing the
37016
37224
  * address asks for, started with the data rather than after it:
@@ -37054,18 +37262,21 @@ const routeAction = (
37054
37262
  ? routes[0].matchingSignal
37055
37263
  : anyMatchingRouteSignal(routes);
37056
37264
  const readParams = paramsEffect || (() => true);
37057
- const actionBoundToRoute = actionRunEffect(
37058
- action,
37059
- () => {
37060
- const matching = routeMatchingSignal.value;
37061
- const params = readParams();
37062
- if (!matching) {
37063
- return null;
37064
- }
37065
- return params;
37066
- },
37067
- options,
37068
- );
37265
+ const readParamsAsked = () => {
37266
+ const matching = routeMatchingSignal.value;
37267
+ const params = readParams();
37268
+ if (!matching) {
37269
+ return null;
37270
+ }
37271
+ return params;
37272
+ };
37273
+ const actionBoundToRoute = actionRunEffect(action, readParamsAsked, options);
37274
+ for (const route of routes) {
37275
+ // Asked of the route's params as they are, like the effect asks them.
37276
+ registerRouteReload(route, () =>
37277
+ untracked(readParamsAsked) ? actionBoundToRoute : null,
37278
+ );
37279
+ }
37069
37280
  if (prefetch) {
37070
37281
  for (const route of routes) {
37071
37282
  if (paramsEffect) {
@@ -38999,10 +39210,11 @@ const flushSyncRendering = (fn) => {
38999
39210
  * without the content, and the browser builds it in an idle moment after
39000
39211
  * load — so by the time anyone clicks, it is usually already there.
39001
39212
  *
39002
- * "while-opened" is the opposite end: content that must be rebuilt from
39003
- * scratch every time, because what it shows is read once at build time and can
39004
- * change while the popup is closed — an uncontrolled field seeded from a
39005
- * `defaultValue`, a form whose fresh state is its initial state.
39213
+ * "while-opened" is the opposite end: content rebuilt from scratch every
39214
+ * time, a form whose fresh state is its initial state. What kept content
39215
+ * carries into the next opening is an unsent edit — an untouched field
39216
+ * follows a new `defaultValue` on its own (see followDefaultValue in
39217
+ * ui_state_controller.js) — and this is what throws that edit away.
39006
39218
  *
39007
39219
  * On top of whichever value is picked, intent on the anchor warms the content:
39008
39220
  * a pointer entering the popup's anchor, or focus landing in it, builds the
@@ -39036,9 +39248,11 @@ const flushSyncRendering = (fn) => {
39036
39248
  * click coming after the release, the browser taking the gesture
39037
39249
  * (`pointercancel`), or the next press starting, before any of its own
39038
39250
  * handlers run: one press at a time holds a content. A keyboard opening builds
39039
- * at open time. A popup with an `onOpen` is never warmed: that callback runs
39040
- * before the content is built (see open_controller.js), and a content seeding
39041
- * itself from what it writes must be built after it.
39251
+ * at open time. A "while-opened" popup with an `onOpen` is not warmed by the
39252
+ * press: that callback runs before the content is built (see
39253
+ * open_controller.js), and a content seeding itself from what it writes must
39254
+ * be built after it. Hover and focus warming, for the other values, builds
39255
+ * ahead of `onOpen` all the same.
39042
39256
  */
39043
39257
 
39044
39258
 
@@ -49646,12 +49860,13 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
49646
49860
  * does so by being NAMED, which is all the browser needs: any change played as
49647
49861
  * a view transition animates it on the same clock as everything else in that
49648
49862
  * transition. Inside a `RouteTravel` that means it follows the pages, and the
49649
- * thumb dragging them, without either of them being told about the other. It
49650
- * is named for a movement between two tabs of the row and for nothing else:
49651
- * when the pages move and the row is on one side only, the bar leaves or
49652
- * arrives with its row. For a nav made of slides (`slideContainer`) the bar
49653
- * is one element for the whole row, and it reads the travel the container
49654
- * publishes.
49863
+ * thumb dragging them, without either of them being told about the other.
49864
+ * During a route transition or a route travel it keeps the name only for a
49865
+ * movement between two tabs of the row: when the row is on one side only,
49866
+ * the bar leaves or arrives with its row. Any other view transition finds it
49867
+ * named, a picture of its own. For a nav made of slides (`slideContainer`)
49868
+ * the bar is one element for the whole row, and it reads the travel the
49869
+ * container publishes.
49655
49870
  * @param {string} [props.slideContainer] - the id of a `<SlideContainer>` these
49656
49871
  * tabs are about: each one says which slide it is (`<Link slide="…">`), the
49657
49872
  * container says which one is on screen, and pressing a tab travels there.
@@ -54160,7 +54375,8 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
54160
54375
  * `command` runs at once — the popup it sits in closes over the run, which
54161
54376
  * goes on behind. A failure then has no popup to be read in: `resetOnError`
54162
54377
  * puts the button back and the error callout is drawn on what surrounds the
54163
- * closed popup (see docs/popup_open.md#the-popup-owns-its-open-state).
54378
+ * closed popup (see
54379
+ * docs/popup_open.md#closing-when-a-button-also-runs-an-action).
54164
54380
  * @param {boolean} [actionAbortable] The person waiting may give up on this
54165
54381
  * button's action: closing the popup the run holds calls it off and goes
54166
54382
  * through, instead of being refused. For a run whose answer may never come —
@@ -64008,7 +64224,7 @@ const css$E = /* css */`
64008
64224
  the length of the movement, and the picture of the lifted node comes out
64009
64225
  empty (Chrome 153, reproduced in a bare page). The price is a page frozen
64010
64226
  and unpressable while the movement plays: under a modal wall at the
64011
- opening, and for a quarter of a second at the closing. */
64227
+ opening, and for the popup's --popup-animation-duration at the closing. */
64012
64228
  :root[data-navi-popup-lift] {
64013
64229
  /* The popup's own pace, published on the root by popup_lift.js because the
64014
64230
  ::view-transition tree hangs off it and inherits from nowhere else. */
@@ -64352,16 +64568,21 @@ const css$E = /* css */`
64352
64568
  * @param {boolean|"last-resort"|"restore"} [props.autoFocus="last-resort"] -
64353
64569
  * Where the keyboard goes when this dialog opens — one rung of the ladder in
64354
64570
  * `docs/autofocus.md`, which is what to read for the whole of it.
64355
- * - `true` — the dialog element itself takes the keyboard, whatever it holds.
64356
- * For a dialog whose content is READ before it is filled: the focus starts
64357
- * at the top of the reading order and no virtual keyboard rises over it.
64571
+ * - `true` — the dialog element itself takes the keyboard rather than the
64572
+ * first focusable it holds; something inside that asks by name
64573
+ * (`autoFocus`) still comes first. For a dialog whose content is READ
64574
+ * before it is filled: the focus starts at the top of the reading
64575
+ * order and no virtual keyboard rises over it.
64358
64576
  * - `"last-resort"` — the dialog takes the keyboard only if it holds nothing
64359
64577
  * focusable of its own.
64360
64578
  * - `"restore"` — the dialog stays out of the opening focus chain unless it
64361
64579
  * held focus when it closed.
64362
- * - `false` — the dialog element itself is never where the focus lands; the
64363
- * opening still hands the focus to what the dialog holds. Unlike
64364
- * `Popover`'s `false`, it does not leave the keyboard where it was.
64580
+ * - `false` — the dialog says nothing, and the opening still hands out the
64581
+ * focus: a `<dialog>` is focusable and the first focusable of its own
64582
+ * subtree, so it takes the keyboard as with `true`. Where the first
64583
+ * focusable is skipped (a touch device, below), a last resort inside is
64584
+ * tried instead, and nothing there leaves the keyboard where it was.
64585
+ * Unlike `Popover`'s `false`, the transfer runs.
64365
64586
  * Wherever the keyboard is a virtual one (a touch device), the surface is
64366
64587
  * already what one arrives on: a popup is read before it is reached there, so
64367
64588
  * the focus only leaves it for something that asked by name (`autoFocus` on
@@ -64395,9 +64616,9 @@ const css$E = /* css */`
64395
64616
  * is "new" or "edit X" depending on the press must know which one it is
64396
64617
  * before what it holds is rendered.
64397
64618
  * @param {(event: Event) => void} [props.onClose] - Called when the dialog
64398
- * actually closes — not preventable (see `open_controller.js`'s own
64399
- * `onRequestClose`/`onClose` distinction; `onRequestClose` is where you'd
64400
- * veto a close instead).
64619
+ * actually closes — not preventable: it is the close happening, not a
64620
+ * request. What refuses a close is navi's: a control inside still
64621
+ * running its action (see popup_busy.js for the runs that let it through).
64401
64622
  * @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
64402
64623
  * Keeps the open state in the history entry, so a screen left and come back
64403
64624
  * to finds this popup as it was — open, and without an entrance playing: it
@@ -64423,8 +64644,9 @@ const css$E = /* css */`
64423
64644
  * `"idle"` builds them in a browser idle moment after load — "always" minus
64424
64645
  * the cost on the critical render. `"while-opened"` throws them away once
64425
64646
  * the popup has finished closing, for content whose fresh state is its
64426
- * initial state: an uncontrolled field seeded from a `defaultValue` that
64427
- * changed while the popup was closed. Intent on the anchor (pointer
64647
+ * initial state: an edit left unsent at the close does not come back at
64648
+ * the next opening (an untouched field needs none of this to show a new
64649
+ * `defaultValue` — it follows one). Intent on the anchor (pointer
64428
64650
  * entering it, focus landing in it) builds the content ahead of the click;
64429
64651
  * under `"while-opened"`, only a press on what opens the popup does (a
64430
64652
  * `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
@@ -65093,10 +65315,10 @@ const useDialogProps = props => {
65093
65315
  }
65094
65316
 
65095
65317
  // Suppressed until committed below — same @starting-style-avoidance
65096
- // reasoning as popover.jsx's own openEffect (see its top comment), even
65097
- // though Dialog never needs to measure/flip anything: it still needs a
65098
- // genuinely rendered "closed" frame to transition from, not a jump
65099
- // straight from not-shown to aria-expanded="true".
65318
+ // reasoning as popover.jsx's own openEffect (see popup_css.js's top
65319
+ // comment), even though Dialog never needs to measure/flip anything: it
65320
+ // still needs a genuinely rendered "closed" frame to transition from, not a
65321
+ // jump straight from not-shown to aria-expanded="true".
65100
65322
  dialogEl.style.transitionProperty = "none";
65101
65323
  if (backdropEl) {
65102
65324
  disarmBackdropHideRef.current?.();
@@ -66173,10 +66395,11 @@ const css$D = /* css */`
66173
66395
  * @param {boolean|"last-resort"|"restore"} [props.autoFocus="last-resort"] -
66174
66396
  * Where the keyboard goes when this popover opens — one rung of the ladder in
66175
66397
  * `docs/autofocus.md`, which is what to read for the whole of it.
66176
- * - `true` — the popover element itself takes the keyboard, whatever it
66177
- * holds. For a popover whose content is READ before it is filled: the focus
66178
- * starts at the top of the reading order and no virtual keyboard rises over
66179
- * it.
66398
+ * - `true` — the popover element itself takes the keyboard rather than the
66399
+ * first focusable it holds; something inside that asks by name
66400
+ * (`autoFocus`) still comes first. For a popover whose content is READ
66401
+ * before it is filled: the focus starts at the top of the reading
66402
+ * order and no virtual keyboard rises over it.
66180
66403
  * - `"last-resort"` — the popover takes the keyboard only if it holds nothing
66181
66404
  * focusable of its own.
66182
66405
  * - `"restore"` — the popover stays out of the opening focus chain unless it
@@ -66217,9 +66440,9 @@ const css$D = /* css */`
66217
66440
  * is "new" or "edit X" depending on the press must know which one it is
66218
66441
  * before what it holds is rendered.
66219
66442
  * @param {(event: Event) => void} [props.onClose] - Called when the popover
66220
- * actually closes — not preventable (see `open_controller.js`'s own
66221
- * `onRequestClose`/`onClose` distinction; `onRequestClose` is where you'd
66222
- * veto a close instead).
66443
+ * actually closes — not preventable: it is the close happening, not a
66444
+ * request. What refuses a close is navi's: a control inside still
66445
+ * running its action (see popup_busy.js for the runs that let it through).
66223
66446
  * @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
66224
66447
  * Keeps the open state in the history entry, so a screen left and come back
66225
66448
  * to finds this popup as it was — open, and without an entrance playing: it
@@ -66243,8 +66466,9 @@ const css$D = /* css */`
66243
66466
  * `"idle"` builds them in a browser idle moment after load — "always" minus
66244
66467
  * the cost on the critical render. `"while-opened"` throws them away once
66245
66468
  * the popup has finished closing, for content whose fresh state is its
66246
- * initial state: an uncontrolled field seeded from a `defaultValue` that
66247
- * changed while the popup was closed. Intent on the anchor (pointer
66469
+ * initial state: an edit left unsent at the close does not come back at
66470
+ * the next opening (an untouched field needs none of this to show a new
66471
+ * `defaultValue` — it follows one). Intent on the anchor (pointer
66248
66472
  * entering it, focus landing in it) builds the content ahead of the click;
66249
66473
  * under `"while-opened"`, only a press on what opens the popup does (a
66250
66474
  * `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
@@ -66669,7 +66893,7 @@ const usePopoverProps = props => {
66669
66893
  });
66670
66894
 
66671
66895
  // Suppressed until the popover is actually measured/positioned below —
66672
- // see this file's top comment for why @starting-style can't drive the
66896
+ // see popup_css.js's top comment for why @starting-style can't drive the
66673
66897
  // opening transition (it needs the popover's actual resting position
66674
66898
  // already in place, which requires a layout box that only exists once
66675
66899
  // shown).
@@ -67017,7 +67241,7 @@ const usePopoverProps = props => {
67017
67241
  // frame set up above as a real rendered state (the reflow), re-enables
67018
67242
  // transitions, flips aria-expanded="true" (only then does the CSS
67019
67243
  // transition play, from that just-committed frame to the open one,
67020
- // with no @starting-style involved at all — see this file's top
67244
+ // with no @starting-style involved at all — see popup_css.js's top
67021
67245
  // comment), suppresses pointer events until it settles, and transfers
67022
67246
  // focus in. Inlined rather than a standalone function since it only
67023
67247
  // has this one call site.
@@ -67504,8 +67728,9 @@ const css$C = /* css */`@layer navi {
67504
67728
  * `"idle"` builds them in a browser idle moment after load — `"always"`
67505
67729
  * minus the cost on the critical render.
67506
67730
  * `"while-opened"` throws them away once the popup has finished closing, for
67507
- * content whose fresh state is its initial state: an uncontrolled field
67508
- * seeded from a `defaultValue` that changed while the popup was closed.
67731
+ * content whose fresh state is its initial state: an edit left unsent at
67732
+ * the close does not come back at the next opening (an untouched field
67733
+ * needs none of this to show a new `defaultValue` — it follows one).
67509
67734
  * @param {import("ignore:preact").ComponentChildren} props.children
67510
67735
  */
67511
67736
  const Popup = props => {
@@ -67804,8 +68029,9 @@ const PickerCustom = props => {
67804
68029
  open,
67805
68030
  defaultOpen,
67806
68031
  // What Escape means for this picker. "cancel" (the default) puts back the
67807
- // value the picker had at open and, for a dialog, goes back in history —
67808
- // so everything written to the url while it was open goes back too.
68032
+ // value the picker had at open and, for a dialog with an explicit id, goes
68033
+ // back in history — so everything written to the url while it was open
68034
+ // goes back too (see pickerNavType below).
67809
68035
  // "close" makes Escape say the same thing as clicking outside: keep what
67810
68036
  // was chosen, close the popup.
67811
68037
  escapeEffect = "cancel",
@@ -68459,9 +68685,10 @@ const PickerContentInsidePopup = props => {
68459
68685
  // itself, and this one does not. Popover ignores it, same as Dialog ignores
68460
68686
  // marginWithAnchor.
68461
68687
  dockedOnSmallTouchScreen,
68462
- // Same again: the dialog takes the trigger's box as a floor (and, with
68463
- // dialogMaxWidth="var(--anchor-width)", as a ceiling) — what keeps a card
68464
- // its own width once lifted. Dialog's own `sizeFromAnchor`.
68688
+ // Same again: the dialog takes the trigger's box as a floor (Dialog's own
68689
+ // `sizeFromAnchor`) and, unless a dialogMaxWidth says otherwise, as a
68690
+ // ceiling (see maxWidth below) — what keeps a card its own width once
68691
+ // lifted.
68465
68692
  dialogSizeFromAnchor,
68466
68693
  // The caller's word on the popup's own box. Written on the popup element,
68467
68694
  // where Popover/Dialog map them to their own vars, rather than as vars on