@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.
- package/dist/dev/jsenv_navi.js +224 -64
- package/dist/dev/jsenv_navi.js.map +3 -3
- package/dist/jsenv_navi.js +224 -64
- package/dist/jsenv_navi.js.map +3 -3
- package/docs/drag_to_travel.md +4 -0
- package/docs/state_binding.md +28 -0
- package/package.json +1 -1
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
70069
|
+
currentSignal.set(value, {
|
|
69961
70070
|
history: "replace"
|
|
69962
70071
|
});
|
|
69963
70072
|
return;
|
|
69964
70073
|
}
|
|
69965
|
-
currentSignal.value =
|
|
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
|
|
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
|
-
|
|
70519
|
-
|
|
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
|
-
|
|
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 (
|
|
70908
|
+
if (areaAsked === areaOnScreen) {
|
|
70793
70909
|
return;
|
|
70794
70910
|
}
|
|
70795
|
-
const reached = reachableTowards(areaOnScreen,
|
|
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
|
|
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
|
-
//
|
|
70900
|
-
//
|
|
70901
|
-
//
|
|
70902
|
-
// and
|
|
70903
|
-
|
|
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
|
-
|
|
70906
|
-
|
|
70907
|
-
|
|
70908
|
-
|
|
70909
|
-
|
|
70910
|
-
|
|
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, {
|