@jsenv/navi 0.29.235 → 0.29.238
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 +192 -84
- package/dist/dev/jsenv_navi.js.map +10 -10
- package/dist/jsenv_navi.js +134 -84
- package/dist/jsenv_navi.js.map +10 -10
- package/docs/AI_INSTRUCTIONS.md +5 -3
- package/docs/css_architecture.md +85 -11
- package/package.json +4 -4
package/dist/jsenv_navi.js
CHANGED
|
@@ -5900,6 +5900,7 @@ installImportMetaCssBuild(import.meta);
|
|
|
5900
5900
|
let calloutCount = 0;
|
|
5901
5901
|
|
|
5902
5902
|
const css$13 = /* css */ `
|
|
5903
|
+
/* jsenv-css-opaque: same as dialog.jsx, cascade order. */
|
|
5903
5904
|
@layer navi {
|
|
5904
5905
|
.navi_callout {
|
|
5905
5906
|
/* A callout is parented to what it explains, so it inherits from it — and
|
|
@@ -18653,6 +18654,20 @@ const resolveSpacingSize = (size, element, property = "padding") => {
|
|
|
18653
18654
|
}
|
|
18654
18655
|
return normalizeStyle(SIZE_MAP[size] || size, property, "js", element);
|
|
18655
18656
|
};
|
|
18657
|
+
// Whether a spacing is nothing at all, answered without resolving it. A
|
|
18658
|
+
// viewport or container length resolves through a computed-style read, and
|
|
18659
|
+
// asked during a render that read forces the browser to compute the style of
|
|
18660
|
+
// a tree still being built, stylesheets still being adopted. Zero is a fact
|
|
18661
|
+
// about the number written, whatever the unit; a scale keyword is never zero.
|
|
18662
|
+
const isZeroSpacingSize = (size) => {
|
|
18663
|
+
if (typeof size === "number") {
|
|
18664
|
+
return size === 0;
|
|
18665
|
+
}
|
|
18666
|
+
if (typeof size !== "string") {
|
|
18667
|
+
return false;
|
|
18668
|
+
}
|
|
18669
|
+
return parseFloat(size) === 0;
|
|
18670
|
+
};
|
|
18656
18671
|
|
|
18657
18672
|
const COLOR_KEYWORD_MAP = {
|
|
18658
18673
|
primary: "var(--navi-color-primary)",
|
|
@@ -25746,6 +25761,47 @@ const resolveEffectiveDocumentState = (
|
|
|
25746
25761
|
return { ...state, ...sharedState };
|
|
25747
25762
|
};
|
|
25748
25763
|
|
|
25764
|
+
// Preact's own useId() (see preact/hooks) returns "P<mask0>-<mask1>", where the
|
|
25765
|
+
// mask is derived from render order within the nearest root/async boundary:
|
|
25766
|
+
// stable across re-renders of the same mount, but not across two mounts on the
|
|
25767
|
+
// same page, and not across two documents.
|
|
25768
|
+
const PREACT_GENERATED_ID_REGEX = /^P\d+-\d+/;
|
|
25769
|
+
const isLikelyPreactGeneratedId = (id) => {
|
|
25770
|
+
return PREACT_GENERATED_ID_REGEX.test(id);
|
|
25771
|
+
};
|
|
25772
|
+
|
|
25773
|
+
/**
|
|
25774
|
+
* A history entry outlives the document that wrote it: `history.state` comes
|
|
25775
|
+
* back as it was left on a reload, and on a back/forward into a document that
|
|
25776
|
+
* has been unloaded since.
|
|
25777
|
+
*
|
|
25778
|
+
* A key named by a generated id names a position in the render order of the
|
|
25779
|
+
* document that wrote it, and nothing else. Read back in another document the
|
|
25780
|
+
* same key names whatever now renders at that position — so an open popup left
|
|
25781
|
+
* behind by the previous document opens a component nobody ever touched (see
|
|
25782
|
+
* `useNavState`, whose key presence IS the open state). Only a caller-given id
|
|
25783
|
+
* means the same thing on both sides of a load.
|
|
25784
|
+
*
|
|
25785
|
+
* Returns the state untouched when it holds no such key, so the caller can tell
|
|
25786
|
+
* whether the entry needs rewriting.
|
|
25787
|
+
*/
|
|
25788
|
+
const dropGeneratedIdKeys = (state) => {
|
|
25789
|
+
if (!state) {
|
|
25790
|
+
return state;
|
|
25791
|
+
}
|
|
25792
|
+
let stateWithoutGeneratedIdKeys = state;
|
|
25793
|
+
for (const key of Object.keys(state)) {
|
|
25794
|
+
if (!isLikelyPreactGeneratedId(key)) {
|
|
25795
|
+
continue;
|
|
25796
|
+
}
|
|
25797
|
+
if (stateWithoutGeneratedIdKeys === state) {
|
|
25798
|
+
stateWithoutGeneratedIdKeys = { ...state };
|
|
25799
|
+
}
|
|
25800
|
+
delete stateWithoutGeneratedIdKeys[key];
|
|
25801
|
+
}
|
|
25802
|
+
return stateWithoutGeneratedIdKeys;
|
|
25803
|
+
};
|
|
25804
|
+
|
|
25749
25805
|
/**
|
|
25750
25806
|
* The document's rendering, held for the one frame a view transition needs.
|
|
25751
25807
|
*
|
|
@@ -26602,7 +26658,14 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
26602
26658
|
|
|
26603
26659
|
const init = () => {
|
|
26604
26660
|
const url = window.location.href;
|
|
26605
|
-
const
|
|
26661
|
+
const stateOnEntry = window.history.state;
|
|
26662
|
+
const state = dropGeneratedIdKeys(stateOnEntry);
|
|
26663
|
+
if (state !== stateOnEntry) {
|
|
26664
|
+
// The entry itself has to lose them too, not just the document state:
|
|
26665
|
+
// getDocumentState() reads the entry back, and every state write copies
|
|
26666
|
+
// what it finds there onto the next one.
|
|
26667
|
+
window.history.replaceState(state, null, url);
|
|
26668
|
+
}
|
|
26606
26669
|
handleRoutingTask(url, {
|
|
26607
26670
|
reason: "routing initialization",
|
|
26608
26671
|
navigationType: "load",
|
|
@@ -26804,18 +26867,6 @@ const isVisited = browserIntegration.isVisited;
|
|
|
26804
26867
|
const visitedUrlsSignal = browserIntegration.visitedUrlsSignal;
|
|
26805
26868
|
browserIntegration.handleActionTask;
|
|
26806
26869
|
|
|
26807
|
-
// Preact's own useId() (see preact/hooks) returns "P<mask0>-<mask1>", where
|
|
26808
|
-
// the mask is derived from render order within the nearest root/async
|
|
26809
|
-
// boundary — stable across re-renders of the *same* mount, but not across a
|
|
26810
|
-
// reload (render order can differ) or even across two mounts on the same
|
|
26811
|
-
// page (two components hitting useId() in the same relative order get the
|
|
26812
|
-
// same string). Storing one of these under type: "push" bakes it into a
|
|
26813
|
-
// history entry: reload the page and the entry's key may now belong to a
|
|
26814
|
-
// completely different component (or none), silently auto-opening whatever
|
|
26815
|
-
// happens to render at that same position instead.
|
|
26816
|
-
const PREACT_GENERATED_ID_REGEX = /^P\d+-\d+/;
|
|
26817
|
-
const isLikelyPreactGeneratedId = (id) => PREACT_GENERATED_ID_REGEX.test(id);
|
|
26818
|
-
|
|
26819
26870
|
const NO_OP = () => {};
|
|
26820
26871
|
const NO_ID_GIVEN = [undefined, NO_OP, NO_OP];
|
|
26821
26872
|
const useNavStateBasic = (
|
|
@@ -26847,6 +26898,10 @@ const useNavStateBasic = (
|
|
|
26847
26898
|
}
|
|
26848
26899
|
|
|
26849
26900
|
let effectiveType = type;
|
|
26901
|
+
// A push bakes the key into a history entry of its own, and an id naming a
|
|
26902
|
+
// render position (see isLikelyPreactGeneratedId) can be claimed by two
|
|
26903
|
+
// mounts of this same page. A replace keeps the key on the entry the document
|
|
26904
|
+
// is already on, where the id at least names one render.
|
|
26850
26905
|
if (type === "push" && isLikelyPreactGeneratedId(id)) {
|
|
26851
26906
|
effectiveType = "replace";
|
|
26852
26907
|
}
|
|
@@ -39362,47 +39417,35 @@ const LoadingIndicatorFluid = ({
|
|
|
39362
39417
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
39363
39418
|
const [containerHeight, setContainerHeight] = useState(0);
|
|
39364
39419
|
const [containerRadius, setContainerRadius] = useState(0);
|
|
39420
|
+
|
|
39421
|
+
// Sized by the observer alone, first frame included: an element gets an
|
|
39422
|
+
// observation for the size it has when first observed, delivered after the
|
|
39423
|
+
// frame's own layout and before its paint. Measuring here instead would
|
|
39424
|
+
// force a layout in the middle of the commit, on a tree the commit is still
|
|
39425
|
+
// writing to, and the frame would lay it out again. Inside the callback style
|
|
39426
|
+
// and layout are clean, so the radius read there is a lookup, not a recalc.
|
|
39365
39427
|
useLayoutEffect(() => {
|
|
39366
39428
|
const indicatorEl = ref.current;
|
|
39367
39429
|
if (!indicatorEl) {
|
|
39368
39430
|
return null;
|
|
39369
39431
|
}
|
|
39370
|
-
const {
|
|
39371
|
-
|
|
39372
|
-
|
|
39373
|
-
|
|
39374
|
-
|
|
39375
|
-
|
|
39376
|
-
|
|
39377
|
-
|
|
39378
|
-
|
|
39379
|
-
|
|
39380
|
-
|
|
39381
|
-
|
|
39382
|
-
|
|
39383
|
-
|
|
39384
|
-
let animationFrameId = null;
|
|
39385
|
-
// Create a resize observer to detect changes in the container's dimensions
|
|
39386
|
-
const resizeObserver = new ResizeObserver(entries => {
|
|
39387
|
-
// Use requestAnimationFrame to debounce updates
|
|
39388
|
-
if (animationFrameId) {
|
|
39389
|
-
cancelAnimationFrame(animationFrameId);
|
|
39390
|
-
}
|
|
39391
|
-
animationFrameId = requestAnimationFrame(() => {
|
|
39392
|
-
const [containerEntry] = entries;
|
|
39393
|
-
const {
|
|
39394
|
-
width,
|
|
39395
|
-
height
|
|
39396
|
-
} = containerEntry.contentRect;
|
|
39397
|
-
setContainerWidth(width);
|
|
39398
|
-
setContainerHeight(height);
|
|
39399
|
-
});
|
|
39432
|
+
const resizeObserver = new ResizeObserver(([containerEntry]) => {
|
|
39433
|
+
const {
|
|
39434
|
+
width,
|
|
39435
|
+
height
|
|
39436
|
+
} = containerEntry.contentRect;
|
|
39437
|
+
setContainerWidth(width);
|
|
39438
|
+
setContainerHeight(height);
|
|
39439
|
+
if (radius === undefined || radius === "inherit") {
|
|
39440
|
+
const parentEl = indicatorEl.parentElement;
|
|
39441
|
+
// The inline style says the radius as the caller wrote it; the
|
|
39442
|
+
// computed longhand otherwise, the shorthand computing to "" when the
|
|
39443
|
+
// four corners differ.
|
|
39444
|
+
setContainerRadius(parentEl.style.borderRadius || parentEl.style.borderTopLeftRadius || getComputedStyle(parentEl).borderTopLeftRadius || "0px");
|
|
39445
|
+
}
|
|
39400
39446
|
});
|
|
39401
39447
|
resizeObserver.observe(indicatorEl);
|
|
39402
39448
|
return () => {
|
|
39403
|
-
if (animationFrameId) {
|
|
39404
|
-
cancelAnimationFrame(animationFrameId);
|
|
39405
|
-
}
|
|
39406
39449
|
resizeObserver.disconnect();
|
|
39407
39450
|
};
|
|
39408
39451
|
}, []);
|
|
@@ -43735,6 +43778,13 @@ const measureDrawing = ({
|
|
|
43735
43778
|
* Measured rather than declared: a bar's size comes from a prop, a theme
|
|
43736
43779
|
* variable, its own content or the device's notch, and only the used value
|
|
43737
43780
|
* knows all four.
|
|
43781
|
+
*
|
|
43782
|
+
* Written once per task, after every bar has measured. The variable resizes
|
|
43783
|
+
* an ancestor of the bars — the scroll container takes its padding from it —
|
|
43784
|
+
* so a write landing between two measurements turns the second one into a
|
|
43785
|
+
* style recalc and a layout of the whole page. Sizes are queued, and the queue
|
|
43786
|
+
* lands in a microtask: still before the browser paints, so the content is
|
|
43787
|
+
* never seen under a bar, and after every read of the commit that queued them.
|
|
43738
43788
|
*/
|
|
43739
43789
|
|
|
43740
43790
|
// Several bars can share an edge — during a page transition the outgoing and
|
|
@@ -43748,10 +43798,14 @@ const sizeMapByArea = new Map();
|
|
|
43748
43798
|
// size again — the ones for the smaller bars, and the second half of a page
|
|
43749
43799
|
// transition.
|
|
43750
43800
|
const writtenValueByArea = new Map();
|
|
43801
|
+
// Sizes said and not yet written. One queue for both callers below: a bar
|
|
43802
|
+
// saying its size twice before the write keeps the last word, whoever said it.
|
|
43803
|
+
const pendingSizeMapByArea = new Map();
|
|
43804
|
+
let flushMicrotaskQueued = false;
|
|
43805
|
+
let flushFrame = null;
|
|
43751
43806
|
|
|
43752
|
-
// A size
|
|
43753
|
-
// the room it takes is given back
|
|
43754
|
-
// painted under it.
|
|
43807
|
+
// A size a render produced: a prop, the children or a theme variable changed
|
|
43808
|
+
// the bar, and the room it takes is given back before that commit is painted.
|
|
43755
43809
|
/**
|
|
43756
43810
|
* @param {"top"|"bottom"|"left"|"right"} area
|
|
43757
43811
|
* @param {Element} barElement - Which bar this size belongs to.
|
|
@@ -43759,41 +43813,47 @@ const writtenValueByArea = new Map();
|
|
|
43759
43813
|
* content.
|
|
43760
43814
|
*/
|
|
43761
43815
|
const setFixedBarSpace = (area, barElement, size) => {
|
|
43762
|
-
|
|
43763
|
-
|
|
43764
|
-
|
|
43765
|
-
}
|
|
43766
|
-
|
|
43767
|
-
|
|
43768
|
-
|
|
43769
|
-
|
|
43770
|
-
|
|
43771
|
-
|
|
43772
|
-
|
|
43773
|
-
//
|
|
43816
|
+
queueSize(area, barElement, size);
|
|
43817
|
+
if (flushMicrotaskQueued) {
|
|
43818
|
+
return;
|
|
43819
|
+
}
|
|
43820
|
+
flushMicrotaskQueued = true;
|
|
43821
|
+
queueMicrotask(() => {
|
|
43822
|
+
flushMicrotaskQueued = false;
|
|
43823
|
+
flushPendingSizes();
|
|
43824
|
+
});
|
|
43825
|
+
};
|
|
43826
|
+
|
|
43827
|
+
// A size nothing asked for, coming from a ResizeObserver. Mutating layout from
|
|
43828
|
+
// inside a resize callback is what makes the browser report "ResizeObserver
|
|
43829
|
+
// loop completed with undelivered notifications", so this write waits for the
|
|
43830
|
+
// frame that resize produced.
|
|
43774
43831
|
/**
|
|
43775
43832
|
* @param {"top"|"bottom"|"left"|"right"} area
|
|
43776
43833
|
* @param {Element} barElement - Which bar this size belongs to.
|
|
43777
43834
|
* @param {number} size - In px.
|
|
43778
43835
|
*/
|
|
43779
43836
|
const requestFixedBarSpace = (area, barElement, size) => {
|
|
43837
|
+
queueSize(area, barElement, size);
|
|
43838
|
+
if (flushFrame !== null) {
|
|
43839
|
+
return;
|
|
43840
|
+
}
|
|
43841
|
+
flushFrame = requestAnimationFrame(() => {
|
|
43842
|
+
flushFrame = null;
|
|
43843
|
+
flushPendingSizes();
|
|
43844
|
+
});
|
|
43845
|
+
};
|
|
43846
|
+
|
|
43847
|
+
const queueSize = (area, barElement, size) => {
|
|
43780
43848
|
let pendingSizeMap = pendingSizeMapByArea.get(area);
|
|
43781
43849
|
if (!pendingSizeMap) {
|
|
43782
43850
|
pendingSizeMap = new Map();
|
|
43783
43851
|
pendingSizeMapByArea.set(area, pendingSizeMap);
|
|
43784
43852
|
}
|
|
43785
43853
|
pendingSizeMap.set(barElement, size);
|
|
43786
|
-
if (flushFrame !== null) {
|
|
43787
|
-
return;
|
|
43788
|
-
}
|
|
43789
|
-
flushFrame = requestAnimationFrame(flushPendingSizes);
|
|
43790
43854
|
};
|
|
43791
43855
|
|
|
43792
|
-
const pendingSizeMapByArea = new Map();
|
|
43793
|
-
let flushFrame = null;
|
|
43794
|
-
|
|
43795
43856
|
const flushPendingSizes = () => {
|
|
43796
|
-
flushFrame = null;
|
|
43797
43857
|
for (const [area, pendingSizeMap] of pendingSizeMapByArea) {
|
|
43798
43858
|
for (const [barElement, size] of pendingSizeMap) {
|
|
43799
43859
|
storeSize(area, barElement, size);
|
|
@@ -43803,20 +43863,6 @@ const flushPendingSizes = () => {
|
|
|
43803
43863
|
pendingSizeMapByArea.clear();
|
|
43804
43864
|
};
|
|
43805
43865
|
|
|
43806
|
-
// What the bar itself just said wins over what its observer had queued about
|
|
43807
|
-
// it: a bar unmounting gives its room back, and a size queued for it before
|
|
43808
|
-
// that must not put it back.
|
|
43809
|
-
const dropPendingSize = (area, barElement) => {
|
|
43810
|
-
const pendingSizeMap = pendingSizeMapByArea.get(area);
|
|
43811
|
-
if (!pendingSizeMap) {
|
|
43812
|
-
return;
|
|
43813
|
-
}
|
|
43814
|
-
pendingSizeMap.delete(barElement);
|
|
43815
|
-
if (pendingSizeMap.size === 0) {
|
|
43816
|
-
pendingSizeMapByArea.delete(area);
|
|
43817
|
-
}
|
|
43818
|
-
};
|
|
43819
|
-
|
|
43820
43866
|
const storeSize = (area, barElement, size) => {
|
|
43821
43867
|
let sizeMap = sizeMapByArea.get(area);
|
|
43822
43868
|
if (!sizeMap) {
|
|
@@ -57862,6 +57908,8 @@ const PopupClose = ({
|
|
|
57862
57908
|
installImportMetaCssBuild(import.meta);
|
|
57863
57909
|
let openLocalDialogCount = 0;
|
|
57864
57910
|
const css$E = /* css */`
|
|
57911
|
+
/* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
|
|
57912
|
+
order depending on which popup renders first. */
|
|
57865
57913
|
@layer navi {
|
|
57866
57914
|
.navi_dialog {
|
|
57867
57915
|
/* Min gap between the dialog and the edges of its container. Written
|
|
@@ -58912,7 +58960,7 @@ const useDialogProps = props => {
|
|
|
58912
58960
|
bottom: false,
|
|
58913
58961
|
left: false
|
|
58914
58962
|
};
|
|
58915
|
-
if (
|
|
58963
|
+
if (isZeroSpacingSize(marginWithContainer)) {
|
|
58916
58964
|
const {
|
|
58917
58965
|
y,
|
|
58918
58966
|
x
|
|
@@ -59626,6 +59674,8 @@ Dialog.Close = PopupClose;
|
|
|
59626
59674
|
installImportMetaCssBuild(import.meta);
|
|
59627
59675
|
let openLocalPopoverCount = 0;
|
|
59628
59676
|
const css$D = /* css */`
|
|
59677
|
+
/* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
|
|
59678
|
+
order depending on which popup renders first. */
|
|
59629
59679
|
@layer navi {
|
|
59630
59680
|
.navi_popover {
|
|
59631
59681
|
/* soft: user-configurable preferred max-height. Kept as a *default*
|