@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.
@@ -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 state = history.state;
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
- width,
39372
- height
39373
- } = indicatorEl.getBoundingClientRect();
39374
- setContainerWidth(width);
39375
- setContainerHeight(height);
39376
- if (radius === undefined || radius === "inherit") {
39377
- const parentEl = indicatorEl.parentElement;
39378
- // Prefer the inline style (always available, even before layout is computed).
39379
- // Fall back to computed longhands — getComputedStyle shorthand may return ""
39380
- // if the browser hasn't resolved the layout yet.
39381
- const radius = parentEl.style.borderRadius || parentEl.style.borderTopLeftRadius || getComputedStyle(parentEl).borderTopLeftRadius || "0px";
39382
- setContainerRadius(radius);
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 that must land before the next paint: a render changed the bar, so
43753
- // the room it takes is given back in that same commit and the content is never
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
- dropPendingSize(area, barElement);
43763
- storeSize(area, barElement, size);
43764
- writeSpace(area);
43765
- };
43766
-
43767
- // A size nothing asked for, coming from a ResizeObserver. Writing the variable
43768
- // resizes an ANCESTOR of the bars — the scroll container takes its padding
43769
- // from it — and mutating layout from inside a resize callback is what makes
43770
- // the browser report "ResizeObserver loop completed with undelivered
43771
- // notifications". So the write waits for the frame that resize produced.
43772
- // Queued here rather than deferred by each bar on its own, so the bars sharing
43773
- // an edge resolve to a single write instead of one per bar.
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 (resolveSpacingSize(marginWithContainer) === 0) {
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*