@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/jsenv_navi.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
68462
|
+
currentSignal.set(value, {
|
|
68354
68463
|
history: "replace"
|
|
68355
68464
|
});
|
|
68356
68465
|
return;
|
|
68357
68466
|
}
|
|
68358
|
-
currentSignal.value =
|
|
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
|
|
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
|
-
|
|
68912
|
-
|
|
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
|
-
|
|
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 (
|
|
69301
|
+
if (areaAsked === areaOnScreen) {
|
|
69186
69302
|
return;
|
|
69187
69303
|
}
|
|
69188
|
-
const reached = reachableTowards(areaOnScreen,
|
|
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
|
|
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
|
-
//
|
|
69293
|
-
//
|
|
69294
|
-
//
|
|
69295
|
-
// and
|
|
69296
|
-
|
|
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
|
-
|
|
69299
|
-
|
|
69300
|
-
|
|
69301
|
-
|
|
69302
|
-
|
|
69303
|
-
|
|
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, {
|