@jsenv/navi 0.29.506 → 0.29.508

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.
@@ -55951,6 +55951,10 @@ const css$$ = /* css */`@layer navi {
55951
55951
  --link-color: unset;
55952
55952
  --link-text-decoration: none;
55953
55953
  }
55954
+
55955
+ .navi_link[data-color-inherit] {
55956
+ --link-color-visited: var(--navi-link-color-visited, var(--link-color));
55957
+ }
55954
55958
  }
55955
55959
 
55956
55960
  .navi_link {
@@ -56767,6 +56771,7 @@ const LinkPlain = props => {
56767
56771
  // Visual
56768
56772
  ,
56769
56773
  "data-variant": variant,
56774
+ "data-color-inherit": props.color === "inherit" ? "" : undefined,
56770
56775
  "data-current-effect-bold": currentEffectBoldTechnique,
56771
56776
  "data-current-effect-shadow": currentEffectShadow ? "" : undefined,
56772
56777
  "data-current-indicator-position": currentIndicatorPosition,
@@ -68014,6 +68019,40 @@ const causeOfEvent = event => {
68014
68019
  // into says (see writeAreaAsked).
68015
68020
  const CAUSES_WRITTEN_BY_REPLACEMENT = ["drag", "refusal", "state"];
68016
68021
  const readArea = slideElement => slideElement.getAttribute("data-slide-area") || slideElement.id || "";
68022
+ const slidesOf = containerElement => Array.from(containerElement.querySelectorAll(":scope > [data-slide-track] > [data-slide]"));
68023
+
68024
+ // Who is drawing something about a box from outside it: a tab bar saying where
68025
+ // one is, a row of dots. They name the box they follow by its id, the way
68026
+ // everything else that talks to it across the document does (commandfor).
68027
+ const readFollowerElements = containerElement => {
68028
+ const {
68029
+ id
68030
+ } = containerElement;
68031
+ if (!id) {
68032
+ return [];
68033
+ }
68034
+ return Array.from(document.querySelectorAll(`[data-slide-container-follows="${CSS.escape(id)}"]`));
68035
+ };
68036
+
68037
+ // The containers inside a slide bound to the same state as the slide's own
68038
+ // container (see `signal`) — the ones directly under it, a container sharing
68039
+ // with one of those is that one's business.
68040
+ const sharingContainersIn = slideElement => {
68041
+ const holderElement = slideElement.closest("[data-slide-container]");
68042
+ return Array.from(slideElement.querySelectorAll("[data-slide-shares-state]")).filter(innerElement => innerElement.parentElement.closest("[data-slide-container]") === holderElement);
68043
+ };
68044
+
68045
+ // Whether the state holding `area` is on screen when this slide is: it is the
68046
+ // slide's own area, or a slide of a container sharing the state inside it.
68047
+ const slideShows = (slideElement, area) => readArea(slideElement) === area || sharingContainersIn(slideElement).some(innerElement => slidesOf(innerElement).some(innerSlide => slideShows(innerSlide, area)));
68048
+
68049
+ // What the state holds while this slide is on screen: the area a container
68050
+ // sharing the state inside it stands on, its own area when there is none.
68051
+ const areaHeldOn = slideElement => {
68052
+ const [innerElement] = sharingContainersIn(slideElement);
68053
+ const innerCurrent = innerElement && slidesOf(innerElement).find(innerSlide => innerSlide.hasAttribute("data-current"));
68054
+ return innerCurrent ? areaHeldOn(innerCurrent) : readArea(slideElement);
68055
+ };
68017
68056
 
68018
68057
  /**
68019
68058
  * The slide shown can be driven from outside (a `signal`, or `current` +
@@ -68048,6 +68087,10 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
68048
68087
  * "create"]`. Spelled like grid-template-areas ("." is a hole, a name
68049
68088
  * repeated spans), except that a row needs no trailing hole: what is not
68050
68089
  * written is simply not there.
68090
+ * A map is read as a map: a step landing on a hole is not offered. So rows
68091
+ * of different lengths, each walked on its own and changed as a whole (a
68092
+ * column of levels, a row of titles in each), are not a map but a container
68093
+ * in each slide of a column, all bound to one signal — see `signal`.
68051
68094
  * @param {string} [props.current] - area (or id) of the slide being shown; omit
68052
68095
  * to keep it here and drive it by command. An area named from outside is a
68053
68096
  * REQUEST rather than a placement — see `signal` for what that means.
@@ -68088,6 +68131,47 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
68088
68131
  * by links built to that route. Written by replacement unless the state says
68089
68132
  * `history: "push"`, and even then a slide reached by DRAGGING replaces — see
68090
68133
  * docs/navigation.md.
68134
+ * ONE STATE FOR CONTAINERS INSIDE ONE ANOTHER: a container in a slide,
68135
+ * bound to the SAME signal as the container holding it, shares that state.
68136
+ * For screens where one place is said at two levels — a column of
68137
+ * tournament levels with a row of titles in each, the address saying which
68138
+ * title (`?cup=P100:2`): the level is not a second state, it is read from
68139
+ * the title.
68140
+ * ```jsx
68141
+ * const cupSignal = stateSignal(undefined, { id: "cup" });
68142
+ *
68143
+ * <SlideContainer layout="column" signal={cupSignal}>
68144
+ * {levels.map((level) => (
68145
+ * <Slide area={level.name}>
68146
+ * <LevelHeader level={level} />
68147
+ * <SlideContainer signal={cupSignal} defaultCurrent={level.entryKey}>
68148
+ * {level.titles.map((title) => (
68149
+ * <Slide area={title.key}>…</Slide>
68150
+ * ))}
68151
+ * </SlideContainer>
68152
+ * </Slide>
68153
+ * ))}
68154
+ * </SlideContainer>
68155
+ * ```
68156
+ * - The state holds an area of an INNER container. The outer one shows the
68157
+ * slide holding it, the inner ones that do not have it stay where they
68158
+ * are.
68159
+ * - A travel of the outer one writes the area the inner one stands on in the
68160
+ * slide it arrives at: a row left keeps its place for when one comes back,
68161
+ * and a row not seen yet opens on its `defaultCurrent`.
68162
+ * - A value written from outside (an address, a traversal) is walked to by
68163
+ * both, each on its own axis.
68164
+ * - The arrows of the inner one on screen are heard on the outer box and on
68165
+ * what follows it (`data-slide-container-follows`), so a surface follows
68166
+ * the OUTER one and all four arrows walk it.
68167
+ * - `onCurrentChange` still tells each one its own area: the level to the
68168
+ * outer one, the title to the inner one.
68169
+ * What an outer slide holds is read in the DOM: one whose content is not
68170
+ * built (`mount="near"`) holds nothing the state can name.
68171
+ * Neither `current` + `onCurrentChange` on each, ignoring `cause: "state"`
68172
+ * and remembering by hand where each row was left — that is a second copy of
68173
+ * the state (see docs/state_binding.md) — nor one map with rows of different
68174
+ * lengths (see `layout`).
68091
68175
  * @param {string} [props.defaultCurrent] - which slide to open on, for a
68092
68176
  * container that owns its position. Mount-only, like every other `default*`:
68093
68177
  * it says where one starts, not where one is — say `current` for that.
@@ -68097,7 +68181,8 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
68097
68181
  * opens is that state's own default (`stateSignal`'s first argument) rather
68098
68182
  * than this: one place says where the area starts, and it is the place a
68099
68183
  * reset goes back to. This still answers for a signal holding nothing at
68100
- * all.
68184
+ * all. And for a container sharing the state of the one holding it (see
68185
+ * `signal`), it is where it opens while the state names a place elsewhere.
68101
68186
  * @param {(area: string, detail: {cause: "drag"|"keyboard"|"command"|"code"|"state", event: Event}) => void|false|Promise<void|false>} [props.onCurrentChange]
68102
68187
  * - the slide being shown has changed. `cause` says what asked for it, which
68103
68188
  * is what tells a place browsed past from a place aimed at — a slide dragged
@@ -68217,6 +68302,10 @@ const SlideContainer = ({
68217
68302
  // The box itself: it is what takes the keyboard when what is on screen holds
68218
68303
  // nothing that can (see handOverFocus).
68219
68304
  const containerRef = useRef();
68305
+ // Bound to the state of the container holding this one: its slides are
68306
+ // places inside one of that container's slides (see `signal`).
68307
+ const holder = useContext(SlideContainerContext);
68308
+ const sharesState = Boolean(currentSignal) && holder?.signal === currentSignal;
68220
68309
  // The AREA of the slide being shown, not its rank: a rank would be wrong the
68221
68310
  // moment a slide appears before it, and there is nothing to renumber here.
68222
68311
  // This is where the container's own answer lives, and it is the only one the
@@ -68227,7 +68316,10 @@ const SlideContainer = ({
68227
68316
  // walking past every lock in exactly the case the locks are there for.
68228
68317
  // So the picture starts where the state itself would open (a stateSignal's
68229
68318
  // own default), or on `defaultCurrent`, and the walk goes from there.
68230
- const [currentAreaState, setCurrentAreaState] = useState(() => currentSignal?.options?.getDefaultValue?.() ?? defaultCurrent);
68319
+ // A container sharing its holder's state starts on `defaultCurrent`: the
68320
+ // state's default is a place in ONE of the containers sharing it, and the
68321
+ // one holding it is walked there like any other.
68322
+ const [currentAreaState, setCurrentAreaState] = useState(() => sharesState ? defaultCurrent : currentSignal?.options?.getDefaultValue?.() ?? defaultCurrent);
68231
68323
  // The slide this container is travelling to while its controller has not been
68232
68324
  // told yet (commit="rest"): for the length of that travel the container is
68233
68325
  // ahead of whoever holds `current`, and this is where it keeps its own answer
@@ -68310,6 +68402,21 @@ const SlideContainer = ({
68310
68402
  const followerElementsRef = useRef([]);
68311
68403
  const currentFromCaller = currentSignal ? currentSignal.value : currentProp;
68312
68404
  const current = rollingArea ?? provisionalArea ?? currentAreaState;
68405
+ // The area of this box a value held outside is shown on: the slide of that
68406
+ // name, or the one holding a container that shares the state and has it.
68407
+ // Read off the last commit, so a render before the first sees it as it is.
68408
+ const ownAreaOf = value => {
68409
+ const track = trackRef.current;
68410
+ if (value === undefined || !track) {
68411
+ return value;
68412
+ }
68413
+ const slideElements = Array.from(track.children);
68414
+ if (slideElements.some(slideElement => readArea(slideElement) === value)) {
68415
+ return value;
68416
+ }
68417
+ const holding = slideElements.find(slideElement => slideShows(slideElement, value));
68418
+ return holding ? readArea(holding) : value;
68419
+ };
68313
68420
  // The last area this container knows the caller holds, whether it wrote it
68314
68421
  // there or read it from there. What makes a two-way binding stop turning: an
68315
68422
  // area held outside is an INSTRUCTION only when it changed by itself (a load,
@@ -68328,7 +68435,7 @@ const SlideContainer = ({
68328
68435
  // it is painted, the first request included — and the container's own answer
68329
68436
  // otherwise. What a slide waiting for a travel reads to have its content in
68330
68437
  // the very render that shows it (see Slide's `mount`).
68331
- const areaShownNext = requestIsNews() ? currentFromCaller : current;
68438
+ const areaShownNext = requestIsNews() ? ownAreaOf(currentFromCaller) : current;
68332
68439
  // Where the container stands, said where the caller holds it.
68333
68440
  //
68334
68441
  // Whether a slide is a place one came from is the STATE's own business (see
@@ -68345,17 +68452,31 @@ const SlideContainer = ({
68345
68452
  const writeAreaAsked = (area, {
68346
68453
  cause
68347
68454
  } = {}) => {
68348
- areaAskedSeenRef.current = area;
68349
68455
  if (!currentSignal) {
68456
+ areaAskedSeenRef.current = area;
68350
68457
  return;
68351
68458
  }
68459
+ const value = valueOfArea(area);
68460
+ areaAskedSeenRef.current = value;
68352
68461
  if (CAUSES_WRITTEN_BY_REPLACEMENT.includes(cause) && currentSignal.set) {
68353
- currentSignal.set(area, {
68462
+ currentSignal.set(value, {
68354
68463
  history: "replace"
68355
68464
  });
68356
68465
  return;
68357
68466
  }
68358
- currentSignal.value = area;
68467
+ currentSignal.value = value;
68468
+ };
68469
+ // What the state holds for one of this box's areas: the place a container
68470
+ // sharing it stands on inside that slide (see areaHeldOn). A state already
68471
+ // naming a place in that slide is kept as it is — a container in there may
68472
+ // have written it in this very commit, before its DOM says so.
68473
+ const valueOfArea = area => {
68474
+ const held = currentSignal.peek();
68475
+ if (held !== area && ownAreaOf(held) === area) {
68476
+ return held;
68477
+ }
68478
+ const slideElement = readMap().slideElements.find(slideElement => readArea(slideElement) === area);
68479
+ return slideElement ? areaHeldOn(slideElement) : area;
68359
68480
  };
68360
68481
  const vertical = layout === "column";
68361
68482
  // What the map has, and what each way of asking is allowed to use of it.
@@ -68407,7 +68528,7 @@ const SlideContainer = ({
68407
68528
  if (provisionalArea === null) {
68408
68529
  return;
68409
68530
  }
68410
- const heldOutside = currentFromCaller ?? currentAreaState;
68531
+ const heldOutside = ownAreaOf(currentFromCaller) ?? currentAreaState;
68411
68532
  if (heldOutside === provisionalArea) {
68412
68533
  setProvisionalArea(null);
68413
68534
  }
@@ -68498,20 +68619,6 @@ const SlideContainer = ({
68498
68619
  };
68499
68620
  };
68500
68621
 
68501
- // Who is drawing something about this box from outside it: a tab bar saying
68502
- // where one is, a row of dots. They name the box they follow by its id, the
68503
- // way everything else that talks to it across the document does (commandfor).
68504
- const readFollowerElements = () => {
68505
- const containerEl = containerRef.current;
68506
- const {
68507
- id
68508
- } = containerEl;
68509
- if (!id) {
68510
- return [];
68511
- }
68512
- return Array.from(document.querySelectorAll(`[data-slide-container-follows="${CSS.escape(id)}"]`));
68513
- };
68514
-
68515
68622
  // Which slide is on screen, said in the DOM: it is what anything outside the
68516
68623
  // box reads to know where one is (a tab bar marking its current tab), and
68517
68624
  // there is nothing else for it to read — a slide the container holds by
@@ -68650,7 +68757,7 @@ const SlideContainer = ({
68650
68757
  // Read on every render rather than subscribed to: a follower says who it
68651
68758
  // follows in the DOM, and the render that mounted one is the render this
68652
68759
  // runs after.
68653
- followerElementsRef.current = readFollowerElements();
68760
+ followerElementsRef.current = readFollowerElements(containerRef.current);
68654
68761
  const {
68655
68762
  slideElements,
68656
68763
  placeOf
@@ -68736,7 +68843,7 @@ const SlideContainer = ({
68736
68843
  // A transfer waiting for a travel that never happened — a controlled
68737
68844
  // `current` the caller chose not to move: dropped, the focus has no
68738
68845
  // business going anywhere. The one for a travel that DID happen stays,
68739
- // and lands when the slide it is for says it is settled (see settleFocus).
68846
+ // and lands once the slide it is for is final (see landFocusHandOver).
68740
68847
  if (focusHandOverRef.current && focusHandOverRef.current.area !== readArea(currentElement)) {
68741
68848
  focusHandOverRef.current = null;
68742
68849
  }
@@ -68890,6 +68997,11 @@ const SlideContainer = ({
68890
68997
  requestAnimationFrame(rollBack);
68891
68998
  }
68892
68999
  }
69000
+ // The slide a transfer waits for is current (any other one was dropped
69001
+ // above): it lands once the renders this commit sets off are over.
69002
+ if (focusHandOverRef.current) {
69003
+ landFocusHandOver();
69004
+ }
68893
69005
  // The keyboard is on a slide about to go out of reach and nobody has moved
68894
69006
  // it: inert would drop it on the floor (document.body), so it is handed to
68895
69007
  // the slide on screen instead. No event to read — this travel was asked for
@@ -68907,9 +69019,10 @@ const SlideContainer = ({
68907
69019
  // Out of reach LAST, once the keyboard has already moved on: a browser
68908
69020
  // takes the focus off an element the moment it becomes inert, and on its
68909
69021
  // own schedule — doing this first would let that undo the landing above and
68910
- // leave the focus on nothing.
68911
- for (const slideElement of slideElements) {
68912
- slideElement.toggleAttribute("inert", slideElement !== currentElement);
69022
+ // leave the focus on nothing. A transfer still on its way does it itself,
69023
+ // once landed.
69024
+ if (!focusHandOverRef.current) {
69025
+ markOutOfReach(currentElement);
68913
69026
  }
68914
69027
  // What is on screen now, for the travel after this one to depart from.
68915
69028
  // While a travel is playing the stage is the one that knows (it holds the
@@ -68923,7 +69036,7 @@ const SlideContainer = ({
68923
69036
  // this one, in the same commit, and the slides around a picture about to be
68924
69037
  // walked away from would be built for nothing. The neighbours only at
68925
69038
  // rest, see renderNear.
68926
- const settlingArea = requestIsNews() && reachableTowards(currentArea, currentFromCaller) || currentArea;
69039
+ const settlingArea = requestIsNews() && reachableTowards(currentArea, ownAreaOf(currentFromCaller)) || currentArea;
68927
69040
  renderNear(settlingArea, {
68928
69041
  alongside: !stageRef.current
68929
69042
  });
@@ -69173,8 +69286,11 @@ const SlideContainer = ({
69173
69286
  // content arrives a request later. Not a refusal: nothing is remembered and
69174
69287
  // nothing is written, and the render that brings the slide asks again. A
69175
69288
  // name that will never be a slide is the state's to refuse (`oneOf`), not
69176
- // the container's: from here, "later" and "never" look the same.
69177
- if (!readMap().slideElements.some(slideElement => readArea(slideElement) === currentFromCaller)) {
69289
+ // the container's: from here, "later" and "never" look the same. Nor is a
69290
+ // place shown by another container sharing the state — a row of a column
69291
+ // of rows stays where it was while the state names a place in another one.
69292
+ const areaAsked = ownAreaOf(currentFromCaller);
69293
+ if (!readMap().slideElements.some(slideElement => readArea(slideElement) === areaAsked)) {
69178
69294
  return;
69179
69295
  }
69180
69296
  areaAskedSeenRef.current = currentFromCaller;
@@ -69182,10 +69298,10 @@ const SlideContainer = ({
69182
69298
  // names a slide, and the container standing on its first one is a fact only
69183
69299
  // the map knows (see the layout effect that paints it).
69184
69300
  const areaOnScreen = containerRef.current.getAttribute(SLIDE_CURRENT_ATTRIBUTE);
69185
- if (currentFromCaller === areaOnScreen) {
69301
+ if (areaAsked === areaOnScreen) {
69186
69302
  return;
69187
69303
  }
69188
- const reached = reachableTowards(areaOnScreen, currentFromCaller);
69304
+ const reached = reachableTowards(areaOnScreen, areaAsked);
69189
69305
  if (reached && reached !== areaOnScreen) {
69190
69306
  // Told rather than asked: the walk above has already put every lock on
69191
69307
  // the way the question goToArea would have put the first one, and this
@@ -69257,14 +69373,7 @@ const SlideContainer = ({
69257
69373
  // dialog is OPEN, not when it was asked to open). `prepareFocusTransfer` has
69258
69374
  // to read the interaction — which element a mousedown landed on, whether the
69259
69375
  // modality is the keyboard — while the event is still being dispatched. The
69260
- // landing waits for the slide to say its DOM is settled (settleFocus below),
69261
- // which is later than one would think: a travel hands the slide a value
69262
- // (useSlideValue) and that value reaches it through context, so Preact
69263
- // re-renders the slide in a flush of its own — the container's own layout
69264
- // effect runs BEFORE its children have seen the value. A screen keyed by it,
69265
- // a form whose uncontrolled fields must start again from a new prefill,
69266
- // replaces its whole subtree there. Landing before that took the focus to a
69267
- // node about to be thrown away, and the focus went to document.body with it.
69376
+ // landing waits for the slide's DOM to be final (see landFocusHandOver).
69268
69377
  const handOverFocus = (slideElement, focusTransfer, event) => {
69269
69378
  focusTransfer.transferFocus(event, slideElement);
69270
69379
  // Asked of the SLIDE, not of this box: the box contains itself, so asking
@@ -69289,21 +69398,46 @@ const SlideContainer = ({
69289
69398
  }
69290
69399
  };
69291
69400
 
69292
- // A slide reporting that its DOM is settled (its own layout effect, see
69293
- // Slide) — the moment the transfer prepared in goToArea can land. Every
69294
- // slide reports on every commit; only the one a transfer is waiting for,
69295
- // and only while it is the current one, takes it.
69296
- const settleFocus = slideArea => {
69401
+ // The transfer prepared in goToArea lands once the slide it is for is the
69402
+ // current one AND everything the travel set off has rendered — which is
69403
+ // later than this box's own commit. A travel hands the slide a value
69404
+ // (useSlideValue), and what reads it below a component that skipped its own
69405
+ // render is re-rendered by Preact in a commit of its own, after this one. A
69406
+ // screen keyed by it, a form whose uncontrolled fields must start again from
69407
+ // a new prefill, replaces its whole subtree there: a focus landed before it
69408
+ // goes to document.body with the node. Nor can the slide say when it is done
69409
+ // from its own layout effect: rendered in the same pass as this box, its
69410
+ // effects run before this box has made it current.
69411
+ // Preact drains its render queue in one go, the renders it cascades into
69412
+ // included, so a microtask queued from this commit runs after all of them.
69413
+ // Until then the slide being left stays within reach (see markOutOfReach):
69414
+ // inert would take the keyboard off it first, and drop it on document.body.
69415
+ const landFocusHandOver = () => {
69297
69416
  const focusHandOver = focusHandOverRef.current;
69298
- if (!focusHandOver || focusHandOver.area !== slideArea) {
69299
- return;
69300
- }
69301
- const slideElement = readMap().slideElements.find(slideElement => readArea(slideElement) === slideArea);
69302
- if (!slideElement || !slideElement.hasAttribute("data-current")) {
69303
- return;
69417
+ queueMicrotask(() => {
69418
+ // Already landed by an earlier commit of this cascade, given up on, or
69419
+ // replaced by the transfer of a newer travel.
69420
+ if (focusHandOverRef.current !== focusHandOver || !trackRef.current) {
69421
+ return;
69422
+ }
69423
+ focusHandOverRef.current = null;
69424
+ const currentElement = readMap().slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
69425
+ if (!currentElement) {
69426
+ return;
69427
+ }
69428
+ if (readArea(currentElement) === focusHandOver.area) {
69429
+ handOverFocus(currentElement, focusHandOver.focusTransfer, focusHandOver.event);
69430
+ }
69431
+ markOutOfReach(currentElement);
69432
+ });
69433
+ };
69434
+
69435
+ // Every slide but the one on screen out of reach — of the keyboard and of
69436
+ // assistive technology alike.
69437
+ const markOutOfReach = currentElement => {
69438
+ for (const slideElement of readMap().slideElements) {
69439
+ slideElement.toggleAttribute("inert", slideElement !== currentElement);
69304
69440
  }
69305
- focusHandOverRef.current = null;
69306
- handOverFocus(slideElement, focusHandOver.focusTransfer, focusHandOver.event);
69307
69441
  };
69308
69442
 
69309
69443
  /**
@@ -69614,7 +69748,7 @@ const SlideContainer = ({
69614
69748
  // Read again at the start of every travel, not only at every render: a
69615
69749
  // follower appearing does not make this box render, and the travel it must
69616
69750
  // draw is the one about to start.
69617
- followerElementsRef.current = readFollowerElements();
69751
+ followerElementsRef.current = readFollowerElements(containerRef.current);
69618
69752
  cancelTravelProgressAnimation();
69619
69753
  paintTravelProgress(0);
69620
69754
  const painters = travelPainters();
@@ -69927,7 +70061,7 @@ const SlideContainer = ({
69927
70061
  trackAnimationRef.current?.cancel();
69928
70062
  trackAnimationRef.current = null;
69929
70063
  cancelTravelProgressAnimation();
69930
- followerElementsRef.current = readFollowerElements();
70064
+ followerElementsRef.current = readFollowerElements(containerRef.current);
69931
70065
  drag.axis = axis;
69932
70066
  drag.areaBack = areaBack;
69933
70067
  drag.areaOn = areaOn;
@@ -70315,6 +70449,35 @@ const SlideContainer = ({
70315
70449
  }
70316
70450
  };
70317
70451
  });
70452
+ // A container sharing the state of the one holding it is part of that one's
70453
+ // surface: the arrows it walks are heard on the holder's box and on what
70454
+ // follows it, while its slide is the one on screen — a row of a column of
70455
+ // rows walks sideways with the keyboard on the column, or on a chevron of
70456
+ // the column. A press something already travelled on is left alone (it is
70457
+ // marked, see applyKeyboardShortcuts), and so is one pressed inside this box,
70458
+ // which heard it first.
70459
+ useLayoutEffect(() => {
70460
+ const holderElement = sharesState && holder.containerRef.current;
70461
+ if (!holderElement) {
70462
+ return undefined;
70463
+ }
70464
+ const surfaceElements = [holderElement, ...readFollowerElements(holderElement)];
70465
+ const onSurfaceKeyDown = keyDownEvent => {
70466
+ const containerEl = containerRef.current;
70467
+ if (keyDownEvent.defaultPrevented || !containerEl || containerEl.contains(keyDownEvent.target) || !containerEl.closest("[data-slide]")?.hasAttribute("data-current")) {
70468
+ return;
70469
+ }
70470
+ onKeyDownShortcuts(keyDownEvent);
70471
+ };
70472
+ for (const surfaceElement of surfaceElements) {
70473
+ surfaceElement.addEventListener("keydown", onSurfaceKeyDown);
70474
+ }
70475
+ return () => {
70476
+ for (const surfaceElement of surfaceElements) {
70477
+ surfaceElement.removeEventListener("keydown", onSurfaceKeyDown);
70478
+ }
70479
+ };
70480
+ });
70318
70481
  return (
70319
70482
  // Box rather than a plain div: it is how every navi component takes the
70320
70483
  // onnavi_* handlers below — they are navi's own event names, and Box is
@@ -70324,6 +70487,10 @@ const SlideContainer = ({
70324
70487
  ref: containerRef,
70325
70488
  baseClassName: "navi_slide_container",
70326
70489
  "data-slide-container": ""
70490
+ // What the holder reads to show the slide holding the place the state
70491
+ // names, and to write the place this box stands on (see `signal`).
70492
+ ,
70493
+ "data-slide-shares-state": sharesState ? "" : undefined
70327
70494
  // Which axes a touch may travel on, said in the DOM: what the browser
70328
70495
  // does with a finger is decided by CSS (touch-action) before any of this
70329
70496
  // has seen the gesture — and it is also what a box HOLDING this one reads
@@ -70458,13 +70625,15 @@ const SlideContainer = ({
70458
70625
  answeredAreas,
70459
70626
  done,
70460
70627
  valueByArea,
70461
- settleFocus,
70462
70628
  // What a slide reads to know whether its content is rendered yet
70463
70629
  // (see Slide's `mount`).
70464
70630
  mount,
70465
70631
  areaShownNext,
70466
70632
  nearAreas,
70467
70633
  claimOpening: areaShownNext === undefined ? claimOpening : null,
70634
+ // What a container nested in a slide compares its own with, to
70635
+ // know it shares this state (see `signal`).
70636
+ signal: currentSignal,
70468
70637
  // The box itself, for what is written INSIDE it to read the same
70469
70638
  // facts what is written outside reads by id (useSlideContainer):
70470
70639
  // a way out is a way out on either side of the box.
@@ -70539,15 +70708,6 @@ const Slide = ({
70539
70708
  }), [nextIsLocked, preventNavPrevious]);
70540
70709
  // What the travel into this slide carried, if anything (see useSlideValue).
70541
70710
  const slideValue = container?.valueByArea?.[slideArea];
70542
- // This slide's DOM is settled — said after EVERY commit, no dependency
70543
- // array, because it is a fact about the render that just happened, not a
70544
- // reaction to a particular prop. The container is the one holding a focus
70545
- // transfer waiting for it (see settleFocus): a travel's value reaches this
70546
- // slide through context one flush after the container moved, and what it
70547
- // shows (a form keyed by a prefill) is only final here.
70548
- useLayoutEffect(() => {
70549
- container?.settleFocus?.(slideArea);
70550
- });
70551
70711
  return jsx(SlideContext.Provider, {
70552
70712
  value: locks,
70553
70713
  children: jsx(SlideValueContext.Provider, {