@jsenv/navi 0.27.55 → 0.27.57

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.
@@ -14530,6 +14530,14 @@ computed(() => {
14530
14530
  return reasonArray;
14531
14531
  });
14532
14532
 
14533
+ const documentStateSignal = signal(null);
14534
+ const useDocumentState = () => {
14535
+ return documentStateSignal.value;
14536
+ };
14537
+ const updateDocumentState = (value) => {
14538
+ documentStateSignal.value = value;
14539
+ };
14540
+
14533
14541
  const documentUrlSignal = signal(
14534
14542
  typeof window === "undefined" ? "http://localhost" : window.location.href,
14535
14543
  );
@@ -14574,14 +14582,6 @@ const urlToScheme = (url) => {
14574
14582
  return scheme;
14575
14583
  };
14576
14584
 
14577
- const documentStateSignal = signal(null);
14578
- const useDocumentState = () => {
14579
- return documentStateSignal.value;
14580
- };
14581
- const updateDocumentState = (value) => {
14582
- documentStateSignal.value = value;
14583
- };
14584
-
14585
14585
  const getHrefTargetInfo = (href) => {
14586
14586
  href = String(href);
14587
14587
 
@@ -14664,18 +14664,6 @@ const setupBrowserIntegrationViaHistory = ({
14664
14664
  return window.history.state ? { ...window.history.state } : null;
14665
14665
  };
14666
14666
 
14667
- const replaceDocumentState = (
14668
- newState,
14669
- { reason = "replaceDocumentState called" } = {},
14670
- ) => {
14671
- const url = window.location.href;
14672
- handleRoutingTask(url, {
14673
- reason,
14674
- navigationType: "replace",
14675
- state: newState,
14676
- });
14677
- };
14678
-
14679
14667
  const historyStartAtStart = getDocumentState();
14680
14668
  const visitedUrlSet = historyStartAtStart
14681
14669
  ? new Set(historyStartAtStart.jsenv_visited_urls || [])
@@ -14695,19 +14683,7 @@ const setupBrowserIntegrationViaHistory = ({
14695
14683
  return;
14696
14684
  }
14697
14685
  visitedUrlSet.add(url);
14698
- visitedUrlsSignal.value++; // Increment signal to notify subscribers that visited URLs changed
14699
-
14700
- const historyState = getDocumentState() || {};
14701
- const historyStateWithVisitedUrls = {
14702
- ...historyState,
14703
- jsenv_visited_urls: Array.from(visitedUrlSet),
14704
- };
14705
- window.history.replaceState(
14706
- historyStateWithVisitedUrls,
14707
- null,
14708
- window.location.href,
14709
- );
14710
- updateDocumentState(historyStateWithVisitedUrls);
14686
+ visitedUrlsSignal.value++;
14711
14687
  };
14712
14688
 
14713
14689
  let abortController = null;
@@ -14715,19 +14691,38 @@ const setupBrowserIntegrationViaHistory = ({
14715
14691
  url,
14716
14692
  {
14717
14693
  reason,
14718
- navigationType, // "push", "reload", "replace", "traverse"
14694
+ navigationType, // "load", "reload", "replace", "push", "traverse"
14719
14695
  state,
14720
14696
  },
14721
14697
  ) => {
14722
- if (navigationType === "push") {
14723
- window.history.pushState(state, null, url);
14724
- } else if (navigationType === "replace") {
14725
- window.history.replaceState(state, null, url);
14698
+ const isSameUrl = url === window.location.href;
14699
+
14700
+ if (navigationType === "push" || navigationType === "replace") {
14701
+ // Pre-merge visited URLs so push/replaceState is called only once.
14702
+ markUrlAsVisited(url);
14703
+ const effectiveState = {
14704
+ ...(state || {}),
14705
+ jsenv_visited_urls: Array.from(visitedUrlSet),
14706
+ };
14707
+ if (navigationType === "push") {
14708
+ window.history.pushState(effectiveState, null, url);
14709
+ } else {
14710
+ window.history.replaceState(effectiveState, null, url);
14711
+ }
14712
+ updateDocumentUrl(url);
14713
+ updateDocumentState(effectiveState);
14714
+ } else {
14715
+ // traverse / reload: state comes from the history entry, no push/replace needed.
14716
+ markUrlAsVisited(url);
14717
+ updateDocumentUrl(url);
14718
+ updateDocumentState(state);
14719
+ }
14720
+
14721
+ // Routes only match on URL — skip route matching for state-only changes.
14722
+ if (isSameUrl && navigationType !== "reload" && navigationType !== "load") {
14723
+ return undefined;
14726
14724
  }
14727
14725
 
14728
- updateDocumentUrl(url);
14729
- updateDocumentState(state);
14730
- markUrlAsVisited(url);
14731
14726
  if (abortController) {
14732
14727
  abortController.abort(`navigating to ${url}`);
14733
14728
  }
@@ -14847,7 +14842,7 @@ const setupBrowserIntegrationViaHistory = ({
14847
14842
  const state = history.state;
14848
14843
  handleRoutingTask(url, {
14849
14844
  reason: "routing initialization",
14850
- navigationType: "replace",
14845
+ navigationType: "load",
14851
14846
  state,
14852
14847
  });
14853
14848
  };
@@ -14861,7 +14856,6 @@ const setupBrowserIntegrationViaHistory = ({
14861
14856
  navBack,
14862
14857
  navForward,
14863
14858
  getDocumentState,
14864
- replaceDocumentState,
14865
14859
  isVisited,
14866
14860
  visitedUrlsSignal,
14867
14861
  };
@@ -14947,12 +14941,19 @@ setOnAllRouteReady((v) => {
14947
14941
  });
14948
14942
  setRouteIntegration(browserIntegration);
14949
14943
 
14950
- const actionIntegratedVia = browserIntegration.integration;
14944
+ const navIntegratedVia = browserIntegration.integration;
14951
14945
  const navTo = (target, options) => {
14952
14946
  const url = new URL(target, window.location.href).href;
14953
14947
  const currentUrl = documentUrlSignal.peek();
14954
14948
  if (url === currentUrl) {
14955
- return null;
14949
+ if (options?.state === undefined) {
14950
+ return null;
14951
+ }
14952
+ // State-only update on same URL: skip if state is identical to current.
14953
+ const currentState = browserIntegration.getDocumentState();
14954
+ if (compareTwoJsValues(options.state, currentState)) {
14955
+ return null;
14956
+ }
14956
14957
  }
14957
14958
  return browserIntegration.navTo(url, options);
14958
14959
  };
@@ -14973,78 +14974,113 @@ const isVisited = browserIntegration.isVisited;
14973
14974
  const visitedUrlsSignal = browserIntegration.visitedUrlsSignal;
14974
14975
  browserIntegration.handleActionTask;
14975
14976
 
14976
- const NOT_SET = {};
14977
14977
  const NO_OP = () => {};
14978
14978
  const NO_ID_GIVEN = [undefined, NO_OP, NO_OP];
14979
- const useNavStateBasic = (id, initialValue, { debug } = {}) => {
14980
- const navStateRef = useRef(NOT_SET);
14979
+ const useNavStateBasic = (
14980
+ id,
14981
+ initialValue,
14982
+ { debug, type = "replace", onLeave } = {},
14983
+ ) => {
14984
+ // Hooks must be called unconditionally — before the !id early return.
14985
+ const state = documentStateSignal.value;
14986
+ const valueInState = id
14987
+ ? state !== null
14988
+ ? state[id]
14989
+ : undefined
14990
+ : undefined;
14991
+ const onLeaveRef = useRef(onLeave);
14992
+ onLeaveRef.current = onLeave;
14993
+ const prevValueInStateRef = useRef(valueInState);
14994
+ // enteredRef tracks whether enter() was called without a matching leave() yet.
14995
+ // It lets the effect distinguish an external disappearance (back button → fire onLeave)
14996
+ // from a programmatic one (leave() already set it to false before the state updates).
14997
+ const enteredRef = useRef(false);
14998
+ useEffect(() => {
14999
+ const prevValue = prevValueInStateRef.current;
15000
+ prevValueInStateRef.current = valueInState;
15001
+ if (
15002
+ prevValue !== undefined &&
15003
+ valueInState === undefined &&
15004
+ enteredRef.current
15005
+ ) {
15006
+ enteredRef.current = false;
15007
+ onLeaveRef.current?.();
15008
+ }
15009
+ }, [valueInState]);
15010
+
14981
15011
  if (!id) {
14982
15012
  return NO_ID_GIVEN;
14983
15013
  }
14984
15014
 
14985
- if (navStateRef.current === NOT_SET) {
14986
- const documentState = browserIntegration.getDocumentState();
14987
- const valueInDocumentState = documentState ? documentState[id] : undefined;
14988
- if (valueInDocumentState === undefined) {
14989
- navStateRef.current = initialValue;
14990
- if (initialValue !== undefined) {
14991
- console.debug(
14992
- `useNavState(${id}) initial value is ${initialValue} (from initialValue passed in as argument)`,
14993
- );
14994
- }
14995
- } else {
14996
- navStateRef.current = valueInDocumentState;
14997
- if (debug) {
14998
- console.debug(
14999
- `useNavState(${id}) initial value is ${initialValue} (from nav state)`,
15000
- );
15001
- }
15002
- }
15003
- }
15015
+ const currentValue = valueInState !== undefined ? valueInState : initialValue;
15004
15016
 
15005
- const set = (value) => {
15006
- const currentValue = navStateRef.current;
15007
- if (typeof value === "function") {
15008
- value = value(currentValue);
15009
- }
15010
- if (debug) {
15011
- console.debug(
15012
- `useNavState(${id}) set ${value} (previous was ${currentValue})`,
15013
- );
15014
- }
15017
+ if (debug) {
15018
+ console.debug(`useNavState(${id}) current value is ${currentValue}`);
15019
+ }
15015
15020
 
15021
+ // enter(value): navigate TO this state (push or replace depending on type).
15022
+ // Calling enter() without a value stores "on" — the mere presence of the key
15023
+ // in the document state is enough to match; the value just allows associating
15024
+ // extra data with the entry when needed.
15025
+ const enter = (value = "on") => {
15026
+ enteredRef.current = true;
15016
15027
  const currentState = browserIntegration.getDocumentState() || {};
15017
-
15018
- if (value === undefined) {
15019
- if (!Object.hasOwn(currentState, id)) {
15020
- return;
15021
- }
15022
- delete currentState[id];
15023
- browserIntegration.replaceDocumentState(currentState, {
15024
- reason: `delete "${id}" from browser state`,
15025
- });
15028
+ if (currentState[id] === value) {
15026
15029
  return;
15027
15030
  }
15031
+ const newState = { ...currentState, [id]: value };
15032
+ navTo(window.location.href, {
15033
+ replace: type !== "push",
15034
+ state: newState,
15035
+ });
15036
+ };
15028
15037
 
15029
- const valueInBrowserState = currentState[id];
15030
- if (valueInBrowserState === value) {
15038
+ // leave(): navigate AWAY FROM this state (navBack in push mode, replace in replace mode).
15039
+ const leave = () => {
15040
+ enteredRef.current = false;
15041
+ const currentState = browserIntegration.getDocumentState() || {};
15042
+ if (!Object.hasOwn(currentState, id)) {
15031
15043
  return;
15032
15044
  }
15033
- currentState[id] = value;
15034
- browserIntegration.replaceDocumentState(currentState, {
15035
- reason: `set { ${id}: ${value} } in browser state`,
15036
- });
15045
+ if (type === "push") {
15046
+ browserIntegration.navBack();
15047
+ } else {
15048
+ const newState = { ...currentState };
15049
+ delete newState[id];
15050
+ navTo(window.location.href, { replace: true, state: newState });
15051
+ }
15037
15052
  };
15038
15053
 
15039
- return [
15040
- navStateRef.current,
15041
- set,
15042
- () => {
15043
- set(undefined);
15044
- },
15045
- ];
15054
+ return [currentValue, enter, leave];
15046
15055
  };
15047
15056
 
15057
+ /**
15058
+ * Stores a named value in the browser's document state and returns it reactively.
15059
+ * The component re-renders whenever the value changes (navigation, back/forward button).
15060
+ *
15061
+ * @param {string} id
15062
+ * Unique key used to store the value in document state. Must be stable across renders.
15063
+ *
15064
+ * @param {*} initialValue
15065
+ * Value returned when `id` is absent from document state (e.g. before enter() is called).
15066
+ *
15067
+ * @param {object} [options]
15068
+ * @param {"push"|"replace"} [options.type="replace"]
15069
+ * Controls how enter() adds the state to browser history.
15070
+ * - "push": creates a new history entry — pressing the back button removes it and calls onLeave.
15071
+ * - "replace": updates the current history entry — no extra history entry is created.
15072
+ * @param {() => void} [options.onLeave]
15073
+ * Called when the state key disappears **externally** — e.g. the user presses the browser
15074
+ * back button. Not called when leave() is invoked programmatically.
15075
+ *
15076
+ * @returns {[value, enter, leave]}
15077
+ * - `value`: current value from document state, or `initialValue` when the key is absent.
15078
+ * - `enter(value = "on")`: navigate TO this state (stores `value` under `id`).
15079
+ * Calling without an argument stores `"on"` — the presence of the key is enough to match;
15080
+ * the value allows associating extra data when needed.
15081
+ * - `leave()`: navigate AWAY FROM this state (removes `id` from document state,
15082
+ * or goes back in history when `type` is "push").
15083
+ */
15048
15084
  const useNavState = useNavStateBasic;
15049
15085
 
15050
15086
  const NEVER_SET = {};
@@ -35382,14 +35418,17 @@ const markAutofocusRestoreOnClose = (containerEl) => {
35382
35418
  // meaning the focus did not yet reach the element receiving the mousedown
35383
35419
  // as a result document.activeElement is not up-to-date (can be document.body for instance)
35384
35420
  const getFocusedBeforeTransfer = (e) => {
35385
- const initiator = e.detail.eventChain[0];
35386
- if (initiator.type === "mousedown") {
35387
- // if we we had let browser give focus, the element would be the one that would be focused
35388
- return initiator.currentTarget;
35389
- }
35390
- if (initiator.type === "click") {
35391
- // label use case
35392
- return initiator.currentTarget;
35421
+ const initiator =
35422
+ e.detail && e.detail.eventChain ? e.detail.eventChain[0] : null;
35423
+ if (initiator) {
35424
+ if (initiator.type === "mousedown") {
35425
+ // if we we had let browser give focus, the element would be the one that would be focused
35426
+ return initiator.currentTarget;
35427
+ }
35428
+ if (initiator.type === "click") {
35429
+ // label use case
35430
+ return initiator.currentTarget;
35431
+ }
35393
35432
  }
35394
35433
  return document.activeElement;
35395
35434
  };
@@ -35486,6 +35525,7 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
35486
35525
  margin-top: var(--dialog-top-inset, auto);
35487
35526
  margin-bottom: auto;
35488
35527
  flex-direction: column;
35528
+ transition: margin-top 0.1s ease-in-out;
35489
35529
  }
35490
35530
 
35491
35531
  &::backdrop {
@@ -35496,6 +35536,8 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
35496
35536
  const Dialog = props => {
35497
35537
  import.meta.css = [css$r, "@jsenv/navi/src/popup/dialog.jsx"];
35498
35538
  const {
35539
+ open: openProp = false,
35540
+ anchorRef,
35499
35541
  children,
35500
35542
  scrollTrap,
35501
35543
  pointerTrap,
@@ -35508,11 +35550,14 @@ const Dialog = props => {
35508
35550
  const debugPopup = useDebugPopup();
35509
35551
  const debugFocus = useDebugFocus();
35510
35552
  const autoFocusProps = useAutoFocus(ref, props.autoFocus);
35553
+
35554
+ // Tracks actual DOM state (open/closed), updated only by open() and close().
35555
+ // Intentionally NOT synced to openProp on every render so the useEffect guard
35556
+ // below can detect whether a prop change requires action.
35511
35557
  const openedRef = useRef(false);
35512
35558
  const [addCleanup, cleanup] = useCleanup();
35513
- const open = (e, {
35514
- anchor
35515
- }) => {
35559
+ const open = e => {
35560
+ const anchor = anchorRef?.current ?? null;
35516
35561
  const effectiveAnchor = anchor || document.documentElement;
35517
35562
  debugPopup(`"${e.type}" on ${getElementSignature(e.target)} -> openDialog`);
35518
35563
  const dialogEl = ref.current;
@@ -35581,9 +35626,7 @@ const Dialog = props => {
35581
35626
  ...detail
35582
35627
  });
35583
35628
  };
35584
- const onRequestOpen = (e, {
35585
- anchor
35586
- }) => {
35629
+ const onRequestOpen = e => {
35587
35630
  const dialogEl = ref.current;
35588
35631
  if (!dialogEl) {
35589
35632
  return;
@@ -35591,9 +35634,7 @@ const Dialog = props => {
35591
35634
  if (openedRef.current) {
35592
35635
  return;
35593
35636
  }
35594
- open(e, {
35595
- anchor
35596
- });
35637
+ open(e);
35597
35638
  };
35598
35639
  const onRequestClose = (e, detail = {}) => {
35599
35640
  const dialogEl = ref.current;
@@ -35623,6 +35664,25 @@ const Dialog = props => {
35623
35664
  }
35624
35665
  close(e, detail);
35625
35666
  };
35667
+ useLayoutEffect(() => {
35668
+ if (openProp === undefined) {
35669
+ return;
35670
+ }
35671
+ // Skip when the popover is already in the desired state.
35672
+ // This avoids a feedback loop: our own close/open dispatches navi_close/navi_open,
35673
+ // the parent updates the prop, and the effect would fire again for a change we
35674
+ // already handled. Comparing against openedRef is the authoritative check because
35675
+ // it reflects the actual DOM state, not the React render cycle.
35676
+ if (openProp === openedRef.current) {
35677
+ return;
35678
+ }
35679
+ const e = new CustomEvent("open_prop_change");
35680
+ if (openProp) {
35681
+ onRequestOpen(e);
35682
+ } else {
35683
+ onRequestClose(e);
35684
+ }
35685
+ }, [openProp]);
35626
35686
  return jsx(Box, {
35627
35687
  ...rest,
35628
35688
  ...autoFocusProps,
@@ -35652,12 +35712,7 @@ const Dialog = props => {
35652
35712
  onRequestClose(e);
35653
35713
  },
35654
35714
  onnavi_request_open: e => {
35655
- const {
35656
- anchor
35657
- } = e.detail;
35658
- onRequestOpen(e, {
35659
- anchor
35660
- });
35715
+ onRequestOpen(e);
35661
35716
  },
35662
35717
  onnavi_request_close: e => {
35663
35718
  onRequestClose(e);
@@ -35692,6 +35747,8 @@ installImportMetaCssBuild(import.meta);const css$q = /* css */`
35692
35747
  const Popover = props => {
35693
35748
  import.meta.css = [css$q, "@jsenv/navi/src/popup/popover.jsx"];
35694
35749
  const {
35750
+ open: openProp = false,
35751
+ anchorRef,
35695
35752
  scrollTrap,
35696
35753
  pointerTrap,
35697
35754
  focusTrap,
@@ -35712,18 +35769,19 @@ const Popover = props => {
35712
35769
  const debugPopup = useDebugPopup();
35713
35770
  const debugFocus = useDebugFocus();
35714
35771
  const autoFocusProps = useAutoFocus(ref, props.autoFocus);
35715
- const [opened, setOpened] = useState(false);
35716
- const openedRef = useRef(opened);
35717
- openedRef.current = opened;
35772
+
35773
+ // Tracks actual DOM state (open/closed), updated only by open() and close().
35774
+ // Intentionally NOT synced to openProp on every render so the useEffect guard
35775
+ // below can detect whether a prop change requires action.
35776
+ const openedRef = useRef(false);
35718
35777
  const [addCleanup, cleanup] = useCleanup();
35719
- const open = (e, {
35720
- anchor
35721
- }) => {
35778
+ const open = e => {
35722
35779
  debugPopup(e, `openPopover()`);
35723
35780
  const popoverEl = ref.current;
35724
35781
  const focusedBeforeOpen = getFocusedBeforeTransfer(e);
35725
35782
  popoverEl.showPopover();
35726
35783
  transferFocus(popoverEl, debugFocus, e, focusedBeforeOpen);
35784
+ const anchor = anchorRef?.current ?? null;
35727
35785
  const effectiveAnchor = anchor || document.documentElement;
35728
35786
  const positionPopover = positionEvent => {
35729
35787
  const {
@@ -35796,7 +35854,6 @@ const Popover = props => {
35796
35854
  rectEffect.disconnect();
35797
35855
  });
35798
35856
  openedRef.current = true;
35799
- setOpened(true);
35800
35857
  dispatchCustomEvent(popoverEl, "navi_open", {
35801
35858
  event: e,
35802
35859
  focusedBeforeOpen
@@ -35809,15 +35866,12 @@ const Popover = props => {
35809
35866
  popoverEl.hidePopover();
35810
35867
  cleanup();
35811
35868
  openedRef.current = false;
35812
- setOpened(false);
35813
35869
  dispatchCustomEvent(popoverEl, "navi_close", {
35814
35870
  event: e,
35815
35871
  ...detail
35816
35872
  });
35817
35873
  };
35818
- const onRequestOpen = (e, {
35819
- anchor
35820
- }) => {
35874
+ const onRequestOpen = e => {
35821
35875
  const popoverEl = ref.current;
35822
35876
  if (!popoverEl) {
35823
35877
  return;
@@ -35825,9 +35879,7 @@ const Popover = props => {
35825
35879
  if (openedRef.current) {
35826
35880
  return;
35827
35881
  }
35828
- open(e, {
35829
- anchor
35830
- });
35882
+ open(e);
35831
35883
  };
35832
35884
  const onRequestClose = (e, detail = {}) => {
35833
35885
  const popoverEl = ref.current;
@@ -35857,6 +35909,27 @@ const Popover = props => {
35857
35909
  }
35858
35910
  close(e, detail);
35859
35911
  };
35912
+ useLayoutEffect(() => {
35913
+ if (openProp === undefined) {
35914
+ return;
35915
+ }
35916
+ // Skip when the popover is already in the desired state.
35917
+ // This avoids a feedback loop: our own close/open dispatches navi_close/navi_open,
35918
+ // the parent updates the prop, and the effect would fire again for a change we
35919
+ // already handled. Comparing against openedRef is the authoritative check because
35920
+ // it reflects the actual DOM state, not the React render cycle.
35921
+ if (openProp === openedRef.current) {
35922
+ return;
35923
+ }
35924
+ const e = new CustomEvent("open_prop_change", {
35925
+ detail: {}
35926
+ });
35927
+ if (openProp) {
35928
+ onRequestOpen(e);
35929
+ } else {
35930
+ onRequestClose(e);
35931
+ }
35932
+ }, [openProp]);
35860
35933
  return jsxs(Box, {
35861
35934
  id: id,
35862
35935
  popover: "manual",
@@ -35866,12 +35939,7 @@ const Popover = props => {
35866
35939
  baseClassName: "navi_popover",
35867
35940
  pseudoClasses: POPOVER_PSEUDO_CLASSES,
35868
35941
  onnavi_request_open: e => {
35869
- const {
35870
- anchor
35871
- } = e.detail;
35872
- onRequestOpen(e, {
35873
- anchor
35874
- });
35942
+ onRequestOpen(e);
35875
35943
  },
35876
35944
  onnavi_request_close: e => {
35877
35945
  onRequestClose(e);
@@ -36177,8 +36245,9 @@ const PickerCustom = props => {
36177
36245
  const popupRef = useRef(null);
36178
36246
  popupProps.ref = popupRef;
36179
36247
  // aria-controls + id
36248
+ const autoId = useId();
36249
+ const popupId = `picker_popup_${autoId}`;
36180
36250
  {
36181
- const popupId = useId();
36182
36251
  Object.assign(pickerProps, {
36183
36252
  "aria-controls": popupId
36184
36253
  });
@@ -36190,14 +36259,27 @@ const PickerCustom = props => {
36190
36259
  {
36191
36260
  const debugFocus = useDebugFocus();
36192
36261
  const debugPopup = useDebugPopup();
36193
- const [expanded, setExpanded] = useState(false);
36194
- const expandedRef = useRef(expanded);
36195
- expandedRef.current = expanded;
36262
+ // In "dialog" mode, enterExpanded() pushes a history entry so the back button closes.
36263
+ // In "popover" mode, it replaces the current history state (no history entry added).
36264
+ const pickerNavType = mode === "dialog" ? "push" : "replace";
36265
+ const expandedRef = useRef(false);
36266
+ const [expanded, enterExpanded, leaveExpanded] = useNavState(popupId, false, {
36267
+ type: pickerNavType,
36268
+ // onLeave fires only when the state key disappears externally (back button/gesture most of the time).
36269
+ onLeave: () => {
36270
+ requestClose(new CustomEvent("navi_nav_away", {
36271
+ detail: {}
36272
+ }), {
36273
+ isCancel: true
36274
+ });
36275
+ }
36276
+ });
36277
+ expandedRef.current = Boolean(expanded);
36196
36278
  const valueAtOpenRef = useRef(null);
36197
36279
  const activeElementAtOpenRef = useRef(null);
36198
36280
  const onOpen = e => {
36199
36281
  expandedRef.current = true;
36200
- setExpanded(true);
36282
+ enterExpanded();
36201
36283
  const focusedBeforeOpen = e.detail.focusedBeforeOpen;
36202
36284
  activeElementAtOpenRef.current = focusedBeforeOpen;
36203
36285
  debugFocus(e, "picked opened, store element focused", focusedBeforeOpen);
@@ -36240,7 +36322,7 @@ const PickerCustom = props => {
36240
36322
  }
36241
36323
  }
36242
36324
  expandedRef.current = false;
36243
- setExpanded(false);
36325
+ leaveExpanded();
36244
36326
  // Reset so the next opening re-evaluates screen size
36245
36327
  defaultModeRef.current = null;
36246
36328
  restoreFocus(e);
@@ -36256,8 +36338,7 @@ const PickerCustom = props => {
36256
36338
  });
36257
36339
  const popupEl = popupRef.current;
36258
36340
  return dispatchCustomEvent(popupEl, "navi_request_open", {
36259
- event: e,
36260
- anchor: pickerEl
36341
+ event: e
36261
36342
  });
36262
36343
  };
36263
36344
  const requestClose = (e = new CustomEvent("programmatic"), {
@@ -36278,7 +36359,7 @@ const PickerCustom = props => {
36278
36359
  uiAction: uiActionProp
36279
36360
  } = props;
36280
36361
  Object.assign(pickerProps, {
36281
- "aria-expanded": expanded,
36362
+ "aria-expanded": Boolean(expanded),
36282
36363
  "onActionStart": e => {
36283
36364
  onActionStart?.(e);
36284
36365
  // requestClose(e);
@@ -36309,6 +36390,8 @@ const PickerCustom = props => {
36309
36390
  children
36310
36391
  });
36311
36392
  Object.assign(popupProps, {
36393
+ anchorRef: props.ref,
36394
+ open: Boolean(expanded),
36312
36395
  closeRequestHandler: (requestCloseEvent, closePermission, {
36313
36396
  isClickOutside
36314
36397
  } = {}) => {
@@ -47089,5 +47172,5 @@ const UserSvg = () => jsx("svg", {
47089
47172
  })
47090
47173
  });
47091
47174
 
47092
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, Details, Dialog, DialogLayout, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, Paragraph, Picker, Popover, Quantity, RadioGroup, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, actionIntegratedVia, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, langSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSidePanelClose, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
47175
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, Details, Dialog, DialogLayout, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, Paragraph, Picker, Popover, Quantity, RadioGroup, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, langSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSidePanelClose, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
47093
47176
  //# sourceMappingURL=jsenv_navi.js.map