@jsenv/navi 0.29.462 → 0.29.464

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.
@@ -11837,6 +11837,8 @@ const keptNameWeakMap = new WeakMap();
11837
11837
  const getRouteActionKeptName = (routeAction) => {
11838
11838
  return keptNameWeakMap.get(routeAction);
11839
11839
  };
11840
+ // Shared by every route action without a `when`.
11841
+ const NOT_GATED_SIGNAL = signal(false);
11840
11842
  let activeRouteActionsPrevious = new Set();
11841
11843
  /**
11842
11844
  * The route actions asking something for the page on screen: their route
@@ -11862,6 +11864,24 @@ const activeRouteActionsSignal = computed(() => {
11862
11864
  return active;
11863
11865
  });
11864
11866
 
11867
+ /**
11868
+ * Whether a route action of the page on screen waits for app state to ask: its
11869
+ * route matches and its `when` says no. Params that say no are not waiting:
11870
+ * they read the address, and this one asks nothing of the action — a later ask
11871
+ * is the page changing under the reader (a search typed), not arriving. Read
11872
+ * by the scroll restoration, which stays ready for it.
11873
+ *
11874
+ * @type {import("@preact/signals").ReadonlySignal<boolean>}
11875
+ */
11876
+ const routeActionYetToAskSignal = computed(() => {
11877
+ for (const { gatedSignal } of routeActionEntriesSignal.value) {
11878
+ if (gatedSignal.value) {
11879
+ return true;
11880
+ }
11881
+ }
11882
+ return false;
11883
+ });
11884
+
11865
11885
  let activeRouteReadsPrevious = new Set();
11866
11886
  /**
11867
11887
  * `activeRouteActionsSignal` without the page's code: what to rerun to read
@@ -11928,7 +11948,9 @@ const activeRouteReadsSignal = computed(() => {
11928
11948
  * params when the condition holds at that moment — a page's code only some
11929
11949
  * readers may download (`() => meIsAdminSignal.value`). A condition reading
11930
11950
  * the address belongs in the params: a link reads `when` as it is, not as
11931
- * the address it leads to will make it.
11951
+ * the address it leads to will make it. It is also what a reload's scroll
11952
+ * position waits for: the page is not whole while a `when` says no, whereas
11953
+ * params that say no ask nothing on this page (see scroll_restoration.js).
11932
11954
  * @param {boolean} [options.prefetch=true] - `false` keeps the action from
11933
11955
  * being prerun ahead of what asks for it, when the read is not worth a
11934
11956
  * hover or a press that may not end in an opening.
@@ -11976,6 +11998,9 @@ const routeAction = (
11976
11998
  {
11977
11999
  routeAction: actionBoundToRoute,
11978
12000
  askingSignal: computed(() => Boolean(readParamsAsked())),
12001
+ gatedSignal: when
12002
+ ? computed(() => Boolean(routeMatchingSignal.value) && !when())
12003
+ : NOT_GATED_SIGNAL,
11979
12004
  },
11980
12005
  ];
11981
12006
  for (const route of routes) {
@@ -12131,11 +12156,11 @@ const haveSameMembers = (setA, setB) => {
12131
12156
  * moment it is put back, and a position beyond what has arrived is clamped.
12132
12157
  * What navi knows a first render left out, it waits for: a `<Box
12133
12158
  * mount="after-paint">` is built before the offset that shows it is written
12134
- * (see mount_after_paint.jsx), and a reload waits for the route actions that
12135
- * render had nothing to draw for — the page's code among them — and for one
12136
- * that starts asking later, when the position did not fit what was there (see
12137
- * restoreScrollPositionOnLoad). Anything else only the page knows, and it says
12138
- * so (useHoldScrollRestoration). A wait can be long, so the reader reaching for
12159
+ * (see mount_after_paint.jsx), and a reload or a return waits for the route
12160
+ * actions the page has nothing to draw for — its code among them — including
12161
+ * one whose `when` lets it ask after the position is written (see
12162
+ * stayForLateAsks). Anything else only the page knows, and it says so
12163
+ * (useHoldScrollRestoration). A wait can be long, so the reader reaching for
12139
12164
  * the page during it cancels the position: moved afterwards, they would lose
12140
12165
  * whatever they had started on.
12141
12166
  *
@@ -12250,17 +12275,16 @@ const suspendScrollRecording = () => {
12250
12275
  };
12251
12276
  };
12252
12277
 
12253
- // The position a reload or a return is about to put back, until it is written.
12254
- // Where the document lands is the last arrival's to say: an arrival decided
12255
- // meanwhile — a page that sends the reader elsewhere as it mounts — replaces it
12256
- // (see startAtTop), and must not be scrolled to where the return was going.
12257
- // So does the reader reaching for the page before it is written: where they
12258
- // are is theirs (see installScrollRestoration).
12278
+ // The position a reload or a return is about to put back, until it is written
12279
+ // into a page that will ask nothing more (see stayForLateAsks). Where the
12280
+ // document lands is the last arrival's to say: an arrival decided meanwhile — a
12281
+ // page that sends the reader elsewhere as it mounts — replaces it (see
12282
+ // startAtTop), and must not be scrolled to where the return was going. So does
12283
+ // the reader reaching for the page: where they are is theirs (see
12284
+ // installScrollRestoration).
12259
12285
  let pendingRestoration = null;
12260
- // `untilReached`: a write the document is too short for leaves it pending (see
12261
- // putBackOnLateAnswer).
12262
- const startRestoration = (position, { untilReached = false } = {}) => {
12263
- const restoration = { position, due: false, untilReached };
12286
+ const startRestoration = (position) => {
12287
+ const restoration = { position, due: false };
12264
12288
  pendingRestoration = restoration;
12265
12289
  return restoration;
12266
12290
  };
@@ -12275,16 +12299,13 @@ const putBack = (restoration) => {
12275
12299
  // The page is not whole yet: the last release puts it back.
12276
12300
  return;
12277
12301
  }
12278
- pendingRestoration = null;
12279
12302
  if (documentHoldCount) {
12303
+ pendingRestoration = null;
12280
12304
  return;
12281
12305
  }
12282
12306
  scrollTo(restoration.position);
12283
- if (restoration.untilReached && fellShortOf(restoration.position)) {
12284
- restoration.due = false;
12285
- pendingRestoration = restoration;
12286
- putBackOnLateAnswer(restoration);
12287
- }
12307
+ restoration.due = false;
12308
+ stayForLateAsks(restoration);
12288
12309
  };
12289
12310
 
12290
12311
  // A list scrolling the document that opens on a row puts the document where
@@ -12342,9 +12363,10 @@ const holdScrollRestoration = () => {
12342
12363
  * `held` turns false — unless the reader has reached for the page in the
12343
12364
  * meantime (a finger, a click, a wheel, a key), or gone elsewhere.
12344
12365
  *
12345
- * After a reload, the page's route actions are waited for without this — its
12346
- * code, a read the previous document did not keep. This is for what navi
12347
- * cannot see: a read the page starts itself, content whose height arrives late.
12366
+ * The page's route actions are waited for without this — its code, a read
12367
+ * with no answer yet, one whose `when` holds later. This is for
12368
+ * what navi cannot see: a read the page starts itself, content whose height
12369
+ * arrives late.
12348
12370
  *
12349
12371
  * ```jsx
12350
12372
  * const [games] = useAsyncData(GAMES, { loading: true, run: true });
@@ -12429,19 +12451,22 @@ const installScrollRestoration = ({ readAddress }) => {
12429
12451
  // page the position was taken on, and the position stays for the reload that
12430
12452
  // brings the page back.
12431
12453
  const restoreScrollPositionOnLoad = (position) => {
12432
- const restoration = startRestoration(position, { untilReached: true });
12454
+ const restoration = startRestoration(position);
12433
12455
  const stopListening = observeRouteRender(() => {
12434
12456
  stopListening();
12435
12457
  putBackOnceAnswered(restoration);
12436
12458
  });
12437
12459
  };
12438
12460
 
12439
- // Reads the actions to wait for at once: from the commit of the first render,
12461
+ // Reads the actions to wait for at once: called from the commit of a render,
12440
12462
  // the ones it had nothing to draw for are those still running then. An answer
12441
12463
  // can start another read (params made of that answer), so the actions asking
12442
12464
  // are read again before the position is written.
12465
+ //
12466
+ // The same for a reload and a return: a return to a page this document has
12467
+ // never drawn, or whose read was reset since, finds no answer either.
12443
12468
  const putBackOnceAnswered = (restoration) => {
12444
- const unanswered = untracked(readUnansweredRouteActions);
12469
+ const unanswered = readUnansweredRouteActions();
12445
12470
  if (unanswered.length === 0) {
12446
12471
  queueMicrotask(() => {
12447
12472
  putBack(restoration);
@@ -12469,10 +12494,10 @@ const putBackOnceAnswered = (restoration) => {
12469
12494
 
12470
12495
  const readUnansweredRouteActions = () => {
12471
12496
  const unanswered = [];
12472
- for (const routeAction of activeRouteActionsSignal.value) {
12497
+ for (const routeAction of activeRouteActionsSignal.peek()) {
12473
12498
  if (
12474
- routeAction.runningStateSignal.value === RUNNING &&
12475
- routeAction.dataSignal.value === undefined
12499
+ routeAction.runningStateSignal.peek() === RUNNING &&
12500
+ routeAction.dataSignal.peek() === undefined
12476
12501
  ) {
12477
12502
  unanswered.push(routeAction);
12478
12503
  }
@@ -12480,33 +12505,49 @@ const readUnansweredRouteActions = () => {
12480
12505
  return unanswered;
12481
12506
  };
12482
12507
 
12483
- // A page can start asking after its first render: an action gated on app
12484
- // state that render did not have yet (`when`, or params reading an app-wide
12485
- // read), and nothing tells "not yet" from "never" before it asks. So the
12486
- // position is written into what the page lets it, and a write the document
12487
- // could not hold is not the end of it: the next route action that starts
12508
+ // A page can still be arriving after the position is written: an action whose
12509
+ // `when` waits for app state not there yet (see routeActionYetToAskSignal). So
12510
+ // a written position stays pending while one does: the next one that starts
12488
12511
  // without an answer is waited for like the first ones, and the position put
12489
- // back again. Not told when the restoration is dropped (pendingRestoration is
12490
- // no signal): it notices at its next run.
12491
- const putBackOnLateAnswer = (restoration) => {
12512
+ // back once it is drawn. Params that say no are not waited for: the address
12513
+ // asks nothing of the action, and one that asks later — a search typed — is
12514
+ // the page moving on under the reader, not arriving. Not told when the
12515
+ // restoration is dropped (pendingRestoration is no signal): it notices at its
12516
+ // next run.
12517
+ const stayForLateAsks = (restoration) => {
12492
12518
  effect(function () {
12519
+ let running = false;
12520
+ let starting = false;
12521
+ for (const routeAction of activeRouteActionsSignal.value) {
12522
+ if (routeAction.dataSignal.value !== undefined) {
12523
+ continue;
12524
+ }
12525
+ const runningState = routeAction.runningStateSignal.value;
12526
+ if (runningState === RUNNING) {
12527
+ running = true;
12528
+ } else if (runningState === IDLE) {
12529
+ // Asking, and run by an effect of the same batch that has not run yet.
12530
+ starting = true;
12531
+ }
12532
+ }
12533
+ const yetToAsk = routeActionYetToAskSignal.value;
12493
12534
  if (pendingRestoration !== restoration) {
12494
12535
  this.dispose();
12495
12536
  return;
12496
12537
  }
12497
- if (readUnansweredRouteActions().length === 0) {
12538
+ if (running) {
12539
+ this.dispose();
12540
+ putBackOnceAnswered(restoration);
12541
+ return;
12542
+ }
12543
+ if (starting || yetToAsk) {
12498
12544
  return;
12499
12545
  }
12500
12546
  this.dispose();
12501
- putBackOnceAnswered(restoration);
12547
+ pendingRestoration = null;
12502
12548
  });
12503
12549
  };
12504
12550
 
12505
- // A pixel of slack: the offset kept is the fractional one the browser reports.
12506
- const fellShortOf = (position) => {
12507
- return position.y - window.scrollY > 1;
12508
- };
12509
-
12510
12551
  // Calls back once none of the actions is running, saying whether all of them
12511
12552
  // completed.
12512
12553
  const whenSettled = (actions, callback) => {
@@ -12555,10 +12596,11 @@ const restoreScrollPosition = (url) => {
12555
12596
  };
12556
12597
 
12557
12598
  // A traversal back to `url`. The offset is written once the page returned to is
12558
- // rendered: written sooner, the document still holds the page being left and
12559
- // clamps it to that page's height. It is read at once all the same — a page
12560
- // left taller than the one arriving is clamped by the render itself, and that
12561
- // clamp is a scroll recorded under the url already returned to.
12599
+ // rendered and answered (see putBackOnceAnswered): written sooner, the document
12600
+ // still holds the page being left, or a stand-in for the one arriving, and
12601
+ // clamps it to that height. It is read at once all the same — a page left
12602
+ // taller than the one arriving is clamped by the render itself, and that clamp
12603
+ // is a scroll recorded under the url already returned to.
12562
12604
  const restoreScrollPositionOnReturn = (url) => {
12563
12605
  pendingRestoration = null;
12564
12606
  const href = new URL(url, window.location.href).href;
@@ -12569,7 +12611,7 @@ const restoreScrollPositionOnReturn = (url) => {
12569
12611
  }
12570
12612
  const restoration = startRestoration(position);
12571
12613
  whenPageRendered(() => {
12572
- putBack(restoration);
12614
+ putBackOnceAnswered(restoration);
12573
12615
  });
12574
12616
  };
12575
12617
 
@@ -84383,6 +84425,24 @@ const PickerButton = props => {
84383
84425
  clearConfirm,
84384
84426
  requestValue
84385
84427
  };
84428
+ // `--navi-select`/`--navi-unselect` about one entry of the list the picker
84429
+ // holds — a chip on the façade, a suggestion beside the field. Answered here
84430
+ // rather than by the control drawing that list in the popup, even though rows
84431
+ // are what such a control owns: a picker given its own value builds its popup
84432
+ // only on first open (see popup_content_mount.js), so before that there is no
84433
+ // such control at all — and building the whole popup to have someone to talk
84434
+ // to, for a cross, is the wrong price. The picker holds the value in the first
84435
+ // place and hands it down whenever the popup is built.
84436
+ //
84437
+ // Heard on both elements, like every request a control answers: a chip inside
84438
+ // the picker aims at its root (the parent control), one standing elsewhere
84439
+ // names it with `commandFor`, and the picker's id is on its input.
84440
+ const onRequestSelect = e => {
84441
+ requestPickerListEntry(ref.current, inputRef.current, e, "select");
84442
+ };
84443
+ const onRequestUnselect = e => {
84444
+ requestPickerListEntry(ref.current, inputRef.current, e, "unselect");
84445
+ };
84386
84446
  return (
84387
84447
  /* Read-only crosses into everything the picker is made of: what it really
84388
84448
  holds is drawn by controls of their own — in the popup, and on the façade
@@ -84443,23 +84503,9 @@ const PickerButton = props => {
84443
84503
  ,
84444
84504
  onnavi_request_open: inputProps.onnavi_request_open,
84445
84505
  onnavi_request_close: inputProps.onnavi_request_close,
84446
- onnavi_open_press: inputProps.onnavi_open_press
84447
- // `--navi-select`/`--navi-unselect` about one entry of the list the
84448
- // picker holds — a chip on the façade, a suggestion beside the field.
84449
- // Answered here rather than by the control drawing that list in the
84450
- // popup, even though rows are what such a control owns: a picker given
84451
- // its own value builds its popup only on first open (see
84452
- // popup_content_mount.js), so before that there is no such control at
84453
- // all — and building the whole popup to have someone to talk to, for a
84454
- // cross, is the wrong price. The picker holds the value in the first
84455
- // place and hands it down whenever the popup is built.
84456
- ,
84457
- onnavi_request_select: e => {
84458
- requestPickerListEntry(ref.current, inputRef.current, e, "select");
84459
- },
84460
- onnavi_request_unselect: e => {
84461
- requestPickerListEntry(ref.current, inputRef.current, e, "unselect");
84462
- },
84506
+ onnavi_open_press: inputProps.onnavi_open_press,
84507
+ onnavi_request_select: onRequestSelect,
84508
+ onnavi_request_unselect: onRequestUnselect,
84463
84509
  children: [jsx("span", {
84464
84510
  className: "navi_picker_box",
84465
84511
  style: hangingChevronSize === undefined ? undefined : {
@@ -84481,6 +84527,8 @@ const PickerButton = props => {
84481
84527
  children: undefined // we will render children into the div
84482
84528
  ,
84483
84529
  ui: ui,
84530
+ onnavi_request_select: onRequestSelect,
84531
+ onnavi_request_unselect: onRequestUnselect,
84484
84532
  onCopy: e => {
84485
84533
  const pickerEl = ref.current;
84486
84534
  if (isWithinPickerContent(e.target, pickerEl)) {