@jsenv/navi 0.29.365 → 0.29.366

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.
@@ -28872,6 +28872,8 @@ const visitedUrlsSignal = browserIntegration.visitedUrlsSignal;
28872
28872
  browserIntegration.handleActionTask;
28873
28873
 
28874
28874
  const idUsageMap = new Map();
28875
+ // Keys found in the entry with nobody to claim them, already reported.
28876
+ const orphanKeysReported = new Set();
28875
28877
  const useNavStateWithWarnings = (id, options) => {
28876
28878
  const idRef = useRef(undefined);
28877
28879
  if (idRef.current !== id) {
@@ -28895,6 +28897,15 @@ Consider using unique IDs for each component instance.`,
28895
28897
  }
28896
28898
 
28897
28899
  useEffect(() => {
28900
+ // Registered here as well as in the render above: preact/compat's
28901
+ // Suspense parks a subtree by running every hook cleanup in it, and the
28902
+ // render resuming it carries the same id.
28903
+ if (!idUsageMap.has(id)) {
28904
+ idUsageMap.set(id, {
28905
+ stackTrace: new Error().stack,
28906
+ });
28907
+ }
28908
+ warnAboutOrphanGeneratedKeys();
28898
28909
  return () => {
28899
28910
  idUsageMap.delete(id);
28900
28911
  };
@@ -28903,6 +28914,31 @@ Consider using unique IDs for each component instance.`,
28903
28914
  return useNavStateBasic(id, options);
28904
28915
  };
28905
28916
 
28917
+ // A generated id (preact's useId()) names one mount. State written under one
28918
+ // outlives that mount in the history entry — a page left with a popup open —
28919
+ // and the mount coming back to the entry generates another id: the state is
28920
+ // there, and nothing reads it. Reported when a component mounting on the entry
28921
+ // finds such a key, the moment someone expected the state back.
28922
+ const warnAboutOrphanGeneratedKeys = () => {
28923
+ const state = browserIntegration.getDocumentState();
28924
+ if (!state) {
28925
+ return;
28926
+ }
28927
+ for (const key of Object.keys(state)) {
28928
+ if (
28929
+ !isLikelyPreactGeneratedId(key) ||
28930
+ idUsageMap.has(key) ||
28931
+ orphanKeysReported.has(key)
28932
+ ) {
28933
+ continue;
28934
+ }
28935
+ orphanKeysReported.add(key);
28936
+ console.warn(
28937
+ `useNavState: this history entry holds "${key}", written by a component whose id was generated (preact's useId()) and that is not mounted anymore — a Picker without an id, a popup with navState and no id. A generated id names one mount, so nothing will read that state again: a popup open when this screen was left comes back closed. Give the component a stable id if it was meant to be found as it was.`,
28938
+ );
28939
+ }
28940
+ };
28941
+
28906
28942
  const NO_OP = () => {};
28907
28943
  const NO_ID_GIVEN = [undefined, NO_OP, NO_OP];
28908
28944
  // What the computed below answers for a key the document state does not hold:
@@ -65393,8 +65429,8 @@ const PickerCustom = props => {
65393
65429
  // before computing popupId below, so two Pickers without an explicit id never collide.
65394
65430
  // Captured before the fallback chain below overwrites props.id — needed to
65395
65431
  // know whether the id actually came from the caller (stable) or from
65396
- // useId()/ControlIdContext (not guaranteed stable across a reload), see
65397
- // pickerNavType below.
65432
+ // useId()/ControlIdContext (a generated id names one mount: a reload, or a
65433
+ // return to this page, generates another), see pickerNavType below.
65398
65434
  const hasExplicitId = Boolean(props.id);
65399
65435
  const idDefault = useId();
65400
65436
  const controlId = useContext(ControlIdContext);
@@ -65471,10 +65507,12 @@ const PickerCustom = props => {
65471
65507
  // pushes a history entry so the back button closes it. Every other case
65472
65508
  // (popover mode, or a dialog whose id was auto-generated via useId()/
65473
65509
  // ControlIdContext) replaces the current history state instead — a
65474
- // generated id isn't stable across a reload, so pushing it would either
65475
- // silently drop the entry or, worse, collide with a different
65476
- // component's own generated id (see useNavState's own fallback for the
65477
- // same concern, applied here proactively for the id we control).
65510
+ // generated id names one mount, so pushing it would either leave an entry
65511
+ // nothing reads or, worse, collide with a different component's own
65512
+ // generated id (see useNavState's own fallback for the same concern,
65513
+ // applied here proactively for the id we control). What a generated id
65514
+ // costs either way: the state is written, and the mount coming back to
65515
+ // the page (or a reload) finds it under a key it does not have.
65478
65516
  const pickerNavType = mode === "dialog" && hasExplicitId ? "push" : "replace";
65479
65517
  const [expanded, enterExpanded, leaveExpanded] = useNavState(popupId, {
65480
65518
  type: pickerNavType,
@@ -75743,6 +75781,12 @@ const PickerFirstResolver = props => {
75743
75781
  * content failed to load, its value could not be resolved…). Shown as a
75744
75782
  * callout on the trigger, open or closed — the caller has nothing to place.
75745
75783
  * Dismissing it discards that error; a new `error` value raises another one.
75784
+ * @param {string} [id] What the popup's open state is kept under in the
75785
+ * history entry: a screen left and come back to finds the picker open, and
75786
+ * in dialog mode the opening is an entry of its own, closed by the back
75787
+ * button before the screen is left. Left out, the key is a generated id,
75788
+ * which names one mount: the state survives neither leaving the screen nor a
75789
+ * reload. A picker whose popup leads somewhere (a link inside it) has one.
75746
75790
  * @param {"popover"|"dialog"|"callout"} [mode] Which popup the children open
75747
75791
  * in. Left out, a popover on a large screen and a dialog on a narrow one.
75748
75792
  * `"callout"` shows them in the picker's own callout — the speech bubble its