@jsenv/navi 0.27.59 → 0.27.61
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/jsenv_navi.js +200 -196
- package/dist/jsenv_navi.js.map +16 -21
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -14696,23 +14696,37 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
14696
14696
|
};
|
|
14697
14697
|
|
|
14698
14698
|
let abortController = null;
|
|
14699
|
-
const handleRoutingTask = (
|
|
14700
|
-
url
|
|
14701
|
-
{
|
|
14699
|
+
const handleRoutingTask = (url, options) => {
|
|
14700
|
+
const isSameUrl = url === window.location.href;
|
|
14701
|
+
const {
|
|
14702
14702
|
reason,
|
|
14703
14703
|
navigationType, // "load", "reload", "replace", "push", "traverse"
|
|
14704
14704
|
state,
|
|
14705
|
-
}
|
|
14706
|
-
) => {
|
|
14707
|
-
const isSameUrl = url === window.location.href;
|
|
14705
|
+
} = options;
|
|
14708
14706
|
|
|
14709
14707
|
if (navigationType === "push" || navigationType === "replace") {
|
|
14710
|
-
// Pre-merge visited URLs so push/replaceState is called only once.
|
|
14711
14708
|
markUrlAsVisited(url);
|
|
14712
|
-
|
|
14713
|
-
|
|
14709
|
+
// undefined → inherit current state (link click, neutral navigation)
|
|
14710
|
+
// null → explicit reset (no nav-state keys carried over)
|
|
14711
|
+
// {...} → explicit state from enter()/leave(), already built from currentState
|
|
14712
|
+
// When state is given it's responsability of the caller to ensure it inherits document state (or not, you want it 99% of the time)
|
|
14713
|
+
let effectiveState;
|
|
14714
|
+
const sharedState = {
|
|
14714
14715
|
jsenv_visited_urls: Array.from(visitedUrlSet),
|
|
14715
14716
|
};
|
|
14717
|
+
if (state === undefined) {
|
|
14718
|
+
effectiveState = {
|
|
14719
|
+
...(getDocumentState() || {}),
|
|
14720
|
+
...sharedState,
|
|
14721
|
+
};
|
|
14722
|
+
} else if (state === null) {
|
|
14723
|
+
effectiveState = sharedState;
|
|
14724
|
+
} else if (state) {
|
|
14725
|
+
effectiveState = {
|
|
14726
|
+
...state,
|
|
14727
|
+
...sharedState,
|
|
14728
|
+
};
|
|
14729
|
+
}
|
|
14716
14730
|
if (navigationType === "push") {
|
|
14717
14731
|
window.history.pushState(effectiveState, null, url);
|
|
14718
14732
|
} else {
|
|
@@ -14798,7 +14812,6 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
14798
14812
|
handleRoutingTask(href, {
|
|
14799
14813
|
reason: `"click" on a[href="${href}"]`,
|
|
14800
14814
|
navigationType: "push",
|
|
14801
|
-
state: null,
|
|
14802
14815
|
});
|
|
14803
14816
|
},
|
|
14804
14817
|
{ capture: true },
|
|
@@ -14823,7 +14836,7 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
14823
14836
|
});
|
|
14824
14837
|
});
|
|
14825
14838
|
|
|
14826
|
-
const navTo = async (url, { replace, state
|
|
14839
|
+
const navTo = async (url, { replace, state } = {}) => {
|
|
14827
14840
|
handleRoutingTask(url, {
|
|
14828
14841
|
reason: `navTo called with "${url}"`,
|
|
14829
14842
|
navigationType: replace ? "replace" : "push",
|
|
@@ -14994,41 +15007,33 @@ const NO_OP = () => {};
|
|
|
14994
15007
|
const NO_ID_GIVEN = [undefined, NO_OP, NO_OP];
|
|
14995
15008
|
const useNavStateBasic = (
|
|
14996
15009
|
id,
|
|
14997
|
-
|
|
14998
|
-
{ debug, type = "replace", onLeave } = {},
|
|
15010
|
+
{ debug, type = "replace", onLeave, defaultValue } = {},
|
|
14999
15011
|
) => {
|
|
15000
15012
|
// Hooks must be called unconditionally — before the !id early return.
|
|
15001
15013
|
const state = documentStateSignal.value;
|
|
15002
|
-
|
|
15003
|
-
|
|
15004
|
-
? state[id]
|
|
15005
|
-
: undefined
|
|
15006
|
-
: undefined;
|
|
15014
|
+
// Key presence is the flag — the value may be anything, including undefined.
|
|
15015
|
+
const keyInState = Boolean(id && state && Object.hasOwn(state, id));
|
|
15007
15016
|
const onLeaveRef = useRef(onLeave);
|
|
15008
15017
|
onLeaveRef.current = onLeave;
|
|
15009
|
-
const
|
|
15018
|
+
const prevKeyInStateRef = useRef(keyInState);
|
|
15010
15019
|
// enteredRef tracks whether enter() was called without a matching leave() yet.
|
|
15011
15020
|
// It lets the effect distinguish an external disappearance (back button → fire onLeave)
|
|
15012
15021
|
// from a programmatic one (leave() already set it to false before the state updates).
|
|
15013
15022
|
const enteredRef = useRef(false);
|
|
15014
15023
|
useEffect(() => {
|
|
15015
|
-
const
|
|
15016
|
-
|
|
15017
|
-
if (
|
|
15018
|
-
prevValue !== undefined &&
|
|
15019
|
-
valueInState === undefined &&
|
|
15020
|
-
enteredRef.current
|
|
15021
|
-
) {
|
|
15024
|
+
const prevKeyInState = prevKeyInStateRef.current;
|
|
15025
|
+
prevKeyInStateRef.current = keyInState;
|
|
15026
|
+
if (prevKeyInState && !keyInState && enteredRef.current) {
|
|
15022
15027
|
enteredRef.current = false;
|
|
15023
15028
|
onLeaveRef.current?.();
|
|
15024
15029
|
}
|
|
15025
|
-
}, [
|
|
15030
|
+
}, [keyInState]);
|
|
15026
15031
|
|
|
15027
15032
|
if (!id) {
|
|
15028
15033
|
return NO_ID_GIVEN;
|
|
15029
15034
|
}
|
|
15030
15035
|
|
|
15031
|
-
const currentValue =
|
|
15036
|
+
const currentValue = keyInState ? state[id] : defaultValue;
|
|
15032
15037
|
|
|
15033
15038
|
if (debug) {
|
|
15034
15039
|
console.debug(`useNavState(${id}) current value is ${currentValue}`);
|
|
@@ -15040,30 +15045,36 @@ const useNavStateBasic = (
|
|
|
15040
15045
|
// extra data with the entry when needed.
|
|
15041
15046
|
const enter = (value = "on") => {
|
|
15042
15047
|
enteredRef.current = true;
|
|
15043
|
-
const
|
|
15044
|
-
if (
|
|
15048
|
+
const currentStateCopy = browserIntegration.getDocumentState() || {};
|
|
15049
|
+
if (Object.hasOwn(currentStateCopy, id) && currentStateCopy[id] === value) {
|
|
15045
15050
|
return;
|
|
15046
15051
|
}
|
|
15047
|
-
|
|
15052
|
+
currentStateCopy[id] = value;
|
|
15048
15053
|
navTo(window.location.href, {
|
|
15049
15054
|
replace: type !== "push",
|
|
15050
|
-
state:
|
|
15055
|
+
state: currentStateCopy,
|
|
15051
15056
|
});
|
|
15052
15057
|
};
|
|
15053
15058
|
|
|
15054
15059
|
// leave(): navigate AWAY FROM this state (navBack in push mode, replace in replace mode).
|
|
15055
|
-
|
|
15060
|
+
// isBack: when true (cancel close in push mode), call history.back() to restore the
|
|
15061
|
+
// pre-open state — discards any in-progress edits.
|
|
15062
|
+
// When false (confirmed close), replace the pushed entry instead: preserves the
|
|
15063
|
+
// current URL state (e.g. a new picker value) while removing the popup key.
|
|
15064
|
+
const leave = ({ isBack } = {}) => {
|
|
15056
15065
|
enteredRef.current = false;
|
|
15057
|
-
const
|
|
15058
|
-
if (!Object.hasOwn(
|
|
15066
|
+
const currentStateCopy = browserIntegration.getDocumentState() || {};
|
|
15067
|
+
if (!Object.hasOwn(currentStateCopy, id)) {
|
|
15059
15068
|
return;
|
|
15060
15069
|
}
|
|
15061
|
-
if (type === "push") {
|
|
15070
|
+
if (type === "push" && isBack) {
|
|
15062
15071
|
browserIntegration.navBack();
|
|
15063
15072
|
} else {
|
|
15064
|
-
|
|
15065
|
-
|
|
15066
|
-
|
|
15073
|
+
delete currentStateCopy[id];
|
|
15074
|
+
navTo(window.location.href, {
|
|
15075
|
+
replace: true,
|
|
15076
|
+
state: currentStateCopy,
|
|
15077
|
+
});
|
|
15067
15078
|
}
|
|
15068
15079
|
};
|
|
15069
15080
|
|
|
@@ -15077,9 +15088,6 @@ const useNavStateBasic = (
|
|
|
15077
15088
|
* @param {string} id
|
|
15078
15089
|
* Unique key used to store the value in document state. Must be stable across renders.
|
|
15079
15090
|
*
|
|
15080
|
-
* @param {*} initialValue
|
|
15081
|
-
* Value returned when `id` is absent from document state (e.g. before enter() is called).
|
|
15082
|
-
*
|
|
15083
15091
|
* @param {object} [options]
|
|
15084
15092
|
* @param {"push"|"replace"} [options.type="replace"]
|
|
15085
15093
|
* Controls how enter() adds the state to browser history.
|
|
@@ -15088,9 +15096,11 @@ const useNavStateBasic = (
|
|
|
15088
15096
|
* @param {() => void} [options.onLeave]
|
|
15089
15097
|
* Called when the state key disappears **externally** — e.g. the user presses the browser
|
|
15090
15098
|
* back button. Not called when leave() is invoked programmatically.
|
|
15099
|
+
* @param {*} [options.defaultValue]
|
|
15100
|
+
* Value returned when `id` is absent from document state. Defaults to `undefined`.
|
|
15091
15101
|
*
|
|
15092
15102
|
* @returns {[value, enter, leave]}
|
|
15093
|
-
* - `value`: current value from document state, or `
|
|
15103
|
+
* - `value`: current value from document state, or `defaultValue` when the key is absent.
|
|
15094
15104
|
* - `enter(value = "on")`: navigate TO this state (stores `value` under `id`).
|
|
15095
15105
|
* Calling without an argument stores `"on"` — the presence of the key is enough to match;
|
|
15096
15106
|
* the value allows associating extra data when needed.
|
|
@@ -22705,6 +22715,23 @@ const useUIStateController = (
|
|
|
22705
22715
|
if (e.type === "facade_propagate_up") {
|
|
22706
22716
|
notifyParentAboutChildUIAction(e, { stateChanged: true });
|
|
22707
22717
|
}
|
|
22718
|
+
// Exception: state_prop_change can only fire on a control with its own
|
|
22719
|
+
// controlled state/value prop (see hasStateProp above) — groups never
|
|
22720
|
+
// cascade state down into such children (they're explicitly skipped,
|
|
22721
|
+
// see shouldPropagateStateToChild/hasStateProp checks), so this change
|
|
22722
|
+
// can never be an echo of the parent's own cascade. The loop risk this
|
|
22723
|
+
// suppression exists for only applies when the parent itself just pushed
|
|
22724
|
+
// this value down, which requires the parent to be controlled (have its
|
|
22725
|
+
// own state/value prop). When the parent is "stateless" (uncontrolled),
|
|
22726
|
+
// notifying it is always safe and necessary — otherwise its aggregated
|
|
22727
|
+
// state silently drifts out of sync with this child.
|
|
22728
|
+
if (
|
|
22729
|
+
e.type === "state_prop_change" &&
|
|
22730
|
+
parentUIStateController &&
|
|
22731
|
+
!parentUIStateController.hasStateProp
|
|
22732
|
+
) {
|
|
22733
|
+
notifyParentAboutChildUIAction(e, { stateChanged: true });
|
|
22734
|
+
}
|
|
22708
22735
|
return true;
|
|
22709
22736
|
}
|
|
22710
22737
|
notifyParentAboutChildUIAction(e, { stateChanged: true });
|
|
@@ -35552,7 +35579,8 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
|
|
|
35552
35579
|
const Dialog = props => {
|
|
35553
35580
|
import.meta.css = [css$r, "@jsenv/navi/src/popup/dialog.jsx"];
|
|
35554
35581
|
const {
|
|
35555
|
-
|
|
35582
|
+
// requestOpen,
|
|
35583
|
+
requestClose,
|
|
35556
35584
|
anchorRef,
|
|
35557
35585
|
children,
|
|
35558
35586
|
scrollTrap,
|
|
@@ -35630,7 +35658,7 @@ const Dialog = props => {
|
|
|
35630
35658
|
focusedBeforeOpen
|
|
35631
35659
|
});
|
|
35632
35660
|
};
|
|
35633
|
-
const close =
|
|
35661
|
+
const close = e => {
|
|
35634
35662
|
debugPopup(`"${e.type}" on ${getElementSignature(e.target)} -> closeDialog`);
|
|
35635
35663
|
const dialogEl = ref.current;
|
|
35636
35664
|
markAutofocusRestoreOnClose(dialogEl);
|
|
@@ -35638,70 +35666,9 @@ const Dialog = props => {
|
|
|
35638
35666
|
cleanup();
|
|
35639
35667
|
openedRef.current = false;
|
|
35640
35668
|
dispatchCustomEvent(dialogEl, "navi_close", {
|
|
35641
|
-
event: e
|
|
35642
|
-
...detail
|
|
35669
|
+
event: e
|
|
35643
35670
|
});
|
|
35644
35671
|
};
|
|
35645
|
-
const onRequestOpen = e => {
|
|
35646
|
-
const dialogEl = ref.current;
|
|
35647
|
-
if (!dialogEl) {
|
|
35648
|
-
return;
|
|
35649
|
-
}
|
|
35650
|
-
if (openedRef.current) {
|
|
35651
|
-
return;
|
|
35652
|
-
}
|
|
35653
|
-
open(e);
|
|
35654
|
-
};
|
|
35655
|
-
const onRequestClose = (e, detail = {}) => {
|
|
35656
|
-
const dialogEl = ref.current;
|
|
35657
|
-
if (!dialogEl) {
|
|
35658
|
-
return;
|
|
35659
|
-
}
|
|
35660
|
-
if (!openedRef.current) {
|
|
35661
|
-
return;
|
|
35662
|
-
}
|
|
35663
|
-
if (closeRequestHandler) {
|
|
35664
|
-
let denied = false;
|
|
35665
|
-
const closePermission = {
|
|
35666
|
-
deny: () => {
|
|
35667
|
-
denied = true;
|
|
35668
|
-
},
|
|
35669
|
-
allow: () => {
|
|
35670
|
-
denied = false;
|
|
35671
|
-
}
|
|
35672
|
-
};
|
|
35673
|
-
closeRequestHandler(e, closePermission, detail);
|
|
35674
|
-
if (denied) {
|
|
35675
|
-
if (e.type === "cancel") {
|
|
35676
|
-
e.preventDefault();
|
|
35677
|
-
}
|
|
35678
|
-
closePermission.allow = () => {
|
|
35679
|
-
close(e, detail);
|
|
35680
|
-
};
|
|
35681
|
-
return;
|
|
35682
|
-
}
|
|
35683
|
-
}
|
|
35684
|
-
close(e, detail);
|
|
35685
|
-
};
|
|
35686
|
-
useLayoutEffect(() => {
|
|
35687
|
-
if (openProp === undefined) {
|
|
35688
|
-
return;
|
|
35689
|
-
}
|
|
35690
|
-
// Skip when the popover is already in the desired state.
|
|
35691
|
-
// This avoids a feedback loop: our own close/open dispatches navi_close/navi_open,
|
|
35692
|
-
// the parent updates the prop, and the effect would fire again for a change we
|
|
35693
|
-
// already handled. Comparing against openedRef is the authoritative check because
|
|
35694
|
-
// it reflects the actual DOM state, not the React render cycle.
|
|
35695
|
-
if (openProp === openedRef.current) {
|
|
35696
|
-
return;
|
|
35697
|
-
}
|
|
35698
|
-
const e = new CustomEvent("open_prop_change");
|
|
35699
|
-
if (openProp) {
|
|
35700
|
-
onRequestOpen(e);
|
|
35701
|
-
} else {
|
|
35702
|
-
onRequestClose(e);
|
|
35703
|
-
}
|
|
35704
|
-
}, [openProp]);
|
|
35705
35672
|
return jsx(Box, {
|
|
35706
35673
|
...rest,
|
|
35707
35674
|
...autoFocusProps,
|
|
@@ -35718,20 +35685,57 @@ const Dialog = props => {
|
|
|
35718
35685
|
const rect = ref.current.getBoundingClientRect();
|
|
35719
35686
|
const isBackdrop = e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom;
|
|
35720
35687
|
if (isBackdrop) {
|
|
35721
|
-
|
|
35722
|
-
|
|
35688
|
+
requestClose(e, {
|
|
35689
|
+
isCancel: true
|
|
35723
35690
|
});
|
|
35724
35691
|
}
|
|
35725
35692
|
}
|
|
35726
35693
|
},
|
|
35727
35694
|
onCancel: e => {
|
|
35728
|
-
|
|
35695
|
+
requestClose(e, {
|
|
35696
|
+
isCancel: true
|
|
35697
|
+
});
|
|
35729
35698
|
},
|
|
35730
35699
|
onnavi_request_open: e => {
|
|
35731
|
-
|
|
35700
|
+
const dialogEl = ref.current;
|
|
35701
|
+
if (!dialogEl) {
|
|
35702
|
+
return;
|
|
35703
|
+
}
|
|
35704
|
+
if (openedRef.current) {
|
|
35705
|
+
return;
|
|
35706
|
+
}
|
|
35707
|
+
open(e);
|
|
35732
35708
|
},
|
|
35733
35709
|
onnavi_request_close: e => {
|
|
35734
|
-
|
|
35710
|
+
const dialogEl = ref.current;
|
|
35711
|
+
if (!dialogEl) {
|
|
35712
|
+
return;
|
|
35713
|
+
}
|
|
35714
|
+
if (!openedRef.current) {
|
|
35715
|
+
return;
|
|
35716
|
+
}
|
|
35717
|
+
if (closeRequestHandler) {
|
|
35718
|
+
let denied = false;
|
|
35719
|
+
const closePermission = {
|
|
35720
|
+
deny: () => {
|
|
35721
|
+
denied = true;
|
|
35722
|
+
},
|
|
35723
|
+
allow: () => {
|
|
35724
|
+
denied = false;
|
|
35725
|
+
}
|
|
35726
|
+
};
|
|
35727
|
+
closeRequestHandler(e, closePermission);
|
|
35728
|
+
if (denied) {
|
|
35729
|
+
if (e.type === "cancel") {
|
|
35730
|
+
e.preventDefault();
|
|
35731
|
+
}
|
|
35732
|
+
closePermission.allow = () => {
|
|
35733
|
+
close(e);
|
|
35734
|
+
};
|
|
35735
|
+
return;
|
|
35736
|
+
}
|
|
35737
|
+
}
|
|
35738
|
+
close(e);
|
|
35735
35739
|
},
|
|
35736
35740
|
children: children
|
|
35737
35741
|
});
|
|
@@ -35763,7 +35767,7 @@ installImportMetaCssBuild(import.meta);const css$q = /* css */`
|
|
|
35763
35767
|
const Popover = props => {
|
|
35764
35768
|
import.meta.css = [css$q, "@jsenv/navi/src/popup/popover.jsx"];
|
|
35765
35769
|
const {
|
|
35766
|
-
|
|
35770
|
+
requestClose,
|
|
35767
35771
|
anchorRef,
|
|
35768
35772
|
scrollTrap,
|
|
35769
35773
|
pointerTrap,
|
|
@@ -35875,7 +35879,7 @@ const Popover = props => {
|
|
|
35875
35879
|
focusedBeforeOpen
|
|
35876
35880
|
});
|
|
35877
35881
|
};
|
|
35878
|
-
const close =
|
|
35882
|
+
const close = e => {
|
|
35879
35883
|
debugPopup(e, `closePopover()`);
|
|
35880
35884
|
const popoverEl = ref.current;
|
|
35881
35885
|
markAutofocusRestoreOnClose(popoverEl);
|
|
@@ -35883,69 +35887,9 @@ const Popover = props => {
|
|
|
35883
35887
|
cleanup();
|
|
35884
35888
|
openedRef.current = false;
|
|
35885
35889
|
dispatchCustomEvent(popoverEl, "navi_close", {
|
|
35886
|
-
event: e
|
|
35887
|
-
...detail
|
|
35890
|
+
event: e
|
|
35888
35891
|
});
|
|
35889
35892
|
};
|
|
35890
|
-
const onRequestOpen = e => {
|
|
35891
|
-
const popoverEl = ref.current;
|
|
35892
|
-
if (!popoverEl) {
|
|
35893
|
-
return;
|
|
35894
|
-
}
|
|
35895
|
-
if (openedRef.current) {
|
|
35896
|
-
return;
|
|
35897
|
-
}
|
|
35898
|
-
open(e);
|
|
35899
|
-
};
|
|
35900
|
-
const onRequestClose = (e, detail = {}) => {
|
|
35901
|
-
const popoverEl = ref.current;
|
|
35902
|
-
if (!popoverEl) {
|
|
35903
|
-
return;
|
|
35904
|
-
}
|
|
35905
|
-
if (!openedRef.current) {
|
|
35906
|
-
return;
|
|
35907
|
-
}
|
|
35908
|
-
if (closeRequestHandler) {
|
|
35909
|
-
let denied = false;
|
|
35910
|
-
const closePermission = {
|
|
35911
|
-
deny: () => {
|
|
35912
|
-
denied = true;
|
|
35913
|
-
},
|
|
35914
|
-
allow: () => {
|
|
35915
|
-
denied = false;
|
|
35916
|
-
}
|
|
35917
|
-
};
|
|
35918
|
-
closeRequestHandler(e, closePermission, detail);
|
|
35919
|
-
if (denied) {
|
|
35920
|
-
closePermission.allow = () => {
|
|
35921
|
-
close(e, detail);
|
|
35922
|
-
};
|
|
35923
|
-
return;
|
|
35924
|
-
}
|
|
35925
|
-
}
|
|
35926
|
-
close(e, detail);
|
|
35927
|
-
};
|
|
35928
|
-
useLayoutEffect(() => {
|
|
35929
|
-
if (openProp === undefined) {
|
|
35930
|
-
return;
|
|
35931
|
-
}
|
|
35932
|
-
// Skip when the popover is already in the desired state.
|
|
35933
|
-
// This avoids a feedback loop: our own close/open dispatches navi_close/navi_open,
|
|
35934
|
-
// the parent updates the prop, and the effect would fire again for a change we
|
|
35935
|
-
// already handled. Comparing against openedRef is the authoritative check because
|
|
35936
|
-
// it reflects the actual DOM state, not the React render cycle.
|
|
35937
|
-
if (openProp === openedRef.current) {
|
|
35938
|
-
return;
|
|
35939
|
-
}
|
|
35940
|
-
const e = new CustomEvent("open_prop_change", {
|
|
35941
|
-
detail: {}
|
|
35942
|
-
});
|
|
35943
|
-
if (openProp) {
|
|
35944
|
-
onRequestOpen(e);
|
|
35945
|
-
} else {
|
|
35946
|
-
onRequestClose(e);
|
|
35947
|
-
}
|
|
35948
|
-
}, [openProp]);
|
|
35949
35893
|
return jsxs(Box, {
|
|
35950
35894
|
id: id,
|
|
35951
35895
|
popover: "manual",
|
|
@@ -35955,10 +35899,42 @@ const Popover = props => {
|
|
|
35955
35899
|
baseClassName: "navi_popover",
|
|
35956
35900
|
pseudoClasses: POPOVER_PSEUDO_CLASSES,
|
|
35957
35901
|
onnavi_request_open: e => {
|
|
35958
|
-
|
|
35902
|
+
const popoverEl = ref.current;
|
|
35903
|
+
if (!popoverEl) {
|
|
35904
|
+
return;
|
|
35905
|
+
}
|
|
35906
|
+
if (openedRef.current) {
|
|
35907
|
+
return;
|
|
35908
|
+
}
|
|
35909
|
+
open(e);
|
|
35959
35910
|
},
|
|
35960
35911
|
onnavi_request_close: e => {
|
|
35961
|
-
|
|
35912
|
+
const popoverEl = ref.current;
|
|
35913
|
+
if (!popoverEl) {
|
|
35914
|
+
return;
|
|
35915
|
+
}
|
|
35916
|
+
if (!openedRef.current) {
|
|
35917
|
+
return;
|
|
35918
|
+
}
|
|
35919
|
+
if (closeRequestHandler) {
|
|
35920
|
+
let denied = false;
|
|
35921
|
+
const closePermission = {
|
|
35922
|
+
deny: () => {
|
|
35923
|
+
denied = true;
|
|
35924
|
+
},
|
|
35925
|
+
allow: () => {
|
|
35926
|
+
denied = false;
|
|
35927
|
+
}
|
|
35928
|
+
};
|
|
35929
|
+
closeRequestHandler(e, closePermission);
|
|
35930
|
+
if (denied) {
|
|
35931
|
+
closePermission.allow = () => {
|
|
35932
|
+
close(e);
|
|
35933
|
+
};
|
|
35934
|
+
return;
|
|
35935
|
+
}
|
|
35936
|
+
}
|
|
35937
|
+
close(e);
|
|
35962
35938
|
},
|
|
35963
35939
|
children: [jsx("div", {
|
|
35964
35940
|
className: "navi_popover_backdrop",
|
|
@@ -35971,8 +35947,8 @@ const Popover = props => {
|
|
|
35971
35947
|
e.preventDefault();
|
|
35972
35948
|
return;
|
|
35973
35949
|
}
|
|
35974
|
-
|
|
35975
|
-
|
|
35950
|
+
requestClose(e, {
|
|
35951
|
+
isCancel: true
|
|
35976
35952
|
});
|
|
35977
35953
|
}
|
|
35978
35954
|
}), children]
|
|
@@ -36261,8 +36237,7 @@ const PickerCustom = props => {
|
|
|
36261
36237
|
const popupRef = useRef(null);
|
|
36262
36238
|
popupProps.ref = popupRef;
|
|
36263
36239
|
// aria-controls + id
|
|
36264
|
-
const
|
|
36265
|
-
const popupId = `picker_popup_${autoId}`;
|
|
36240
|
+
const popupId = `${props.id}_picker_popup`;
|
|
36266
36241
|
{
|
|
36267
36242
|
Object.assign(pickerProps, {
|
|
36268
36243
|
"aria-controls": popupId
|
|
@@ -36279,7 +36254,7 @@ const PickerCustom = props => {
|
|
|
36279
36254
|
// In "popover" mode, it replaces the current history state (no history entry added).
|
|
36280
36255
|
const pickerNavType = mode === "dialog" ? "push" : "replace";
|
|
36281
36256
|
const expandedRef = useRef(false);
|
|
36282
|
-
const [expanded, enterExpanded, leaveExpanded] = useNavState(popupId,
|
|
36257
|
+
const [expanded, enterExpanded, leaveExpanded] = useNavState(popupId, {
|
|
36283
36258
|
type: pickerNavType,
|
|
36284
36259
|
// onLeave fires only when the state key disappears externally (back button/gesture most of the time).
|
|
36285
36260
|
onLeave: () => {
|
|
@@ -36290,7 +36265,8 @@ const PickerCustom = props => {
|
|
|
36290
36265
|
});
|
|
36291
36266
|
}
|
|
36292
36267
|
});
|
|
36293
|
-
expandedRef
|
|
36268
|
+
// expandedRef tracks actual open/close state, updated only by onOpen/onClose.
|
|
36269
|
+
// NOT synced to expanded on every render so the useLayoutEffect guard below works.
|
|
36294
36270
|
const valueAtOpenRef = useRef(null);
|
|
36295
36271
|
const activeElementAtOpenRef = useRef(null);
|
|
36296
36272
|
const onOpen = e => {
|
|
@@ -36338,7 +36314,9 @@ const PickerCustom = props => {
|
|
|
36338
36314
|
}
|
|
36339
36315
|
}
|
|
36340
36316
|
expandedRef.current = false;
|
|
36341
|
-
leaveExpanded(
|
|
36317
|
+
leaveExpanded({
|
|
36318
|
+
isBack: e.detail.isCancel
|
|
36319
|
+
});
|
|
36342
36320
|
// Reset so the next opening re-evaluates screen size
|
|
36343
36321
|
defaultModeRef.current = null;
|
|
36344
36322
|
restoreFocus(e);
|
|
@@ -36346,7 +36324,7 @@ const PickerCustom = props => {
|
|
|
36346
36324
|
const disableClickFor = useIgnoreClickForMousedown(ref, e => {
|
|
36347
36325
|
debugPopup(e, `click ignored`);
|
|
36348
36326
|
});
|
|
36349
|
-
const requestOpen = e => {
|
|
36327
|
+
const requestOpen = (e, detail) => {
|
|
36350
36328
|
// scroll <button> of the picker into view when opening it
|
|
36351
36329
|
const pickerEl = ref.current;
|
|
36352
36330
|
pickerEl.scrollIntoView({
|
|
@@ -36354,18 +36332,47 @@ const PickerCustom = props => {
|
|
|
36354
36332
|
});
|
|
36355
36333
|
const popupEl = popupRef.current;
|
|
36356
36334
|
return dispatchCustomEvent(popupEl, "navi_request_open", {
|
|
36357
|
-
event: e
|
|
36335
|
+
event: e,
|
|
36336
|
+
...detail
|
|
36358
36337
|
});
|
|
36359
36338
|
};
|
|
36360
|
-
const requestClose = (e = new CustomEvent("programmatic"
|
|
36361
|
-
|
|
36362
|
-
}
|
|
36339
|
+
const requestClose = (e = new CustomEvent("programmatic", {
|
|
36340
|
+
detail: {}
|
|
36341
|
+
}), detail) => {
|
|
36363
36342
|
const popupEl = popupRef.current;
|
|
36364
36343
|
return dispatchCustomEvent(popupEl, "navi_request_close", {
|
|
36365
36344
|
event: e,
|
|
36366
|
-
|
|
36345
|
+
...detail
|
|
36367
36346
|
});
|
|
36368
36347
|
};
|
|
36348
|
+
const open = Boolean(expanded);
|
|
36349
|
+
useLayoutEffect(() => {
|
|
36350
|
+
if (open === undefined) {
|
|
36351
|
+
return;
|
|
36352
|
+
}
|
|
36353
|
+
// Skip when the popup is already in the desired state.
|
|
36354
|
+
// expandedRef tracks actual open/close (updated by onOpen/onClose, not by renders)
|
|
36355
|
+
// so it is the authoritative check against feedback loops.
|
|
36356
|
+
if (open === expandedRef.current) {
|
|
36357
|
+
return;
|
|
36358
|
+
}
|
|
36359
|
+
// open_prop_change means the parent is driving the open state directly
|
|
36360
|
+
// (e.g. back-button navigation flipped openProp to false before onLeave fires).
|
|
36361
|
+
// Always treat it as cancel — the user's in-progress edit should be discarded.
|
|
36362
|
+
if (open) {
|
|
36363
|
+
requestOpen(new CustomEvent("open_by_prop", {
|
|
36364
|
+
detail: {}
|
|
36365
|
+
}), {
|
|
36366
|
+
isCancel: true
|
|
36367
|
+
});
|
|
36368
|
+
} else {
|
|
36369
|
+
requestClose(new CustomEvent("close_by_prop", {
|
|
36370
|
+
detail: {}
|
|
36371
|
+
}), {
|
|
36372
|
+
isCancel: true
|
|
36373
|
+
});
|
|
36374
|
+
}
|
|
36375
|
+
}, [open]);
|
|
36369
36376
|
const requestInteraction = options => {
|
|
36370
36377
|
dispatchRequestInteraction(ref.current, options);
|
|
36371
36378
|
};
|
|
@@ -36407,12 +36414,9 @@ const PickerCustom = props => {
|
|
|
36407
36414
|
});
|
|
36408
36415
|
Object.assign(popupProps, {
|
|
36409
36416
|
anchorRef: props.ref,
|
|
36410
|
-
|
|
36411
|
-
closeRequestHandler: (requestCloseEvent, closePermission
|
|
36412
|
-
|
|
36413
|
-
} = {}) => {
|
|
36414
|
-
const cancelEvent = findEvent(requestCloseEvent, eInChain => eInChain.type === "navi_request_close" && eInChain.detail.isCancel);
|
|
36415
|
-
const isCancel = isClickOutside || Boolean(cancelEvent);
|
|
36417
|
+
requestClose,
|
|
36418
|
+
closeRequestHandler: (requestCloseEvent, closePermission) => {
|
|
36419
|
+
const isCancel = requestCloseEvent.detail.isCancel;
|
|
36416
36420
|
if (isCancel) {
|
|
36417
36421
|
const pickerEl = ref.current;
|
|
36418
36422
|
const inputEl = getPickerInput(pickerEl);
|