@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.
@@ -11409,6 +11409,8 @@ const keptNameWeakMap = new WeakMap();
11409
11409
  const getRouteActionKeptName = (routeAction) => {
11410
11410
  return keptNameWeakMap.get(routeAction);
11411
11411
  };
11412
+ // Shared by every route action without a `when`.
11413
+ const NOT_GATED_SIGNAL = signal(false);
11412
11414
  let activeRouteActionsPrevious = new Set();
11413
11415
  /**
11414
11416
  * The route actions asking something for the page on screen: their route
@@ -11434,6 +11436,24 @@ const activeRouteActionsSignal = computed(() => {
11434
11436
  return active;
11435
11437
  });
11436
11438
 
11439
+ /**
11440
+ * Whether a route action of the page on screen waits for app state to ask: its
11441
+ * route matches and its `when` says no. Params that say no are not waiting:
11442
+ * they read the address, and this one asks nothing of the action — a later ask
11443
+ * is the page changing under the reader (a search typed), not arriving. Read
11444
+ * by the scroll restoration, which stays ready for it.
11445
+ *
11446
+ * @type {import("@preact/signals").ReadonlySignal<boolean>}
11447
+ */
11448
+ const routeActionYetToAskSignal = computed(() => {
11449
+ for (const { gatedSignal } of routeActionEntriesSignal.value) {
11450
+ if (gatedSignal.value) {
11451
+ return true;
11452
+ }
11453
+ }
11454
+ return false;
11455
+ });
11456
+
11437
11457
  let activeRouteReadsPrevious = new Set();
11438
11458
  /**
11439
11459
  * `activeRouteActionsSignal` without the page's code: what to rerun to read
@@ -11500,7 +11520,9 @@ const activeRouteReadsSignal = computed(() => {
11500
11520
  * params when the condition holds at that moment — a page's code only some
11501
11521
  * readers may download (`() => meIsAdminSignal.value`). A condition reading
11502
11522
  * the address belongs in the params: a link reads `when` as it is, not as
11503
- * the address it leads to will make it.
11523
+ * the address it leads to will make it. It is also what a reload's scroll
11524
+ * position waits for: the page is not whole while a `when` says no, whereas
11525
+ * params that say no ask nothing on this page (see scroll_restoration.js).
11504
11526
  * @param {boolean} [options.prefetch=true] - `false` keeps the action from
11505
11527
  * being prerun ahead of what asks for it, when the read is not worth a
11506
11528
  * hover or a press that may not end in an opening.
@@ -11548,6 +11570,9 @@ const routeAction = (
11548
11570
  {
11549
11571
  routeAction: actionBoundToRoute,
11550
11572
  askingSignal: computed(() => Boolean(readParamsAsked())),
11573
+ gatedSignal: when
11574
+ ? computed(() => Boolean(routeMatchingSignal.value) && !when())
11575
+ : NOT_GATED_SIGNAL,
11551
11576
  },
11552
11577
  ];
11553
11578
  for (const route of routes) {
@@ -11703,11 +11728,11 @@ const haveSameMembers = (setA, setB) => {
11703
11728
  * moment it is put back, and a position beyond what has arrived is clamped.
11704
11729
  * What navi knows a first render left out, it waits for: a `<Box
11705
11730
  * mount="after-paint">` is built before the offset that shows it is written
11706
- * (see mount_after_paint.jsx), and a reload waits for the route actions that
11707
- * render had nothing to draw for — the page's code among them — and for one
11708
- * that starts asking later, when the position did not fit what was there (see
11709
- * restoreScrollPositionOnLoad). Anything else only the page knows, and it says
11710
- * so (useHoldScrollRestoration). A wait can be long, so the reader reaching for
11731
+ * (see mount_after_paint.jsx), and a reload or a return waits for the route
11732
+ * actions the page has nothing to draw for — its code among them — including
11733
+ * one whose `when` lets it ask after the position is written (see
11734
+ * stayForLateAsks). Anything else only the page knows, and it says so
11735
+ * (useHoldScrollRestoration). A wait can be long, so the reader reaching for
11711
11736
  * the page during it cancels the position: moved afterwards, they would lose
11712
11737
  * whatever they had started on.
11713
11738
  *
@@ -11822,17 +11847,16 @@ const suspendScrollRecording = () => {
11822
11847
  };
11823
11848
  };
11824
11849
 
11825
- // The position a reload or a return is about to put back, until it is written.
11826
- // Where the document lands is the last arrival's to say: an arrival decided
11827
- // meanwhile — a page that sends the reader elsewhere as it mounts — replaces it
11828
- // (see startAtTop), and must not be scrolled to where the return was going.
11829
- // So does the reader reaching for the page before it is written: where they
11830
- // are is theirs (see installScrollRestoration).
11850
+ // The position a reload or a return is about to put back, until it is written
11851
+ // into a page that will ask nothing more (see stayForLateAsks). Where the
11852
+ // document lands is the last arrival's to say: an arrival decided meanwhile — a
11853
+ // page that sends the reader elsewhere as it mounts — replaces it (see
11854
+ // startAtTop), and must not be scrolled to where the return was going. So does
11855
+ // the reader reaching for the page: where they are is theirs (see
11856
+ // installScrollRestoration).
11831
11857
  let pendingRestoration = null;
11832
- // `untilReached`: a write the document is too short for leaves it pending (see
11833
- // putBackOnLateAnswer).
11834
- const startRestoration = (position, { untilReached = false } = {}) => {
11835
- const restoration = { position, due: false, untilReached };
11858
+ const startRestoration = (position) => {
11859
+ const restoration = { position, due: false };
11836
11860
  pendingRestoration = restoration;
11837
11861
  return restoration;
11838
11862
  };
@@ -11847,16 +11871,13 @@ const putBack = (restoration) => {
11847
11871
  // The page is not whole yet: the last release puts it back.
11848
11872
  return;
11849
11873
  }
11850
- pendingRestoration = null;
11851
11874
  if (documentHoldCount) {
11875
+ pendingRestoration = null;
11852
11876
  return;
11853
11877
  }
11854
11878
  scrollTo(restoration.position);
11855
- if (restoration.untilReached && fellShortOf(restoration.position)) {
11856
- restoration.due = false;
11857
- pendingRestoration = restoration;
11858
- putBackOnLateAnswer(restoration);
11859
- }
11879
+ restoration.due = false;
11880
+ stayForLateAsks(restoration);
11860
11881
  };
11861
11882
 
11862
11883
  // A list scrolling the document that opens on a row puts the document where
@@ -11914,9 +11935,10 @@ const holdScrollRestoration = () => {
11914
11935
  * `held` turns false — unless the reader has reached for the page in the
11915
11936
  * meantime (a finger, a click, a wheel, a key), or gone elsewhere.
11916
11937
  *
11917
- * After a reload, the page's route actions are waited for without this — its
11918
- * code, a read the previous document did not keep. This is for what navi
11919
- * cannot see: a read the page starts itself, content whose height arrives late.
11938
+ * The page's route actions are waited for without this — its code, a read
11939
+ * with no answer yet, one whose `when` holds later. This is for
11940
+ * what navi cannot see: a read the page starts itself, content whose height
11941
+ * arrives late.
11920
11942
  *
11921
11943
  * ```jsx
11922
11944
  * const [games] = useAsyncData(GAMES, { loading: true, run: true });
@@ -12001,19 +12023,22 @@ const installScrollRestoration = ({ readAddress }) => {
12001
12023
  // page the position was taken on, and the position stays for the reload that
12002
12024
  // brings the page back.
12003
12025
  const restoreScrollPositionOnLoad = (position) => {
12004
- const restoration = startRestoration(position, { untilReached: true });
12026
+ const restoration = startRestoration(position);
12005
12027
  const stopListening = observeRouteRender(() => {
12006
12028
  stopListening();
12007
12029
  putBackOnceAnswered(restoration);
12008
12030
  });
12009
12031
  };
12010
12032
 
12011
- // Reads the actions to wait for at once: from the commit of the first render,
12033
+ // Reads the actions to wait for at once: called from the commit of a render,
12012
12034
  // the ones it had nothing to draw for are those still running then. An answer
12013
12035
  // can start another read (params made of that answer), so the actions asking
12014
12036
  // are read again before the position is written.
12037
+ //
12038
+ // The same for a reload and a return: a return to a page this document has
12039
+ // never drawn, or whose read was reset since, finds no answer either.
12015
12040
  const putBackOnceAnswered = (restoration) => {
12016
- const unanswered = untracked(readUnansweredRouteActions);
12041
+ const unanswered = readUnansweredRouteActions();
12017
12042
  if (unanswered.length === 0) {
12018
12043
  queueMicrotask(() => {
12019
12044
  putBack(restoration);
@@ -12041,10 +12066,10 @@ const putBackOnceAnswered = (restoration) => {
12041
12066
 
12042
12067
  const readUnansweredRouteActions = () => {
12043
12068
  const unanswered = [];
12044
- for (const routeAction of activeRouteActionsSignal.value) {
12069
+ for (const routeAction of activeRouteActionsSignal.peek()) {
12045
12070
  if (
12046
- routeAction.runningStateSignal.value === RUNNING &&
12047
- routeAction.dataSignal.value === undefined
12071
+ routeAction.runningStateSignal.peek() === RUNNING &&
12072
+ routeAction.dataSignal.peek() === undefined
12048
12073
  ) {
12049
12074
  unanswered.push(routeAction);
12050
12075
  }
@@ -12052,33 +12077,49 @@ const readUnansweredRouteActions = () => {
12052
12077
  return unanswered;
12053
12078
  };
12054
12079
 
12055
- // A page can start asking after its first render: an action gated on app
12056
- // state that render did not have yet (`when`, or params reading an app-wide
12057
- // read), and nothing tells "not yet" from "never" before it asks. So the
12058
- // position is written into what the page lets it, and a write the document
12059
- // could not hold is not the end of it: the next route action that starts
12080
+ // A page can still be arriving after the position is written: an action whose
12081
+ // `when` waits for app state not there yet (see routeActionYetToAskSignal). So
12082
+ // a written position stays pending while one does: the next one that starts
12060
12083
  // without an answer is waited for like the first ones, and the position put
12061
- // back again. Not told when the restoration is dropped (pendingRestoration is
12062
- // no signal): it notices at its next run.
12063
- const putBackOnLateAnswer = (restoration) => {
12084
+ // back once it is drawn. Params that say no are not waited for: the address
12085
+ // asks nothing of the action, and one that asks later — a search typed — is
12086
+ // the page moving on under the reader, not arriving. Not told when the
12087
+ // restoration is dropped (pendingRestoration is no signal): it notices at its
12088
+ // next run.
12089
+ const stayForLateAsks = (restoration) => {
12064
12090
  effect(function () {
12091
+ let running = false;
12092
+ let starting = false;
12093
+ for (const routeAction of activeRouteActionsSignal.value) {
12094
+ if (routeAction.dataSignal.value !== undefined) {
12095
+ continue;
12096
+ }
12097
+ const runningState = routeAction.runningStateSignal.value;
12098
+ if (runningState === RUNNING) {
12099
+ running = true;
12100
+ } else if (runningState === IDLE) {
12101
+ // Asking, and run by an effect of the same batch that has not run yet.
12102
+ starting = true;
12103
+ }
12104
+ }
12105
+ const yetToAsk = routeActionYetToAskSignal.value;
12065
12106
  if (pendingRestoration !== restoration) {
12066
12107
  this.dispose();
12067
12108
  return;
12068
12109
  }
12069
- if (readUnansweredRouteActions().length === 0) {
12110
+ if (running) {
12111
+ this.dispose();
12112
+ putBackOnceAnswered(restoration);
12113
+ return;
12114
+ }
12115
+ if (starting || yetToAsk) {
12070
12116
  return;
12071
12117
  }
12072
12118
  this.dispose();
12073
- putBackOnceAnswered(restoration);
12119
+ pendingRestoration = null;
12074
12120
  });
12075
12121
  };
12076
12122
 
12077
- // A pixel of slack: the offset kept is the fractional one the browser reports.
12078
- const fellShortOf = (position) => {
12079
- return position.y - window.scrollY > 1;
12080
- };
12081
-
12082
12123
  // Calls back once none of the actions is running, saying whether all of them
12083
12124
  // completed.
12084
12125
  const whenSettled = (actions, callback) => {
@@ -12127,10 +12168,11 @@ const restoreScrollPosition = (url) => {
12127
12168
  };
12128
12169
 
12129
12170
  // A traversal back to `url`. The offset is written once the page returned to is
12130
- // rendered: written sooner, the document still holds the page being left and
12131
- // clamps it to that page's height. It is read at once all the same — a page
12132
- // left taller than the one arriving is clamped by the render itself, and that
12133
- // clamp is a scroll recorded under the url already returned to.
12171
+ // rendered and answered (see putBackOnceAnswered): written sooner, the document
12172
+ // still holds the page being left, or a stand-in for the one arriving, and
12173
+ // clamps it to that height. It is read at once all the same — a page left
12174
+ // taller than the one arriving is clamped by the render itself, and that clamp
12175
+ // is a scroll recorded under the url already returned to.
12134
12176
  const restoreScrollPositionOnReturn = (url) => {
12135
12177
  pendingRestoration = null;
12136
12178
  const href = new URL(url, window.location.href).href;
@@ -12141,7 +12183,7 @@ const restoreScrollPositionOnReturn = (url) => {
12141
12183
  }
12142
12184
  const restoration = startRestoration(position);
12143
12185
  whenPageRendered(() => {
12144
- putBack(restoration);
12186
+ putBackOnceAnswered(restoration);
12145
12187
  });
12146
12188
  };
12147
12189
 
@@ -82798,6 +82840,24 @@ const PickerButton = props => {
82798
82840
  clearConfirm,
82799
82841
  requestValue
82800
82842
  };
82843
+ // `--navi-select`/`--navi-unselect` about one entry of the list the picker
82844
+ // holds — a chip on the façade, a suggestion beside the field. Answered here
82845
+ // rather than by the control drawing that list in the popup, even though rows
82846
+ // are what such a control owns: a picker given its own value builds its popup
82847
+ // only on first open (see popup_content_mount.js), so before that there is no
82848
+ // such control at all — and building the whole popup to have someone to talk
82849
+ // to, for a cross, is the wrong price. The picker holds the value in the first
82850
+ // place and hands it down whenever the popup is built.
82851
+ //
82852
+ // Heard on both elements, like every request a control answers: a chip inside
82853
+ // the picker aims at its root (the parent control), one standing elsewhere
82854
+ // names it with `commandFor`, and the picker's id is on its input.
82855
+ const onRequestSelect = e => {
82856
+ requestPickerListEntry(ref.current, inputRef.current, e, "select");
82857
+ };
82858
+ const onRequestUnselect = e => {
82859
+ requestPickerListEntry(ref.current, inputRef.current, e, "unselect");
82860
+ };
82801
82861
  return (
82802
82862
  /* Read-only crosses into everything the picker is made of: what it really
82803
82863
  holds is drawn by controls of their own — in the popup, and on the façade
@@ -82858,23 +82918,9 @@ const PickerButton = props => {
82858
82918
  ,
82859
82919
  onnavi_request_open: inputProps.onnavi_request_open,
82860
82920
  onnavi_request_close: inputProps.onnavi_request_close,
82861
- onnavi_open_press: inputProps.onnavi_open_press
82862
- // `--navi-select`/`--navi-unselect` about one entry of the list the
82863
- // picker holds — a chip on the façade, a suggestion beside the field.
82864
- // Answered here rather than by the control drawing that list in the
82865
- // popup, even though rows are what such a control owns: a picker given
82866
- // its own value builds its popup only on first open (see
82867
- // popup_content_mount.js), so before that there is no such control at
82868
- // all — and building the whole popup to have someone to talk to, for a
82869
- // cross, is the wrong price. The picker holds the value in the first
82870
- // place and hands it down whenever the popup is built.
82871
- ,
82872
- onnavi_request_select: e => {
82873
- requestPickerListEntry(ref.current, inputRef.current, e, "select");
82874
- },
82875
- onnavi_request_unselect: e => {
82876
- requestPickerListEntry(ref.current, inputRef.current, e, "unselect");
82877
- },
82921
+ onnavi_open_press: inputProps.onnavi_open_press,
82922
+ onnavi_request_select: onRequestSelect,
82923
+ onnavi_request_unselect: onRequestUnselect,
82878
82924
  children: [jsx("span", {
82879
82925
  className: "navi_picker_box",
82880
82926
  style: hangingChevronSize === undefined ? undefined : {
@@ -82896,6 +82942,8 @@ const PickerButton = props => {
82896
82942
  children: undefined // we will render children into the div
82897
82943
  ,
82898
82944
  ui: ui,
82945
+ onnavi_request_select: onRequestSelect,
82946
+ onnavi_request_unselect: onRequestUnselect,
82899
82947
  onCopy: e => {
82900
82948
  const pickerEl = ref.current;
82901
82949
  if (isWithinPickerContent(e.target, pickerEl)) {