@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.
@@ -426,6 +426,22 @@ const whenRenderingResumes = (callback) => {
426
426
  renderingHold.waiting.push(callback);
427
427
  };
428
428
 
429
+ // After the render the change being applied has queued, for what depends on
430
+ // the page that change puts on screen — an offset given back to a page is
431
+ // clamped to whatever page the document holds when it is written. Under a hold
432
+ // that render runs when the hold is released, and this right after it.
433
+ // Otherwise the render is Preact's own, queued in a microtask, and every
434
+ // microtask has run by the next frame — before it paints.
435
+ const whenPageRendered = (callback) => {
436
+ if (renderingHold) {
437
+ renderingHold.waiting.push(callback);
438
+ return;
439
+ }
440
+ requestAnimationFrame(() => {
441
+ callback();
442
+ });
443
+ };
444
+
429
445
  // The hold a navigation takes on its way in — from before its first write,
430
446
  // because by the time a route announces that it matches, Preact has already
431
447
  // been told and the render is queued; a hold taken then is a hold taken too
@@ -800,7 +816,10 @@ const writeTarget = (element) => {
800
816
  silencedElement.id = addressTargetId;
801
817
  }
802
818
  window.history.replaceState(historyState, null, address);
803
- if (entry && navigation.currentEntry) {
819
+ // Only a state the entry had is put back: an entry nobody wrote one on
820
+ // (via_history.js never does) has nothing to lose, and updateCurrentEntry
821
+ // cannot say "none" — `state` is a required member, and undefined throws.
822
+ if (entry && navigation.currentEntry && entryState !== undefined) {
804
823
  navigation.updateCurrentEntry({ state: entryState });
805
824
  }
806
825
  targetWriteInProgress = false;
@@ -3675,6 +3694,8 @@ const actionWeakMap = new WeakMap();
3675
3694
  * than a first load (data_states.md). Called at the start of every run whose
3676
3695
  * value is undefined; the answer replaces it as usual, and `undefined` means
3677
3696
  * "nothing known". A resource `GET` uses it to draw the row its store holds.
3697
+ * An action declaring it is asked for it by a binding moving to new params,
3698
+ * rather than handed the previous params' answer (see inheritData).
3678
3699
  * @param {{ ms?: number, max?: number }} [rootOptions.keep] - how long an
3679
3700
  * answer stays good once it has landed. Without it an answer lives exactly as
3680
3701
  * long as something references it: the screen that asked goes away, and
@@ -4342,7 +4363,14 @@ const createAction = (callback, rootOptions = {}) => {
4342
4363
  if (rejected) {
4343
4364
  return failRun(rejectedValue);
4344
4365
  }
4345
- return onRunEnd();
4366
+ // Turning the answer into a value can throw as well — a resource
4367
+ // write answering nothing to upsert. That fails the run, as the
4368
+ // catch below does when the callback answered synchronously.
4369
+ try {
4370
+ return onRunEnd();
4371
+ } catch (e) {
4372
+ return failRun(e);
4373
+ }
4346
4374
  });
4347
4375
  } catch (e) {
4348
4376
  return failRun(e);
@@ -4385,6 +4413,7 @@ const createAction = (callback, rootOptions = {}) => {
4385
4413
 
4386
4414
  const privateProperties = {
4387
4415
  valueInitial,
4416
+ provisionalValue,
4388
4417
 
4389
4418
  performRun,
4390
4419
  performReset,
@@ -4416,7 +4445,8 @@ const createAction = (callback, rootOptions = {}) => {
4416
4445
  * This enables live updates - for example, performing an HTTP GET request every time
4417
4446
  * a list of filters changes, providing real-time results without user interaction.
4418
4447
  * @param {boolean} options.inheritData - When false, each new target action starts fresh with no inherited state.
4419
- * By default (true), the proxy carries over the previous target's value and error into the new action.
4448
+ * By default (true), the proxy carries over the previous target's value and error into the new action —
4449
+ * unless the action declares a `provisionalValue`, which then says what the new one holds.
4420
4450
  * This keeps the facade in sync with the latest known data: `action.dataSignal.value` only changes when a
4421
4451
  * new action completes, not when it starts loading. Code that needs to distinguish loading state can still
4422
4452
  * check `action.runningState`, while code that just reads `action.data` always sees the most recent
@@ -4463,8 +4493,14 @@ const createActionProxyFromSignal = (
4463
4493
  let currentActionPrivateProperties = getActionPrivateProperties(action);
4464
4494
  let actionTargetPreviousWeakRef = null;
4465
4495
 
4496
+ // An action saying for itself what it holds for params it has not answered
4497
+ // (provisionalValue: a resource GET draws the row its store holds) is not
4498
+ // handed the previous params' answer — an answer about another row. What it
4499
+ // does not know stays unknown: a skeleton, never a different row.
4500
+ const inheritsAnswer =
4501
+ inheritData && !getActionPrivateProperties(action).provisionalValue;
4466
4502
  const createTarget = (params) => {
4467
- if (inheritData) {
4503
+ if (inheritsAnswer) {
4468
4504
  const previousActionTarget = actionTargetPreviousWeakRef?.deref();
4469
4505
  const previousTarget = previousActionTarget || action;
4470
4506
  return action.bindParams(params, {
@@ -4577,6 +4613,12 @@ const createActionProxyFromSignal = (
4577
4613
  return signalProxy;
4578
4614
  };
4579
4615
 
4616
+ // Params bound onto this binding make the action bound to both — these over
4617
+ // the ones it follows, as an instance's go over its action's — following the
4618
+ // signals of both: what a control does with the action it is given, and
4619
+ // actionRunEffect. Kept per params like the action's own bindings, since a
4620
+ // control binds its action again at every render.
4621
+ const bindingOnProxyWeakMap = createJsValueWeakMap();
4580
4622
  Object.assign(actionProxy, {
4581
4623
  isAction: true,
4582
4624
  isProxy: true,
@@ -4601,14 +4643,27 @@ const createActionProxyFromSignal = (
4601
4643
  });
4602
4644
  return currentAction;
4603
4645
  },
4604
- bindParams: () => {
4605
- throw new Error(
4606
- `bindParams() is not supported on action proxies, use the underlying action instead`,
4646
+ bindParams: (newParamsOrSignal, options) => {
4647
+ const existingBinding = bindingOnProxyWeakMap.get(newParamsOrSignal);
4648
+ if (existingBinding) {
4649
+ return existingBinding;
4650
+ }
4651
+ const binding = action.bindParams(
4652
+ computed(() =>
4653
+ mergeActionParams(
4654
+ paramsSignal.value,
4655
+ readParamsHandedToBind(newParamsOrSignal),
4656
+ ),
4657
+ ),
4658
+ options,
4607
4659
  );
4660
+ bindingOnProxyWeakMap.set(newParamsOrSignal, binding);
4661
+ return binding;
4608
4662
  },
4609
4663
  replaceParams: null, // Will be set below
4610
4664
  toString: () => callSourceSignal.peek(), // peek: see the action's toString
4611
4665
  meta: action.meta,
4666
+ debug: action.debug,
4612
4667
 
4613
4668
  paramsSignal: proxyParamsSignal,
4614
4669
  paramsSettlingSignal,
@@ -4751,6 +4806,29 @@ const generateActionCallSource = (name, params) => {
4751
4806
  return `${name}${argsString}`;
4752
4807
  };
4753
4808
 
4809
+ // What params handed to bindParams say at this moment, read as a binding reads
4810
+ // them (see _bindParams): a signal is its value, and so is each signal a plain
4811
+ // object holds at its first level. Read inside a computed, it follows them.
4812
+ const readParamsHandedToBind = (newParamsOrSignal) => {
4813
+ if (isSignal(newParamsOrSignal)) {
4814
+ return newParamsOrSignal.value;
4815
+ }
4816
+ if (!isPlainObject(newParamsOrSignal)) {
4817
+ return newParamsOrSignal;
4818
+ }
4819
+ const params = {};
4820
+ for (const key of Object.keys(newParamsOrSignal)) {
4821
+ const value = newParamsOrSignal[key];
4822
+ const valueSignal = isSignal(value)
4823
+ ? value
4824
+ : value
4825
+ ? value[SYMBOL_OBJECT_SIGNAL]
4826
+ : undefined;
4827
+ params[key] = valueSignal ? valueSignal.value : value;
4828
+ }
4829
+ return params;
4830
+ };
4831
+
4754
4832
  const isPlainObject = (obj) => {
4755
4833
  if (typeof obj !== "object" || obj === null) {
4756
4834
  return false;
@@ -4863,9 +4941,16 @@ const actionRunEffect = (
4863
4941
  }
4864
4942
  return params;
4865
4943
  });
4944
+ // Whether the getter asks for a run is read off what it returned, never off
4945
+ // the params of the instance it leads to: an action handed here holding
4946
+ // params of its own (an instance, a binding) keeps them when the getter
4947
+ // returns nothing — and nothing must still run nothing.
4948
+ let asking = Boolean(actionParamsSignal.peek());
4866
4949
  const actionRunnedByThisEffect = action.bindParams(actionParamsSignal, {
4867
4950
  debounce,
4868
4951
  onChange: (actionTarget, actionTargetPrevious, { explicitRunIntent }) => {
4952
+ const askingBefore = asking;
4953
+ asking = Boolean(actionParamsSignal.peek());
4869
4954
  if (explicitRunIntent) {
4870
4955
  // The caller already issued an explicit run/rerun/prerun/reset/abort —
4871
4956
  // don't attempt to also auto-run from the params change to avoid double-runs.
@@ -4876,7 +4961,7 @@ const actionRunEffect = (
4876
4961
  }
4877
4962
  if (!actionTargetPrevious && actionTarget) {
4878
4963
  // first run
4879
- if (!actionTarget.params) {
4964
+ if (!asking) {
4880
4965
  // falsy params, don't run
4881
4966
  return;
4882
4967
  }
@@ -4892,12 +4977,12 @@ const actionRunEffect = (
4892
4977
  actionTarget
4893
4978
  ) {
4894
4979
  // params changed
4895
- if (!actionTarget.params) {
4980
+ if (!asking) {
4896
4981
  // falsy params, don't run
4897
4982
  actionTargetPrevious.abort("abortOnFalsyParams");
4898
4983
  return;
4899
4984
  }
4900
- if (!actionTargetPrevious.params) {
4985
+ if (!askingBefore) {
4901
4986
  // coming from falsy-params state: action may already be cached, avoid unnecessary rerun
4902
4987
  runUnwatched(() =>
4903
4988
  actionTarget.run({ reason: "params restored from falsy state" }),
@@ -4909,7 +4994,7 @@ const actionRunEffect = (
4909
4994
  },
4910
4995
  ...options,
4911
4996
  });
4912
- if (actionParamsSignal.peek()) {
4997
+ if (asking) {
4913
4998
  runUnwatched(() =>
4914
4999
  actionRunnedByThisEffect.run({ reason: "initial truthy params" }),
4915
5000
  );
@@ -6981,6 +7066,28 @@ let navDepthMax = 0;
6981
7066
 
6982
7067
  const getNavDepth = () => navDepth;
6983
7068
 
7069
+ // Where an entry's state says it stands, undefined when it says nothing.
7070
+ const readNavDepthInState = (state) => {
7071
+ if (state && typeof state[NAV_DEPTH_STATE_KEY] === "number") {
7072
+ return state[NAV_DEPTH_STATE_KEY];
7073
+ }
7074
+ return undefined;
7075
+ };
7076
+
7077
+ // The state of the entry a document is loaded into, carrying its depth. One
7078
+ // that says a depth was written by this document (a reload in the middle of
7079
+ // the stack) and keeps it. One that says none was not: a url opened cold, the
7080
+ // first of the stack — or, rarely, an entry the browser made for a fragment,
7081
+ // reloaded, which 0 under-reports as above. Written into the entry by the
7082
+ // integrations, because a back that returns there reads where it stands from
7083
+ // the entry alone.
7084
+ const stateWithNavDepthOnLoad = (state) => {
7085
+ if (readNavDepthInState(state) !== undefined) {
7086
+ return state;
7087
+ }
7088
+ return { ...state, [NAV_DEPTH_STATE_KEY]: 0 };
7089
+ };
7090
+
6984
7091
  const applyNavigationToNavDepth = (navigationType, state) => {
6985
7092
  if (navigationType === "push") {
6986
7093
  navDepth++;
@@ -6988,10 +7095,7 @@ const applyNavigationToNavDepth = (navigationType, state) => {
6988
7095
  navDepthMax = navDepth;
6989
7096
  } else if (navigationType === "replace") ; else {
6990
7097
  // load, reload, traverse: the entry itself says where it stands.
6991
- const depthInState =
6992
- state && typeof state[NAV_DEPTH_STATE_KEY] === "number"
6993
- ? state[NAV_DEPTH_STATE_KEY]
6994
- : undefined;
7098
+ const depthInState = readNavDepthInState(state);
6995
7099
  if (depthInState !== undefined) {
6996
7100
  navDepth = depthInState;
6997
7101
  if (navDepth > navDepthMax) {
@@ -10524,6 +10628,18 @@ const preloadUrl = (url) => {
10524
10628
  }
10525
10629
  };
10526
10630
 
10631
+ // What a reload asks of each action bound to a route (see routeAction): the
10632
+ // action to run again, or nothing when its params ask nothing.
10633
+ const routeReloadMap = new WeakMap();
10634
+ const registerRouteReload = (route, reload) => {
10635
+ let reloadSet = routeReloadMap.get(route);
10636
+ if (!reloadSet) {
10637
+ reloadSet = new Set();
10638
+ routeReloadMap.set(route, reloadSet);
10639
+ }
10640
+ reloadSet.add(reload);
10641
+ };
10642
+
10527
10643
  const routeStatePreloadMap = new WeakMap();
10528
10644
  const registerRouteStatePreload = (route, preload) => {
10529
10645
  let preloadSet = routeStatePreloadMap.get(route);
@@ -10613,6 +10729,7 @@ This prevents cross-test pollution and ensures clean state.`,
10613
10729
  const updateRoutes = (
10614
10730
  url,
10615
10731
  {
10732
+ navigationType,
10616
10733
  isVisited = () => false,
10617
10734
  // state
10618
10735
  } = {},
@@ -10912,6 +11029,27 @@ This prevents cross-test pollution and ensures clean state.`,
10912
11029
  }
10913
11030
  }
10914
11031
 
11032
+ // A route's actions run again when their params change (see routeAction),
11033
+ // and a reload changes none: it asks again for the page one is on. So the
11034
+ // actions of every matching route are handed back, to be rerun — except
11035
+ // those whose params ask nothing.
11036
+ if (navigationType === "reload") {
11037
+ const reloadSet = new Set();
11038
+ for (const route of returnValue.matchingRouteSet) {
11039
+ const routeReloadSet = routeReloadMap.get(route);
11040
+ if (!routeReloadSet) {
11041
+ continue;
11042
+ }
11043
+ for (const reload of routeReloadSet) {
11044
+ const actionToRerun = reload();
11045
+ if (actionToRerun) {
11046
+ reloadSet.add(actionToRerun);
11047
+ }
11048
+ }
11049
+ }
11050
+ returnValue.reloadSet = reloadSet;
11051
+ }
11052
+
10915
11053
  return returnValue;
10916
11054
  };
10917
11055
 
@@ -11318,11 +11456,18 @@ const installScrollRestoration = () => {
11318
11456
  }
11319
11457
  };
11320
11458
 
11459
+ // Where the document lands is the last arrival's to say. A return waits for its
11460
+ // page (see restoreScrollPositionOnReturn), and an arrival decided meanwhile — a
11461
+ // page that sends the reader elsewhere as it mounts — must not be scrolled to
11462
+ // where the return was going.
11463
+ let arrivalCount = 0;
11464
+
11321
11465
  // Nothing to put back is not the same as putting back the top: a page arrived
11322
11466
  // at for the first time is startAtTop's business, and this must not step on it.
11323
11467
  // Whether there was anything, for a caller who has an answer of its own for the
11324
11468
  // page that has never been read.
11325
11469
  const restoreScrollPosition = (url) => {
11470
+ arrivalCount++;
11326
11471
  const position = positionByUrl.get(new URL(url, window.location.href).href);
11327
11472
  if (!position) {
11328
11473
  return false;
@@ -11336,6 +11481,28 @@ const restoreScrollPosition = (url) => {
11336
11481
  return true;
11337
11482
  };
11338
11483
 
11484
+ // A traversal back to `url`. The offset is written once the page returned to is
11485
+ // rendered: written sooner, the document still holds the page being left and
11486
+ // clamps it to that page's height. It is read at once all the same — a page
11487
+ // left taller than the one arriving is clamped by the render itself, and that
11488
+ // clamp is a scroll recorded under the url already returned to.
11489
+ const restoreScrollPositionOnReturn = (url) => {
11490
+ const arrival = ++arrivalCount;
11491
+ const position = positionByUrl.get(new URL(url, window.location.href).href);
11492
+ if (!position) {
11493
+ return;
11494
+ }
11495
+ whenPageRendered(() => {
11496
+ if (arrival !== arrivalCount) {
11497
+ return;
11498
+ }
11499
+ if (documentHoldCount) {
11500
+ return;
11501
+ }
11502
+ scrollTo(position);
11503
+ });
11504
+ };
11505
+
11339
11506
  // A page one arrives at for the first time starts at its top. Only a document
11340
11507
  // navigation does that on its own: a pushState creates its entry with whatever
11341
11508
  // scroll happened to be there, so without this the page opens at the offset of
@@ -11361,6 +11528,7 @@ const startAtTop = (url, { from } = {}) => {
11361
11528
  if (!isArrival(url, { from })) {
11362
11529
  return;
11363
11530
  }
11531
+ arrivalCount++;
11364
11532
  window.scrollTo({ top: 0, left: 0, behavior: "instant" });
11365
11533
  };
11366
11534
  const isArrival = (url, { from }) => {
@@ -11831,12 +11999,12 @@ const setupBrowserIntegrationViaHistory = ({
11831
11999
  isVisited,
11832
12000
  state,
11833
12001
  });
11834
- // Where the document lands, said by what kind of arrival this is. Both are
11835
- // waited for, and for the same two reasons: the page has to be there to be
11836
- // scrolled, and a picture taken before it would be of a page at its top
11837
- // (see rendering_hold.js, which is where the waiting happens). After the
11838
- // history has been written too, so the entry being left keeps the offset
11839
- // it is at.
12002
+ // Where the document lands, said by what kind of arrival this is. Never
12003
+ // before the picture of a transition is taken, which would be of a page
12004
+ // at its top (see rendering_hold.js), and after the history has been
12005
+ // written, so the entry being left keeps the offset it is at. A return
12006
+ // also waits for the page it returns to: an offset is clamped to whatever
12007
+ // page the document holds when it is written.
11840
12008
  //
11841
12009
  // A replace gets neither: it is the same place said differently — a param
11842
12010
  // settling, a state written — and moving the reader for it would throw
@@ -11847,7 +12015,7 @@ const setupBrowserIntegrationViaHistory = ({
11847
12015
  if (navigationType === "push") {
11848
12016
  whenRenderingResumes(() => startAtTop(url, { from: urlLeft }));
11849
12017
  } else if (navigationType === "traverse") {
11850
- whenRenderingResumes(() => restoreScrollPosition(url));
12018
+ restoreScrollPositionOnReturn(url);
11851
12019
  }
11852
12020
  executeWithCleanup(
11853
12021
  () => allResult,
@@ -12082,11 +12250,12 @@ const setupBrowserIntegrationViaHistory = ({
12082
12250
  const init = () => {
12083
12251
  const url = window.location.href;
12084
12252
  const stateOnEntry = window.history.state;
12085
- const state = dropGeneratedIdKeys(stateOnEntry);
12253
+ const state = stateWithNavDepthOnLoad(dropGeneratedIdKeys(stateOnEntry));
12086
12254
  if (state !== stateOnEntry) {
12087
- // The entry itself has to lose them too, not just the document state:
12088
- // getDocumentState() reads the entry back, and every state write copies
12089
- // what it finds there onto the next one.
12255
+ // The entry itself has to carry it, not just the document state:
12256
+ // getDocumentState() reads the entry back, every state write copies what
12257
+ // it finds there onto the next one, and a back returning here reads its
12258
+ // depth from it.
12090
12259
  writeHistoryEntry("replace", state, url);
12091
12260
  }
12092
12261
  handleRoutingTask(url, {
@@ -12256,7 +12425,6 @@ const applyRouting = (
12256
12425
  url,
12257
12426
  {
12258
12427
  globalAbortSignal,
12259
- abortSignal,
12260
12428
  // state
12261
12429
  navigationType,
12262
12430
  isVisited,
@@ -12268,45 +12436,36 @@ const applyRouting = (
12268
12436
  // likely because code does not uses routing at all
12269
12437
  return {};
12270
12438
  }
12271
- const {
12272
- loadSet,
12273
- reloadSet,
12274
- abortSignalMap,
12275
- routeLoadRequestedMap,
12276
- activeRouteSet,
12277
- } = updateRoutes(url, {
12439
+ // The routes' actions follow the address by themselves (see routeAction):
12440
+ // what a navigation leaves to the routing is a reload, the one that asks
12441
+ // them again with the address unchanged.
12442
+ const { reloadSet } = updateRoutes(url, {
12278
12443
  navigationType,
12279
12444
  isVisited,
12280
12445
  // state,
12281
12446
  });
12282
- if (
12283
- (!loadSet || loadSet.size === 0) &&
12284
- (!reloadSet || reloadSet.size === 0)
12285
- ) {
12447
+ if (!reloadSet || reloadSet.size === 0) {
12286
12448
  return {
12287
12449
  allResult: undefined,
12288
12450
  requestedResult: undefined,
12289
- activeRouteSet: new Set(),
12290
12451
  };
12291
12452
  }
12292
12453
  const updateActionsResult = updateActions({
12293
12454
  globalAbortSignal,
12294
- abortSignal,
12295
- runSet: loadSet,
12455
+ // Not the navigation's own signal: the next navigation aborts that one, and
12456
+ // an action whose route still matches there would be left aborted — its
12457
+ // params do not change, so nothing would run it again.
12458
+ abortSignal: new AbortController().signal,
12296
12459
  rerunSet: reloadSet,
12297
- abortSignalMap,
12298
12460
  reason,
12299
- isReplace: navigationType === "replace",
12300
12461
  });
12301
12462
  const { allResult, runningActionSet } = updateActionsResult;
12302
12463
  const pendingTaskNameArray = [];
12303
- for (const [route, routeAction] of routeLoadRequestedMap) {
12304
- if (runningActionSet.has(routeAction)) {
12305
- pendingTaskNameArray.push(`${route.relativeUrl} -> ${routeAction.name}`);
12306
- }
12464
+ for (const runningAction of runningActionSet) {
12465
+ pendingTaskNameArray.push(runningAction.name);
12307
12466
  }
12308
12467
  routingWhile(() => allResult, pendingTaskNameArray);
12309
- return { ...updateActionsResult, activeRouteSet };
12468
+ return updateActionsResult;
12310
12469
  };
12311
12470
 
12312
12471
  const setupBrowserIntegration =
@@ -12388,6 +12547,12 @@ const stopLoad = (reason = "stopLoad() called") => {
12388
12547
  browserIntegration.stop(reason);
12389
12548
  }
12390
12549
  };
12550
+ /**
12551
+ * Asks again for the page one is on, without reloading the document: the
12552
+ * actions of the routes matching the url run again (see routeAction), one that
12553
+ * failed included — what a "Retry" under a page that could not load calls.
12554
+ * The address, the history and the scroll stay as they are.
12555
+ */
12391
12556
  const reload = browserIntegration.reload;
12392
12557
  /**
12393
12558
  * Go back to the screen this document came from.
@@ -12410,11 +12575,13 @@ const reload = browserIntegration.reload;
12410
12575
  * way a screen closed over a url keeps what was written to the url while it
12411
12576
  * was open (see useNavState's leave()).
12412
12577
  * @returns {Promise<boolean>|undefined}
12413
- * When there is something to go back to: a promise resolved once the back
12414
- * has landed and been applied (`true` — the document url and state say
12415
- * where it landed), or `false` when the traversal was preempted and landed
12416
- * nowhere. `undefined` otherwise — nothing happened, or the fallback took
12417
- * the current entry's place.
12578
+ * When there is something to go back to: a promise resolved with `true`
12579
+ * once the back has landed and been applied — the document url and state
12580
+ * then say where it landed. A back preempted by another navigation, which
12581
+ * lands nowhere, resolves `false` only under the Navigation API integration
12582
+ * (see USE_NAVIGATION_API, off today): the History API does not tell it
12583
+ * apart. `undefined` otherwise — nothing happened, or the fallback took the
12584
+ * current entry's place.
12418
12585
  */
12419
12586
  const navBack = browserIntegration.navBack;
12420
12587
  const navForward = browserIntegration.navForward;
@@ -13429,10 +13596,11 @@ const isKeyboardModality = () => keyboardNavigationUsed;
13429
13596
  {
13430
13597
  // We implement :focus and :focus-visible with enriched semantics:
13431
13598
  // an element is considered focused not only when it natively has focus, but also
13432
- // when a "focus proxy" element has focus (e.g. a read-only range input delegates
13433
- // focus to a sibling span) or when a controlling element has focus (e.g. a combobox
13434
- // input with aria-controls pointing to a listbox — the listbox should appear focused
13435
- // while the input is focused).
13599
+ // when it is a control proxy whose real control has focus (the proxy is what is
13600
+ // seen, the focus is dealt to the real control — see control_proxy.js) or when
13601
+ // a controlling element has focus (e.g. a combobox input with aria-controls
13602
+ // pointing to a listbox — the listbox should appear focused while the input is
13603
+ // focused).
13436
13604
  //
13437
13605
  // We intentionally reuse the native :focus / :focus-visible names rather than
13438
13606
  // introducing new navi-specific pseudo-classes (e.g. :-navi-focus). This is a
@@ -13524,7 +13692,7 @@ const isKeyboardModality = () => keyboardNavigationUsed;
13524
13692
 
13525
13693
  // Returns true when el holds focus indirectly — either because a controlling
13526
13694
  // element (aria-controls) has focus, or because el is a proxy whose target
13527
- // is itself controlled by a focused element.
13695
+ // has focus or is itself controlled by a focused element.
13528
13696
  const hasIndirectFocus = (el, { requireFocusVisible = false } = {}) => {
13529
13697
  // No ring inheritance without a keyboard: an editable target draws its own
13530
13698
  // ring on any focus (see isMatchingFocusVisible), but propagating that ring
@@ -22271,10 +22439,11 @@ const swipeTypeOf = (axis, pulled) => {
22271
22439
  * insists.
22272
22440
  *
22273
22441
  * So the interaction stays declared and says "refuse" in place of what it does.
22274
- * The threshold is the same one — a mouse travelling, a finger holding still, the
22275
- * first pixel inside a `data-drag-on-contact` — and at the instant the grab would
22276
- * have been acquired there is none: nothing translates, no copy is made, no
22277
- * release is answered. `refuse` is that instant.
22442
+ * The threshold is the same one — a mouse travelling a few pixels, a finger
22443
+ * holding still, or travelling those same few pixels inside a
22444
+ * `data-drag-on-contact` — and at the instant the grab would have been
22445
+ * acquired there is none: nothing translates, no copy is made, no release is
22446
+ * answered. `refuse` is that instant.
22278
22447
  *
22279
22448
  * It walks no axis, so it takes none: a box that travels and a surface that pans
22280
22449
  * both step over an element that is refusing, and a gesture starting on a locked
@@ -25991,8 +26160,9 @@ const createResourceLifecycleManager = () => {
25991
26160
  }
25992
26161
 
25993
26162
  // scopedMany auto-dependency: only rerun parent singular GET on child POST,
25994
- // and only when the parent GET previously returned the sub-resource embedded
25995
- // inside its response (detected via action._resultProperties).
26163
+ // only the GET of the owner the child was posted to, and only when that
26164
+ // GET previously returned the sub-resource embedded inside its response
26165
+ // (detected via action._resultProperties).
25996
26166
  // GET_MANY is excluded — a list of parents is not stale just because one
25997
26167
  // child item was added to one of them.
25998
26168
  scoped_many_effect: {
@@ -26013,6 +26183,15 @@ const createResourceLifecycleManager = () => {
26013
26183
  if (parentResource !== resourceScope) {
26014
26184
  continue;
26015
26185
  }
26186
+ if (
26187
+ !isOwnerReadBy(
26188
+ triggerResourceScope,
26189
+ triggeringAction,
26190
+ actionCandidate,
26191
+ )
26192
+ ) {
26193
+ break scoped_many_effect;
26194
+ }
26016
26195
  // Only rerun if the last GET response included the embedded sub-resource.
26017
26196
  if (
26018
26197
  !getActionResultProperties(actionCandidate)?.has(propertyName)
@@ -26129,6 +26308,27 @@ const isParamSubset = (parentParams, childParams) => {
26129
26308
  return true;
26130
26309
  };
26131
26310
 
26311
+ // Whether a GET of the owner read the item a scoped child's action was answered
26312
+ // for. The child's value names its owner first ([ownerKey, …], see scopedMany);
26313
+ // the GET's value is the id of the item it read. The key may be a unique key
26314
+ // rather than that id: both lead to the owner's one store of children. Values
26315
+ // are read off the signals: the child's action is still completing, and its
26316
+ // `value` property is only written once that batch ends.
26317
+ const isOwnerReadBy = (childResource, childAction, ownerGetAction) => {
26318
+ const childValue = childAction.valueSignal.peek();
26319
+ if (!Array.isArray(childValue)) {
26320
+ return false;
26321
+ }
26322
+ const [ownerKey] = childValue;
26323
+ const ownerId = ownerGetAction.valueSignal.peek();
26324
+ const { getChildStore } = childResource;
26325
+ if (!getChildStore) {
26326
+ return ownerKey === ownerId;
26327
+ }
26328
+ const childStore = getChildStore(ownerKey);
26329
+ return childStore !== undefined && childStore === getChildStore(ownerId);
26330
+ };
26331
+
26132
26332
  const paramScopeWeakSet = createIterableWeakSet();
26133
26333
  let paramScopeIdCounter = 0;
26134
26334
  const getParamScope = (params) => {
@@ -34729,11 +34929,13 @@ const bandTheCutMayBeRelaxedTo = (bandBefore, bandAfter, rectAfter) => {
34729
34929
  * where they are going, at the movement's own pace, each animation at the rate
34730
34930
  * that gets it there in that time.
34731
34931
  *
34732
- * Where they visibly stand is computed from the clock THROUGH the easing curve,
34733
- * never read off the pseudo-elements: getComputedStyle on them answers with the
34734
- * un-animated value — the animated one lives on the compositor, where no
34735
- * reading from here reaches. It is the same trap as the playbackRate setter,
34736
- * which is why every rate below is handed over with updatePlaybackRate.
34932
+ * Where they visibly stand is computed from the clock THROUGH the easing curve:
34933
+ * a fraction of the way, whatever property carries it — getComputedStyle on
34934
+ * the pseudo-elements gives the animated value of each property, with nothing
34935
+ * saying how far along its keyframes that value is. The rates are handed over
34936
+ * with updatePlaybackRate: in Chromium, the playbackRate setter given a
34937
+ * negative rate puts the pictures at their start at once, and leaves them
34938
+ * there while the clock counts down.
34737
34939
  */
34738
34940
 
34739
34941
  const viewTransitionAnimations = () => {
@@ -35318,11 +35520,11 @@ const css$12 = /* css */`@property --navi-route-transition-leave {
35318
35520
  *
35319
35521
  * It is a real box, and it must be: what is photographed and clipped IS its
35320
35522
  * rectangle. So `display: contents` cannot be used on it — an element with no
35321
- * box is never captured, the movement plays on nothing and the browser aborts
35322
- * the transition. Give it the layout the pages need instead — it is a `Box`,
35323
- * so `flex`, `className`, `style` and the rest are there for that. An
35324
- * application that already has an element holding its pages can mark that one
35325
- * with `data-navi-route-transition-area` rather than nesting another.
35523
+ * box is never captured, and the movement then plays on nothing (navi warns
35524
+ * once the pictures are taken). Give it the layout the pages need instead —
35525
+ * it is a `Box`, so `flex`, `className`, `style` and the rest are there for
35526
+ * that. An application that already has an element holding its pages can mark
35527
+ * that one with `data-navi-route-transition-area` rather than nesting another.
35326
35528
  *
35327
35529
  * @type {import("ignore:preact").FunctionComponent<{ children?: any, [key: string]: any }>}
35328
35530
  */
@@ -35383,10 +35585,14 @@ const RouteTransitionArea = ({
35383
35585
  * length of the transition the root carries
35384
35586
  * `data-navi-route-transition-type="<type>"` next to
35385
35587
  * `data-navi-route-transition="forward"|"back"`, and the application's CSS
35386
- * defines the movement against the view transition pseudo-elements:
35588
+ * defines the movement against the view transition pseudo-elements, for
35589
+ * both groups: `root` when the document travels, `navi-route-transition`
35590
+ * when an area is marked — the root is then unnamed, and a rule written for
35591
+ * `(root)` alone never applies:
35387
35592
  *
35388
35593
  * :root[data-navi-route-transition-type="spin"][data-navi-route-transition="forward"] {
35389
- * &::view-transition-new(root) {
35594
+ * &::view-transition-new(root),
35595
+ * &::view-transition-new(navi-route-transition) {
35390
35596
  * animation-name: my-spin-in;
35391
35597
  * }
35392
35598
  * }
@@ -36044,7 +36250,7 @@ const beginTransition = ({
36044
36250
  // and the movement then plays on the document itself.
36045
36251
  const areaElements = document.querySelectorAll(`[${TRANSITION_AREA_ATTRIBUTE}]`);
36046
36252
  if (areaElements.length > 1) {
36047
- 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.`);
36253
+ 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.`);
36048
36254
  }
36049
36255
  const areaElement = areaElements.length > 0 ? areaElements[0] : null;
36050
36256
  // Everything about to be photographed goes deaf to the pointer; the controls
@@ -36788,8 +36994,9 @@ const css$11 = /* css */`.navi_route_travel {
36788
36994
  * - how to go to a tab. The default REPLACES the current history entry
36789
36995
  * rather than pushing one: a swipe is how one browses a page, not a place one
36790
36996
  * aimed at, and three swipes back and forth must not bury the way out of the
36791
- * page under six entries. A tab pressed is the other case and pushes, which
36792
- * is what its <Link> already does.
36997
+ * page under six entries. A tab pressed goes the way its <Link> says —
36998
+ * `<Link replace>` for a row of tabs, so a press and a swipe towards the
36999
+ * same neighbour write the same history.
36793
37000
  *
36794
37001
  * Every other prop lands on the box itself (`id`, `data-testid`, `className`,
36795
37002
  * …), which is how one of these is named: a page can hold several — a row of
@@ -36804,10 +37011,11 @@ const css$11 = /* css */`.navi_route_travel {
36804
37011
  * gesture applies its change on release instead of dragging a picture that does
36805
37012
  * not exist.
36806
37013
  *
36807
- * While a travel plays, the rest of the page is taken as a picture too — this
36808
- * box asks for `view-transition-name: root` back for that time, so an
36809
- * application that opts the document out for its own transitions gets its rule
36810
- * back the moment the travel is over.
37014
+ * While a travel plays, the rest of the page is NOT taken as a picture: the
37015
+ * root's name is dropped for that time (`view-transition-name: none`), so what
37016
+ * stands around the box — a tab row beside it — stays live and answers a press
37017
+ * mid-travel. The application's own rule for the root applies again the moment
37018
+ * the travel is over.
36811
37019
  */
36812
37020
  const RouteTravel = ({
36813
37021
  routes: routesProp,
@@ -37996,9 +38204,9 @@ const writeCover = (edge, element, size) => {
37996
38204
 
37997
38205
  /**
37998
38206
  * Binds an action to a route: it runs when the route matches, with the params
37999
- * the effect reads off the address, and is aborted when the route is left.
38000
- * Its data, its wait and its failure are read by the page through
38001
- * `useAsyncData`.
38207
+ * the effect reads off the address, runs again on a `reload()`, and is aborted
38208
+ * when the route is left. Its data, its wait and its failure are read by the
38209
+ * page through `useAsyncData`.
38002
38210
  *
38003
38211
  * A page's CODE is declared the same way — the import is one more thing the
38004
38212
  * address asks for, started with the data rather than after it:
@@ -38042,18 +38250,21 @@ const routeAction = (
38042
38250
  ? routes[0].matchingSignal
38043
38251
  : anyMatchingRouteSignal(routes);
38044
38252
  const readParams = paramsEffect || (() => true);
38045
- const actionBoundToRoute = actionRunEffect(
38046
- action,
38047
- () => {
38048
- const matching = routeMatchingSignal.value;
38049
- const params = readParams();
38050
- if (!matching) {
38051
- return null;
38052
- }
38053
- return params;
38054
- },
38055
- options,
38056
- );
38253
+ const readParamsAsked = () => {
38254
+ const matching = routeMatchingSignal.value;
38255
+ const params = readParams();
38256
+ if (!matching) {
38257
+ return null;
38258
+ }
38259
+ return params;
38260
+ };
38261
+ const actionBoundToRoute = actionRunEffect(action, readParamsAsked, options);
38262
+ for (const route of routes) {
38263
+ // Asked of the route's params as they are, like the effect asks them.
38264
+ registerRouteReload(route, () =>
38265
+ untracked(readParamsAsked) ? actionBoundToRoute : null,
38266
+ );
38267
+ }
38057
38268
  if (prefetch) {
38058
38269
  for (const route of routes) {
38059
38270
  if (paramsEffect) {
@@ -40038,10 +40249,11 @@ const flushSyncRendering = (fn) => {
40038
40249
  * without the content, and the browser builds it in an idle moment after
40039
40250
  * load — so by the time anyone clicks, it is usually already there.
40040
40251
  *
40041
- * "while-opened" is the opposite end: content that must be rebuilt from
40042
- * scratch every time, because what it shows is read once at build time and can
40043
- * change while the popup is closed — an uncontrolled field seeded from a
40044
- * `defaultValue`, a form whose fresh state is its initial state.
40252
+ * "while-opened" is the opposite end: content rebuilt from scratch every
40253
+ * time, a form whose fresh state is its initial state. What kept content
40254
+ * carries into the next opening is an unsent edit — an untouched field
40255
+ * follows a new `defaultValue` on its own (see followDefaultValue in
40256
+ * ui_state_controller.js) — and this is what throws that edit away.
40045
40257
  *
40046
40258
  * On top of whichever value is picked, intent on the anchor warms the content:
40047
40259
  * a pointer entering the popup's anchor, or focus landing in it, builds the
@@ -40075,9 +40287,11 @@ const flushSyncRendering = (fn) => {
40075
40287
  * click coming after the release, the browser taking the gesture
40076
40288
  * (`pointercancel`), or the next press starting, before any of its own
40077
40289
  * handlers run: one press at a time holds a content. A keyboard opening builds
40078
- * at open time. A popup with an `onOpen` is never warmed: that callback runs
40079
- * before the content is built (see open_controller.js), and a content seeding
40080
- * itself from what it writes must be built after it.
40290
+ * at open time. A "while-opened" popup with an `onOpen` is not warmed by the
40291
+ * press: that callback runs before the content is built (see
40292
+ * open_controller.js), and a content seeding itself from what it writes must
40293
+ * be built after it. Hover and focus warming, for the other values, builds
40294
+ * ahead of `onOpen` all the same.
40081
40295
  */
40082
40296
 
40083
40297
 
@@ -50841,12 +51055,13 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
50841
51055
  * does so by being NAMED, which is all the browser needs: any change played as
50842
51056
  * a view transition animates it on the same clock as everything else in that
50843
51057
  * transition. Inside a `RouteTravel` that means it follows the pages, and the
50844
- * thumb dragging them, without either of them being told about the other. It
50845
- * is named for a movement between two tabs of the row and for nothing else:
50846
- * when the pages move and the row is on one side only, the bar leaves or
50847
- * arrives with its row. For a nav made of slides (`slideContainer`) the bar
50848
- * is one element for the whole row, and it reads the travel the container
50849
- * publishes.
51058
+ * thumb dragging them, without either of them being told about the other.
51059
+ * During a route transition or a route travel it keeps the name only for a
51060
+ * movement between two tabs of the row: when the row is on one side only,
51061
+ * the bar leaves or arrives with its row. Any other view transition finds it
51062
+ * named, a picture of its own. For a nav made of slides (`slideContainer`)
51063
+ * the bar is one element for the whole row, and it reads the travel the
51064
+ * container publishes.
50850
51065
  * @param {string} [props.slideContainer] - the id of a `<SlideContainer>` these
50851
51066
  * tabs are about: each one says which slide it is (`<Link slide="…">`), the
50852
51067
  * container says which one is on screen, and pressing a tab travels there.
@@ -55358,7 +55573,8 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
55358
55573
  * `command` runs at once — the popup it sits in closes over the run, which
55359
55574
  * goes on behind. A failure then has no popup to be read in: `resetOnError`
55360
55575
  * puts the button back and the error callout is drawn on what surrounds the
55361
- * closed popup (see docs/popup_open.md#the-popup-owns-its-open-state).
55576
+ * closed popup (see
55577
+ * docs/popup_open.md#closing-when-a-button-also-runs-an-action).
55362
55578
  * @param {boolean} [actionAbortable] The person waiting may give up on this
55363
55579
  * button's action: closing the popup the run holds calls it off and goes
55364
55580
  * through, instead of being refused. For a run whose answer may never come —
@@ -65286,7 +65502,7 @@ const css$E = /* css */`
65286
65502
  the length of the movement, and the picture of the lifted node comes out
65287
65503
  empty (Chrome 153, reproduced in a bare page). The price is a page frozen
65288
65504
  and unpressable while the movement plays: under a modal wall at the
65289
- opening, and for a quarter of a second at the closing. */
65505
+ opening, and for the popup's --popup-animation-duration at the closing. */
65290
65506
  :root[data-navi-popup-lift] {
65291
65507
  /* The popup's own pace, published on the root by popup_lift.js because the
65292
65508
  ::view-transition tree hangs off it and inherits from nowhere else. */
@@ -65630,16 +65846,21 @@ const css$E = /* css */`
65630
65846
  * @param {boolean|"last-resort"|"restore"} [props.autoFocus="last-resort"] -
65631
65847
  * Where the keyboard goes when this dialog opens — one rung of the ladder in
65632
65848
  * `docs/autofocus.md`, which is what to read for the whole of it.
65633
- * - `true` — the dialog element itself takes the keyboard, whatever it holds.
65634
- * For a dialog whose content is READ before it is filled: the focus starts
65635
- * at the top of the reading order and no virtual keyboard rises over it.
65849
+ * - `true` — the dialog element itself takes the keyboard rather than the
65850
+ * first focusable it holds; something inside that asks by name
65851
+ * (`autoFocus`) still comes first. For a dialog whose content is READ
65852
+ * before it is filled: the focus starts at the top of the reading
65853
+ * order and no virtual keyboard rises over it.
65636
65854
  * - `"last-resort"` — the dialog takes the keyboard only if it holds nothing
65637
65855
  * focusable of its own.
65638
65856
  * - `"restore"` — the dialog stays out of the opening focus chain unless it
65639
65857
  * held focus when it closed.
65640
- * - `false` — the dialog element itself is never where the focus lands; the
65641
- * opening still hands the focus to what the dialog holds. Unlike
65642
- * `Popover`'s `false`, it does not leave the keyboard where it was.
65858
+ * - `false` — the dialog says nothing, and the opening still hands out the
65859
+ * focus: a `<dialog>` is focusable and the first focusable of its own
65860
+ * subtree, so it takes the keyboard as with `true`. Where the first
65861
+ * focusable is skipped (a touch device, below), a last resort inside is
65862
+ * tried instead, and nothing there leaves the keyboard where it was.
65863
+ * Unlike `Popover`'s `false`, the transfer runs.
65643
65864
  * Wherever the keyboard is a virtual one (a touch device), the surface is
65644
65865
  * already what one arrives on: a popup is read before it is reached there, so
65645
65866
  * the focus only leaves it for something that asked by name (`autoFocus` on
@@ -65673,9 +65894,9 @@ const css$E = /* css */`
65673
65894
  * is "new" or "edit X" depending on the press must know which one it is
65674
65895
  * before what it holds is rendered.
65675
65896
  * @param {(event: Event) => void} [props.onClose] - Called when the dialog
65676
- * actually closes — not preventable (see `open_controller.js`'s own
65677
- * `onRequestClose`/`onClose` distinction; `onRequestClose` is where you'd
65678
- * veto a close instead).
65897
+ * actually closes — not preventable: it is the close happening, not a
65898
+ * request. What refuses a close is navi's: a control inside still
65899
+ * running its action (see popup_busy.js for the runs that let it through).
65679
65900
  * @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
65680
65901
  * Keeps the open state in the history entry, so a screen left and come back
65681
65902
  * to finds this popup as it was — open, and without an entrance playing: it
@@ -65701,8 +65922,9 @@ const css$E = /* css */`
65701
65922
  * `"idle"` builds them in a browser idle moment after load — "always" minus
65702
65923
  * the cost on the critical render. `"while-opened"` throws them away once
65703
65924
  * the popup has finished closing, for content whose fresh state is its
65704
- * initial state: an uncontrolled field seeded from a `defaultValue` that
65705
- * changed while the popup was closed. Intent on the anchor (pointer
65925
+ * initial state: an edit left unsent at the close does not come back at
65926
+ * the next opening (an untouched field needs none of this to show a new
65927
+ * `defaultValue` — it follows one). Intent on the anchor (pointer
65706
65928
  * entering it, focus landing in it) builds the content ahead of the click;
65707
65929
  * under `"while-opened"`, only a press on what opens the popup does (a
65708
65930
  * `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
@@ -66391,10 +66613,10 @@ const useDialogProps = props => {
66391
66613
  }
66392
66614
 
66393
66615
  // Suppressed until committed below — same @starting-style-avoidance
66394
- // reasoning as popover.jsx's own openEffect (see its top comment), even
66395
- // though Dialog never needs to measure/flip anything: it still needs a
66396
- // genuinely rendered "closed" frame to transition from, not a jump
66397
- // straight from not-shown to aria-expanded="true".
66616
+ // reasoning as popover.jsx's own openEffect (see popup_css.js's top
66617
+ // comment), even though Dialog never needs to measure/flip anything: it
66618
+ // still needs a genuinely rendered "closed" frame to transition from, not a
66619
+ // jump straight from not-shown to aria-expanded="true".
66398
66620
  dialogEl.style.transitionProperty = "none";
66399
66621
  if (backdropEl) {
66400
66622
  disarmBackdropHideRef.current?.();
@@ -67533,10 +67755,11 @@ const css$D = /* css */`
67533
67755
  * @param {boolean|"last-resort"|"restore"} [props.autoFocus="last-resort"] -
67534
67756
  * Where the keyboard goes when this popover opens — one rung of the ladder in
67535
67757
  * `docs/autofocus.md`, which is what to read for the whole of it.
67536
- * - `true` — the popover element itself takes the keyboard, whatever it
67537
- * holds. For a popover whose content is READ before it is filled: the focus
67538
- * starts at the top of the reading order and no virtual keyboard rises over
67539
- * it.
67758
+ * - `true` — the popover element itself takes the keyboard rather than the
67759
+ * first focusable it holds; something inside that asks by name
67760
+ * (`autoFocus`) still comes first. For a popover whose content is READ
67761
+ * before it is filled: the focus starts at the top of the reading
67762
+ * order and no virtual keyboard rises over it.
67540
67763
  * - `"last-resort"` — the popover takes the keyboard only if it holds nothing
67541
67764
  * focusable of its own.
67542
67765
  * - `"restore"` — the popover stays out of the opening focus chain unless it
@@ -67577,9 +67800,9 @@ const css$D = /* css */`
67577
67800
  * is "new" or "edit X" depending on the press must know which one it is
67578
67801
  * before what it holds is rendered.
67579
67802
  * @param {(event: Event) => void} [props.onClose] - Called when the popover
67580
- * actually closes — not preventable (see `open_controller.js`'s own
67581
- * `onRequestClose`/`onClose` distinction; `onRequestClose` is where you'd
67582
- * veto a close instead).
67803
+ * actually closes — not preventable: it is the close happening, not a
67804
+ * request. What refuses a close is navi's: a control inside still
67805
+ * running its action (see popup_busy.js for the runs that let it through).
67583
67806
  * @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
67584
67807
  * Keeps the open state in the history entry, so a screen left and come back
67585
67808
  * to finds this popup as it was — open, and without an entrance playing: it
@@ -67603,8 +67826,9 @@ const css$D = /* css */`
67603
67826
  * `"idle"` builds them in a browser idle moment after load — "always" minus
67604
67827
  * the cost on the critical render. `"while-opened"` throws them away once
67605
67828
  * the popup has finished closing, for content whose fresh state is its
67606
- * initial state: an uncontrolled field seeded from a `defaultValue` that
67607
- * changed while the popup was closed. Intent on the anchor (pointer
67829
+ * initial state: an edit left unsent at the close does not come back at
67830
+ * the next opening (an untouched field needs none of this to show a new
67831
+ * `defaultValue` — it follows one). Intent on the anchor (pointer
67608
67832
  * entering it, focus landing in it) builds the content ahead of the click;
67609
67833
  * under `"while-opened"`, only a press on what opens the popup does (a
67610
67834
  * `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
@@ -68037,7 +68261,7 @@ const usePopoverProps = props => {
68037
68261
  });
68038
68262
 
68039
68263
  // Suppressed until the popover is actually measured/positioned below —
68040
- // see this file's top comment for why @starting-style can't drive the
68264
+ // see popup_css.js's top comment for why @starting-style can't drive the
68041
68265
  // opening transition (it needs the popover's actual resting position
68042
68266
  // already in place, which requires a layout box that only exists once
68043
68267
  // shown).
@@ -68385,7 +68609,7 @@ const usePopoverProps = props => {
68385
68609
  // frame set up above as a real rendered state (the reflow), re-enables
68386
68610
  // transitions, flips aria-expanded="true" (only then does the CSS
68387
68611
  // transition play, from that just-committed frame to the open one,
68388
- // with no @starting-style involved at all — see this file's top
68612
+ // with no @starting-style involved at all — see popup_css.js's top
68389
68613
  // comment), suppresses pointer events until it settles, and transfers
68390
68614
  // focus in. Inlined rather than a standalone function since it only
68391
68615
  // has this one call site.
@@ -68873,8 +69097,9 @@ const css$C = /* css */`@layer navi {
68873
69097
  * `"idle"` builds them in a browser idle moment after load — `"always"`
68874
69098
  * minus the cost on the critical render.
68875
69099
  * `"while-opened"` throws them away once the popup has finished closing, for
68876
- * content whose fresh state is its initial state: an uncontrolled field
68877
- * seeded from a `defaultValue` that changed while the popup was closed.
69100
+ * content whose fresh state is its initial state: an edit left unsent at
69101
+ * the close does not come back at the next opening (an untouched field
69102
+ * needs none of this to show a new `defaultValue` — it follows one).
68878
69103
  * @param {import("ignore:preact").ComponentChildren} props.children
68879
69104
  */
68880
69105
  const Popup = props => {
@@ -69173,8 +69398,9 @@ const PickerCustom = props => {
69173
69398
  open,
69174
69399
  defaultOpen,
69175
69400
  // What Escape means for this picker. "cancel" (the default) puts back the
69176
- // value the picker had at open and, for a dialog, goes back in history —
69177
- // so everything written to the url while it was open goes back too.
69401
+ // value the picker had at open and, for a dialog with an explicit id, goes
69402
+ // back in history — so everything written to the url while it was open
69403
+ // goes back too (see pickerNavType below).
69178
69404
  // "close" makes Escape say the same thing as clicking outside: keep what
69179
69405
  // was chosen, close the popup.
69180
69406
  escapeEffect = "cancel",
@@ -69842,9 +70068,10 @@ const PickerContentInsidePopup = props => {
69842
70068
  // itself, and this one does not. Popover ignores it, same as Dialog ignores
69843
70069
  // marginWithAnchor.
69844
70070
  dockedOnSmallTouchScreen,
69845
- // Same again: the dialog takes the trigger's box as a floor (and, with
69846
- // dialogMaxWidth="var(--anchor-width)", as a ceiling) — what keeps a card
69847
- // its own width once lifted. Dialog's own `sizeFromAnchor`.
70071
+ // Same again: the dialog takes the trigger's box as a floor (Dialog's own
70072
+ // `sizeFromAnchor`) and, unless a dialogMaxWidth says otherwise, as a
70073
+ // ceiling (see maxWidth below) — what keeps a card its own width once
70074
+ // lifted.
69848
70075
  dialogSizeFromAnchor,
69849
70076
  // The caller's word on the popup's own box. Written on the popup element,
69850
70077
  // where Popover/Dialog map them to their own vars, rather than as vars on
@@ -79824,6 +80051,7 @@ const PickerFirstResolver = props => {
79824
80051
  * dialogExpandX?: boolean,
79825
80052
  * dialogExpandY?: boolean,
79826
80053
  * dialogSizeFromAnchor?: boolean,
80054
+ * dockedOnSmallTouchScreen?: boolean | "top" | "bottom",
79827
80055
  * marginWithContainer?: number | string,
79828
80056
  * anchor?: import("ignore:preact").RefObject<HTMLElement> | HTMLElement,
79829
80057
  * escapeEffect?: "cancel" | "close",
@@ -80202,14 +80430,23 @@ const PickerFirstResolver = props => {
80202
80430
  * about it changes. `dialogMaxWidth="var(--anchor-width)"` says the same
80203
80431
  * ceiling by hand: the popup props are written on the dialog element, where
80204
80432
  * that variable lives.
80433
+ * @param {boolean|"top"|"bottom"} [dockedOnSmallTouchScreen] Dialog mode,
80434
+ * Dialog's own: on a small touch screen the dialog becomes a sheet docked
80435
+ * full width to one edge — `true` or `"top"` the top edge, out of the
80436
+ * virtual keyboard's way; `"bottom"` the bottom one, for a sheet that is
80437
+ * read and tapped. Anywhere else it stays the centered box. Ignored in
80438
+ * popover mode.
80205
80439
  * @param {"cancel"|"close"} [escapeEffect="cancel"] What Escape does to an open
80206
80440
  * picker. "cancel" puts back the value the picker had at open, and a dialog
80207
- * picker also goes back in history — so anything written to the url while it
80208
- * was open (a route `stateSignal`, a search param) goes back with it. "close"
80209
- * makes Escape say what clicking outside says: keep what was chosen, close —
80210
- * a last resort, see docs/popup_open.md ("Escape cancels, the other gestures
80211
- * keep") for why Escape should go on meaning cancel, and for what the value
80212
- * at open is on the picker's very first open.
80441
+ * picker with an explicit `id` (its opening being an entry of its own)
80442
+ * also goes back in history — so anything written to the url while it was
80443
+ * open (a route `stateSignal`, a search param) goes back with it. "close"
80444
+ * makes Escape say what clicking outside says: keep what was chosen,
80445
+ * close — a last resort, see
80446
+ * docs/popup_open.md#escape-cancels-the-other-gestures-keep for why Escape
80447
+ * should go on meaning cancel, and
80448
+ * docs/popup_open.md#what-cancel-actually-undoes for what a cancel puts
80449
+ * back.
80213
80450
  * @param {import("ignore:preact").RefObject<HTMLElement>|HTMLElement} [anchor] What
80214
80451
  * the popup hangs off, when that is not the picker itself: a picker whose
80215
80452
  * trigger is one piece of a bigger control (the chevron half of a split
@@ -80264,7 +80501,9 @@ const PickerFirstResolver = props => {
80264
80501
  * positioned ancestor for `popupLayer="local"`). Caps the popup's size as
80265
80502
  * well as its placement, so what an expanded dialog leaves visible around
80266
80503
  * itself is set here. Defaults to `popoverSpacing` in popover mode, and to
80267
- * Dialog's own 3vvw in dialog mode.
80504
+ * Dialog's own in dialog mode: `3appw` in the top layer, `3cqw` under
80505
+ * `popupLayer="local"`, 0 for a dialog docked by
80506
+ * `dockedOnSmallTouchScreen`.
80268
80507
  * @param {string} [popupTestId] The `data-testid` of the popup element — the
80269
80508
  * popover or dialog the children open in. A `data-testid` on the picker
80270
80509
  * itself names the trigger (see docs/testid.md); this one is for a test that