@jsenv/navi 0.29.119 → 0.29.120
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/jsenv_navi.js +223 -13
- package/dist/jsenv_navi.js.map +24 -5
- package/docs/create_and_edit.md +2 -2
- package/docs/drag_to_travel.md +13 -0
- package/docs/navigation.md +62 -4
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -38845,7 +38845,6 @@ installImportMetaCssBuild(import.meta);const css$W = /* css */`
|
|
|
38845
38845
|
--link-text-decoration: underline;
|
|
38846
38846
|
--link-text-decoration-hover: var(--link-text-decoration);
|
|
38847
38847
|
--link-cursor: pointer;
|
|
38848
|
-
--link-loading-outline-size: 1px;
|
|
38849
38848
|
--link-outline-width: 2px;
|
|
38850
38849
|
|
|
38851
38850
|
--link-current-indicator-size: 2px;
|
|
@@ -38904,12 +38903,7 @@ installImportMetaCssBuild(import.meta);const css$W = /* css */`
|
|
|
38904
38903
|
aspect-ratio: inherit;
|
|
38905
38904
|
padding-top: var(--x-link-padding-top);
|
|
38906
38905
|
padding-right: var(--x-link-padding-right);
|
|
38907
|
-
|
|
38908
|
-
the bottom on top of whatever padding was asked for, so a link that
|
|
38909
|
-
starts loading never reflows. */
|
|
38910
|
-
padding-bottom: calc(
|
|
38911
|
-
var(--x-link-padding-bottom) + var(--link-loading-outline-size)
|
|
38912
|
-
);
|
|
38906
|
+
padding-bottom: var(--x-link-padding-bottom);
|
|
38913
38907
|
padding-left: var(--x-link-padding-left);
|
|
38914
38908
|
color: var(--x-link-color);
|
|
38915
38909
|
text-decoration: var(--x-link-text-decoration);
|
|
@@ -49856,7 +49850,7 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
|
|
|
49856
49850
|
* it says where one starts, not where one is — say `current` for that.
|
|
49857
49851
|
* Without it the first slide is the one shown, the way a stack of pages opens
|
|
49858
49852
|
* on its first page.
|
|
49859
|
-
* @param {(area: string, detail: {cause: "drag"|"keyboard"|"command"|"code", event: Event}) => void|false|Promise<void|false>} [props.onCurrentChange]
|
|
49853
|
+
* @param {(area: string, detail: {cause: "drag"|"keyboard"|"command"|"code"|"url", event: Event}) => void|false|Promise<void|false>} [props.onCurrentChange]
|
|
49860
49854
|
* - the slide being shown has changed. `cause` says what asked for it, which
|
|
49861
49855
|
* is what tells a place browsed past from a place aimed at: a caller writing
|
|
49862
49856
|
* this into the URL pushes a history entry for a tab that was pressed and
|
|
@@ -49866,6 +49860,8 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
|
|
|
49866
49860
|
* from — a guard that says no, a session that is gone. A promise refuses it
|
|
49867
49861
|
* late, once whatever it had to ask has answered; the travel plays meanwhile
|
|
49868
49862
|
* and is undone if the answer is no.
|
|
49863
|
+
* `cause: "url"` is the address asking (see `urlParam`) — a load on a link, a
|
|
49864
|
+
* traversal — rather than anything done inside the box.
|
|
49869
49865
|
* @param {"now"|"rest"} [props.commit="now"] - when the change is told.
|
|
49870
49866
|
* "rest" waits for the travel to be over, and lets the container hold the
|
|
49871
49867
|
* slide it is going to meanwhile: the picture moves with the finger and the
|
|
@@ -49883,6 +49879,33 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
|
|
|
49883
49879
|
* one that just travelled there. Called once the travel is over, and in the
|
|
49884
49880
|
* same render as the return to rest — anything later shows the old content
|
|
49885
49881
|
* for a frame.
|
|
49882
|
+
* @param {string|{name: string, history?: "replace"|"push"}} [props.urlParam]
|
|
49883
|
+
* - the search param this container owns, and where it stands is written
|
|
49884
|
+
* into: `urlParam="step"` puts `?step=<area>` in the address on every travel,
|
|
49885
|
+
* and opens on the area it names on a load, a bookmark, a traversal, a link
|
|
49886
|
+
* from anywhere else in the application.
|
|
49887
|
+
* It is the answer to "the step should be readable and should survive a
|
|
49888
|
+
* reload" WITHOUT one route per slide: no route to declare, no route guard to
|
|
49889
|
+
* re-express the walk's own rules in, and no page transition — what travels
|
|
49890
|
+
* is the box, and the address is a label on where the box stands. Say it on a
|
|
49891
|
+
* container whose slides are places one may be sent to; a walk nobody links
|
|
49892
|
+
* into keeps its position to itself.
|
|
49893
|
+
* Written by REPLACEMENT: four steps that each stacked an entry would turn
|
|
49894
|
+
* one back-press into four, and the back arrow of a page means "leave this
|
|
49895
|
+
* page", not "one question back". `history: "push"` says the opposite for the
|
|
49896
|
+
* containers that mean it — slides that ARE places one came from, a gallery
|
|
49897
|
+
* one browses — and even there a slide reached by dragging replaces, so
|
|
49898
|
+
* swiping back and forth does not bury the way out.
|
|
49899
|
+
* The address is READ through the walk, not jumped to: it comes from outside
|
|
49900
|
+
* the box, so every slide between here and there is asked to let go the way a
|
|
49901
|
+
* key going that way would ask it, and the first one that holds
|
|
49902
|
+
* (`preventNav`, or a `required` step still unanswered) is where one stops —
|
|
49903
|
+
* `?step=done` cannot open a confirmation screen for something nobody sent.
|
|
49904
|
+
* Whatever comes of it, the address is then rewritten with the area actually
|
|
49905
|
+
* shown: it says where one IS, never where one asked to be. A step the app
|
|
49906
|
+
* knows is already answered on a reload is told so by its own `required`
|
|
49907
|
+
* (`required={!alreadyFilled}`) — the container remembers nothing across a
|
|
49908
|
+
* load, and cannot.
|
|
49886
49909
|
* Each way of travelling can be shut off, or narrowed to one axis: `true`
|
|
49887
49910
|
* (every axis the map has), `false`, `"x"`, `"y"`, `"xy"`.
|
|
49888
49911
|
*
|
|
@@ -49928,6 +49951,7 @@ const SlideContainer = ({
|
|
|
49928
49951
|
commit = "now",
|
|
49929
49952
|
loop,
|
|
49930
49953
|
onLoop,
|
|
49954
|
+
urlParam,
|
|
49931
49955
|
travelByKeyboard = true,
|
|
49932
49956
|
travelByDrag = true,
|
|
49933
49957
|
travelByScroll = "x",
|
|
@@ -50023,6 +50047,51 @@ const SlideContainer = ({
|
|
|
50023
50047
|
const followerElementsRef = useRef([]);
|
|
50024
50048
|
const currentFromCaller = currentSignal ? currentSignal.value : currentProp;
|
|
50025
50049
|
const current = rollingArea ?? provisionalArea ?? currentFromCaller ?? currentAreaState;
|
|
50050
|
+
// The search param this container owns, when it owns one: the name it is
|
|
50051
|
+
// written under, and whether going somewhere is somewhere one CAME from.
|
|
50052
|
+
const urlParamName = typeof urlParam === "string" ? urlParam : urlParam?.name;
|
|
50053
|
+
const urlParamHistory = urlParam?.history || "replace";
|
|
50054
|
+
// What the address says right now. Read from the signal rather than from
|
|
50055
|
+
// window.location so that a traversal — the browser's own back and forward —
|
|
50056
|
+
// is a render like any other: the address moved, and the slides follow it.
|
|
50057
|
+
// Read only when there is a param to read, so a container that owns none is
|
|
50058
|
+
// not re-rendered by every navigation in the application.
|
|
50059
|
+
const areaInUrl = urlParamName ? new URL(documentUrlSignal.value).searchParams.get(urlParamName) : null;
|
|
50060
|
+
// The last thing this container knows the address said, whether it wrote it
|
|
50061
|
+
// or read it. What makes a two-way binding stop turning: the address is only
|
|
50062
|
+
// an INSTRUCTION when it changed by itself (a load, a traversal, a link), and
|
|
50063
|
+
// what this container put there is not news. Without it, a container ahead of
|
|
50064
|
+
// its own URL (commit="rest", where the picture arrives before the address is
|
|
50065
|
+
// written) would read its own lateness as an order to go back.
|
|
50066
|
+
const areaInUrlSeenRef = useRef(undefined);
|
|
50067
|
+
// Where the container stands, said in the address. `replace` amends the entry
|
|
50068
|
+
// one is on: a walk of four steps must not turn one back-press into four, and
|
|
50069
|
+
// the back arrow of a page keeps meaning "leave this page". `push` is for
|
|
50070
|
+
// slides that ARE places one came from — and even there a slide reached by
|
|
50071
|
+
// DRAGGING replaces, because browsing back and forth with a thumb is not a
|
|
50072
|
+
// trail one wants to walk home along.
|
|
50073
|
+
const writeUrlParam = (area, cause, {
|
|
50074
|
+
replace = false
|
|
50075
|
+
} = {}) => {
|
|
50076
|
+
if (!urlParamName) {
|
|
50077
|
+
return;
|
|
50078
|
+
}
|
|
50079
|
+
areaInUrlSeenRef.current = area;
|
|
50080
|
+
const urlObject = new URL(window.location.href);
|
|
50081
|
+
if (urlObject.searchParams.get(urlParamName) === area) {
|
|
50082
|
+
return;
|
|
50083
|
+
}
|
|
50084
|
+
urlObject.searchParams.set(urlParamName, area);
|
|
50085
|
+
navTo(urlObject.href, {
|
|
50086
|
+
replace: replace || urlParamHistory !== "push" || cause === "drag" ||
|
|
50087
|
+
// The address already says it: reading it back is not a place one went.
|
|
50088
|
+
cause === "url",
|
|
50089
|
+
// What travels is the box, not the page: the address is a label on where
|
|
50090
|
+
// the box stands, and a route transition would move the whole document
|
|
50091
|
+
// for a slide that has already travelled by itself.
|
|
50092
|
+
routeTransition: "none"
|
|
50093
|
+
});
|
|
50094
|
+
};
|
|
50026
50095
|
const vertical = layout === "column";
|
|
50027
50096
|
// What the map has, and what each way of asking is allowed to use of it.
|
|
50028
50097
|
const mapAxes = travelAxesOf(layout);
|
|
@@ -50625,7 +50694,7 @@ const SlideContainer = ({
|
|
|
50625
50694
|
}
|
|
50626
50695
|
const leftArea = readArea(currentElement);
|
|
50627
50696
|
setCurrentAreaState(area);
|
|
50628
|
-
if (!onCurrentChange && !currentSignal) {
|
|
50697
|
+
if (!onCurrentChange && !currentSignal && !urlParamName) {
|
|
50629
50698
|
return true;
|
|
50630
50699
|
}
|
|
50631
50700
|
// What asked for this, read off the interaction rather than carried down
|
|
@@ -50660,6 +50729,12 @@ const SlideContainer = ({
|
|
|
50660
50729
|
// `onCurrentChange` is called, in that order, so a caller reading the signal
|
|
50661
50730
|
// from inside its own handler reads where it now is.
|
|
50662
50731
|
const tellCurrentChange = (area, detail, leftArea) => {
|
|
50732
|
+
// The address first, because it is the one thing that must never disagree
|
|
50733
|
+
// with the picture — and it is told here rather than by the caller so that
|
|
50734
|
+
// it is told about the travels that HAPPENED and about no others: the ones
|
|
50735
|
+
// a lock refused never reach this point, and one refused late is written
|
|
50736
|
+
// back below (see goBackToRefusedArea).
|
|
50737
|
+
writeUrlParam(area, detail.cause);
|
|
50663
50738
|
if (currentSignal) {
|
|
50664
50739
|
currentSignal.value = area;
|
|
50665
50740
|
}
|
|
@@ -50693,8 +50768,62 @@ const SlideContainer = ({
|
|
|
50693
50768
|
if (currentSignal) {
|
|
50694
50769
|
currentSignal.value = leftArea;
|
|
50695
50770
|
}
|
|
50771
|
+
// Written over rather than stacked on, whatever this container does with
|
|
50772
|
+
// the history otherwise: the entry the refused travel wrote is the one
|
|
50773
|
+
// being corrected, and a refusal is not a place one was.
|
|
50774
|
+
writeUrlParam(leftArea, "code", {
|
|
50775
|
+
replace: true
|
|
50776
|
+
});
|
|
50696
50777
|
};
|
|
50697
50778
|
|
|
50779
|
+
// The address asks for a slide. Read on every render it changes on rather
|
|
50780
|
+
// than at mount alone, because the browser's own arrows are exactly that —
|
|
50781
|
+
// the address moving by itself — and a link from elsewhere in the application
|
|
50782
|
+
// is too. What follows is a travel like any other: the slides move, the
|
|
50783
|
+
// caller is told, and the address ends up saying where the box actually
|
|
50784
|
+
// stands.
|
|
50785
|
+
useLayoutEffect(() => {
|
|
50786
|
+
if (!urlParamName) {
|
|
50787
|
+
return;
|
|
50788
|
+
}
|
|
50789
|
+
if (areaInUrl === areaInUrlSeenRef.current) {
|
|
50790
|
+
// Not news: either nothing moved, or this container is reading back what
|
|
50791
|
+
// it wrote itself.
|
|
50792
|
+
return;
|
|
50793
|
+
}
|
|
50794
|
+
areaInUrlSeenRef.current = areaInUrl;
|
|
50795
|
+
if (areaInUrl === null) {
|
|
50796
|
+
// An address that says nothing is not an address saying "the first
|
|
50797
|
+
// slide": a container opened without the param opens where it would have
|
|
50798
|
+
// opened anyway, and the param appears the first time one travels.
|
|
50799
|
+
return;
|
|
50800
|
+
}
|
|
50801
|
+
// Where the box IS, read off the DOM: `current` is undefined until someone
|
|
50802
|
+
// names a slide, and the container standing on its first one is a fact only
|
|
50803
|
+
// the map knows (see the layout effect that paints it).
|
|
50804
|
+
const areaOnScreen = containerRef.current?.getAttribute("data-slide-current");
|
|
50805
|
+
if (!areaOnScreen || areaInUrl === areaOnScreen) {
|
|
50806
|
+
return;
|
|
50807
|
+
}
|
|
50808
|
+
const reached = reachableTowards(areaOnScreen, areaInUrl);
|
|
50809
|
+
if (reached && reached !== areaOnScreen) {
|
|
50810
|
+
// Told rather than asked: the walk above has already put every lock on
|
|
50811
|
+
// the way the question goToArea would have put the first one, and this
|
|
50812
|
+
// travel has no interaction behind it to hand a focus to — the slide
|
|
50813
|
+
// arriving is handed the keyboard by the layout effect, the way it is for
|
|
50814
|
+
// any travel nobody pressed anything for.
|
|
50815
|
+
setCurrentAreaState(reached);
|
|
50816
|
+
tellCurrentChange(reached, {
|
|
50817
|
+
cause: "url"
|
|
50818
|
+
}, areaOnScreen);
|
|
50819
|
+
return;
|
|
50820
|
+
}
|
|
50821
|
+
// The param names nowhere this map knows, or somewhere the walk is not
|
|
50822
|
+
// allowed to reach: the address is put back on the slide one is actually
|
|
50823
|
+
// looking at, rather than left saying one is somewhere one is not.
|
|
50824
|
+
writeUrlParam(areaOnScreen, "url");
|
|
50825
|
+
}, [urlParamName, areaInUrl]);
|
|
50826
|
+
|
|
50698
50827
|
// The press kept during a roll, taken once the window rests and the travel is
|
|
50699
50828
|
// given back (noTravel off): by direction when there was one, so it is read
|
|
50700
50829
|
// against the map as it is NOW — the content moved one step under the window
|
|
@@ -50788,20 +50917,25 @@ const SlideContainer = ({
|
|
|
50788
50917
|
};
|
|
50789
50918
|
|
|
50790
50919
|
/**
|
|
50920
|
+
* @param {number} dx
|
|
50921
|
+
* @param {number} dy
|
|
50922
|
+
* @param {string} [fromArea] - where to step from. The slide on screen when
|
|
50923
|
+
* nothing says otherwise; named only by a walk that is not standing there
|
|
50924
|
+
* (see reachableTowards).
|
|
50791
50925
|
* @returns {string|undefined} the area one step that way, if there is one.
|
|
50792
50926
|
* Nothing there means the direction is simply not offered — no wrapping, no
|
|
50793
50927
|
* nearest-match: a map is read as a map, and a move landing nowhere would
|
|
50794
50928
|
* break that reading. Walks over its own cells first, so a spanning area
|
|
50795
50929
|
* leaves by its far edge rather than onto itself.
|
|
50796
50930
|
*/
|
|
50797
|
-
const areaTowards = (dx, dy) => {
|
|
50931
|
+
const areaTowards = (dx, dy, fromArea) => {
|
|
50798
50932
|
const {
|
|
50799
50933
|
slideElements,
|
|
50800
50934
|
areaAt,
|
|
50801
50935
|
placeOf
|
|
50802
50936
|
} = readMap();
|
|
50803
50937
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current")) || slideElements[0];
|
|
50804
|
-
const currentArea = readArea(currentElement);
|
|
50938
|
+
const currentArea = fromArea ?? readArea(currentElement);
|
|
50805
50939
|
let {
|
|
50806
50940
|
x,
|
|
50807
50941
|
y
|
|
@@ -50860,6 +50994,64 @@ const SlideContainer = ({
|
|
|
50860
50994
|
const moveNext = (event, options) => vertical ? move(0, 1, event, options) || move(1, 0, event, options) : move(1, 0, event, options) || move(0, 1, event, options);
|
|
50861
50995
|
const movePrevious = (event, options) => vertical ? move(0, -1, event, options) || move(-1, 0, event, options) : move(-1, 0, event, options) || move(0, -1, event, options);
|
|
50862
50996
|
|
|
50997
|
+
/**
|
|
50998
|
+
* How far the map lets one get towards an area, walking from where the
|
|
50999
|
+
* container stands.
|
|
51000
|
+
*
|
|
51001
|
+
* What the ADDRESS asks for goes through here rather than straight to
|
|
51002
|
+
* goToArea: a URL comes from OUTSIDE the walk — typed, shared, kept from a
|
|
51003
|
+
* session that has moved on — so every slide on the way is asked to let go,
|
|
51004
|
+
* exactly as a hand or a key going that way would ask it, and the first one
|
|
51005
|
+
* that holds is where one stops. Jumped to instead, an address would open the
|
|
51006
|
+
* one screen the walk itself cannot reach: the confirmation of something
|
|
51007
|
+
* nobody sent. Stopping short is not a failure either — a wizard reopens as
|
|
51008
|
+
* far along as it is allowed to, which is where the reader left off.
|
|
51009
|
+
*
|
|
51010
|
+
* @returns {string|undefined} the area one ends up on, undefined for a name
|
|
51011
|
+
* this map does not know.
|
|
51012
|
+
*/
|
|
51013
|
+
const reachableTowards = (fromArea, targetArea) => {
|
|
51014
|
+
const {
|
|
51015
|
+
slideElements,
|
|
51016
|
+
placeOf
|
|
51017
|
+
} = readMap();
|
|
51018
|
+
const target = placeOf.get(targetArea);
|
|
51019
|
+
if (!target || !placeOf.has(fromArea)) {
|
|
51020
|
+
return undefined;
|
|
51021
|
+
}
|
|
51022
|
+
if (loop) {
|
|
51023
|
+
// A window has no walls to walk into: its slides are one endless line,
|
|
51024
|
+
// and what they show is whoever owns the content to place.
|
|
51025
|
+
return targetArea;
|
|
51026
|
+
}
|
|
51027
|
+
const holds = (area, forward) => {
|
|
51028
|
+
const slideElement = slideElements.find(slideElement => readArea(slideElement) === area);
|
|
51029
|
+
return slideElement?.hasAttribute(forward ? "data-prevent-nav-next" : "data-prevent-nav-previous");
|
|
51030
|
+
};
|
|
51031
|
+
let area = fromArea;
|
|
51032
|
+
// A walk crosses each slide at most once — beyond that a map is reading
|
|
51033
|
+
// itself in circles.
|
|
51034
|
+
let stepsLeft = slideElements.length;
|
|
51035
|
+
while (area !== targetArea && stepsLeft--) {
|
|
51036
|
+
const place = placeOf.get(area);
|
|
51037
|
+
// Along the row first, then down: the order a map is read in.
|
|
51038
|
+
const dx = Math.sign(target.x - place.x);
|
|
51039
|
+
const dy = dx ? 0 : Math.sign(target.y - place.y);
|
|
51040
|
+
if (!dx && !dy) {
|
|
51041
|
+
break;
|
|
51042
|
+
}
|
|
51043
|
+
if (holds(area, dx > 0 || dy > 0)) {
|
|
51044
|
+
break;
|
|
51045
|
+
}
|
|
51046
|
+
const next = areaTowards(dx, dy, area);
|
|
51047
|
+
if (!next) {
|
|
51048
|
+
break;
|
|
51049
|
+
}
|
|
51050
|
+
area = next;
|
|
51051
|
+
}
|
|
51052
|
+
return area;
|
|
51053
|
+
};
|
|
51054
|
+
|
|
50863
51055
|
// Where the track is right now, as the gesture left it: the resting place of
|
|
50864
51056
|
// the slide being dragged, plus what the pointer has pulled since.
|
|
50865
51057
|
const paintDrag = () => {
|
|
@@ -51113,8 +51305,8 @@ const SlideContainer = ({
|
|
|
51113
51305
|
sign,
|
|
51114
51306
|
target
|
|
51115
51307
|
}) => {
|
|
51116
|
-
|
|
51117
|
-
|
|
51308
|
+
let areaBack = axis === "x" ? areaTowards(-1, 0) : areaTowards(0, -1);
|
|
51309
|
+
let areaOn = axis === "x" ? areaTowards(1, 0) : areaTowards(0, 1);
|
|
51118
51310
|
// Everything positional is read HERE rather than when the pointer
|
|
51119
51311
|
// landed: the travel that was playing then may have arrived since, and
|
|
51120
51312
|
// it is what the slides are doing at the moment the gesture takes them
|
|
@@ -51125,6 +51317,24 @@ const SlideContainer = ({
|
|
|
51125
51317
|
placeOf
|
|
51126
51318
|
} = readMap();
|
|
51127
51319
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current")) || slideElements[0];
|
|
51320
|
+
// The hold goToArea reads at the release, read again HERE, off the same
|
|
51321
|
+
// slide and the same attribute: a slide that will refuse the arrival
|
|
51322
|
+
// must not offer the journey. A locked direction simply has nowhere to
|
|
51323
|
+
// go for the length of this gesture — the one case the gesture already
|
|
51324
|
+
// knows, being the last slide of a walk. The hand then gets the wall it
|
|
51325
|
+
// can lean on and never walk through (see drag_to_travel), the slide
|
|
51326
|
+
// behind it stays offstage instead of being read on the way, and the
|
|
51327
|
+
// release has nothing left to refuse.
|
|
51328
|
+
// Nothing here about `released` (--navi-done): that is one particular
|
|
51329
|
+
// departure letting go, decided as it happens, and a gesture armed
|
|
51330
|
+
// before it has no such thing to read — the attribute as rendered is
|
|
51331
|
+
// what the hand is answered from.
|
|
51332
|
+
if (currentElement?.hasAttribute("data-prevent-nav-previous")) {
|
|
51333
|
+
areaBack = undefined;
|
|
51334
|
+
}
|
|
51335
|
+
if (currentElement?.hasAttribute("data-prevent-nav-next")) {
|
|
51336
|
+
areaOn = undefined;
|
|
51337
|
+
}
|
|
51128
51338
|
const box = track.getBoundingClientRect();
|
|
51129
51339
|
if (!areaBack && !areaOn || !currentElement || !box.width || !box.height ||
|
|
51130
51340
|
// Something else with a better claim on the gesture: a scroller
|