@jsenv/navi 0.29.436 → 0.29.438

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.
@@ -2943,6 +2943,41 @@ const setActionPrivateProperties = (action, properties) => {
2943
2943
  actionPrivatePropertiesWeakMap.set(action, properties);
2944
2944
  };
2945
2945
 
2946
+ /*
2947
+ * Where an action that declares no provisionalValue of its own finds one: the
2948
+ * value the page kept from the previous document, for a route action that is
2949
+ * not a resource read (see keepPageOnScreen in nav/page_kept.js). routeAction
2950
+ * adds the source when it binds the action; a run asks for it at its start,
2951
+ * like it asks provisionalValue (actions.js).
2952
+ *
2953
+ * A registry beside actions.js rather than an import of the kept reads: those
2954
+ * know about stores and params keys, which import actions.js.
2955
+ */
2956
+
2957
+ const keptValueSourcesWeakMap = new WeakMap();
2958
+ const addKeptValueSource = (rootAction, source) => {
2959
+ let sources = keptValueSourcesWeakMap.get(rootAction);
2960
+ if (!sources) {
2961
+ sources = [];
2962
+ keptValueSourcesWeakMap.set(rootAction, sources);
2963
+ }
2964
+ sources.push(source);
2965
+ };
2966
+ const takeKeptValue = (action, params) => {
2967
+ const rootAction = action.rootAction || action;
2968
+ const sources = keptValueSourcesWeakMap.get(rootAction);
2969
+ if (!sources) {
2970
+ return undefined;
2971
+ }
2972
+ for (const source of sources) {
2973
+ const value = source(params);
2974
+ if (value !== undefined) {
2975
+ return value;
2976
+ }
2977
+ }
2978
+ return undefined;
2979
+ };
2980
+
2946
2981
  /*
2947
2982
  * Actions: async callbacks wrapped in reactive state.
2948
2983
  *
@@ -3548,8 +3583,11 @@ const actionWeakMap = new WeakMap();
3548
3583
  * than a first load (data_states.md). Called at the start of every run whose
3549
3584
  * value is undefined; the answer replaces it as usual, and `undefined` means
3550
3585
  * "nothing known". A resource `GET` uses it to draw the row its store holds.
3551
- * An action declaring it is asked for it by a binding moving to new params,
3552
- * rather than handed the previous params' answer (see inheritData).
3586
+ * @param {boolean} [rootOptions.inheritData=true] - `false`: a binding moving
3587
+ * to new params starts the new instance without the previous params' answer
3588
+ * (see createActionProxyFromSignal). For an answer that would say something
3589
+ * false about the new params (a `GET` drawing another row), not for one that
3590
+ * stands in while the next one comes (a list of search results).
3553
3591
  * @param {{ ms?: number, max?: number }} [rootOptions.keep] - how long an
3554
3592
  * answer stays good once it has landed. Without it an answer lives exactly as
3555
3593
  * long as something references it: the screen that asked goes away, and
@@ -3592,6 +3630,7 @@ const createAction = (callback, rootOptions = {}) => {
3592
3630
  resultToValue,
3593
3631
  valueToData,
3594
3632
  provisionalValue,
3633
+ inheritData = true,
3595
3634
  dataDefault,
3596
3635
  data = dataDefault,
3597
3636
 
@@ -4054,8 +4093,12 @@ const createAction = (callback, rootOptions = {}) => {
4054
4093
  actionAbortMap.set(action, abort);
4055
4094
 
4056
4095
  batch(() => {
4057
- if (provisionalValue && valueSignal.peek() === undefined) {
4058
- const provisional = provisionalValue(params, action);
4096
+ if (valueSignal.peek() === undefined) {
4097
+ // An action saying nothing of its own may still have a value the
4098
+ // page kept from the previous document (see kept_values.js).
4099
+ const provisional = provisionalValue
4100
+ ? provisionalValue(params, action)
4101
+ : takeKeptValue(action, params);
4059
4102
  if (provisional !== undefined) {
4060
4103
  valueSignal.value = provisional;
4061
4104
  }
@@ -4255,6 +4298,7 @@ const createAction = (callback, rootOptions = {}) => {
4255
4298
  const privateProperties = {
4256
4299
  valueInitial,
4257
4300
  provisionalValue,
4301
+ inheritData,
4258
4302
 
4259
4303
  performRun,
4260
4304
  performReset,
@@ -4287,7 +4331,7 @@ const createAction = (callback, rootOptions = {}) => {
4287
4331
  * a list of filters changes, providing real-time results without user interaction.
4288
4332
  * @param {boolean} options.inheritData - When false, each new target action starts fresh with no inherited state.
4289
4333
  * By default (true), the proxy carries over the previous target's value and error into the new action —
4290
- * unless the action declares a `provisionalValue`, which then says what the new one holds.
4334
+ * unless the action itself declares `inheritData: false` (createAction), which a resource `GET` does.
4291
4335
  * This keeps the facade in sync with the latest known data: `action.dataSignal.value` only changes when a
4292
4336
  * new action completes, not when it starts loading. Code that needs to distinguish loading state can still
4293
4337
  * check `action.runningState`, while code that just reads `action.data` always sees the most recent
@@ -4334,12 +4378,12 @@ const createActionProxyFromSignal = (
4334
4378
  let currentActionPrivateProperties = getActionPrivateProperties(action);
4335
4379
  let actionTargetPreviousWeakRef = null;
4336
4380
 
4337
- // An action saying for itself what it holds for params it has not answered
4338
- // (provisionalValue: a resource GET draws the row its store holds) is not
4339
- // handed the previous params' answer — an answer about another row. What it
4340
- // does not know stays unknown: a skeleton, never a different row.
4381
+ // An action whose answer for other params would be false for these (a
4382
+ // resource GET: another row) says so itself, and is not handed it. What it
4383
+ // does not know stays unknown: a skeleton, or its provisionalValue, never a
4384
+ // different row.
4341
4385
  const inheritsAnswer =
4342
- inheritData && !getActionPrivateProperties(action).provisionalValue;
4386
+ inheritData && getActionPrivateProperties(action).inheritData;
4343
4387
  const createTarget = (params) => {
4344
4388
  if (inheritsAnswer) {
4345
4389
  const previousActionTarget = actionTargetPreviousWeakRef?.deref();
@@ -10242,6 +10286,9 @@ const paramsTargetCanPlace = (redirectRoute, urlParams) => {
10242
10286
 
10243
10287
  let setupRoutesCalled = false;
10244
10288
  let activeRouteSet = null;
10289
+ // True once the routes have been matched against an address. Before that, no
10290
+ // route matching says nothing about the page: nothing has been asked yet.
10291
+ const routedSignal = signal(false);
10245
10292
 
10246
10293
  const routePreloadMap = new WeakMap();
10247
10294
  const registerRoutePreload = (route, preload) => {
@@ -10666,6 +10713,7 @@ This prevents cross-test pollution and ensures clean state.`,
10666
10713
  }
10667
10714
  // Reset flag after URL -> Signal synchronization is complete
10668
10715
  isUpdatingRoutesFromUrl = false;
10716
+ routedSignal.value = true;
10669
10717
  Object.assign(returnValue, { matchingRouteSet });
10670
10718
  for (const [route, repairedParams] of repairedParamsByRoute) {
10671
10719
  route.replaceParams(repairedParams, {
@@ -10715,6 +10763,7 @@ This prevents cross-test pollution and ensures clean state.`,
10715
10763
  droppingRouteSet = null;
10716
10764
  setupRoutesCalled = false;
10717
10765
  activeRouteSet = null;
10766
+ routedSignal.value = false;
10718
10767
  };
10719
10768
  return {
10720
10769
  updateRoutes,
@@ -15033,6 +15082,13 @@ const createOpenController = (
15033
15082
  change();
15034
15083
  };
15035
15084
  const runChange = (change, { opened, event }) => {
15085
+ // What the controller answers about itself is the decision, written before
15086
+ // the change runs, whatever runs it. Whoever asks meanwhile reads it: the
15087
+ // render an opening forces (onOpen, mountContent) reads the open state
15088
+ // the caller holds, and an onOpen writing the popup's own `signal` has
15089
+ // useOpenPropsEffectOnOpenController asking for this very opening — a
15090
+ // second one, nested in the first, if `opened` still said closed.
15091
+ controller.opened = opened;
15036
15092
  const applyChange = () => {
15037
15093
  // Recorded with the change itself: what the DOM shows is what a render
15038
15094
  // landing between the ask and the picture must draw (see `openedInDom`).
@@ -15048,12 +15104,8 @@ const createOpenController = (
15048
15104
  applyChange();
15049
15105
  return;
15050
15106
  }
15051
- // What the controller answers about itself does not wait for the picture:
15052
- // whoever just asked reads `opened` on the spot (see
15053
- // useOpenPropsEffectOnOpenController, which writes it back into the
15054
- // caller's own signal), and a request arriving before the change lands
15055
- // runs it first rather than reading a DOM that disagrees.
15056
- controller.opened = opened;
15107
+ // A request arriving before the change lands runs it first rather than
15108
+ // reading a DOM that disagrees.
15057
15109
  changeAwaitingTransition = applyChange;
15058
15110
  transitionChange(
15059
15111
  () => {
@@ -15066,7 +15118,6 @@ const createOpenController = (
15066
15118
  };
15067
15119
 
15068
15120
  const performClose = (closeEvent) => {
15069
- controller.opened = false;
15070
15121
  // Read before any close effect touches the DOM: closing a native <dialog>
15071
15122
  // hands the focus back to whatever held it at showModal() time, so by the
15072
15123
  // time the close cleanup runs, the popup's content has already lost the
@@ -15334,7 +15385,6 @@ const createOpenController = (
15334
15385
  // told it opened right after (see popup_content_mount.js and
15335
15386
  // use_displayed_layout_effect.js).
15336
15387
  controller.openedInDom = true;
15337
- controller.opened = true;
15338
15388
  // Which press it opened during, so the release of that press is not
15339
15389
  // read as somebody dismissing it (see openedDuringThisPress).
15340
15390
  controller.pressCountAtOpen = pressCount;
@@ -15761,8 +15811,10 @@ const useOpenPropsEffectOnOpenController = (
15761
15811
  return undefined;
15762
15812
  }
15763
15813
  // Skip when the controller is already in the desired state.
15764
- // openController.opened tracks actual open/close (updated by onopen/onclose,
15765
- // not by renders) so it is the authoritative check against feedback loops.
15814
+ // openController.opened is what the controller decided, written before an
15815
+ // opening renders anything (see runChange), so it is the authoritative
15816
+ // check against feedback loops — the popup's own onOpen writing its
15817
+ // `signal` included.
15766
15818
  if (open === openController.opened) {
15767
15819
  return undefined;
15768
15820
  }
@@ -25828,6 +25880,172 @@ const isOwnerReadBy = (childResource, childAction, ownerGetAction) => {
25828
25880
  return childStore !== undefined && childStore === getChildStore(ownerId);
25829
25881
  };
25830
25882
 
25883
+ /*
25884
+ * What a previous document kept of the page it was on (see keepPageOnScreen in
25885
+ * nav/page_kept.js), waiting for the reads that draw it again, and what those
25886
+ * reads are made of on the way out: rows with their relations inline.
25887
+ *
25888
+ * A kept read is found by the read's name and the params it was asked with
25889
+ * (getKeptReadKey). It is handed out once: the first run of the read draws it
25890
+ * while its request is out, and from then on the store holds the rows. Handed
25891
+ * out again, after a reset say, it would write the previous document's rows
25892
+ * over a fresher answer.
25893
+ *
25894
+ * This module is the half the reads see: `resource()` and the range reader
25895
+ * take from it and say which of their reads are kept as rows, routeAction takes
25896
+ * from it for any other action (through action/kept_values.js). Which reads are
25897
+ * on the page on screen, and what the slot is written into, is page_kept.js.
25898
+ */
25899
+
25900
+ // key → what the read answered in the previous document, not handed out yet
25901
+ const offeredReadMap = new Map();
25902
+ const offerKeptReads = (entryMap) => {
25903
+ offeredReadMap.clear();
25904
+ for (const [key, entry] of entryMap) {
25905
+ offeredReadMap.set(key, entry);
25906
+ }
25907
+ };
25908
+ const withdrawKeptReads = () => {
25909
+ offeredReadMap.clear();
25910
+ };
25911
+ const takeKeptRead = (key) => {
25912
+ const entry = offeredReadMap.get(key);
25913
+ if (entry === undefined) {
25914
+ return undefined;
25915
+ }
25916
+ offeredReadMap.delete(key);
25917
+ return entry;
25918
+ };
25919
+
25920
+ // The root actions whose answer is rows of a store: a resource's GET and
25921
+ // GET_MANY. They are kept as rows, since their value (ids) names rows a new
25922
+ // document's store does not hold. Any other route action is kept by its value
25923
+ // (see page_kept.js).
25924
+ const keepableReadWeakSet = new WeakSet();
25925
+ const markKeepableRead = (rootAction) => {
25926
+ keepableReadWeakSet.add(rootAction);
25927
+ };
25928
+ const isKeepableRead = (action) => {
25929
+ const rootAction = action.rootAction || action;
25930
+ return keepableReadWeakSet.has(rootAction);
25931
+ };
25932
+
25933
+ // The range readers a <List.Items> on screen reads through, each with the number
25934
+ // of lists reading it: the reads of the page a route action does not cover.
25935
+ const shownRangeReadersSignal = signal(new Map());
25936
+ const showRangeReader = (reader) => {
25937
+ const shownMap = new Map(shownRangeReadersSignal.peek());
25938
+ shownMap.set(reader, (shownMap.get(reader) || 0) + 1);
25939
+ shownRangeReadersSignal.value = shownMap;
25940
+ return () => {
25941
+ const shownMapNow = new Map(shownRangeReadersSignal.peek());
25942
+ const count = shownMapNow.get(reader);
25943
+ if (count === 1) {
25944
+ shownMapNow.delete(reader);
25945
+ } else {
25946
+ shownMapNow.set(reader, count - 1);
25947
+ }
25948
+ shownRangeReadersSignal.value = shownMapNow;
25949
+ };
25950
+ };
25951
+
25952
+ // A row already in the store is the fresher one: the kept copy only stands in
25953
+ // for rows nothing has answered yet. Its relations are written as they were
25954
+ // kept, which a real answer corrects like any other.
25955
+ const upsertKeptItem = (store, keptItem) => {
25956
+ const id = keptItem[store.idKey];
25957
+ if (id !== undefined) {
25958
+ const existing = store.select(id);
25959
+ if (existing) {
25960
+ return existing;
25961
+ }
25962
+ }
25963
+ return store.upsert(keptItem);
25964
+ };
25965
+
25966
+ const getKeptReadKey = (name, params) => {
25967
+ return `${name}|${getParamsKey(params)}`;
25968
+ };
25969
+ // `true` is what a route action without params is bound to (see routeAction),
25970
+ // and a key holding undefined says nothing a missing one does not.
25971
+ const getParamsKey = (params) => {
25972
+ if (params === undefined || params === NO_PARAMS || params === true) {
25973
+ return "";
25974
+ }
25975
+ if (isPlainParams(params)) {
25976
+ const keys = Object.keys(params).filter((key) => params[key] !== undefined);
25977
+ if (keys.length === 0) {
25978
+ return "";
25979
+ }
25980
+ }
25981
+ return stableStringify(params);
25982
+ };
25983
+ const isPlainParams = (value) => {
25984
+ return value !== null && typeof value === "object" && !Array.isArray(value);
25985
+ };
25986
+ const stableStringify = (value) => {
25987
+ if (value === null || typeof value !== "object") {
25988
+ return JSON.stringify(value);
25989
+ }
25990
+ if (Array.isArray(value)) {
25991
+ return `[${value.map(stableStringify).join(",")}]`;
25992
+ }
25993
+ const keys = Object.keys(value)
25994
+ .filter((key) => value[key] !== undefined)
25995
+ .sort();
25996
+ const parts = [];
25997
+ for (const key of keys) {
25998
+ parts.push(`${JSON.stringify(key)}:${stableStringify(value[key])}`);
25999
+ }
26000
+ return `{${parts.join(",")}}`;
26001
+ };
26002
+
26003
+ // What a read's data is made of, ready to be written: a row, or the rows of a
26004
+ // list, each with its relations inline.
26005
+ const serializeData = (data) => {
26006
+ if (Array.isArray(data)) {
26007
+ return data.map((item) => serializeItem(item, new Set()));
26008
+ }
26009
+ return serializeItem(data, new Set());
26010
+ };
26011
+ // The item with its relations inline: a relation value stands for what its
26012
+ // signal holds (the child row, the child rows, or nothing), a row reached
26013
+ // twice on the same path is written as its id — the setters accept both.
26014
+ const serializeItem = (item, ancestorSet) => {
26015
+ ancestorSet.add(item);
26016
+ const serialized = {};
26017
+ for (const key of Object.keys(item)) {
26018
+ serialized[key] = serializeValue(item[key], ancestorSet);
26019
+ }
26020
+ ancestorSet.delete(item);
26021
+ return serialized;
26022
+ };
26023
+ const serializeValue = (value, ancestorSet) => {
26024
+ if (value === null || typeof value !== "object") {
26025
+ return value;
26026
+ }
26027
+ if (Array.isArray(value)) {
26028
+ return value.map((entry) => serializeValue(entry, ancestorSet));
26029
+ }
26030
+ const objectSignal = value[SYMBOL_OBJECT_SIGNAL];
26031
+ if (objectSignal) {
26032
+ const held = objectSignal.value;
26033
+ if (held === undefined || held === null) {
26034
+ return null;
26035
+ }
26036
+ return serializeValue(held, ancestorSet);
26037
+ }
26038
+ if (typeof value.toJSON === "function") {
26039
+ return value;
26040
+ }
26041
+ if (ancestorSet.has(value)) {
26042
+ return Object.hasOwn(value, SYMBOL_IDENTITY)
26043
+ ? value[SYMBOL_IDENTITY]
26044
+ : null;
26045
+ }
26046
+ return serializeItem(value, ancestorSet);
26047
+ };
26048
+
25831
26049
  const paramScopeWeakSet = createIterableWeakSet();
25832
26050
  let paramScopeIdCounter = 0;
25833
26051
  const getParamScope = (params) => {
@@ -25998,66 +26216,6 @@ const createResourcePersistence = (
25998
26216
  };
25999
26217
  };
26000
26218
 
26001
- const getParamsKey = (params) => {
26002
- if (params === undefined || params === NO_PARAMS) {
26003
- return "";
26004
- }
26005
- return stableStringify(params);
26006
- };
26007
-
26008
- const stableStringify = (value) => {
26009
- if (value === null || typeof value !== "object") {
26010
- return JSON.stringify(value);
26011
- }
26012
- if (Array.isArray(value)) {
26013
- return `[${value.map(stableStringify).join(",")}]`;
26014
- }
26015
- const keys = Object.keys(value).sort();
26016
- const parts = [];
26017
- for (const key of keys) {
26018
- parts.push(`${JSON.stringify(key)}:${stableStringify(value[key])}`);
26019
- }
26020
- return `{${parts.join(",")}}`;
26021
- };
26022
-
26023
- // The item with its relations inline: a relation value stands for what its
26024
- // signal holds (the child row, the child rows, or nothing), a row reached
26025
- // twice on the same path is written as its id — the setters accept both.
26026
- const serializeItem = (item, ancestorSet) => {
26027
- ancestorSet.add(item);
26028
- const serialized = {};
26029
- for (const key of Object.keys(item)) {
26030
- serialized[key] = serializeValue(item[key], ancestorSet);
26031
- }
26032
- ancestorSet.delete(item);
26033
- return serialized;
26034
- };
26035
- const serializeValue = (value, ancestorSet) => {
26036
- if (value === null || typeof value !== "object") {
26037
- return value;
26038
- }
26039
- if (Array.isArray(value)) {
26040
- return value.map((entry) => serializeValue(entry, ancestorSet));
26041
- }
26042
- const objectSignal = value[SYMBOL_OBJECT_SIGNAL];
26043
- if (objectSignal) {
26044
- const held = objectSignal.value;
26045
- if (held === undefined || held === null) {
26046
- return null;
26047
- }
26048
- return serializeValue(held, ancestorSet);
26049
- }
26050
- if (typeof value.toJSON === "function") {
26051
- return value;
26052
- }
26053
- if (ancestorSet.has(value)) {
26054
- return Object.hasOwn(value, SYMBOL_IDENTITY)
26055
- ? value[SYMBOL_IDENTITY]
26056
- : null;
26057
- }
26058
- return serializeItem(value, ancestorSet);
26059
- };
26060
-
26061
26219
  /*
26062
26220
  * GET_RANGE: reading a resource one slice at a time, for a list that draws its
26063
26221
  * rows as it goes (`<List.Items itemsAction>`).
@@ -26094,9 +26252,23 @@ const serializeValue = (value, ancestorSet) => {
26094
26252
  * of what a rerun is for an action. Every reader made by `bindParams` shares
26095
26253
  * the signal and the compositions of the one it comes from: the params say
26096
26254
  * which slices are read, not which collection.
26255
+ *
26256
+ * A composition also outlives the document when a list on the page on screen
26257
+ * reads it (see keepPageOnScreen): the ranks around the window it draws are
26258
+ * written with their rows, and the first read of the next document finds them
26259
+ * as a composition left by a list that went away — the same return, after a
26260
+ * longer absence.
26097
26261
  */
26098
26262
 
26099
26263
 
26264
+ // The ranks kept of a composition for the next document: the window a list
26265
+ // draws, widened by at least one step on each side, and snapped to steps so a
26266
+ // scroll within one draws the same ranks and writes nothing.
26267
+ const KEPT_RANK_STEP = 20;
26268
+ // Counts the answers every composition took, so that one landing can be told
26269
+ // from one seen before (see readKept).
26270
+ let answerSequence = 0;
26271
+
26100
26272
  const createRangeReader = (
26101
26273
  actionName,
26102
26274
  callback,
@@ -26105,6 +26277,9 @@ const createRangeReader = (
26105
26277
  params: boundParams,
26106
26278
  invalidationSignal = signal(0),
26107
26279
  compositionSet = new Set(),
26280
+ // Bumped whenever what is kept of a composition for the next document
26281
+ // changes (see readKept); shared like the compositions themselves.
26282
+ keptVersionSignal = signal(0),
26108
26283
  },
26109
26284
  ) => {
26110
26285
  // Which composition this reader is about: the values its params hold, not its
@@ -26121,6 +26296,40 @@ const createRangeReader = (
26121
26296
  }
26122
26297
  return null;
26123
26298
  };
26299
+ const keptKeyOf = (params) => getKeptReadKey(actionName, params);
26300
+ const bumpKeptVersion = () => {
26301
+ keptVersionSignal.value = keptVersionSignal.peek() + 1;
26302
+ };
26303
+ // The composition the previous document kept for these params, entering the
26304
+ // store and the compositions as if a list had just left it.
26305
+ const restoreKeptComposition = (params) => {
26306
+ const kept = takeKeptRead(keptKeyOf(params));
26307
+ if (!kept || typeof kept.count !== "number" || !kept.items) {
26308
+ return null;
26309
+ }
26310
+ const composition = {
26311
+ params,
26312
+ idByIndex: new Map(),
26313
+ count: kept.count,
26314
+ keptFrom: undefined,
26315
+ keptTo: undefined,
26316
+ answeredAt: 0,
26317
+ };
26318
+ for (const rank of Object.keys(kept.items)) {
26319
+ const index = Number(rank);
26320
+ const item = upsertKeptItem(store, kept.items[rank]);
26321
+ composition.idByIndex.set(index, item[store.idKey]);
26322
+ if (composition.keptFrom === undefined || index < composition.keptFrom) {
26323
+ composition.keptFrom = index;
26324
+ }
26325
+ if (composition.keptTo === undefined || index + 1 > composition.keptTo) {
26326
+ composition.keptTo = index + 1;
26327
+ }
26328
+ }
26329
+ compositionSet.add(composition);
26330
+ bumpKeptVersion();
26331
+ return composition;
26332
+ };
26124
26333
  const readRange = async (range = {}) => {
26125
26334
  const { signal, ...rangeParams } = range;
26126
26335
  const paramsResolved = { ...resolveParams(boundParams), ...rangeParams };
@@ -26154,11 +26363,15 @@ const createRangeReader = (
26154
26363
  readRange.invalidate = () => {
26155
26364
  compositionSet.clear();
26156
26365
  invalidationSignal.value = invalidationSignal.peek() + 1;
26366
+ bumpKeptVersion();
26157
26367
  };
26158
26368
  // The rows of a composition, drawn from the store: a rank whose row is gone
26159
26369
  // from the store resolves to nothing and is asked for again.
26160
26370
  readRange.readComposition = () => {
26161
- const composition = findComposition(currentParams());
26371
+ const params = currentParams();
26372
+ const composition = untracked(
26373
+ () => findComposition(params) || restoreKeptComposition(params),
26374
+ );
26162
26375
  if (!composition) {
26163
26376
  return null;
26164
26377
  }
@@ -26182,18 +26395,85 @@ const createRangeReader = (
26182
26395
  const params = currentParams();
26183
26396
  let composition = findComposition(params);
26184
26397
  if (!composition) {
26185
- composition = { params, idByIndex: new Map(), count };
26398
+ composition = {
26399
+ params,
26400
+ idByIndex: new Map(),
26401
+ count,
26402
+ keptFrom: undefined,
26403
+ keptTo: undefined,
26404
+ answeredAt: 0,
26405
+ };
26186
26406
  compositionSet.add(composition);
26187
26407
  } else if (replace) {
26188
26408
  composition.idByIndex = new Map();
26189
26409
  }
26190
26410
  composition.count = count;
26411
+ answerSequence++;
26412
+ composition.answeredAt = answerSequence;
26191
26413
  for (const [index, item] of byIndex) {
26192
26414
  const id = item ? item[store.idKey] : undefined;
26193
26415
  if (id !== undefined) {
26194
26416
  composition.idByIndex.set(index, id);
26195
26417
  }
26196
26418
  }
26419
+ bumpKeptVersion();
26420
+ };
26421
+ // The ranks [windowFrom, windowTo) a list draws of this composition: what is
26422
+ // kept of it for the next document is around them (see readKept).
26423
+ readRange.frameComposition = (windowFrom, windowTo) => {
26424
+ const composition = findComposition(currentParams());
26425
+ if (!composition) {
26426
+ return;
26427
+ }
26428
+ let keptFrom =
26429
+ Math.floor((windowFrom - KEPT_RANK_STEP) / KEPT_RANK_STEP) *
26430
+ KEPT_RANK_STEP;
26431
+ if (keptFrom < 0) {
26432
+ keptFrom = 0;
26433
+ }
26434
+ const keptTo =
26435
+ Math.ceil((windowTo + KEPT_RANK_STEP) / KEPT_RANK_STEP) * KEPT_RANK_STEP;
26436
+ if (composition.keptFrom === keptFrom && composition.keptTo === keptTo) {
26437
+ return;
26438
+ }
26439
+ composition.keptFrom = keptFrom;
26440
+ composition.keptTo = keptTo;
26441
+ bumpKeptVersion();
26442
+ };
26443
+ // What a document keeps of the composition these params read, for the
26444
+ // next one (see keepPageOnScreen): the count, and the ranks around the
26445
+ // window last drawn with their rows inline. Read inside an effect, it
26446
+ // follows the composition and the rows. `answeredAt` grows with every answer
26447
+ // the composition takes (0: none in this document, restored from a kept one).
26448
+ readRange.readKept = () => {
26449
+ // eslint-disable-next-line no-unused-expressions
26450
+ keptVersionSignal.value;
26451
+ const params = resolveParams(boundParams);
26452
+ const key = keptKeyOf(params);
26453
+ const composition = findComposition(params);
26454
+ if (!composition || composition.count === undefined) {
26455
+ return { key, answeredAt: 0, readEntry: () => undefined };
26456
+ }
26457
+ const readEntry = () => {
26458
+ const items = {};
26459
+ for (const [index, id] of composition.idByIndex) {
26460
+ if (
26461
+ composition.keptFrom !== undefined &&
26462
+ index < composition.keptFrom
26463
+ ) {
26464
+ continue;
26465
+ }
26466
+ if (composition.keptTo !== undefined && index >= composition.keptTo) {
26467
+ continue;
26468
+ }
26469
+ const item = store.select(id);
26470
+ if (item) {
26471
+ items[index] = serializeItem(item, new Set());
26472
+ }
26473
+ }
26474
+ return { count: composition.count, items };
26475
+ };
26476
+ return { key, answeredAt: composition.answeredAt, readEntry };
26197
26477
  };
26198
26478
  // The same trade a list makes with the rows it holds, applied to what is kept
26199
26479
  // for the next mount. Two lists on one collection each trim by their own
@@ -26204,11 +26484,16 @@ const createRangeReader = (
26204
26484
  if (!composition || composition.idByIndex.size <= budget) {
26205
26485
  return;
26206
26486
  }
26487
+ let trimmed = false;
26207
26488
  for (const index of composition.idByIndex.keys()) {
26208
26489
  if (index < keepFrom || index > keepTo) {
26209
26490
  composition.idByIndex.delete(index);
26491
+ trimmed = true;
26210
26492
  }
26211
26493
  }
26494
+ if (trimmed) {
26495
+ bumpKeptVersion();
26496
+ }
26212
26497
  };
26213
26498
  // Memoized for the reasons an action's bindParams is (see actions.js): params
26214
26499
  // and the reader they make have synchronized lifetimes, and params equal in
@@ -26224,6 +26509,7 @@ const createRangeReader = (
26224
26509
  params: boundParams ? { ...boundParams, ...paramsToBind } : paramsToBind,
26225
26510
  invalidationSignal,
26226
26511
  compositionSet,
26512
+ keptVersionSignal,
26227
26513
  });
26228
26514
  readerByParams.set(paramsToBind, reader);
26229
26515
  return reader;
@@ -27658,7 +27944,7 @@ const createRestActionFactoryForRoot = (
27658
27944
  `${name}.${verb}`,
27659
27945
  declarationSite,
27660
27946
  );
27661
- return createAction(callback, {
27947
+ const action = createAction(callback, {
27662
27948
  name: `${name}.${verb}`,
27663
27949
  meta: { verb, isMany: false, paramScope },
27664
27950
  resultToValue: (result, action) => {
@@ -27694,17 +27980,33 @@ const createRestActionFactoryForRoot = (
27694
27980
  },
27695
27981
  valueToData: (itemId) => store.select(itemId),
27696
27982
  // While the request is out, the row the store already holds for these
27697
- // params is drawn: the action starts on the "refresh over a known
27983
+ // params is drawn, or the row the page kept from the previous document
27984
+ // (kept_reads.js): the action starts on the "refresh over a known
27698
27985
  // answer" line of data_states.md rather than the first-load one.
27699
27986
  provisionalValue:
27700
27987
  verb === "GET"
27701
- ? (params) => {
27702
- const item = untracked(() => findItemForGet(params));
27988
+ ? (params, action) => {
27989
+ const item = untracked(() => {
27990
+ const itemForGet = findItemForGet(params);
27991
+ if (itemForGet) {
27992
+ return itemForGet;
27993
+ }
27994
+ const keptItem = takeKeptRead(
27995
+ getKeptReadKey(action.name, params),
27996
+ );
27997
+ return keptItem ? upsertKeptItem(store, keptItem) : null;
27998
+ });
27703
27999
  return item ? item[idKey] : undefined;
27704
28000
  }
27705
28001
  : undefined,
28002
+ // Another params' answer is another row.
28003
+ inheritData: verb !== "GET",
27706
28004
  completeSideEffect: onActionComplete,
27707
28005
  });
28006
+ if (verb === "GET") {
28007
+ markKeepableRead(action);
28008
+ }
28009
+ return action;
27708
28010
  };
27709
28011
  const createActionAffectingManyItems = (
27710
28012
  verb,
@@ -27719,7 +28021,7 @@ const createRestActionFactoryForRoot = (
27719
28021
  return itemArray.map((item) => item[idKey]);
27720
28022
  };
27721
28023
 
27722
- return createAction(callback, {
28024
+ const action = createAction(callback, {
27723
28025
  meta: { verb, isMany: true, paramScope },
27724
28026
  name: `${name}.${verb}_MANY`,
27725
28027
  resultToValue: applyResultToValue,
@@ -27727,6 +28029,25 @@ const createRestActionFactoryForRoot = (
27727
28029
  // undefined so a screen can tell "not asked yet" from "answered: none".
27728
28030
  valueToData: (idArray) =>
27729
28031
  idArray === undefined ? undefined : store.selectAll(idArray),
28032
+ // The rows the page kept from the previous document (kept_reads.js),
28033
+ // drawn while the request is out. A binding moving to new params still
28034
+ // hands over the previous answer when there are none: a list standing
28035
+ // in for the next one beats a skeleton.
28036
+ provisionalValue:
28037
+ verb === "GET"
28038
+ ? (params, action) =>
28039
+ untracked(() => {
28040
+ const keptItems = takeKeptRead(
28041
+ getKeptReadKey(action.name, params),
28042
+ );
28043
+ if (!Array.isArray(keptItems)) {
28044
+ return undefined;
28045
+ }
28046
+ return keptItems.map(
28047
+ (keptItem) => upsertKeptItem(store, keptItem)[idKey],
28048
+ );
28049
+ })
28050
+ : undefined,
27730
28051
  completeSideEffect: (actionCompleted) => {
27731
28052
  onActionComplete(actionCompleted);
27732
28053
  if (
@@ -27742,6 +28063,10 @@ const createRestActionFactoryForRoot = (
27742
28063
  return syncIdArrayOnRename(store, idKey, actionCompleted.valueSignal);
27743
28064
  },
27744
28065
  });
28066
+ if (verb === "GET") {
28067
+ markKeepableRead(action);
28068
+ }
28069
+ return action;
27745
28070
  };
27746
28071
 
27747
28072
  return createActionForRoot;
@@ -27752,10 +28077,12 @@ const createRestActionFactoryForRoot = (
27752
28077
  // NetworkPolicyError carrying the policy's reason. A read is called or not depending
27753
28078
  // on where the policy answers reads from. Answered from the store, a GET of a
27754
28079
  // root resource completes with the row the store holds for it (see
27755
- // findItemInStore) — handing the item back is an upsert without effect, so the
27756
- // action completes with what it had and nothing is asked; a relationship GET
27757
- // has no row of its own to answer with and settles with the same error. A
27758
- // completed GET asked to rerun never gets here (actions.js holds it).
28080
+ // findItemInStore), and a root GET or GET_MANY holding rows for these params
28081
+ // (the kept page, drawn while the request is out) with those — handing them
28082
+ // back is an upsert without effect, so the action completes with what it had
28083
+ // and nothing is asked. A relationship GET has no row of its own to answer
28084
+ // with and settles with the same error. A completed GET asked to rerun never
28085
+ // gets here (actions.js holds it).
27759
28086
  const applyNetworkPolicy = (
27760
28087
  restCallback,
27761
28088
  { verb, isMany, findItemInStore },
@@ -27769,6 +28096,12 @@ const applyNetworkPolicy = (
27769
28096
  if (!policy.readsFromStore) {
27770
28097
  return restCallback(params, context);
27771
28098
  }
28099
+ if (isKeepableRead(context.action)) {
28100
+ const dataHeld = context.action.dataSignal.peek();
28101
+ if (dataHeld !== undefined) {
28102
+ return dataHeld;
28103
+ }
28104
+ }
27772
28105
  if (!isMany && findItemInStore) {
27773
28106
  const item = findItemInStore(params, context.action);
27774
28107
  if (item) {
@@ -37919,6 +38252,46 @@ const writeCover = (edge, element, size) => {
37919
38252
  style.setProperty(property, value);
37920
38253
  };
37921
38254
 
38255
+ // Every route action, with whether it asks something of the address at the
38256
+ // moment: its route matches and its params getter returns something.
38257
+ const routeActionEntriesSignal = signal([]);
38258
+ // What a route action is called in the page kept for the next document (see
38259
+ // keepPageOnScreen): its place among the route actions declared, which the
38260
+ // next document of the same build declares in the same order. A resource read
38261
+ // has a name of its own and is kept by it; an action made from a callback is
38262
+ // most often "anonymous", and several of them read the same page.
38263
+ const keptNameWeakMap = new WeakMap();
38264
+ const getRouteActionKeptName = (routeAction) => {
38265
+ return keptNameWeakMap.get(routeAction);
38266
+ };
38267
+ let activeRouteActionsPrevious = new Set();
38268
+ /**
38269
+ * The route actions asking something for the page on screen: their route
38270
+ * matches and their params getter returns something — the running ones, not
38271
+ * every matching one, so an action bound to every page that asks only when a
38272
+ * popup is open over it counts while it is open. Read to rerun what the page
38273
+ * reads (the app back from the background), and by `keepPageOnScreen`.
38274
+ * The set is handed out again only when its members change.
38275
+ *
38276
+ * A page's code is declared as a route action too; `action.meta.verb` says
38277
+ * which ones read data.
38278
+ *
38279
+ * @type {import("@preact/signals").ReadonlySignal<Set<object>>}
38280
+ */
38281
+ const activeRouteActionsSignal = computed(() => {
38282
+ const active = new Set();
38283
+ for (const { routeAction, askingSignal } of routeActionEntriesSignal.value) {
38284
+ if (askingSignal.value) {
38285
+ active.add(routeAction);
38286
+ }
38287
+ }
38288
+ if (haveSameMembers(active, activeRouteActionsPrevious)) {
38289
+ return activeRouteActionsPrevious;
38290
+ }
38291
+ activeRouteActionsPrevious = active;
38292
+ return active;
38293
+ });
38294
+
37922
38295
  /**
37923
38296
  * Binds an action to a route: it runs when the route matches, with the params
37924
38297
  * the effect reads off the address, runs again on a `reload()`, and is aborted
@@ -37975,7 +38348,23 @@ const routeAction = (
37975
38348
  }
37976
38349
  return params;
37977
38350
  };
38351
+ const rootAction = rootActionOf(action);
38352
+ const keptName = `route_action#${routeActionEntriesSignal.peek().length} ${rootAction.name}`;
38353
+ if (!isKeepableRead(rootAction)) {
38354
+ // Before the effect below: it may run the action at once.
38355
+ addKeptValueSource(rootAction, (params) =>
38356
+ takeKeptRead(getKeptReadKey(keptName, params)),
38357
+ );
38358
+ }
37978
38359
  const actionBoundToRoute = actionRunEffect(action, readParamsAsked, options);
38360
+ keptNameWeakMap.set(actionBoundToRoute, keptName);
38361
+ routeActionEntriesSignal.value = [
38362
+ ...routeActionEntriesSignal.peek(),
38363
+ {
38364
+ routeAction: actionBoundToRoute,
38365
+ askingSignal: computed(() => Boolean(readParamsAsked())),
38366
+ },
38367
+ ];
37979
38368
  for (const route of routes) {
37980
38369
  // Asked of the route's params as they are, like the effect asks them.
37981
38370
  registerRouteReload(route, () =>
@@ -38059,6 +38448,416 @@ const anyMatchingRouteSignal = (routes) => {
38059
38448
  return anyMatchingSignal;
38060
38449
  };
38061
38450
 
38451
+ const rootActionOf = (action) => {
38452
+ if (action.isProxy) {
38453
+ return rootActionOf(getActionPrivateProperties(action).currentAction);
38454
+ }
38455
+ return action.rootAction || action;
38456
+ };
38457
+
38458
+ const haveSameMembers = (setA, setB) => {
38459
+ if (setA.size !== setB.size) {
38460
+ return false;
38461
+ }
38462
+ for (const value of setA) {
38463
+ if (!setB.has(value)) {
38464
+ return false;
38465
+ }
38466
+ }
38467
+ return true;
38468
+ };
38469
+
38470
+ /*
38471
+ * The page on screen, kept for the next document: what its reads answered,
38472
+ * written into a signal the app hands over, and drawn again by the same reads
38473
+ * in a document opening on the same page (a reload, a tab the system
38474
+ * discarded). The read side is state/rest/kept_reads.js; this is what decides
38475
+ * which reads are the page, and when the slot is written.
38476
+ *
38477
+ * The unit is the page, not the resource. One resource is read by several
38478
+ * screens — a search, someone's games, a game opened from anywhere — and kept
38479
+ * per resource it would keep every row ever opened. The page's reads are what
38480
+ * navi already runs for it: the route actions asking something for the page
38481
+ * (activeRouteActionsSignal) and the compositions the lists on screen read
38482
+ * (shownRangeReadersSignal). A search typed on the page is neither, and is not
38483
+ * kept.
38484
+ *
38485
+ * What a read is made of on the way out depends on what it holds. A resource
38486
+ * read holds ids, which name rows a new document's store does not have: its
38487
+ * rows are written, and re-enter the store. Any other route action holds its
38488
+ * value, written as it is when JSON brings it back as it was — a page's code
38489
+ * (a function), a Date built by the callback, a Map, are not, and are left
38490
+ * out. Such an action is recognized in the next document by its place among
38491
+ * the route actions declared (see getRouteActionKeptName), not by a name the
38492
+ * app would have to give every action made from a callback.
38493
+ *
38494
+ * A read is written once it has answered in this document. Before that it
38495
+ * keeps what the previous document kept for it, never what it shows meanwhile:
38496
+ * an answer handed over from other params (a list standing in for the next
38497
+ * one) says nothing about these.
38498
+ *
38499
+ * The write waits for the page to settle and is flushed when the document is
38500
+ * hidden. A navigation goes through pages half there — the route matched, its
38501
+ * list not mounted yet, the page left still drawn under a transition — and
38502
+ * each would otherwise be written out in full. Hidden is the moment the system
38503
+ * may discard the document, so nothing is lost by waiting until then.
38504
+ *
38505
+ * The signal turning `undefined` from outside (sign-out) forgets the page, and
38506
+ * nothing is written until a read lands again: written at once, the page still
38507
+ * on screen would put back the account just left.
38508
+ */
38509
+
38510
+ const WRITE_DELAY = 1000;
38511
+ const NOTHING_PENDING = {};
38512
+ let stopKeepingCurrent = null;
38513
+
38514
+ /**
38515
+ * Keeps what the page on screen reads, so that a new document opening on it —
38516
+ * a reload, a pull-to-refresh, a tab the system discarded in the background —
38517
+ * draws it again while its reads go out, rather than skeletons.
38518
+ *
38519
+ * What the page reads is what navi runs for it: the route actions asking
38520
+ * something for it (see `activeRouteActionsSignal`), and the `GET_RANGE`
38521
+ * compositions the `<List.Items>` on screen read, around the window they draw.
38522
+ * A resource read keeps its rows; any other route action keeps its value when
38523
+ * JSON brings it back as it was. A navigation replaces the slot with the next
38524
+ * page's reads, and the page left is dropped. In the next document, the first
38525
+ * run of the same read with the same params draws the kept answer as its
38526
+ * provisional value: `data` set while `loading` is `true`, the answer replacing
38527
+ * it. Under a network policy answering reads from the store, a resource read
38528
+ * completes with it. The copy follows the store: a `PUT` on a row rewrites it.
38529
+ *
38530
+ * Call it before the routes start: the first runs are the ones that look. And
38531
+ * declare the route actions where the app starts: one that is not a resource
38532
+ * read is recognized by its place among them.
38533
+ *
38534
+ * @param {object} options
38535
+ * @param {import("@preact/signals").Signal} options.signal - where the slot is
38536
+ * written: a `stateSignal` with `persists: true` and `type: "object"`, its
38537
+ * `id` holding whatever makes a kept page unusable (the deployed version).
38538
+ * Writing `undefined` into it (sign-out) forgets the page, and nothing is
38539
+ * written again until a read lands.
38540
+ * @param {() => boolean} [options.when] - read whenever the slot is about to be
38541
+ * read or written: `false` reads nothing, writes nothing, and empties the
38542
+ * signal (a tab viewing as someone else). A function, so it can read a signal.
38543
+ * @param {object[]} [options.always] - routes whose reads stay kept once the
38544
+ * page is left, until it is visited again: the page an app opens on when it
38545
+ * is launched rather than reloaded.
38546
+ * @returns {() => void} stops keeping the page.
38547
+ * @see docs/resource.md — what is kept and what is not, what stays the app's
38548
+ */
38549
+ const keepPageOnScreen = ({
38550
+ signal: keptSignal,
38551
+ when,
38552
+ always = [],
38553
+ } = {}) => {
38554
+ if (!isSignal(keptSignal)) {
38555
+ throw new TypeError(
38556
+ `keepPageOnScreen() needs a signal to write the page into, received ${keptSignal}`,
38557
+ );
38558
+ }
38559
+ if (stopKeepingCurrent) {
38560
+ stopKeepingCurrent();
38561
+ }
38562
+ const isAllowed = () => {
38563
+ return when ? Boolean(when()) : true;
38564
+ };
38565
+ const alwaysMatchingSignal = anyMatchingRouteSignal(always);
38566
+
38567
+ // What the signal held when it was read: the previous document's page.
38568
+ let loadedEntryMap = new Map();
38569
+ let loadedAlwaysKeys = [];
38570
+ // Which reads answered in this document (since the last forget): only
38571
+ // those write what they show.
38572
+ let answeredActionWeakSet = new WeakSet();
38573
+ let answeredRangeKeySet = new Set();
38574
+ // The state each action was last seen in, and the last answer seen of each
38575
+ // range reader: a read lands when they move, and not because the slot looks
38576
+ // again at something that had answered before a forget.
38577
+ const stateSeenWeakMap = new WeakMap();
38578
+ const answeredAtSeenWeakMap = new WeakMap();
38579
+ // Forgotten: nothing is written until a read lands.
38580
+ let paused = false;
38581
+ // The reads of the always-kept page, as they were the last time it was on
38582
+ // screen in this document; null until then.
38583
+ let alwaysReads = null;
38584
+
38585
+ const forget = () => {
38586
+ loadedEntryMap = new Map();
38587
+ loadedAlwaysKeys = [];
38588
+ answeredActionWeakSet = new WeakSet();
38589
+ answeredRangeKeySet = new Set();
38590
+ alwaysReads = null;
38591
+ paused = true;
38592
+ withdrawKeptReads();
38593
+ };
38594
+
38595
+ let lastWritten;
38596
+ let pendingWrite = NOTHING_PENDING;
38597
+ let writeTimeout;
38598
+ const flush = () => {
38599
+ clearTimeout(writeTimeout);
38600
+ if (pendingWrite === NOTHING_PENDING) {
38601
+ return;
38602
+ }
38603
+ const value = pendingWrite;
38604
+ pendingWrite = NOTHING_PENDING;
38605
+ if (compareTwoJsValues(value, lastWritten)) {
38606
+ return;
38607
+ }
38608
+ lastWritten = value;
38609
+ keptSignal.value = value;
38610
+ };
38611
+ const scheduleWrite = (value) => {
38612
+ pendingWrite = value;
38613
+ clearTimeout(writeTimeout);
38614
+ writeTimeout = setTimeout(flush, WRITE_DELAY);
38615
+ };
38616
+ const onVisibilityChange = () => {
38617
+ if (document.visibilityState === "hidden") {
38618
+ flush();
38619
+ }
38620
+ };
38621
+ document.addEventListener("visibilitychange", onVisibilityChange);
38622
+ window.addEventListener("pagehide", flush);
38623
+
38624
+ // Runs at once with what the signal already holds (the slot read from
38625
+ // storage), then on every write — the slot's own ones are recognized and
38626
+ // left alone.
38627
+ const unsubscribe = keptSignal.subscribe((value) => {
38628
+ if (value === lastWritten) {
38629
+ return;
38630
+ }
38631
+ lastWritten = value;
38632
+ pendingWrite = NOTHING_PENDING;
38633
+ clearTimeout(writeTimeout);
38634
+ if (
38635
+ value === undefined ||
38636
+ value === null ||
38637
+ typeof value !== "object" ||
38638
+ !isAllowed()
38639
+ ) {
38640
+ forget();
38641
+ return;
38642
+ }
38643
+ const entries =
38644
+ value.entries && typeof value.entries === "object" ? value.entries : {};
38645
+ loadedEntryMap = new Map(Object.entries(entries));
38646
+ loadedAlwaysKeys = Array.isArray(value.always)
38647
+ ? value.always.filter((key) => loadedEntryMap.has(key))
38648
+ : [];
38649
+ alwaysReads = null;
38650
+ paused = false;
38651
+ offerKeptReads(loadedEntryMap);
38652
+ });
38653
+
38654
+ const actionReadWeakMap = new WeakMap();
38655
+ const actionReadOf = (action, keptName, readEntryOf) => {
38656
+ let read = actionReadWeakMap.get(action);
38657
+ if (read) {
38658
+ return read;
38659
+ }
38660
+ read = () => {
38661
+ const state = action.runningStateSignal.value;
38662
+ const stateSeen = stateSeenWeakMap.get(action);
38663
+ stateSeenWeakMap.set(action, state);
38664
+ const landed = state === COMPLETED && stateSeen !== COMPLETED;
38665
+ if (landed) {
38666
+ answeredActionWeakSet.add(action);
38667
+ }
38668
+ return {
38669
+ key: getKeptReadKey(keptName, action.params),
38670
+ answered: answeredActionWeakSet.has(action),
38671
+ landed,
38672
+ readEntry: () => readEntryOf(action),
38673
+ };
38674
+ };
38675
+ actionReadWeakMap.set(action, read);
38676
+ return read;
38677
+ };
38678
+ const rangeReadWeakMap = new WeakMap();
38679
+ const rangeReadOf = (reader) => {
38680
+ let read = rangeReadWeakMap.get(reader);
38681
+ if (read) {
38682
+ return read;
38683
+ }
38684
+ read = () => {
38685
+ const { key, answeredAt, readEntry } = reader.readKept();
38686
+ const answeredAtSeen = answeredAtSeenWeakMap.get(reader) || 0;
38687
+ answeredAtSeenWeakMap.set(reader, answeredAt);
38688
+ const landed = answeredAt > answeredAtSeen;
38689
+ if (landed) {
38690
+ answeredRangeKeySet.add(key);
38691
+ }
38692
+ return {
38693
+ key,
38694
+ answered: answeredRangeKeySet.has(key),
38695
+ landed,
38696
+ readEntry,
38697
+ };
38698
+ };
38699
+ rangeReadWeakMap.set(reader, read);
38700
+ return read;
38701
+ };
38702
+ const readsOnScreen = () => {
38703
+ const reads = [];
38704
+ for (const routeAction of activeRouteActionsSignal.value) {
38705
+ // The instance is what is kept, so that a page left keeps what it read.
38706
+ // The binding's callSource names that instance and is rewritten once the
38707
+ // binding has moved to another one: read, it brings the slot along.
38708
+ // eslint-disable-next-line no-unused-expressions
38709
+ routeAction.callSource;
38710
+ const action = getActionPrivateProperties(routeAction).currentAction;
38711
+ if (action.params === NO_PARAMS) {
38712
+ continue;
38713
+ }
38714
+ if (isKeepableRead(action)) {
38715
+ reads.push(actionReadOf(action, action.name, readRows));
38716
+ } else {
38717
+ reads.push(
38718
+ actionReadOf(
38719
+ action,
38720
+ getRouteActionKeptName(routeAction),
38721
+ readPlainValue,
38722
+ ),
38723
+ );
38724
+ }
38725
+ }
38726
+ for (const reader of shownRangeReadersSignal.value.keys()) {
38727
+ reads.push(rangeReadOf(reader));
38728
+ }
38729
+ return reads;
38730
+ };
38731
+
38732
+ const stopEffect = effect(() => {
38733
+ const allowed = isAllowed();
38734
+ const routed = routedSignal.value;
38735
+ const alwaysMatching = alwaysMatchingSignal.value;
38736
+ if (!allowed) {
38737
+ forget();
38738
+ pendingWrite = undefined;
38739
+ flush();
38740
+ return;
38741
+ }
38742
+ if (!routed) {
38743
+ // No page yet: the slot is what the first reads will draw.
38744
+ return;
38745
+ }
38746
+ const screenReads = readsOnScreen();
38747
+ if (alwaysMatching) {
38748
+ alwaysReads = screenReads;
38749
+ }
38750
+ const entries = {};
38751
+ let landed = false;
38752
+ const collect = (reads) => {
38753
+ const keys = [];
38754
+ for (const read of reads) {
38755
+ const result = read();
38756
+ if (result.landed) {
38757
+ landed = true;
38758
+ }
38759
+ const entry = result.answered
38760
+ ? result.readEntry()
38761
+ : loadedEntryMap.get(result.key);
38762
+ if (entry === undefined) {
38763
+ continue;
38764
+ }
38765
+ entries[result.key] = entry;
38766
+ keys.push(result.key);
38767
+ }
38768
+ return keys;
38769
+ };
38770
+ const screenKeys = collect(screenReads);
38771
+ let alwaysKeys;
38772
+ if (alwaysReads === screenReads) {
38773
+ alwaysKeys = screenKeys;
38774
+ } else if (alwaysReads) {
38775
+ alwaysKeys = collect(alwaysReads);
38776
+ } else {
38777
+ alwaysKeys = loadedAlwaysKeys;
38778
+ for (const key of alwaysKeys) {
38779
+ entries[key] = loadedEntryMap.get(key);
38780
+ }
38781
+ }
38782
+ if (paused) {
38783
+ if (!landed) {
38784
+ return;
38785
+ }
38786
+ paused = false;
38787
+ }
38788
+ if (screenKeys.length === 0 && alwaysKeys.length === 0) {
38789
+ scheduleWrite(undefined);
38790
+ return;
38791
+ }
38792
+ scheduleWrite({ entries, screen: screenKeys, always: alwaysKeys });
38793
+ });
38794
+
38795
+ const stop = () => {
38796
+ flush();
38797
+ stopEffect();
38798
+ unsubscribe();
38799
+ document.removeEventListener("visibilitychange", onVisibilityChange);
38800
+ window.removeEventListener("pagehide", flush);
38801
+ withdrawKeptReads();
38802
+ if (stopKeepingCurrent === stop) {
38803
+ stopKeepingCurrent = null;
38804
+ }
38805
+ };
38806
+ stopKeepingCurrent = stop;
38807
+ return stop;
38808
+ };
38809
+
38810
+ const readRows = (action) => {
38811
+ const data = action.dataSignal.value;
38812
+ if (data === undefined || data === null) {
38813
+ return undefined;
38814
+ }
38815
+ return serializeData(data);
38816
+ };
38817
+ const readPlainValue = (action) => {
38818
+ const value = action.valueSignal.value;
38819
+ if (value === undefined || !isPlainData(value, new Set())) {
38820
+ return undefined;
38821
+ }
38822
+ return value;
38823
+ };
38824
+ // What JSON writes and reads back as it was. `undefined` inside is accepted:
38825
+ // JSON drops the key, which reads the same.
38826
+ const isPlainData = (value, ancestorSet) => {
38827
+ if (value === null || value === undefined) {
38828
+ return true;
38829
+ }
38830
+ const type = typeof value;
38831
+ if (type === "string" || type === "boolean") {
38832
+ return true;
38833
+ }
38834
+ if (type === "number") {
38835
+ return Number.isFinite(value);
38836
+ }
38837
+ if (type !== "object") {
38838
+ return false;
38839
+ }
38840
+ if (ancestorSet.has(value)) {
38841
+ return false;
38842
+ }
38843
+ const isArray = Array.isArray(value);
38844
+ if (!isArray) {
38845
+ const proto = Object.getPrototypeOf(value);
38846
+ if (proto !== Object.prototype && proto !== null) {
38847
+ return false;
38848
+ }
38849
+ }
38850
+ ancestorSet.add(value);
38851
+ const entries = isArray ? value : Object.values(value);
38852
+ for (const entry of entries) {
38853
+ if (!isPlainData(entry, ancestorSet)) {
38854
+ return false;
38855
+ }
38856
+ }
38857
+ ancestorSet.delete(value);
38858
+ return true;
38859
+ };
38860
+
38062
38861
  /**
38063
38862
  * A popup that is the anchor, brought to the front.
38064
38863
  *
@@ -65131,7 +65930,7 @@ const css$E = /* css */`
65131
65930
  /* A plain div, unlike dialogEl itself (a real <dialog>, natively hidden
65132
65931
  by default until .show()/.showModal() adds [open]) — needs its own
65133
65932
  starting-hidden mechanism. [navi-hidden] is set from useDialogProps'
65134
- own backdropProps (recomputed from openController.opened on every
65933
+ own backdropProps (recomputed from openController.openedInDom on every
65135
65934
  render, present from the very first one), then toggled by plain
65136
65935
  removeAttribute/setAttribute in openEffect/close, never an explicit
65137
65936
  display override — removing the attribute just lets this rule stop
@@ -66732,7 +67531,7 @@ const useDialogProps = props => {
66732
67531
  "navi-out-of-flow": "",
66733
67532
  "baseClassName": "navi_dialog_backdrop",
66734
67533
  "aria-hidden": "true",
66735
- // Recomputed fresh on every render from openController.opened (not
67534
+ // Recomputed fresh on every render from openController.openedInDom (not
66736
67535
  // driven through a mount-time layout effect, unlike this file's own
66737
67536
  // imperative open/close toggling below) — present in the DOM
66738
67537
  // synchronously from the very first commit, matching this file's own
@@ -66742,7 +67541,7 @@ const useDialogProps = props => {
66742
67541
  // needs — see popover.jsx's own identical prop for the full reasoning.
66743
67542
  "aria-expanded": openController.openedInDom ? "true" : "false",
66744
67543
  // Present from this very first render (recomputed fresh on every one
66745
- // from openController.opened, not a frozen mount-time constant) so
67544
+ // from openController.openedInDom, not a frozen mount-time constant) so
66746
67545
  // there's no gap for the browser to ever paint this plain-div backdrop
66747
67546
  // visible before anything has actually opened it — see popover.jsx's
66748
67547
  // own identical prop for the full reasoning, and this file's own CSS
@@ -66762,7 +67561,7 @@ const useDialogProps = props => {
66762
67561
  // (kept once, not repeated here).
66763
67562
  "aria-expanded": openController.openedInDom ? "true" : "false",
66764
67563
  // Present from the very first render (recomputed fresh from
66765
- // openController.opened every time, not a frozen mount-time constant —
67564
+ // openController.openedInDom every time, not a frozen mount-time constant —
66766
67565
  // see popover.jsx's own identical prop for the full reasoning) so a
66767
67566
  // consumer whose own CSS also sets display (e.g. Popup's flex prop)
66768
67567
  // can't silently defeat showModal()/close()'s native open/close — see
@@ -67138,7 +67937,7 @@ const css$D = /* css */`
67138
67937
  without this it would flash visible for one frame on mount, before
67139
67938
  openEffect's own JS ever gets a chance to hide it. [navi-hidden] is
67140
67939
  set from usePopoverProps' own contentProps (recomputed from
67141
- openController.opened on every render, present from the very first
67940
+ openController.openedInDom on every render, present from the very first
67142
67941
  one — see there for why), then toggled by plain
67143
67942
  removeAttribute/setAttribute in openEffect/close, never an explicit
67144
67943
  display override: removing the attribute just lets this rule stop
@@ -68494,7 +69293,7 @@ const usePopoverProps = props => {
68494
69293
  "navi-out-of-flow": "",
68495
69294
  "baseClassName": "navi_popover_backdrop",
68496
69295
  "aria-hidden": "true",
68497
- // Recomputed fresh on every render from openController.opened (not a
69296
+ // Recomputed fresh on every render from openController.openedInDom (not a
68498
69297
  // frozen mount-time constant, same pattern as navi-hidden just below)
68499
69298
  // rather than driven through a mount-time layout effect — its own
68500
69299
  // actual open/close toggling is still done entirely imperatively
@@ -68569,7 +69368,7 @@ const usePopoverProps = props => {
68569
69368
  // starting-hidden default) — present from this very first render so
68570
69369
  // there's no gap for the browser to ever paint it visible before
68571
69370
  // anything has actually opened it. Recomputed fresh on every render from
68572
- // openController.opened (not a frozen mount-time constant) — Preact
69371
+ // openController.openedInDom (not a frozen mount-time constant) — Preact
68573
69372
  // only touches the DOM for a prop whose value actually changed since
68574
69373
  // the last render, so as long as this always reflects the *current*
68575
69374
  // truth, it never fights the imperative
@@ -72995,7 +73794,6 @@ const ListUI = props => {
72995
73794
  // What the runs ask for and stand for, in items: a source paginates in items,
72996
73795
  // and asks for the first picture's items and the rest in one round trip.
72997
73796
  listItems.pageSize = PAGE_SIZE_DEFAULT;
72998
- listItems.scrolled = scrolled ?? defaultScrolled;
72999
73797
  const {
73000
73798
  virtualItemSizeSignal,
73001
73799
  renderWindow,
@@ -73335,6 +74133,45 @@ const useListScrollSync = ({
73335
74133
  columns
73336
74134
  }) => {
73337
74135
  const debugScroll = useDebugScroll();
74136
+ // Where the list must be: what the caller holds it at (`scrolled`), or where
74137
+ // it opens and then lets go (`defaultScrolled`). A `scrolled` that changes is
74138
+ // the caller moving the list, so the hold is armed again — that is what makes
74139
+ // it controlled. A `defaultScrolled` that changes is where the list opens,
74140
+ // read again from something that answered again (a count fresher than the
74141
+ // one kept from the previous visit): followed for as long as nobody has
74142
+ // moved the list, and compared by value, since it is said at every render.
74143
+ // Nothing to hold it at (a position not saved yet) is not a position: the
74144
+ // list opens where it opens.
74145
+ const startPlaceRef = useRef(null);
74146
+ if (startPlaceRef.current === null) {
74147
+ startPlaceRef.current = {
74148
+ userTookOver: false,
74149
+ wanted: scrolled ?? defaultScrolled,
74150
+ scrolled,
74151
+ defaultScrolled,
74152
+ way: null,
74153
+ arrived: false
74154
+ };
74155
+ } else {
74156
+ const place = startPlaceRef.current;
74157
+ if (place.scrolled !== scrolled) {
74158
+ place.scrolled = scrolled;
74159
+ place.defaultScrolled = defaultScrolled;
74160
+ place.wanted = scrolled ?? defaultScrolled;
74161
+ place.userTookOver = false;
74162
+ } else if ((scrolled === undefined || scrolled === null) && !compareTwoJsValues(place.defaultScrolled, defaultScrolled)) {
74163
+ place.defaultScrolled = defaultScrolled;
74164
+ if (!place.userTookOver) {
74165
+ place.wanted = defaultScrolled;
74166
+ // Placed again from where it stands: the way to the previous place
74167
+ // says nothing about whose the next scrolls are.
74168
+ place.way = null;
74169
+ place.arrived = false;
74170
+ }
74171
+ }
74172
+ }
74173
+ const scrolledWanted = startPlaceRef.current.wanted;
74174
+ listItems.scrolled = scrolledWanted;
73338
74175
  // The items drawn, [start, end) among the list's own. A ref as well as a
73339
74176
  // state: the render moves it where the list is held (holdWindow) without a
73340
74177
  // commit of its own.
@@ -73349,7 +74186,7 @@ const useListScrollSync = ({
73349
74186
  const virtualItemSizeSignal = useVirtualItemSizeSignal(ref, virtualItemSize, horizontal, {
73350
74187
  inLines: Boolean(columns),
73351
74188
  windowLeavesItemsOut,
73352
- scrolledWanted: scrolled ?? defaultScrolled,
74189
+ scrolledWanted,
73353
74190
  // The fillers hold the room of items above the screen at this size: a
73354
74191
  // size that changes moves what is on screen, like items landing above it.
73355
74192
  beforeSizeChange: () => listItems.captureAnchor()
@@ -73680,25 +74517,7 @@ const useListScrollSync = ({
73680
74517
  const newEnd = newStart + windowSize;
73681
74518
  updateRenderWindow(newStart, newEnd);
73682
74519
  };
73683
-
73684
- // Where the list must be: what the caller holds it at (`scrolled`), or where
73685
- // it opens and then lets go (`defaultScrolled`). A `scrolled` that changes is
73686
- // the caller moving the list, so the hold is armed again — that is what makes
73687
- // it controlled.
73688
- // Nothing to hold it at (a position not saved yet) is not a position: the
73689
- // list opens where it opens.
73690
- const scrolledWanted = scrolled ?? defaultScrolled;
73691
74520
  const scrolledFallback = scrolled === undefined || scrolled === null ? "start" : defaultScrolled ?? "start";
73692
- const startPlaceRef = useRef({
73693
- userTookOver: false,
73694
- wanted: scrolledWanted,
73695
- way: null,
73696
- arrived: false
73697
- });
73698
- if (startPlaceRef.current.wanted !== scrolledWanted) {
73699
- startPlaceRef.current.wanted = scrolledWanted;
73700
- startPlaceRef.current.userTookOver = false;
73701
- }
73702
74521
  // Only one thing owns the scroll at a time: while the list is holding itself
73703
74522
  // somewhere, the anchoring stays out of it (holding an item still is precisely
73704
74523
  // not being at the end anymore once what is above it shrinks).
@@ -77418,6 +78237,17 @@ const useItemStore = ({
77418
78237
  }
77419
78238
  const pages = pagesRef.current;
77420
78239
  const [, setPageVersion] = useState(0);
78240
+ // A list on screen is part of what the page reads: the composition it reads
78241
+ // is kept with the page for the next document (see keepPageOnScreen).
78242
+ useLayoutEffect(() => {
78243
+ if (inMemory || typeof itemsAction !== "function") {
78244
+ return undefined;
78245
+ }
78246
+ if (!itemsAction.isRangeReader) {
78247
+ return undefined;
78248
+ }
78249
+ return showRangeReader(itemsAction);
78250
+ }, [inMemory, itemsAction]);
77421
78251
  // The ranks whose item is from before: the run came back to the screen, or
77422
78252
  // the source said the collection moved. They stay drawn until a page confirms
77423
78253
  // or replaces them, rank by rank. A page covers what the window framed when it
@@ -77551,6 +78381,7 @@ const useItemStore = ({
77551
78381
  }
77552
78382
  }
77553
78383
  if (typeof itemsAction === "function" && itemsAction.trimComposition) {
78384
+ itemsAction.frameComposition(windowFrom, windowTo);
77554
78385
  itemsAction.trimComposition(keepFrom, keepTo, budget);
77555
78386
  }
77556
78387
  },
@@ -78120,6 +78951,9 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
78120
78951
  * room has to be restated as a number by whoever asks. The `index` and
78121
78952
  * `visibleCount` it also hands out say where to aim before the item is found,
78122
78953
  * and how many items to draw before the first paint (see `renderBudget`).
78954
+ * A value that changes before anyone has moved the list is followed (a place
78955
+ * read from a count that answered again): it is compared by value, so it can
78956
+ * be computed at every render.
78123
78957
  * @param {"start"|"end"|number|{id: string, index?: number, offset?: number, visibleCount?: number}} [props.scrolled]
78124
78958
  * The same, but held: the list goes back there every time this changes, even
78125
78959
  * after the user has scrolled — the caller owns where the list is (see
@@ -93540,5 +94374,5 @@ const UserSvg = () => jsx("svg", {
93540
94374
  })
93541
94375
  });
93542
94376
 
93543
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, ControlSwap, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, Expandable, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, InfoSvg, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NetworkPolicyError, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, Step, StepList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRange, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, constraintFromValidityRule, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, dispatchRequestSetUIState, enableDebugActions, enableDebugOnDocumentLoading, enableVirtualKeyboardOverlay, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, getNowHours, getNowHoursRoundedToStep, isCellSelected, isColumnSelected, isNetworkPolicyError, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markAsOutsideTextFlow, markErrorAsDisplayedBy, moveArrayItemByIndex, moveFocusTo, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, preloadUrl, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, routeFallback, scrollActivitySignal, setBaseUrl, setNetworkPolicy, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutElement, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useNetworkPolicyReason, useOrderedColumns, usePickerState, usePopupMode, useRouteStatus, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideContainer, useSlideValue, useStateArray, useTitleLevel, useTransitionCover, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
94377
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, ControlSwap, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, Expandable, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, InfoSvg, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NetworkPolicyError, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, Step, StepList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRange, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, activeRouteActionsSignal, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, constraintFromValidityRule, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, dispatchRequestSetUIState, enableDebugActions, enableDebugOnDocumentLoading, enableVirtualKeyboardOverlay, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, getNowHours, getNowHoursRoundedToStep, isCellSelected, isColumnSelected, isNetworkPolicyError, isRowSelected, isScrolling, isToday, keepPageOnScreen, languagesSignal, localStorageSignal, markAsOutsideTextFlow, markErrorAsDisplayedBy, moveArrayItemByIndex, moveFocusTo, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, preloadUrl, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, routeFallback, scrollActivitySignal, setBaseUrl, setNetworkPolicy, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutElement, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useNetworkPolicyReason, useOrderedColumns, usePickerState, usePopupMode, useRouteStatus, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideContainer, useSlideValue, useStateArray, useTitleLevel, useTransitionCover, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
93544
94378
  //# sourceMappingURL=jsenv_navi.js.map