@jsenv/navi 0.29.433 → 0.29.434
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 +128 -42
- package/dist/dev/jsenv_navi.js.map +6 -6
- package/dist/jsenv_navi.js +128 -42
- package/dist/jsenv_navi.js.map +6 -6
- package/docs/AI_INSTRUCTIONS.md +4 -3
- package/docs/route_transitions.md +4 -0
- package/docs/scroll.md +38 -5
- package/package.json +1 -1
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -11514,9 +11514,16 @@ const [publishRouteRender, observeRouteRender] = createPubSub();
|
|
|
11514
11514
|
* (recallScrollerPosition). What a push means for them is what it means for
|
|
11515
11515
|
* the document — an arrival opens at the top (see startAtTop): the page
|
|
11516
11516
|
* arrived at has its named positions dropped before its lists render, so a
|
|
11517
|
-
* list recalls only on the way back. A scroller that
|
|
11518
|
-
* stays
|
|
11519
|
-
*
|
|
11517
|
+
* list recalls only on the way back. A scroller that outlives a navigation —
|
|
11518
|
+
* its page stays under a new address, or it lives outside the page that
|
|
11519
|
+
* changed — is shown under the new address too, and is carried to it (see
|
|
11520
|
+
* carryScrollersOnceRendered). A scroller that goes while its page stays — a
|
|
11521
|
+
* popup closing over the same address — has nothing to come back to, and says
|
|
11522
|
+
* so as it leaves (forgetScrollerUnlessPageLeft).
|
|
11523
|
+
*
|
|
11524
|
+
* Every position is kept under the address the routing shows, which can be
|
|
11525
|
+
* ahead of the browser's: a replace waiting for its state to settle has moved
|
|
11526
|
+
* the page and not yet `window.location` (see readShownUrl).
|
|
11520
11527
|
*
|
|
11521
11528
|
* What is NOT covered, and cannot be from here: a page whose height depends on
|
|
11522
11529
|
* something still loading. Its content is not there at the moment it is put
|
|
@@ -11545,9 +11552,20 @@ let documentOffset = null;
|
|
|
11545
11552
|
// url -> (scroller name -> position). The position is whatever the scroller
|
|
11546
11553
|
// handed in: what it can put itself back on, in its own terms.
|
|
11547
11554
|
const scrollerPositionsByUrl = new Map();
|
|
11548
|
-
//
|
|
11549
|
-
// a page that stays from one
|
|
11550
|
-
|
|
11555
|
+
// What each named scroller still mounted last said: its position and the url
|
|
11556
|
+
// it said it under. What tells a scroller leaving a page that stays from one
|
|
11557
|
+
// leaving with its page, and what one outliving a navigation is carried with.
|
|
11558
|
+
const saidByScrollerName = new Map();
|
|
11559
|
+
|
|
11560
|
+
// The address the page is shown under, as the routing knows it (see
|
|
11561
|
+
// installScrollRestoration): `window.location` lags behind a replace waiting
|
|
11562
|
+
// for its state to settle (replaceAddressWhenSettled in either integration),
|
|
11563
|
+
// and a scroll keyed by it meanwhile is written for the address being left.
|
|
11564
|
+
// Resolved, as an address handed to navTo can be kept as it was given.
|
|
11565
|
+
let readRoutingAddress = () => window.location.href;
|
|
11566
|
+
const readShownUrl = () => {
|
|
11567
|
+
return new URL(readRoutingAddress(), window.location.href).href;
|
|
11568
|
+
};
|
|
11551
11569
|
|
|
11552
11570
|
// Read once, the first time anyone needs the positions: the document's
|
|
11553
11571
|
// restoration is installed by the routing, and a list remembering itself may
|
|
@@ -11648,11 +11666,12 @@ const holdDocumentScroll = () => {
|
|
|
11648
11666
|
};
|
|
11649
11667
|
|
|
11650
11668
|
let installed = false;
|
|
11651
|
-
const installScrollRestoration = () => {
|
|
11669
|
+
const installScrollRestoration = ({ readAddress }) => {
|
|
11652
11670
|
if (installed) {
|
|
11653
11671
|
return;
|
|
11654
11672
|
}
|
|
11655
11673
|
installed = true;
|
|
11674
|
+
readRoutingAddress = readAddress;
|
|
11656
11675
|
if (!("scrollRestoration" in window.history)) {
|
|
11657
11676
|
return;
|
|
11658
11677
|
}
|
|
@@ -11669,14 +11688,16 @@ const installScrollRestoration = () => {
|
|
|
11669
11688
|
if (suspendCount) {
|
|
11670
11689
|
return;
|
|
11671
11690
|
}
|
|
11672
|
-
positionByUrl.set(
|
|
11691
|
+
positionByUrl.set(readShownUrl(), documentOffset);
|
|
11673
11692
|
},
|
|
11674
11693
|
{ passive: true },
|
|
11675
11694
|
);
|
|
11676
|
-
const
|
|
11695
|
+
const urlOnLoad = readShownUrl();
|
|
11696
|
+
const positionOnLoad = positionByUrl.get(urlOnLoad);
|
|
11677
11697
|
if (!positionOnLoad) {
|
|
11678
|
-
// The first page, where the browser landed it
|
|
11679
|
-
stayInPlace
|
|
11698
|
+
// The first page, where the browser landed it: no scroll event says so
|
|
11699
|
+
// (see stayInPlace).
|
|
11700
|
+
positionByUrl.set(urlOnLoad, documentOffset);
|
|
11680
11701
|
return;
|
|
11681
11702
|
}
|
|
11682
11703
|
// What a reload asks for, now that the browser has been told not to do it.
|
|
@@ -11729,7 +11750,9 @@ const restoreScrollPosition = (url) => {
|
|
|
11729
11750
|
// clamp is a scroll recorded under the url already returned to.
|
|
11730
11751
|
const restoreScrollPositionOnReturn = (url) => {
|
|
11731
11752
|
const arrival = ++arrivalCount;
|
|
11732
|
-
const
|
|
11753
|
+
const href = new URL(url, window.location.href).href;
|
|
11754
|
+
carryScrollersOnceRendered(href);
|
|
11755
|
+
const position = positionByUrl.get(href);
|
|
11733
11756
|
if (!position) {
|
|
11734
11757
|
return;
|
|
11735
11758
|
}
|
|
@@ -11771,11 +11794,13 @@ const startAtTop = (url, { from } = {}) => {
|
|
|
11771
11794
|
return;
|
|
11772
11795
|
}
|
|
11773
11796
|
arrivalCount++;
|
|
11797
|
+
const href = new URL(url, window.location.href).href;
|
|
11798
|
+
carryScrollersOnceRendered(href);
|
|
11774
11799
|
// Written down as well as done: a document already at its top fires no
|
|
11775
11800
|
// scroll event, and a page left without ever being scrolled would have no
|
|
11776
11801
|
// position to come back to — the return would keep the offset of the page
|
|
11777
11802
|
// being left.
|
|
11778
|
-
positionByUrl.set(
|
|
11803
|
+
positionByUrl.set(href, { x: 0, y: 0 });
|
|
11779
11804
|
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
|
|
11780
11805
|
};
|
|
11781
11806
|
const isArrival = (url, { from }) => {
|
|
@@ -11789,21 +11814,51 @@ const isArrival = (url, { from }) => {
|
|
|
11789
11814
|
return true;
|
|
11790
11815
|
};
|
|
11791
11816
|
|
|
11792
|
-
// A url the
|
|
11793
|
-
// keeps the page (see startAtTop)
|
|
11794
|
-
//
|
|
11795
|
-
// nothing and keep the offset of the page being left.
|
|
11796
|
-
// events last told, not read again: a read here would lay the page out in the
|
|
11797
|
-
// middle of a routing, once per address a settling param writes.
|
|
11817
|
+
// A url the page is shown under without changing: a replace, a push that
|
|
11818
|
+
// keeps the page (see startAtTop). Nothing scrolls, so nothing says where the
|
|
11819
|
+
// document stands under it — written down here, or a return to it would find
|
|
11820
|
+
// nothing and keep the offset of the page being left.
|
|
11798
11821
|
//
|
|
11799
|
-
//
|
|
11800
|
-
//
|
|
11801
|
-
//
|
|
11822
|
+
// The offset as its scroll events last told it, not read again: a read here
|
|
11823
|
+
// would lay the page out in the middle of a routing, once per address a
|
|
11824
|
+
// settling param writes. Deaf with the recording: a row of tabs replaces the
|
|
11825
|
+
// url as it travels, and the offset belongs to nobody until the row says where
|
|
11826
|
+
// its tab lands (see arriveAtScrollPosition).
|
|
11802
11827
|
const stayInPlace = (url) => {
|
|
11803
|
-
|
|
11804
|
-
|
|
11828
|
+
const href = new URL(url, window.location.href).href;
|
|
11829
|
+
carryScrollersOnceRendered(href);
|
|
11830
|
+
if (documentOffset !== null && !suspendCount) {
|
|
11831
|
+
positionByUrl.set(href, documentOffset);
|
|
11805
11832
|
}
|
|
11806
|
-
|
|
11833
|
+
};
|
|
11834
|
+
|
|
11835
|
+
// A scroller that outlives a navigation — its page stays under the new url, or
|
|
11836
|
+
// it lives outside the page that changed (a sidebar list kept by every page) —
|
|
11837
|
+
// is shown under that url as well, and says nothing there unless it moves: a
|
|
11838
|
+
// return to it would find no position and open the list at its top. So it is
|
|
11839
|
+
// carried, with the position it last said.
|
|
11840
|
+
//
|
|
11841
|
+
// Told apart once the navigation has rendered: until then a list about to be
|
|
11842
|
+
// swapped out is as mounted as one that stays, and carried, its position is
|
|
11843
|
+
// what a list of the same name arriving would read. By then one that left has
|
|
11844
|
+
// said so (forgetScrollerUnlessPageLeft), and one that arrived has read its
|
|
11845
|
+
// own and speaks under this url. One still mounted on its way out (a page kept
|
|
11846
|
+
// hidden while its route transition plays) is carried, and forgets the copy as
|
|
11847
|
+
// it unmounts under this url.
|
|
11848
|
+
const carryScrollersOnceRendered = (url) => {
|
|
11849
|
+
whenPageRendered(() => {
|
|
11850
|
+
if (readShownUrl() !== url) {
|
|
11851
|
+
// Another navigation came first: what is mounted is shown under its url,
|
|
11852
|
+
// and its own carry says so.
|
|
11853
|
+
return;
|
|
11854
|
+
}
|
|
11855
|
+
for (const [name, said] of saidByScrollerName) {
|
|
11856
|
+
if (said.url === url) {
|
|
11857
|
+
continue;
|
|
11858
|
+
}
|
|
11859
|
+
writeScrollerPosition(url, name, said.position);
|
|
11860
|
+
}
|
|
11861
|
+
});
|
|
11807
11862
|
};
|
|
11808
11863
|
|
|
11809
11864
|
// The same arrival, for the page's own scrollers. The document is scrolled to
|
|
@@ -11846,19 +11901,21 @@ const scrollTo = ({ x, y }) => {
|
|
|
11846
11901
|
|
|
11847
11902
|
const rememberScrollerPosition = (name, position) => {
|
|
11848
11903
|
loadStore();
|
|
11849
|
-
|
|
11904
|
+
writeScrollerPosition(readShownUrl(), name, position);
|
|
11905
|
+
};
|
|
11906
|
+
const writeScrollerPosition = (url, name, position) => {
|
|
11850
11907
|
let positionByName = scrollerPositionsByUrl.get(url);
|
|
11851
11908
|
if (!positionByName) {
|
|
11852
11909
|
positionByName = new Map();
|
|
11853
11910
|
scrollerPositionsByUrl.set(url, positionByName);
|
|
11854
11911
|
}
|
|
11855
11912
|
positionByName.set(name, position);
|
|
11856
|
-
|
|
11913
|
+
saidByScrollerName.set(name, { url, position });
|
|
11857
11914
|
};
|
|
11858
11915
|
|
|
11859
11916
|
const recallScrollerPosition = (name) => {
|
|
11860
11917
|
loadStore();
|
|
11861
|
-
const positionByName = scrollerPositionsByUrl.get(
|
|
11918
|
+
const positionByName = scrollerPositionsByUrl.get(readShownUrl());
|
|
11862
11919
|
if (!positionByName) {
|
|
11863
11920
|
return undefined;
|
|
11864
11921
|
}
|
|
@@ -11868,17 +11925,18 @@ const recallScrollerPosition = (name) => {
|
|
|
11868
11925
|
// Said by a scroller as it unmounts. Its page is being left when the url is
|
|
11869
11926
|
// already another one — the history is written before the page it names is
|
|
11870
11927
|
// taken down — and then its position is kept for the way back. The url still
|
|
11871
|
-
// being the one it
|
|
11872
|
-
// goes (a popup closing, a section folding): there is no coming back to
|
|
11873
|
-
// place that was not left, and a position kept would greet the next mount
|
|
11928
|
+
// being the one it was last kept under means the page stays and the scroller
|
|
11929
|
+
// alone goes (a popup closing, a section folding): there is no coming back to
|
|
11930
|
+
// a place that was not left, and a position kept would greet the next mount
|
|
11874
11931
|
// under this address as a return.
|
|
11875
11932
|
const forgetScrollerUnlessPageLeft = (name) => {
|
|
11876
|
-
const
|
|
11877
|
-
if (
|
|
11933
|
+
const said = saidByScrollerName.get(name);
|
|
11934
|
+
if (said === undefined) {
|
|
11878
11935
|
return;
|
|
11879
11936
|
}
|
|
11880
|
-
|
|
11881
|
-
|
|
11937
|
+
saidByScrollerName.delete(name);
|
|
11938
|
+
const { url } = said;
|
|
11939
|
+
if (url !== readShownUrl()) {
|
|
11882
11940
|
return;
|
|
11883
11941
|
}
|
|
11884
11942
|
const positionByName = scrollerPositionsByUrl.get(url);
|
|
@@ -12406,7 +12464,7 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
12406
12464
|
// The browser's own scroll restoration is taken over here rather than left
|
|
12407
12465
|
// to whoever navigates: it is a decision about the document, and the entry
|
|
12408
12466
|
// being left must be recorded from the first pixel scrolled.
|
|
12409
|
-
installScrollRestoration();
|
|
12467
|
+
installScrollRestoration({ readAddress });
|
|
12410
12468
|
|
|
12411
12469
|
// The document is leaving, or may be killed in the background: what the
|
|
12412
12470
|
// entry says about it has to be what the document says (see
|
|
@@ -33440,6 +33498,20 @@ const toHex = ([r, g, b]) =>
|
|
|
33440
33498
|
// sure to be understood in, whatever getComputedStyle answered with.
|
|
33441
33499
|
let colorContext = null;
|
|
33442
33500
|
const parseColor = (color) => {
|
|
33501
|
+
const [r, g, b, a] = drawColor(color);
|
|
33502
|
+
if (a === 0 || a === 255) {
|
|
33503
|
+
return [r, g, b, a / 255];
|
|
33504
|
+
}
|
|
33505
|
+
// The canvas keeps a pixel premultiplied by its alpha, in bytes: the
|
|
33506
|
+
// channels of a translucent colour come back rounded at that alpha —
|
|
33507
|
+
// rgba(12, 23, 44, 0.45) reads 11, 22, 44. Made opaque, they read whole.
|
|
33508
|
+
const [rOpaque, gOpaque, bOpaque] = drawColor(
|
|
33509
|
+
`color(from ${color} srgb r g b / 1)`,
|
|
33510
|
+
);
|
|
33511
|
+
return [rOpaque, gOpaque, bOpaque, a / 255];
|
|
33512
|
+
};
|
|
33513
|
+
|
|
33514
|
+
const drawColor = (color) => {
|
|
33443
33515
|
if (!colorContext) {
|
|
33444
33516
|
const canvas = document.createElement("canvas");
|
|
33445
33517
|
canvas.width = 1;
|
|
@@ -33452,8 +33524,7 @@ const parseColor = (color) => {
|
|
|
33452
33524
|
colorContext.fillStyle = "transparent";
|
|
33453
33525
|
colorContext.fillStyle = color;
|
|
33454
33526
|
colorContext.fillRect(0, 0, 1, 1);
|
|
33455
|
-
|
|
33456
|
-
return [r, g, b, a / 255];
|
|
33527
|
+
return colorContext.getImageData(0, 0, 1, 1).data;
|
|
33457
33528
|
};
|
|
33458
33529
|
|
|
33459
33530
|
// The title belongs to the last Head to arrive, whatever order they leave in:
|
|
@@ -49662,6 +49733,15 @@ const applySpacingOnTextChildren = (children, spacing, defaultSpace) => {
|
|
|
49662
49733
|
}
|
|
49663
49734
|
return childrenWithGap;
|
|
49664
49735
|
};
|
|
49736
|
+
const isSpacingWidth = spacing => {
|
|
49737
|
+
if (typeof spacing === "number") {
|
|
49738
|
+
return true;
|
|
49739
|
+
}
|
|
49740
|
+
if (typeof spacing !== "string") {
|
|
49741
|
+
return false;
|
|
49742
|
+
}
|
|
49743
|
+
return isSizeSpacingKey(spacing) || hasCSSSizeUnit(spacing) || spacing.startsWith("var(");
|
|
49744
|
+
};
|
|
49665
49745
|
const outsideTextFlowSet = new Set();
|
|
49666
49746
|
const markAsOutsideTextFlow = jsxElement => {
|
|
49667
49747
|
outsideTextFlowSet.add(jsxElement);
|
|
@@ -49929,7 +50009,11 @@ const TextUI = props => {
|
|
|
49929
50009
|
}
|
|
49930
50010
|
const shouldPreserveSpacing = rest.as === "pre" || rest.flex || rest.grid;
|
|
49931
50011
|
if (shouldPreserveSpacing) {
|
|
49932
|
-
|
|
50012
|
+
// Between flex/grid items spacing is a gap, so only a width carries over: a
|
|
50013
|
+
// separator (the default space, a text, an element) or "pre" has no gap.
|
|
50014
|
+
if (isSpacingWidth(spacing)) {
|
|
50015
|
+
boxProps.spacing = spacing;
|
|
50016
|
+
}
|
|
49933
50017
|
} else {
|
|
49934
50018
|
children = applySpacingOnTextChildren(children, resolvedSpacing, defaultSpace);
|
|
49935
50019
|
}
|
|
@@ -79582,9 +79666,11 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
79582
79666
|
* does, and for a list that scrolls itself the page's own restoration cannot
|
|
79583
79667
|
* see. The position is kept under the list's `id` and the page's url, for
|
|
79584
79668
|
* the session (a reload comes back too); a list without an `id` of its own
|
|
79585
|
-
* has nothing to be remembered by
|
|
79586
|
-
* `
|
|
79587
|
-
*
|
|
79669
|
+
* has nothing to be remembered by, and two lists mounted at once under one
|
|
79670
|
+
* `id` — a popup over the page, the page a route transition is leaving —
|
|
79671
|
+
* overwrite each other (see docs/scroll.md). A fresh arrival at the page
|
|
79672
|
+
* opens at `defaultScrolled` either way, and so does a list the caller holds
|
|
79673
|
+
* through `scrolled`.
|
|
79588
79674
|
* @param {boolean} [props.deselectable]
|
|
79589
79675
|
* A single-select list allowed to hold nothing: the selected item, pressed
|
|
79590
79676
|
* again, lets go. Without it the list is a radio group — a choice, once
|