@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.
@@ -57481,6 +57481,10 @@ const css$$ = /* css */`@layer navi {
57481
57481
  --link-color: unset;
57482
57482
  --link-text-decoration: none;
57483
57483
  }
57484
+
57485
+ .navi_link[data-color-inherit] {
57486
+ --link-color-visited: var(--navi-link-color-visited, var(--link-color));
57487
+ }
57484
57488
  }
57485
57489
 
57486
57490
  .navi_link {
@@ -58313,6 +58317,7 @@ const LinkPlain = props => {
58313
58317
  // Visual
58314
58318
  ,
58315
58319
  "data-variant": variant,
58320
+ "data-color-inherit": props.color === "inherit" ? "" : undefined,
58316
58321
  "data-current-effect-bold": currentEffectBoldTechnique,
58317
58322
  "data-current-effect-shadow": currentEffectShadow ? "" : undefined,
58318
58323
  "data-current-indicator-position": currentIndicatorPosition,
@@ -69614,6 +69619,40 @@ const causeOfEvent = event => {
69614
69619
  // into says (see writeAreaAsked).
69615
69620
  const CAUSES_WRITTEN_BY_REPLACEMENT = ["drag", "refusal", "state"];
69616
69621
  const readArea = slideElement => slideElement.getAttribute("data-slide-area") || slideElement.id || "";
69622
+ const slidesOf = containerElement => Array.from(containerElement.querySelectorAll(":scope > [data-slide-track] > [data-slide]"));
69623
+
69624
+ // Who is drawing something about a box from outside it: a tab bar saying where
69625
+ // one is, a row of dots. They name the box they follow by its id, the way
69626
+ // everything else that talks to it across the document does (commandfor).
69627
+ const readFollowerElements = containerElement => {
69628
+ const {
69629
+ id
69630
+ } = containerElement;
69631
+ if (!id) {
69632
+ return [];
69633
+ }
69634
+ return Array.from(document.querySelectorAll(`[data-slide-container-follows="${CSS.escape(id)}"]`));
69635
+ };
69636
+
69637
+ // The containers inside a slide bound to the same state as the slide's own
69638
+ // container (see `signal`) — the ones directly under it, a container sharing
69639
+ // with one of those is that one's business.
69640
+ const sharingContainersIn = slideElement => {
69641
+ const holderElement = slideElement.closest("[data-slide-container]");
69642
+ return Array.from(slideElement.querySelectorAll("[data-slide-shares-state]")).filter(innerElement => innerElement.parentElement.closest("[data-slide-container]") === holderElement);
69643
+ };
69644
+
69645
+ // Whether the state holding `area` is on screen when this slide is: it is the
69646
+ // slide's own area, or a slide of a container sharing the state inside it.
69647
+ const slideShows = (slideElement, area) => readArea(slideElement) === area || sharingContainersIn(slideElement).some(innerElement => slidesOf(innerElement).some(innerSlide => slideShows(innerSlide, area)));
69648
+
69649
+ // What the state holds while this slide is on screen: the area a container
69650
+ // sharing the state inside it stands on, its own area when there is none.
69651
+ const areaHeldOn = slideElement => {
69652
+ const [innerElement] = sharingContainersIn(slideElement);
69653
+ const innerCurrent = innerElement && slidesOf(innerElement).find(innerSlide => innerSlide.hasAttribute("data-current"));
69654
+ return innerCurrent ? areaHeldOn(innerCurrent) : readArea(slideElement);
69655
+ };
69617
69656
 
69618
69657
  /**
69619
69658
  * The slide shown can be driven from outside (a `signal`, or `current` +
@@ -69648,6 +69687,10 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
69648
69687
  * "create"]`. Spelled like grid-template-areas ("." is a hole, a name
69649
69688
  * repeated spans), except that a row needs no trailing hole: what is not
69650
69689
  * written is simply not there.
69690
+ * A map is read as a map: a step landing on a hole is not offered. So rows
69691
+ * of different lengths, each walked on its own and changed as a whole (a
69692
+ * column of levels, a row of titles in each), are not a map but a container
69693
+ * in each slide of a column, all bound to one signal — see `signal`.
69651
69694
  * @param {string} [props.current] - area (or id) of the slide being shown; omit
69652
69695
  * to keep it here and drive it by command. An area named from outside is a
69653
69696
  * REQUEST rather than a placement — see `signal` for what that means.
@@ -69688,6 +69731,47 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
69688
69731
  * by links built to that route. Written by replacement unless the state says
69689
69732
  * `history: "push"`, and even then a slide reached by DRAGGING replaces — see
69690
69733
  * docs/navigation.md.
69734
+ * ONE STATE FOR CONTAINERS INSIDE ONE ANOTHER: a container in a slide,
69735
+ * bound to the SAME signal as the container holding it, shares that state.
69736
+ * For screens where one place is said at two levels — a column of
69737
+ * tournament levels with a row of titles in each, the address saying which
69738
+ * title (`?cup=P100:2`): the level is not a second state, it is read from
69739
+ * the title.
69740
+ * ```jsx
69741
+ * const cupSignal = stateSignal(undefined, { id: "cup" });
69742
+ *
69743
+ * <SlideContainer layout="column" signal={cupSignal}>
69744
+ * {levels.map((level) => (
69745
+ * <Slide area={level.name}>
69746
+ * <LevelHeader level={level} />
69747
+ * <SlideContainer signal={cupSignal} defaultCurrent={level.entryKey}>
69748
+ * {level.titles.map((title) => (
69749
+ * <Slide area={title.key}>…</Slide>
69750
+ * ))}
69751
+ * </SlideContainer>
69752
+ * </Slide>
69753
+ * ))}
69754
+ * </SlideContainer>
69755
+ * ```
69756
+ * - The state holds an area of an INNER container. The outer one shows the
69757
+ * slide holding it, the inner ones that do not have it stay where they
69758
+ * are.
69759
+ * - A travel of the outer one writes the area the inner one stands on in the
69760
+ * slide it arrives at: a row left keeps its place for when one comes back,
69761
+ * and a row not seen yet opens on its `defaultCurrent`.
69762
+ * - A value written from outside (an address, a traversal) is walked to by
69763
+ * both, each on its own axis.
69764
+ * - The arrows of the inner one on screen are heard on the outer box and on
69765
+ * what follows it (`data-slide-container-follows`), so a surface follows
69766
+ * the OUTER one and all four arrows walk it.
69767
+ * - `onCurrentChange` still tells each one its own area: the level to the
69768
+ * outer one, the title to the inner one.
69769
+ * What an outer slide holds is read in the DOM: one whose content is not
69770
+ * built (`mount="near"`) holds nothing the state can name.
69771
+ * Neither `current` + `onCurrentChange` on each, ignoring `cause: "state"`
69772
+ * and remembering by hand where each row was left — that is a second copy of
69773
+ * the state (see docs/state_binding.md) — nor one map with rows of different
69774
+ * lengths (see `layout`).
69691
69775
  * @param {string} [props.defaultCurrent] - which slide to open on, for a
69692
69776
  * container that owns its position. Mount-only, like every other `default*`:
69693
69777
  * it says where one starts, not where one is — say `current` for that.
@@ -69697,7 +69781,8 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
69697
69781
  * opens is that state's own default (`stateSignal`'s first argument) rather
69698
69782
  * than this: one place says where the area starts, and it is the place a
69699
69783
  * reset goes back to. This still answers for a signal holding nothing at
69700
- * all.
69784
+ * all. And for a container sharing the state of the one holding it (see
69785
+ * `signal`), it is where it opens while the state names a place elsewhere.
69701
69786
  * @param {(area: string, detail: {cause: "drag"|"keyboard"|"command"|"code"|"state", event: Event}) => void|false|Promise<void|false>} [props.onCurrentChange]
69702
69787
  * - the slide being shown has changed. `cause` says what asked for it, which
69703
69788
  * is what tells a place browsed past from a place aimed at — a slide dragged
@@ -69817,6 +69902,10 @@ const SlideContainer = ({
69817
69902
  // The box itself: it is what takes the keyboard when what is on screen holds
69818
69903
  // nothing that can (see handOverFocus).
69819
69904
  const containerRef = useRef();
69905
+ // Bound to the state of the container holding this one: its slides are
69906
+ // places inside one of that container's slides (see `signal`).
69907
+ const holder = useContext(SlideContainerContext);
69908
+ const sharesState = Boolean(currentSignal) && holder?.signal === currentSignal;
69820
69909
  // The AREA of the slide being shown, not its rank: a rank would be wrong the
69821
69910
  // moment a slide appears before it, and there is nothing to renumber here.
69822
69911
  // This is where the container's own answer lives, and it is the only one the
@@ -69827,7 +69916,10 @@ const SlideContainer = ({
69827
69916
  // walking past every lock in exactly the case the locks are there for.
69828
69917
  // So the picture starts where the state itself would open (a stateSignal's
69829
69918
  // own default), or on `defaultCurrent`, and the walk goes from there.
69830
- const [currentAreaState, setCurrentAreaState] = useState(() => currentSignal?.options?.getDefaultValue?.() ?? defaultCurrent);
69919
+ // A container sharing its holder's state starts on `defaultCurrent`: the
69920
+ // state's default is a place in ONE of the containers sharing it, and the
69921
+ // one holding it is walked there like any other.
69922
+ const [currentAreaState, setCurrentAreaState] = useState(() => sharesState ? defaultCurrent : currentSignal?.options?.getDefaultValue?.() ?? defaultCurrent);
69831
69923
  // The slide this container is travelling to while its controller has not been
69832
69924
  // told yet (commit="rest"): for the length of that travel the container is
69833
69925
  // ahead of whoever holds `current`, and this is where it keeps its own answer
@@ -69917,6 +70009,21 @@ const SlideContainer = ({
69917
70009
  }
69918
70010
  const currentFromCaller = currentSignal ? currentSignal.value : currentProp;
69919
70011
  const current = rollingArea ?? provisionalArea ?? currentAreaState;
70012
+ // The area of this box a value held outside is shown on: the slide of that
70013
+ // name, or the one holding a container that shares the state and has it.
70014
+ // Read off the last commit, so a render before the first sees it as it is.
70015
+ const ownAreaOf = value => {
70016
+ const track = trackRef.current;
70017
+ if (value === undefined || !track) {
70018
+ return value;
70019
+ }
70020
+ const slideElements = Array.from(track.children);
70021
+ if (slideElements.some(slideElement => readArea(slideElement) === value)) {
70022
+ return value;
70023
+ }
70024
+ const holding = slideElements.find(slideElement => slideShows(slideElement, value));
70025
+ return holding ? readArea(holding) : value;
70026
+ };
69920
70027
  // The last area this container knows the caller holds, whether it wrote it
69921
70028
  // there or read it from there. What makes a two-way binding stop turning: an
69922
70029
  // area held outside is an INSTRUCTION only when it changed by itself (a load,
@@ -69935,7 +70042,7 @@ const SlideContainer = ({
69935
70042
  // it is painted, the first request included — and the container's own answer
69936
70043
  // otherwise. What a slide waiting for a travel reads to have its content in
69937
70044
  // the very render that shows it (see Slide's `mount`).
69938
- const areaShownNext = requestIsNews() ? currentFromCaller : current;
70045
+ const areaShownNext = requestIsNews() ? ownAreaOf(currentFromCaller) : current;
69939
70046
  // Where the container stands, said where the caller holds it.
69940
70047
  //
69941
70048
  // Whether a slide is a place one came from is the STATE's own business (see
@@ -69952,17 +70059,31 @@ const SlideContainer = ({
69952
70059
  const writeAreaAsked = (area, {
69953
70060
  cause
69954
70061
  } = {}) => {
69955
- areaAskedSeenRef.current = area;
69956
70062
  if (!currentSignal) {
70063
+ areaAskedSeenRef.current = area;
69957
70064
  return;
69958
70065
  }
70066
+ const value = valueOfArea(area);
70067
+ areaAskedSeenRef.current = value;
69959
70068
  if (CAUSES_WRITTEN_BY_REPLACEMENT.includes(cause) && currentSignal.set) {
69960
- currentSignal.set(area, {
70069
+ currentSignal.set(value, {
69961
70070
  history: "replace"
69962
70071
  });
69963
70072
  return;
69964
70073
  }
69965
- currentSignal.value = area;
70074
+ currentSignal.value = value;
70075
+ };
70076
+ // What the state holds for one of this box's areas: the place a container
70077
+ // sharing it stands on inside that slide (see areaHeldOn). A state already
70078
+ // naming a place in that slide is kept as it is — a container in there may
70079
+ // have written it in this very commit, before its DOM says so.
70080
+ const valueOfArea = area => {
70081
+ const held = currentSignal.peek();
70082
+ if (held !== area && ownAreaOf(held) === area) {
70083
+ return held;
70084
+ }
70085
+ const slideElement = readMap().slideElements.find(slideElement => readArea(slideElement) === area);
70086
+ return slideElement ? areaHeldOn(slideElement) : area;
69966
70087
  };
69967
70088
  const vertical = layout === "column";
69968
70089
  // What the map has, and what each way of asking is allowed to use of it.
@@ -70014,7 +70135,7 @@ const SlideContainer = ({
70014
70135
  if (provisionalArea === null) {
70015
70136
  return;
70016
70137
  }
70017
- const heldOutside = currentFromCaller ?? currentAreaState;
70138
+ const heldOutside = ownAreaOf(currentFromCaller) ?? currentAreaState;
70018
70139
  if (heldOutside === provisionalArea) {
70019
70140
  setProvisionalArea(null);
70020
70141
  }
@@ -70105,20 +70226,6 @@ const SlideContainer = ({
70105
70226
  };
70106
70227
  };
70107
70228
 
70108
- // Who is drawing something about this box from outside it: a tab bar saying
70109
- // where one is, a row of dots. They name the box they follow by its id, the
70110
- // way everything else that talks to it across the document does (commandfor).
70111
- const readFollowerElements = () => {
70112
- const containerEl = containerRef.current;
70113
- const {
70114
- id
70115
- } = containerEl;
70116
- if (!id) {
70117
- return [];
70118
- }
70119
- return Array.from(document.querySelectorAll(`[data-slide-container-follows="${CSS.escape(id)}"]`));
70120
- };
70121
-
70122
70229
  // Which slide is on screen, said in the DOM: it is what anything outside the
70123
70230
  // box reads to know where one is (a tab bar marking its current tab), and
70124
70231
  // there is nothing else for it to read — a slide the container holds by
@@ -70257,7 +70364,7 @@ const SlideContainer = ({
70257
70364
  // Read on every render rather than subscribed to: a follower says who it
70258
70365
  // follows in the DOM, and the render that mounted one is the render this
70259
70366
  // runs after.
70260
- followerElementsRef.current = readFollowerElements();
70367
+ followerElementsRef.current = readFollowerElements(containerRef.current);
70261
70368
  const {
70262
70369
  slideElements,
70263
70370
  placeOf
@@ -70343,7 +70450,7 @@ const SlideContainer = ({
70343
70450
  // A transfer waiting for a travel that never happened — a controlled
70344
70451
  // `current` the caller chose not to move: dropped, the focus has no
70345
70452
  // business going anywhere. The one for a travel that DID happen stays,
70346
- // and lands when the slide it is for says it is settled (see settleFocus).
70453
+ // and lands once the slide it is for is final (see landFocusHandOver).
70347
70454
  if (focusHandOverRef.current && focusHandOverRef.current.area !== readArea(currentElement)) {
70348
70455
  focusHandOverRef.current = null;
70349
70456
  }
@@ -70497,6 +70604,11 @@ const SlideContainer = ({
70497
70604
  requestAnimationFrame(rollBack);
70498
70605
  }
70499
70606
  }
70607
+ // The slide a transfer waits for is current (any other one was dropped
70608
+ // above): it lands once the renders this commit sets off are over.
70609
+ if (focusHandOverRef.current) {
70610
+ landFocusHandOver();
70611
+ }
70500
70612
  // The keyboard is on a slide about to go out of reach and nobody has moved
70501
70613
  // it: inert would drop it on the floor (document.body), so it is handed to
70502
70614
  // the slide on screen instead. No event to read — this travel was asked for
@@ -70514,9 +70626,10 @@ const SlideContainer = ({
70514
70626
  // Out of reach LAST, once the keyboard has already moved on: a browser
70515
70627
  // takes the focus off an element the moment it becomes inert, and on its
70516
70628
  // own schedule — doing this first would let that undo the landing above and
70517
- // leave the focus on nothing.
70518
- for (const slideElement of slideElements) {
70519
- slideElement.toggleAttribute("inert", slideElement !== currentElement);
70629
+ // leave the focus on nothing. A transfer still on its way does it itself,
70630
+ // once landed.
70631
+ if (!focusHandOverRef.current) {
70632
+ markOutOfReach(currentElement);
70520
70633
  }
70521
70634
  // What is on screen now, for the travel after this one to depart from.
70522
70635
  // While a travel is playing the stage is the one that knows (it holds the
@@ -70530,7 +70643,7 @@ const SlideContainer = ({
70530
70643
  // this one, in the same commit, and the slides around a picture about to be
70531
70644
  // walked away from would be built for nothing. The neighbours only at
70532
70645
  // rest, see renderNear.
70533
- const settlingArea = requestIsNews() && reachableTowards(currentArea, currentFromCaller) || currentArea;
70646
+ const settlingArea = requestIsNews() && reachableTowards(currentArea, ownAreaOf(currentFromCaller)) || currentArea;
70534
70647
  renderNear(settlingArea, {
70535
70648
  alongside: !stageRef.current
70536
70649
  });
@@ -70780,8 +70893,11 @@ const SlideContainer = ({
70780
70893
  // content arrives a request later. Not a refusal: nothing is remembered and
70781
70894
  // nothing is written, and the render that brings the slide asks again. A
70782
70895
  // name that will never be a slide is the state's to refuse (`oneOf`), not
70783
- // the container's: from here, "later" and "never" look the same.
70784
- if (!readMap().slideElements.some(slideElement => readArea(slideElement) === currentFromCaller)) {
70896
+ // the container's: from here, "later" and "never" look the same. Nor is a
70897
+ // place shown by another container sharing the state — a row of a column
70898
+ // of rows stays where it was while the state names a place in another one.
70899
+ const areaAsked = ownAreaOf(currentFromCaller);
70900
+ if (!readMap().slideElements.some(slideElement => readArea(slideElement) === areaAsked)) {
70785
70901
  return;
70786
70902
  }
70787
70903
  areaAskedSeenRef.current = currentFromCaller;
@@ -70789,10 +70905,10 @@ const SlideContainer = ({
70789
70905
  // names a slide, and the container standing on its first one is a fact only
70790
70906
  // the map knows (see the layout effect that paints it).
70791
70907
  const areaOnScreen = containerRef.current.getAttribute(SLIDE_CURRENT_ATTRIBUTE);
70792
- if (currentFromCaller === areaOnScreen) {
70908
+ if (areaAsked === areaOnScreen) {
70793
70909
  return;
70794
70910
  }
70795
- const reached = reachableTowards(areaOnScreen, currentFromCaller);
70911
+ const reached = reachableTowards(areaOnScreen, areaAsked);
70796
70912
  if (reached && reached !== areaOnScreen) {
70797
70913
  // Told rather than asked: the walk above has already put every lock on
70798
70914
  // the way the question goToArea would have put the first one, and this
@@ -70864,14 +70980,7 @@ const SlideContainer = ({
70864
70980
  // dialog is OPEN, not when it was asked to open). `prepareFocusTransfer` has
70865
70981
  // to read the interaction — which element a mousedown landed on, whether the
70866
70982
  // modality is the keyboard — while the event is still being dispatched. The
70867
- // landing waits for the slide to say its DOM is settled (settleFocus below),
70868
- // which is later than one would think: a travel hands the slide a value
70869
- // (useSlideValue) and that value reaches it through context, so Preact
70870
- // re-renders the slide in a flush of its own — the container's own layout
70871
- // effect runs BEFORE its children have seen the value. A screen keyed by it,
70872
- // a form whose uncontrolled fields must start again from a new prefill,
70873
- // replaces its whole subtree there. Landing before that took the focus to a
70874
- // node about to be thrown away, and the focus went to document.body with it.
70983
+ // landing waits for the slide's DOM to be final (see landFocusHandOver).
70875
70984
  const handOverFocus = (slideElement, focusTransfer, event) => {
70876
70985
  focusTransfer.transferFocus(event, slideElement);
70877
70986
  // Asked of the SLIDE, not of this box: the box contains itself, so asking
@@ -70896,21 +71005,46 @@ const SlideContainer = ({
70896
71005
  }
70897
71006
  };
70898
71007
 
70899
- // A slide reporting that its DOM is settled (its own layout effect, see
70900
- // Slide) — the moment the transfer prepared in goToArea can land. Every
70901
- // slide reports on every commit; only the one a transfer is waiting for,
70902
- // and only while it is the current one, takes it.
70903
- const settleFocus = slideArea => {
71008
+ // The transfer prepared in goToArea lands once the slide it is for is the
71009
+ // current one AND everything the travel set off has rendered — which is
71010
+ // later than this box's own commit. A travel hands the slide a value
71011
+ // (useSlideValue), and what reads it below a component that skipped its own
71012
+ // render is re-rendered by Preact in a commit of its own, after this one. A
71013
+ // screen keyed by it, a form whose uncontrolled fields must start again from
71014
+ // a new prefill, replaces its whole subtree there: a focus landed before it
71015
+ // goes to document.body with the node. Nor can the slide say when it is done
71016
+ // from its own layout effect: rendered in the same pass as this box, its
71017
+ // effects run before this box has made it current.
71018
+ // Preact drains its render queue in one go, the renders it cascades into
71019
+ // included, so a microtask queued from this commit runs after all of them.
71020
+ // Until then the slide being left stays within reach (see markOutOfReach):
71021
+ // inert would take the keyboard off it first, and drop it on document.body.
71022
+ const landFocusHandOver = () => {
70904
71023
  const focusHandOver = focusHandOverRef.current;
70905
- if (!focusHandOver || focusHandOver.area !== slideArea) {
70906
- return;
70907
- }
70908
- const slideElement = readMap().slideElements.find(slideElement => readArea(slideElement) === slideArea);
70909
- if (!slideElement || !slideElement.hasAttribute("data-current")) {
70910
- return;
71024
+ queueMicrotask(() => {
71025
+ // Already landed by an earlier commit of this cascade, given up on, or
71026
+ // replaced by the transfer of a newer travel.
71027
+ if (focusHandOverRef.current !== focusHandOver || !trackRef.current) {
71028
+ return;
71029
+ }
71030
+ focusHandOverRef.current = null;
71031
+ const currentElement = readMap().slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
71032
+ if (!currentElement) {
71033
+ return;
71034
+ }
71035
+ if (readArea(currentElement) === focusHandOver.area) {
71036
+ handOverFocus(currentElement, focusHandOver.focusTransfer, focusHandOver.event);
71037
+ }
71038
+ markOutOfReach(currentElement);
71039
+ });
71040
+ };
71041
+
71042
+ // Every slide but the one on screen out of reach — of the keyboard and of
71043
+ // assistive technology alike.
71044
+ const markOutOfReach = currentElement => {
71045
+ for (const slideElement of readMap().slideElements) {
71046
+ slideElement.toggleAttribute("inert", slideElement !== currentElement);
70911
71047
  }
70912
- focusHandOverRef.current = null;
70913
- handOverFocus(slideElement, focusHandOver.focusTransfer, focusHandOver.event);
70914
71048
  };
70915
71049
 
70916
71050
  /**
@@ -71221,7 +71355,7 @@ const SlideContainer = ({
71221
71355
  // Read again at the start of every travel, not only at every render: a
71222
71356
  // follower appearing does not make this box render, and the travel it must
71223
71357
  // draw is the one about to start.
71224
- followerElementsRef.current = readFollowerElements();
71358
+ followerElementsRef.current = readFollowerElements(containerRef.current);
71225
71359
  cancelTravelProgressAnimation();
71226
71360
  paintTravelProgress(0);
71227
71361
  const painters = travelPainters();
@@ -71534,7 +71668,7 @@ const SlideContainer = ({
71534
71668
  trackAnimationRef.current?.cancel();
71535
71669
  trackAnimationRef.current = null;
71536
71670
  cancelTravelProgressAnimation();
71537
- followerElementsRef.current = readFollowerElements();
71671
+ followerElementsRef.current = readFollowerElements(containerRef.current);
71538
71672
  drag.axis = axis;
71539
71673
  drag.areaBack = areaBack;
71540
71674
  drag.areaOn = areaOn;
@@ -71922,6 +72056,35 @@ const SlideContainer = ({
71922
72056
  }
71923
72057
  };
71924
72058
  });
72059
+ // A container sharing the state of the one holding it is part of that one's
72060
+ // surface: the arrows it walks are heard on the holder's box and on what
72061
+ // follows it, while its slide is the one on screen — a row of a column of
72062
+ // rows walks sideways with the keyboard on the column, or on a chevron of
72063
+ // the column. A press something already travelled on is left alone (it is
72064
+ // marked, see applyKeyboardShortcuts), and so is one pressed inside this box,
72065
+ // which heard it first.
72066
+ useLayoutEffect(() => {
72067
+ const holderElement = sharesState && holder.containerRef.current;
72068
+ if (!holderElement) {
72069
+ return undefined;
72070
+ }
72071
+ const surfaceElements = [holderElement, ...readFollowerElements(holderElement)];
72072
+ const onSurfaceKeyDown = keyDownEvent => {
72073
+ const containerEl = containerRef.current;
72074
+ if (keyDownEvent.defaultPrevented || !containerEl || containerEl.contains(keyDownEvent.target) || !containerEl.closest("[data-slide]")?.hasAttribute("data-current")) {
72075
+ return;
72076
+ }
72077
+ onKeyDownShortcuts(keyDownEvent);
72078
+ };
72079
+ for (const surfaceElement of surfaceElements) {
72080
+ surfaceElement.addEventListener("keydown", onSurfaceKeyDown);
72081
+ }
72082
+ return () => {
72083
+ for (const surfaceElement of surfaceElements) {
72084
+ surfaceElement.removeEventListener("keydown", onSurfaceKeyDown);
72085
+ }
72086
+ };
72087
+ });
71925
72088
  return (
71926
72089
  // Box rather than a plain div: it is how every navi component takes the
71927
72090
  // onnavi_* handlers below — they are navi's own event names, and Box is
@@ -71931,6 +72094,10 @@ const SlideContainer = ({
71931
72094
  ref: containerRef,
71932
72095
  baseClassName: "navi_slide_container",
71933
72096
  "data-slide-container": ""
72097
+ // What the holder reads to show the slide holding the place the state
72098
+ // names, and to write the place this box stands on (see `signal`).
72099
+ ,
72100
+ "data-slide-shares-state": sharesState ? "" : undefined
71934
72101
  // Which axes a touch may travel on, said in the DOM: what the browser
71935
72102
  // does with a finger is decided by CSS (touch-action) before any of this
71936
72103
  // has seen the gesture — and it is also what a box HOLDING this one reads
@@ -72065,13 +72232,15 @@ const SlideContainer = ({
72065
72232
  answeredAreas,
72066
72233
  done,
72067
72234
  valueByArea,
72068
- settleFocus,
72069
72235
  // What a slide reads to know whether its content is rendered yet
72070
72236
  // (see Slide's `mount`).
72071
72237
  mount,
72072
72238
  areaShownNext,
72073
72239
  nearAreas,
72074
72240
  claimOpening: areaShownNext === undefined ? claimOpening : null,
72241
+ // What a container nested in a slide compares its own with, to
72242
+ // know it shares this state (see `signal`).
72243
+ signal: currentSignal,
72075
72244
  // The box itself, for what is written INSIDE it to read the same
72076
72245
  // facts what is written outside reads by id (useSlideContainer):
72077
72246
  // a way out is a way out on either side of the box.
@@ -72166,15 +72335,6 @@ const Slide = ({
72166
72335
  }), [nextIsLocked, preventNavPrevious]);
72167
72336
  // What the travel into this slide carried, if anything (see useSlideValue).
72168
72337
  const slideValue = container?.valueByArea?.[slideArea];
72169
- // This slide's DOM is settled — said after EVERY commit, no dependency
72170
- // array, because it is a fact about the render that just happened, not a
72171
- // reaction to a particular prop. The container is the one holding a focus
72172
- // transfer waiting for it (see settleFocus): a travel's value reaches this
72173
- // slide through context one flush after the container moved, and what it
72174
- // shows (a form keyed by a prefill) is only final here.
72175
- useLayoutEffect(() => {
72176
- container?.settleFocus?.(slideArea);
72177
- });
72178
72338
  return jsx(SlideContext.Provider, {
72179
72339
  value: locks,
72180
72340
  children: jsx(SlideValueContext.Provider, {