@jsenv/navi 0.29.423 → 0.29.424

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.
@@ -60814,6 +60814,20 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
60814
60814
  * `sizing={loading ? "largest" : "frozen"}` and is measured once the real
60815
60815
  * content is there. The freeze holds against what happens INSIDE the box, not
60816
60816
  * against the room it is given: a window resize measures again.
60817
+ * @param {"always"|"near"} [props.mount="always"] - when a slide's CONTENT is
60818
+ * rendered. The <Slide> element always is, so areas, tabs following the box
60819
+ * and `aria-labelledby` are the same either way. "always": every slide's,
60820
+ * from the start. "near": once a travel can show it — the slide on screen,
60821
+ * the one a travel is going to, and at rest the ones a hand dragging the box
60822
+ * would bring alongside (neighbours on the map, along the axes
60823
+ * `travelByDrag` opens, the other end of a `loop` included) — and kept from
60824
+ * then on. For slides that are costly to build (a grid of a few hundred
60825
+ * controls each): opening builds the slide shown and its neighbours rather
60826
+ * than all of them, and a jump to a far slide builds that one and none of
60827
+ * those in between, which a travel never shows. The box is then as large as
60828
+ * the largest slide BUILT so far (see `sizing`), so it grows as a larger one
60829
+ * is reached: give it a size of its own when the slides differ.
60830
+ * `<Slide mount>` says it for one slide — a heavy one among light ones.
60817
60831
  */
60818
60832
  const SlideContainer = ({
60819
60833
  layout = "row",
@@ -60829,6 +60843,7 @@ const SlideContainer = ({
60829
60843
  travelByScroll = "x",
60830
60844
  duration = "300ms",
60831
60845
  sizing = "largest",
60846
+ mount = "always",
60832
60847
  children,
60833
60848
  ...rest
60834
60849
  }) => {
@@ -60948,6 +60963,15 @@ const SlideContainer = ({
60948
60963
  // Undefined at mount on purpose: the first area held outside is news like any
60949
60964
  // other, and it is walked to rather than opened on.
60950
60965
  const areaAskedSeenRef = useRef(undefined);
60966
+ // An area held outside that this container has not answered yet: the effect
60967
+ // reading currentFromCaller walks to it, after the render that brings it.
60968
+ const requestIsNews = () => currentFromCaller !== undefined && currentFromCaller !== areaAskedSeenRef.current;
60969
+ // The slide about to be on screen, as far as a render can tell: the area
60970
+ // asked from outside while it is news — the picture is walked there before
60971
+ // it is painted, the first request included — and the container's own answer
60972
+ // otherwise. What a slide waiting for a travel reads to have its content in
60973
+ // the very render that shows it (see Slide's `mount`).
60974
+ const areaShownNext = requestIsNews() ? currentFromCaller : current;
60951
60975
  // Where the container stands, said where the caller holds it.
60952
60976
  //
60953
60977
  // Whether a slide is a place one came from is the STATE's own business (see
@@ -61002,6 +61026,22 @@ const SlideContainer = ({
61002
61026
  // state: nothing on screen depends on it, and a travel must read what the
61003
61027
  // one before it wrote, not what the last render saw.
61004
61028
  const cameFromRef = useRef({});
61029
+ // The slides a travel has come near while their content was not rendered
61030
+ // (mount="near", see renderNear): only ever grows, and only for a slide that
61031
+ // was waiting — a container whose slides all render never re-renders for it.
61032
+ const [nearAreas, setNearAreas] = useState([]);
61033
+ // Nothing names the slide on screen (no area held outside, no default): it
61034
+ // is the first one in the DOM, which the first render cannot read yet. The
61035
+ // first Slide to render is that one, so it claims it — the slide on screen
61036
+ // then has its content from the first commit (a size frozen at mount, a
61037
+ // focus landing in it) rather than one render late.
61038
+ const openingClaimRef = useRef(null);
61039
+ const claimOpening = area => {
61040
+ if (openingClaimRef.current === null) {
61041
+ openingClaimRef.current = area;
61042
+ }
61043
+ return openingClaimRef.current === area;
61044
+ };
61005
61045
 
61006
61046
  // The controller has caught up with the slide the container went to on its
61007
61047
  // own (commit="rest"): there are no longer two answers to give, so the
@@ -61142,6 +61182,44 @@ const SlideContainer = ({
61142
61182
  }
61143
61183
  };
61144
61184
 
61185
+ // The slides a hand dragging the box from this area would bring alongside:
61186
+ // the map's neighbours on the axes it drags, read the way the drag reads them
61187
+ // (areaTowards knows the holes and a loop's two ends).
61188
+ const areasAlongside = area => {
61189
+ const alongside = [];
61190
+ if (dragAxes?.includes("x")) {
61191
+ alongside.push(areaTowards(-1, 0, area), areaTowards(1, 0, area));
61192
+ }
61193
+ if (dragAxes?.includes("y")) {
61194
+ alongside.push(areaTowards(0, -1, area), areaTowards(0, 1, area));
61195
+ }
61196
+ return alongside;
61197
+ };
61198
+
61199
+ // mount="near": the content of a slide is asked for before a travel shows
61200
+ // it. The neighbours are asked for AT REST, never when a travel sets off: the
61201
+ // travel shows the slide it goes to and nothing past it, and a slide built in
61202
+ // the render that starts it is a frame the travel waits for. At rest nothing
61203
+ // moves, and a hand landing next is still answered after the build.
61204
+ const renderNear = (area, {
61205
+ alongside
61206
+ }) => {
61207
+ // Asked on every render: a container with nothing waiting stops here.
61208
+ if (!trackRef.current?.querySelector(":scope > [data-slide-unrendered]")) {
61209
+ return;
61210
+ }
61211
+ const unrendered = readMap().slideElements.filter(slideElement => slideElement.hasAttribute("data-slide-unrendered")).map(readArea);
61212
+ const near = alongside ? [area, ...areasAlongside(area)] : [area];
61213
+ const toRender = near.filter(nearArea => unrendered.includes(nearArea));
61214
+ if (!toRender.length) {
61215
+ return;
61216
+ }
61217
+ setNearAreas(previous => {
61218
+ const added = toRender.filter(nearArea => !previous.includes(nearArea));
61219
+ return added.length ? [...previous, ...added] : previous;
61220
+ });
61221
+ };
61222
+
61145
61223
  // The travel is over: the stage is struck and every slide goes back where the
61146
61224
  // map says it is. Nothing is seen moving for it — the slide on screen sits at
61147
61225
  // the same place whatever the arrangement (its own offset and the track's are
@@ -61190,6 +61268,9 @@ const SlideContainer = ({
61190
61268
  const offset = `${-x * 100}% ${-y * 100}%`;
61191
61269
  offsetRef.current = offset;
61192
61270
  track.style.setProperty("--slide-container-offset", offset);
61271
+ renderNear(currentArea, {
61272
+ alongside: true
61273
+ });
61193
61274
  // Arrived, so the change can be told (commit="rest"): the picture is at
61194
61275
  // rest and whatever the caller does with it — write the URL, ask a server —
61195
61276
  // costs the gesture nothing anymore.
@@ -61467,6 +61548,15 @@ const SlideContainer = ({
61467
61548
  if (!stageRef.current) {
61468
61549
  drawnAreaRef.current = currentArea;
61469
61550
  }
61551
+ // What a travel can show next (mount="near"), around where the picture
61552
+ // SETTLES: a request from outside is walked to by an effect running after
61553
+ // this one, in the same commit, and the slides around a picture about to be
61554
+ // walked away from would be built for nothing. The neighbours only at
61555
+ // rest, see renderNear.
61556
+ const settlingArea = requestIsNews() && reachableTowards(currentArea, currentFromCaller) || currentArea;
61557
+ renderNear(settlingArea, {
61558
+ alongside: !stageRef.current
61559
+ });
61470
61560
  // Said last, and after data-current has been written: what a travel would do
61471
61561
  // is read off the map from the slide that is now current, and off the locks
61472
61562
  // that slide is wearing in this very commit.
@@ -61704,12 +61794,9 @@ const SlideContainer = ({
61704
61794
  // request later), and what nothing has answered yet must still be answered
61705
61795
  // once it can be.
61706
61796
  useLayoutEffect(() => {
61707
- if (currentFromCaller === undefined) {
61708
- return;
61709
- }
61710
- if (currentFromCaller === areaAskedSeenRef.current) {
61711
- // Not news: either nothing moved, or this container is reading back what
61712
- // it wrote itself.
61797
+ if (!requestIsNews()) {
61798
+ // Not news: nothing held outside, nothing moved, or this container
61799
+ // reading back what it wrote itself.
61713
61800
  return;
61714
61801
  }
61715
61802
  // The slide asked for is not rendered yet — no slides at all, or one whose
@@ -62539,6 +62626,11 @@ const SlideContainer = ({
62539
62626
  if (!past) {
62540
62627
  return false;
62541
62628
  }
62629
+ // Two slides from the one at rest, so possibly never rendered: asked
62630
+ // for now, and built before the frame that brings it in is painted.
62631
+ renderNear(past, {
62632
+ alongside: false
62633
+ });
62542
62634
  const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current")) || slideElements[0];
62543
62635
  const basePlace = stageRef.current?.placeByArea.get(reached) || placeOf.get(reached) || {
62544
62636
  x: 0,
@@ -62997,6 +63089,12 @@ const SlideContainer = ({
62997
63089
  done,
62998
63090
  valueByArea,
62999
63091
  settleFocus,
63092
+ // What a slide reads to know whether its content is rendered yet
63093
+ // (see Slide's `mount`).
63094
+ mount,
63095
+ areaShownNext,
63096
+ nearAreas,
63097
+ claimOpening: areaShownNext === undefined ? claimOpening : null,
63000
63098
  // The box itself, for what is written INSIDE it to read the same
63001
63099
  // facts what is written outside reads by id (useSlideContainer):
63002
63100
  // a way out is a way out on either side of the box.
@@ -63053,9 +63151,12 @@ const warnOnNonStringArea = area => {
63053
63151
  * try to leave.
63054
63152
  * @param {boolean} [props.preventNavNext] - hold them from going right or down.
63055
63153
  * @param {boolean} [props.preventNavPrevious] - …left or up.
63154
+ * @param {"always"|"near"} [props.mount] - when this slide's content is
63155
+ * rendered; the container's `mount` when not said (see there).
63056
63156
  */
63057
63157
  const Slide = ({
63058
63158
  area,
63159
+ mount,
63059
63160
  required,
63060
63161
  preventNav,
63061
63162
  preventNavNext = preventNav,
@@ -63068,6 +63169,17 @@ const Slide = ({
63068
63169
  {
63069
63170
  warnOnNonStringArea(slideArea);
63070
63171
  }
63172
+ // Asked by every slide, whatever its own mode: the claim goes to the first
63173
+ // slide to RENDER, and a slide that skipped asking would hand it to the one
63174
+ // after it.
63175
+ const opening = container?.claimOpening?.(slideArea);
63176
+ // Kept once rendered (mount="near"): going back builds nothing again, and a
63177
+ // slide being left keeps its content for as long as it is still in the frame.
63178
+ const renderedRef = useRef(false);
63179
+ if (!renderedRef.current && (!container || (mount ?? container.mount) !== "near" || opening || slideArea === container.areaShownNext || container.nearAreas.includes(slideArea))) {
63180
+ renderedRef.current = true;
63181
+ }
63182
+ const rendered = renderedRef.current;
63071
63183
  const answered = Boolean(container?.answeredAreas.includes(slideArea));
63072
63184
  const holdsUntilAnswered = Boolean(required) && !answered;
63073
63185
  const nextIsLocked = Boolean(preventNavNext) || holdsUntilAnswered;
@@ -63117,13 +63229,17 @@ const Slide = ({
63117
63229
  "data-slide-area": slideArea,
63118
63230
  "data-prevent-nav-next": nextIsLocked ? "" : undefined,
63119
63231
  "data-prevent-nav-previous": preventNavPrevious ? "" : undefined
63232
+ // What the container reads to know a slide is waiting for a travel
63233
+ // to come near (see renderNear).
63234
+ ,
63235
+ "data-slide-unrendered": rendered ? undefined : ""
63120
63236
  // A surface one interacts with says what state it is in, the way every
63121
63237
  // other surface does — a dialog carries the very same set.
63122
63238
  // :focus-within is the one a slide is really about: it is what tells
63123
63239
  // the slide holding the keyboard from the ones waiting.
63124
63240
  ,
63125
63241
  pseudoClasses: SLIDE_PSEUDO_CLASSES,
63126
- children: children
63242
+ children: rendered ? children : null
63127
63243
  })
63128
63244
  })
63129
63245
  });