@jsenv/navi 0.29.433 → 0.29.435

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.
@@ -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 goes while its page
11518
- * stays — a popup closing over the same address — has nothing to come back
11519
- * to, and says so as it leaves (forgetScrollerUnlessPageLeft).
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
- // The url each named scroller last spoke under: what tells a scroller leaving
11549
- // a page that stays from one leaving with its page.
11550
- const urlByScrollerName = new Map();
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(window.location.href, documentOffset);
11691
+ positionByUrl.set(readShownUrl(), documentOffset);
11673
11692
  },
11674
11693
  { passive: true },
11675
11694
  );
11676
- const positionOnLoad = positionByUrl.get(window.location.href);
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(window.location.href);
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 position = positionByUrl.get(new URL(url, window.location.href).href);
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(new URL(url, window.location.href).href, { x: 0, y: 0 });
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 document is shown under without moving: a replace, a push that
11793
- // keeps the page (see startAtTop), the first page loaded. No scroll event says
11794
- // where it stands, so it is written down here, or a return to it would find
11795
- // nothing and keep the offset of the page being left. The offset the scroll
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
- // Deaf with the recording: a row of tabs replaces the url as it travels, and
11800
- // the offset belongs to nobody until the row says where its tab lands (see
11801
- // arriveAtScrollPosition).
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
- if (documentOffset === null || suspendCount) {
11804
- return;
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
- positionByUrl.set(new URL(url, window.location.href).href, documentOffset);
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
- const url = window.location.href;
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
- urlByScrollerName.set(name, url);
11913
+ saidByScrollerName.set(name, { url, position });
11857
11914
  };
11858
11915
 
11859
11916
  const recallScrollerPosition = (name) => {
11860
11917
  loadStore();
11861
- const positionByName = scrollerPositionsByUrl.get(window.location.href);
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 spoke under means the page stays and the scroller alone
11872
- // goes (a popup closing, a section folding): there is no coming back to a
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 url = urlByScrollerName.get(name);
11877
- if (url === undefined) {
11933
+ const said = saidByScrollerName.get(name);
11934
+ if (said === undefined) {
11878
11935
  return;
11879
11936
  }
11880
- urlByScrollerName.delete(name);
11881
- if (url !== window.location.href) {
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
@@ -12880,15 +12938,12 @@ const useNavStateWithWarnings = (id, options) => {
12880
12938
  ownerRef.current = {};
12881
12939
  }
12882
12940
  const owner = ownerRef.current;
12883
- // Claimed during render so warnAboutOrphanGeneratedKeys sees every id of
12884
- // the commit, including the ones whose effect has not run yet. The conflict
12885
- // is judged in the effect: when a screen replaces another in one commit,
12886
- // preact renders the new children before unmounting the old ones, so at
12887
- // render time the id may still be held by a component about to go.
12888
- if (id !== undefined && !idUsageMap.has(id)) {
12889
- idUsageMap.set(id, { owner, stackTrace: new Error().stack });
12890
- }
12891
12941
 
12942
+ // Claimed in the effect, never during render: only the effect has a cleanup
12943
+ // to give the id back, and a render is not always followed by its effects.
12944
+ // A page parked by a Suspense boundary (every hook cleanup run), rendered
12945
+ // again and taken down before the paint, leaves a render-time claim holding
12946
+ // the id for nobody — and the next mount reads a conflict.
12892
12947
  useEffect(() => {
12893
12948
  if (id === undefined) {
12894
12949
  return undefined;
@@ -12902,12 +12957,9 @@ This can cause UI state conflicts and unexpected behavior.
12902
12957
  Consider using unique IDs for each component instance.`,
12903
12958
  );
12904
12959
  } else {
12905
- // Also set when absent: preact/compat's Suspense parks a subtree by
12906
- // running every hook cleanup in it, and the render resuming it carries
12907
- // the same id.
12908
12960
  idUsageMap.set(id, { owner, stackTrace: new Error().stack });
12909
12961
  }
12910
- warnAboutOrphanGeneratedKeys();
12962
+ scheduleOrphanGeneratedKeysCheck();
12911
12963
  return () => {
12912
12964
  if (idUsageMap.get(id)?.owner === owner) {
12913
12965
  idUsageMap.delete(id);
@@ -12923,6 +12975,20 @@ Consider using unique IDs for each component instance.`,
12923
12975
  // and the mount coming back to the entry generates another id: the state is
12924
12976
  // there, and nothing reads it. Reported when a component mounting on the entry
12925
12977
  // finds such a key, the moment someone expected the state back.
12978
+ //
12979
+ // Checked once every effect of the flush has claimed its id: a component
12980
+ // whose effect comes later in the same flush is not an orphan.
12981
+ let orphanGeneratedKeysCheckScheduled = false;
12982
+ const scheduleOrphanGeneratedKeysCheck = () => {
12983
+ if (orphanGeneratedKeysCheckScheduled) {
12984
+ return;
12985
+ }
12986
+ orphanGeneratedKeysCheckScheduled = true;
12987
+ queueMicrotask(() => {
12988
+ orphanGeneratedKeysCheckScheduled = false;
12989
+ warnAboutOrphanGeneratedKeys();
12990
+ });
12991
+ };
12926
12992
  const warnAboutOrphanGeneratedKeys = () => {
12927
12993
  const state = browserIntegration.getDocumentState();
12928
12994
  if (!state) {
@@ -33440,6 +33506,20 @@ const toHex = ([r, g, b]) =>
33440
33506
  // sure to be understood in, whatever getComputedStyle answered with.
33441
33507
  let colorContext = null;
33442
33508
  const parseColor = (color) => {
33509
+ const [r, g, b, a] = drawColor(color);
33510
+ if (a === 0 || a === 255) {
33511
+ return [r, g, b, a / 255];
33512
+ }
33513
+ // The canvas keeps a pixel premultiplied by its alpha, in bytes: the
33514
+ // channels of a translucent colour come back rounded at that alpha —
33515
+ // rgba(12, 23, 44, 0.45) reads 11, 22, 44. Made opaque, they read whole.
33516
+ const [rOpaque, gOpaque, bOpaque] = drawColor(
33517
+ `color(from ${color} srgb r g b / 1)`,
33518
+ );
33519
+ return [rOpaque, gOpaque, bOpaque, a / 255];
33520
+ };
33521
+
33522
+ const drawColor = (color) => {
33443
33523
  if (!colorContext) {
33444
33524
  const canvas = document.createElement("canvas");
33445
33525
  canvas.width = 1;
@@ -33452,8 +33532,7 @@ const parseColor = (color) => {
33452
33532
  colorContext.fillStyle = "transparent";
33453
33533
  colorContext.fillStyle = color;
33454
33534
  colorContext.fillRect(0, 0, 1, 1);
33455
- const [r, g, b, a] = colorContext.getImageData(0, 0, 1, 1).data;
33456
- return [r, g, b, a / 255];
33535
+ return colorContext.getImageData(0, 0, 1, 1).data;
33457
33536
  };
33458
33537
 
33459
33538
  // The title belongs to the last Head to arrive, whatever order they leave in:
@@ -49662,6 +49741,15 @@ const applySpacingOnTextChildren = (children, spacing, defaultSpace) => {
49662
49741
  }
49663
49742
  return childrenWithGap;
49664
49743
  };
49744
+ const isSpacingWidth = spacing => {
49745
+ if (typeof spacing === "number") {
49746
+ return true;
49747
+ }
49748
+ if (typeof spacing !== "string") {
49749
+ return false;
49750
+ }
49751
+ return isSizeSpacingKey(spacing) || hasCSSSizeUnit(spacing) || spacing.startsWith("var(");
49752
+ };
49665
49753
  const outsideTextFlowSet = new Set();
49666
49754
  const markAsOutsideTextFlow = jsxElement => {
49667
49755
  outsideTextFlowSet.add(jsxElement);
@@ -49929,7 +50017,11 @@ const TextUI = props => {
49929
50017
  }
49930
50018
  const shouldPreserveSpacing = rest.as === "pre" || rest.flex || rest.grid;
49931
50019
  if (shouldPreserveSpacing) {
49932
- boxProps.spacing = resolvedSpacing;
50020
+ // Between flex/grid items spacing is a gap, so only a width carries over: a
50021
+ // separator (the default space, a text, an element) or "pre" has no gap.
50022
+ if (isSpacingWidth(spacing)) {
50023
+ boxProps.spacing = spacing;
50024
+ }
49933
50025
  } else {
49934
50026
  children = applySpacingOnTextChildren(children, resolvedSpacing, defaultSpace);
49935
50027
  }
@@ -54578,6 +54670,26 @@ const Expandable = props => {
54578
54670
  return stopRide;
54579
54671
  };
54580
54672
 
54673
+ // A silent change is a state the page was already in (see openEffect): it
54674
+ // lands at once. Not playing a reveal is not enough — the stylesheet's own
54675
+ // transition plays it whenever the previous state's style was resolved
54676
+ // before the render: an expandable mounted closed and measured in the same
54677
+ // commit (a List sizing its items) before its deferred mount-time open, or
54678
+ // one kept on screen across the page change.
54679
+ const renderWithoutMovement = (contentContainer, render) => {
54680
+ if (!animation || !contentContainer) {
54681
+ flushSyncRendering(render);
54682
+ return;
54683
+ }
54684
+ contentContainer.style.transitionProperty = "none";
54685
+ flushSyncRendering(render);
54686
+ // The new style must be resolved while transitions are off — re-enabled
54687
+ // in the same recalc, the change would transition after all.
54688
+ // eslint-disable-next-line no-unused-expressions
54689
+ getComputedStyle(contentContainer).transitionProperty;
54690
+ contentContainer.style.transitionProperty = "";
54691
+ };
54692
+
54581
54693
  // What opening LOOKS like here, and how to undo it — the one thing an
54582
54694
  // expandable owns that a popup does not (see open_controller.js). Reassigned
54583
54695
  // on every render so it always closes over the latest props.
@@ -54598,10 +54710,15 @@ const Expandable = props => {
54598
54710
  // reopening during a collapse — read while the DOM still says closed.
54599
54711
  const startRect = revealing ? contentContainer.getBoundingClientRect() : null;
54600
54712
  cancelSettleWatch();
54601
- flushSyncRendering(() => {
54713
+ const renderOpened = () => {
54602
54714
  setOpened(true);
54603
54715
  setSettled(!revealing);
54604
- });
54716
+ };
54717
+ if (silent) {
54718
+ renderWithoutMovement(contentContainer, renderOpened);
54719
+ } else {
54720
+ flushSyncRendering(renderOpened);
54721
+ }
54605
54722
  if (hasAction) {
54606
54723
  runUnwatched(() => effectiveAction.run());
54607
54724
  }
@@ -54640,16 +54757,22 @@ const Expandable = props => {
54640
54757
  cancelSettleWatch();
54641
54758
  // `silent`: closed by the navigation that puts another page on screen,
54642
54759
  // which is its movement (see page_change.js).
54643
- const collapsing = animation && !closeEvent.detail.silent && Boolean(contentContainerAtClose);
54760
+ const silentClose = Boolean(closeEvent.detail.silent);
54761
+ const collapsing = animation && !silentClose && Boolean(contentContainerAtClose);
54644
54762
  if (collapsing) {
54645
54763
  // Now, while the content is still fully laid out — the collapsing
54646
54764
  // track uncovers a content frozen at that size.
54647
54765
  freezeContentSize();
54648
54766
  }
54649
- flushSyncRendering(() => {
54767
+ const renderClosed = () => {
54650
54768
  setOpened(false);
54651
54769
  setSettled(!collapsing);
54652
- });
54770
+ };
54771
+ if (silentClose) {
54772
+ renderWithoutMovement(contentContainerAtClose, renderClosed);
54773
+ } else {
54774
+ flushSyncRendering(renderClosed);
54775
+ }
54653
54776
  if (restoreFocus) {
54654
54777
  restoreFocus(closeEvent);
54655
54778
  } else if (focusedBeforeClose && contentContainerAtClose && contentContainerAtClose.contains(focusedBeforeClose)) {
@@ -74567,6 +74690,45 @@ const LIST_STYLE_CSS_VARS = {
74567
74690
  backgroundColor: "--list-background-color"
74568
74691
  };
74569
74692
  const LIST_PSEUDO_CLASSES = [":hover", ":focus", ":focus-visible", ":focus-within", ":read-only", ":disabled", ":-navi-void", ":-navi-expanded"];
74693
+ // Two lists mounted at once under one `id` are one entry to the scroll memory:
74694
+ // each overwrites the other's position (see docs/scroll.md). Said the moment
74695
+ // the second one mounts, once per id — on a route transition, that is the page
74696
+ // arriving while the page being left is still mounted, hidden.
74697
+ const createSharedRememberedIdWarning = () => {
74698
+ const mountedListsById = new Map();
74699
+ const warnedIdSet = new Set();
74700
+ return (listId, rememberScroll, ref) => {
74701
+ useLayoutEffect(() => {
74702
+ if (!rememberScroll) {
74703
+ return undefined;
74704
+ }
74705
+ let mountedLists = mountedListsById.get(listId);
74706
+ if (!mountedLists) {
74707
+ mountedLists = new Set();
74708
+ mountedListsById.set(listId, mountedLists);
74709
+ }
74710
+ const mounted = {
74711
+ element: ref.current
74712
+ };
74713
+ if (mountedLists.size > 0 && !warnedIdSet.has(listId)) {
74714
+ warnedIdSet.add(listId);
74715
+ const [other] = mountedLists;
74716
+ console.warn(`<List id="${listId}"> mounted while another list with this id is mounted. The position a list comes back to is kept under its id, so the two overwrite each other's: the one arriving can open where the other one was, or come back at its top. Lists are mounted together on one page, under a popup, in a bar every page keeps, and during a route transition, whose page being left stays mounted until the movement ends. Give each list an id naming it in the app (a component drawing a list on several pages takes its id from the page), or set scrollResetOnNavigation on a list with no use for coming back. See docs/scroll.md.`, {
74717
+ list: mounted.element,
74718
+ otherList: other.element
74719
+ });
74720
+ }
74721
+ mountedLists.add(mounted);
74722
+ return () => {
74723
+ mountedLists.delete(mounted);
74724
+ if (mountedLists.size === 0) {
74725
+ mountedListsById.delete(listId);
74726
+ }
74727
+ };
74728
+ }, [listId, rememberScroll]);
74729
+ };
74730
+ };
74731
+ const useWarnSharedRememberedId = createSharedRememberedIdWarning() ;
74570
74732
  const useListScrollSync = ({
74571
74733
  ref,
74572
74734
  listItems,
@@ -75473,6 +75635,7 @@ const useListScrollSync = ({
75473
75635
  }
75474
75636
  };
75475
75637
  }, []);
75638
+ useWarnSharedRememberedId(listId, rememberScroll, ref);
75476
75639
 
75477
75640
  // A list that gets narrower rewraps every item it holds, so everything below
75478
75641
  // moves and the reader loses their place — the very thing scrolling a long
@@ -79582,9 +79745,11 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
79582
79745
  * does, and for a list that scrolls itself the page's own restoration cannot
79583
79746
  * see. The position is kept under the list's `id` and the page's url, for
79584
79747
  * the session (a reload comes back too); a list without an `id` of its own
79585
- * has nothing to be remembered by. A fresh arrival at the page opens at
79586
- * `defaultScrolled` either way, and so does a list the caller holds through
79587
- * `scrolled`.
79748
+ * has nothing to be remembered by, and two lists mounted at once under one
79749
+ * `id` — a popup over the page, the page a route transition is leaving —
79750
+ * overwrite each other (see docs/scroll.md). A fresh arrival at the page
79751
+ * opens at `defaultScrolled` either way, and so does a list the caller holds
79752
+ * through `scrolled`.
79588
79753
  * @param {boolean} [props.deselectable]
79589
79754
  * A single-select list allowed to hold nothing: the selected item, pressed
79590
79755
  * again, lets go. Without it the list is a radio group — a choice, once