@jsenv/navi 0.27.60 → 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 -205
- package/dist/jsenv_navi.js.map +16 -22
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -14696,28 +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
|
-
// Merge onto the current state so existing useNavState entries (e.g. an
|
|
14711
|
-
// open dialog/popover) survive URL changes triggered by signal updates.
|
|
14712
|
-
// "traverse" is intentionally excluded: it restores the exact historical
|
|
14713
|
-
// state from the history entry, which is the source of truth for back/forward.
|
|
14714
|
-
const currentState = getDocumentState() || {};
|
|
14715
14708
|
markUrlAsVisited(url);
|
|
14716
|
-
|
|
14717
|
-
|
|
14718
|
-
|
|
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 = {
|
|
14719
14715
|
jsenv_visited_urls: Array.from(visitedUrlSet),
|
|
14720
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
|
+
}
|
|
14721
14730
|
if (navigationType === "push") {
|
|
14722
14731
|
window.history.pushState(effectiveState, null, url);
|
|
14723
14732
|
} else {
|
|
@@ -14803,7 +14812,6 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
14803
14812
|
handleRoutingTask(href, {
|
|
14804
14813
|
reason: `"click" on a[href="${href}"]`,
|
|
14805
14814
|
navigationType: "push",
|
|
14806
|
-
state: null,
|
|
14807
14815
|
});
|
|
14808
14816
|
},
|
|
14809
14817
|
{ capture: true },
|
|
@@ -14828,7 +14836,7 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
14828
14836
|
});
|
|
14829
14837
|
});
|
|
14830
14838
|
|
|
14831
|
-
const navTo = async (url, { replace, state
|
|
14839
|
+
const navTo = async (url, { replace, state } = {}) => {
|
|
14832
14840
|
handleRoutingTask(url, {
|
|
14833
14841
|
reason: `navTo called with "${url}"`,
|
|
14834
14842
|
navigationType: replace ? "replace" : "push",
|
|
@@ -14999,41 +15007,33 @@ const NO_OP = () => {};
|
|
|
14999
15007
|
const NO_ID_GIVEN = [undefined, NO_OP, NO_OP];
|
|
15000
15008
|
const useNavStateBasic = (
|
|
15001
15009
|
id,
|
|
15002
|
-
|
|
15003
|
-
{ debug, type = "replace", onLeave } = {},
|
|
15010
|
+
{ debug, type = "replace", onLeave, defaultValue } = {},
|
|
15004
15011
|
) => {
|
|
15005
15012
|
// Hooks must be called unconditionally — before the !id early return.
|
|
15006
15013
|
const state = documentStateSignal.value;
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
? state[id]
|
|
15010
|
-
: undefined
|
|
15011
|
-
: undefined;
|
|
15014
|
+
// Key presence is the flag — the value may be anything, including undefined.
|
|
15015
|
+
const keyInState = Boolean(id && state && Object.hasOwn(state, id));
|
|
15012
15016
|
const onLeaveRef = useRef(onLeave);
|
|
15013
15017
|
onLeaveRef.current = onLeave;
|
|
15014
|
-
const
|
|
15018
|
+
const prevKeyInStateRef = useRef(keyInState);
|
|
15015
15019
|
// enteredRef tracks whether enter() was called without a matching leave() yet.
|
|
15016
15020
|
// It lets the effect distinguish an external disappearance (back button → fire onLeave)
|
|
15017
15021
|
// from a programmatic one (leave() already set it to false before the state updates).
|
|
15018
15022
|
const enteredRef = useRef(false);
|
|
15019
15023
|
useEffect(() => {
|
|
15020
|
-
const
|
|
15021
|
-
|
|
15022
|
-
if (
|
|
15023
|
-
prevValue !== undefined &&
|
|
15024
|
-
valueInState === undefined &&
|
|
15025
|
-
enteredRef.current
|
|
15026
|
-
) {
|
|
15024
|
+
const prevKeyInState = prevKeyInStateRef.current;
|
|
15025
|
+
prevKeyInStateRef.current = keyInState;
|
|
15026
|
+
if (prevKeyInState && !keyInState && enteredRef.current) {
|
|
15027
15027
|
enteredRef.current = false;
|
|
15028
15028
|
onLeaveRef.current?.();
|
|
15029
15029
|
}
|
|
15030
|
-
}, [
|
|
15030
|
+
}, [keyInState]);
|
|
15031
15031
|
|
|
15032
15032
|
if (!id) {
|
|
15033
15033
|
return NO_ID_GIVEN;
|
|
15034
15034
|
}
|
|
15035
15035
|
|
|
15036
|
-
const currentValue =
|
|
15036
|
+
const currentValue = keyInState ? state[id] : defaultValue;
|
|
15037
15037
|
|
|
15038
15038
|
if (debug) {
|
|
15039
15039
|
console.debug(`useNavState(${id}) current value is ${currentValue}`);
|
|
@@ -15045,30 +15045,36 @@ const useNavStateBasic = (
|
|
|
15045
15045
|
// extra data with the entry when needed.
|
|
15046
15046
|
const enter = (value = "on") => {
|
|
15047
15047
|
enteredRef.current = true;
|
|
15048
|
-
const
|
|
15049
|
-
if (
|
|
15048
|
+
const currentStateCopy = browserIntegration.getDocumentState() || {};
|
|
15049
|
+
if (Object.hasOwn(currentStateCopy, id) && currentStateCopy[id] === value) {
|
|
15050
15050
|
return;
|
|
15051
15051
|
}
|
|
15052
|
-
|
|
15052
|
+
currentStateCopy[id] = value;
|
|
15053
15053
|
navTo(window.location.href, {
|
|
15054
15054
|
replace: type !== "push",
|
|
15055
|
-
state:
|
|
15055
|
+
state: currentStateCopy,
|
|
15056
15056
|
});
|
|
15057
15057
|
};
|
|
15058
15058
|
|
|
15059
15059
|
// leave(): navigate AWAY FROM this state (navBack in push mode, replace in replace mode).
|
|
15060
|
-
|
|
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 } = {}) => {
|
|
15061
15065
|
enteredRef.current = false;
|
|
15062
|
-
const
|
|
15063
|
-
if (!Object.hasOwn(
|
|
15066
|
+
const currentStateCopy = browserIntegration.getDocumentState() || {};
|
|
15067
|
+
if (!Object.hasOwn(currentStateCopy, id)) {
|
|
15064
15068
|
return;
|
|
15065
15069
|
}
|
|
15066
|
-
if (type === "push") {
|
|
15070
|
+
if (type === "push" && isBack) {
|
|
15067
15071
|
browserIntegration.navBack();
|
|
15068
15072
|
} else {
|
|
15069
|
-
|
|
15070
|
-
|
|
15071
|
-
|
|
15073
|
+
delete currentStateCopy[id];
|
|
15074
|
+
navTo(window.location.href, {
|
|
15075
|
+
replace: true,
|
|
15076
|
+
state: currentStateCopy,
|
|
15077
|
+
});
|
|
15072
15078
|
}
|
|
15073
15079
|
};
|
|
15074
15080
|
|
|
@@ -15082,9 +15088,6 @@ const useNavStateBasic = (
|
|
|
15082
15088
|
* @param {string} id
|
|
15083
15089
|
* Unique key used to store the value in document state. Must be stable across renders.
|
|
15084
15090
|
*
|
|
15085
|
-
* @param {*} initialValue
|
|
15086
|
-
* Value returned when `id` is absent from document state (e.g. before enter() is called).
|
|
15087
|
-
*
|
|
15088
15091
|
* @param {object} [options]
|
|
15089
15092
|
* @param {"push"|"replace"} [options.type="replace"]
|
|
15090
15093
|
* Controls how enter() adds the state to browser history.
|
|
@@ -15093,9 +15096,11 @@ const useNavStateBasic = (
|
|
|
15093
15096
|
* @param {() => void} [options.onLeave]
|
|
15094
15097
|
* Called when the state key disappears **externally** — e.g. the user presses the browser
|
|
15095
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`.
|
|
15096
15101
|
*
|
|
15097
15102
|
* @returns {[value, enter, leave]}
|
|
15098
|
-
* - `value`: current value from document state, or `
|
|
15103
|
+
* - `value`: current value from document state, or `defaultValue` when the key is absent.
|
|
15099
15104
|
* - `enter(value = "on")`: navigate TO this state (stores `value` under `id`).
|
|
15100
15105
|
* Calling without an argument stores `"on"` — the presence of the key is enough to match;
|
|
15101
15106
|
* the value allows associating extra data when needed.
|
|
@@ -22710,6 +22715,23 @@ const useUIStateController = (
|
|
|
22710
22715
|
if (e.type === "facade_propagate_up") {
|
|
22711
22716
|
notifyParentAboutChildUIAction(e, { stateChanged: true });
|
|
22712
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
|
+
}
|
|
22713
22735
|
return true;
|
|
22714
22736
|
}
|
|
22715
22737
|
notifyParentAboutChildUIAction(e, { stateChanged: true });
|
|
@@ -35557,7 +35579,8 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
|
|
|
35557
35579
|
const Dialog = props => {
|
|
35558
35580
|
import.meta.css = [css$r, "@jsenv/navi/src/popup/dialog.jsx"];
|
|
35559
35581
|
const {
|
|
35560
|
-
|
|
35582
|
+
// requestOpen,
|
|
35583
|
+
requestClose,
|
|
35561
35584
|
anchorRef,
|
|
35562
35585
|
children,
|
|
35563
35586
|
scrollTrap,
|
|
@@ -35635,7 +35658,7 @@ const Dialog = props => {
|
|
|
35635
35658
|
focusedBeforeOpen
|
|
35636
35659
|
});
|
|
35637
35660
|
};
|
|
35638
|
-
const close =
|
|
35661
|
+
const close = e => {
|
|
35639
35662
|
debugPopup(`"${e.type}" on ${getElementSignature(e.target)} -> closeDialog`);
|
|
35640
35663
|
const dialogEl = ref.current;
|
|
35641
35664
|
markAutofocusRestoreOnClose(dialogEl);
|
|
@@ -35643,70 +35666,9 @@ const Dialog = props => {
|
|
|
35643
35666
|
cleanup();
|
|
35644
35667
|
openedRef.current = false;
|
|
35645
35668
|
dispatchCustomEvent(dialogEl, "navi_close", {
|
|
35646
|
-
event: e
|
|
35647
|
-
...detail
|
|
35669
|
+
event: e
|
|
35648
35670
|
});
|
|
35649
35671
|
};
|
|
35650
|
-
const onRequestOpen = e => {
|
|
35651
|
-
const dialogEl = ref.current;
|
|
35652
|
-
if (!dialogEl) {
|
|
35653
|
-
return;
|
|
35654
|
-
}
|
|
35655
|
-
if (openedRef.current) {
|
|
35656
|
-
return;
|
|
35657
|
-
}
|
|
35658
|
-
open(e);
|
|
35659
|
-
};
|
|
35660
|
-
const onRequestClose = (e, detail = {}) => {
|
|
35661
|
-
const dialogEl = ref.current;
|
|
35662
|
-
if (!dialogEl) {
|
|
35663
|
-
return;
|
|
35664
|
-
}
|
|
35665
|
-
if (!openedRef.current) {
|
|
35666
|
-
return;
|
|
35667
|
-
}
|
|
35668
|
-
if (closeRequestHandler) {
|
|
35669
|
-
let denied = false;
|
|
35670
|
-
const closePermission = {
|
|
35671
|
-
deny: () => {
|
|
35672
|
-
denied = true;
|
|
35673
|
-
},
|
|
35674
|
-
allow: () => {
|
|
35675
|
-
denied = false;
|
|
35676
|
-
}
|
|
35677
|
-
};
|
|
35678
|
-
closeRequestHandler(e, closePermission, detail);
|
|
35679
|
-
if (denied) {
|
|
35680
|
-
if (e.type === "cancel") {
|
|
35681
|
-
e.preventDefault();
|
|
35682
|
-
}
|
|
35683
|
-
closePermission.allow = () => {
|
|
35684
|
-
close(e, detail);
|
|
35685
|
-
};
|
|
35686
|
-
return;
|
|
35687
|
-
}
|
|
35688
|
-
}
|
|
35689
|
-
close(e, detail);
|
|
35690
|
-
};
|
|
35691
|
-
useLayoutEffect(() => {
|
|
35692
|
-
if (openProp === undefined) {
|
|
35693
|
-
return;
|
|
35694
|
-
}
|
|
35695
|
-
// Skip when the popover is already in the desired state.
|
|
35696
|
-
// This avoids a feedback loop: our own close/open dispatches navi_close/navi_open,
|
|
35697
|
-
// the parent updates the prop, and the effect would fire again for a change we
|
|
35698
|
-
// already handled. Comparing against openedRef is the authoritative check because
|
|
35699
|
-
// it reflects the actual DOM state, not the React render cycle.
|
|
35700
|
-
if (openProp === openedRef.current) {
|
|
35701
|
-
return;
|
|
35702
|
-
}
|
|
35703
|
-
const e = new CustomEvent("open_prop_change");
|
|
35704
|
-
if (openProp) {
|
|
35705
|
-
onRequestOpen(e);
|
|
35706
|
-
} else {
|
|
35707
|
-
onRequestClose(e);
|
|
35708
|
-
}
|
|
35709
|
-
}, [openProp]);
|
|
35710
35672
|
return jsx(Box, {
|
|
35711
35673
|
...rest,
|
|
35712
35674
|
...autoFocusProps,
|
|
@@ -35723,20 +35685,57 @@ const Dialog = props => {
|
|
|
35723
35685
|
const rect = ref.current.getBoundingClientRect();
|
|
35724
35686
|
const isBackdrop = e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom;
|
|
35725
35687
|
if (isBackdrop) {
|
|
35726
|
-
|
|
35727
|
-
|
|
35688
|
+
requestClose(e, {
|
|
35689
|
+
isCancel: true
|
|
35728
35690
|
});
|
|
35729
35691
|
}
|
|
35730
35692
|
}
|
|
35731
35693
|
},
|
|
35732
35694
|
onCancel: e => {
|
|
35733
|
-
|
|
35695
|
+
requestClose(e, {
|
|
35696
|
+
isCancel: true
|
|
35697
|
+
});
|
|
35734
35698
|
},
|
|
35735
35699
|
onnavi_request_open: e => {
|
|
35736
|
-
|
|
35700
|
+
const dialogEl = ref.current;
|
|
35701
|
+
if (!dialogEl) {
|
|
35702
|
+
return;
|
|
35703
|
+
}
|
|
35704
|
+
if (openedRef.current) {
|
|
35705
|
+
return;
|
|
35706
|
+
}
|
|
35707
|
+
open(e);
|
|
35737
35708
|
},
|
|
35738
35709
|
onnavi_request_close: e => {
|
|
35739
|
-
|
|
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);
|
|
35740
35739
|
},
|
|
35741
35740
|
children: children
|
|
35742
35741
|
});
|
|
@@ -35768,7 +35767,7 @@ installImportMetaCssBuild(import.meta);const css$q = /* css */`
|
|
|
35768
35767
|
const Popover = props => {
|
|
35769
35768
|
import.meta.css = [css$q, "@jsenv/navi/src/popup/popover.jsx"];
|
|
35770
35769
|
const {
|
|
35771
|
-
|
|
35770
|
+
requestClose,
|
|
35772
35771
|
anchorRef,
|
|
35773
35772
|
scrollTrap,
|
|
35774
35773
|
pointerTrap,
|
|
@@ -35880,7 +35879,7 @@ const Popover = props => {
|
|
|
35880
35879
|
focusedBeforeOpen
|
|
35881
35880
|
});
|
|
35882
35881
|
};
|
|
35883
|
-
const close =
|
|
35882
|
+
const close = e => {
|
|
35884
35883
|
debugPopup(e, `closePopover()`);
|
|
35885
35884
|
const popoverEl = ref.current;
|
|
35886
35885
|
markAutofocusRestoreOnClose(popoverEl);
|
|
@@ -35888,69 +35887,9 @@ const Popover = props => {
|
|
|
35888
35887
|
cleanup();
|
|
35889
35888
|
openedRef.current = false;
|
|
35890
35889
|
dispatchCustomEvent(popoverEl, "navi_close", {
|
|
35891
|
-
event: e
|
|
35892
|
-
...detail
|
|
35890
|
+
event: e
|
|
35893
35891
|
});
|
|
35894
35892
|
};
|
|
35895
|
-
const onRequestOpen = e => {
|
|
35896
|
-
const popoverEl = ref.current;
|
|
35897
|
-
if (!popoverEl) {
|
|
35898
|
-
return;
|
|
35899
|
-
}
|
|
35900
|
-
if (openedRef.current) {
|
|
35901
|
-
return;
|
|
35902
|
-
}
|
|
35903
|
-
open(e);
|
|
35904
|
-
};
|
|
35905
|
-
const onRequestClose = (e, detail = {}) => {
|
|
35906
|
-
const popoverEl = ref.current;
|
|
35907
|
-
if (!popoverEl) {
|
|
35908
|
-
return;
|
|
35909
|
-
}
|
|
35910
|
-
if (!openedRef.current) {
|
|
35911
|
-
return;
|
|
35912
|
-
}
|
|
35913
|
-
if (closeRequestHandler) {
|
|
35914
|
-
let denied = false;
|
|
35915
|
-
const closePermission = {
|
|
35916
|
-
deny: () => {
|
|
35917
|
-
denied = true;
|
|
35918
|
-
},
|
|
35919
|
-
allow: () => {
|
|
35920
|
-
denied = false;
|
|
35921
|
-
}
|
|
35922
|
-
};
|
|
35923
|
-
closeRequestHandler(e, closePermission, detail);
|
|
35924
|
-
if (denied) {
|
|
35925
|
-
closePermission.allow = () => {
|
|
35926
|
-
close(e, detail);
|
|
35927
|
-
};
|
|
35928
|
-
return;
|
|
35929
|
-
}
|
|
35930
|
-
}
|
|
35931
|
-
close(e, detail);
|
|
35932
|
-
};
|
|
35933
|
-
useLayoutEffect(() => {
|
|
35934
|
-
if (openProp === undefined) {
|
|
35935
|
-
return;
|
|
35936
|
-
}
|
|
35937
|
-
// Skip when the popover is already in the desired state.
|
|
35938
|
-
// This avoids a feedback loop: our own close/open dispatches navi_close/navi_open,
|
|
35939
|
-
// the parent updates the prop, and the effect would fire again for a change we
|
|
35940
|
-
// already handled. Comparing against openedRef is the authoritative check because
|
|
35941
|
-
// it reflects the actual DOM state, not the React render cycle.
|
|
35942
|
-
if (openProp === openedRef.current) {
|
|
35943
|
-
return;
|
|
35944
|
-
}
|
|
35945
|
-
const e = new CustomEvent("open_prop_change", {
|
|
35946
|
-
detail: {}
|
|
35947
|
-
});
|
|
35948
|
-
if (openProp) {
|
|
35949
|
-
onRequestOpen(e);
|
|
35950
|
-
} else {
|
|
35951
|
-
onRequestClose(e);
|
|
35952
|
-
}
|
|
35953
|
-
}, [openProp]);
|
|
35954
35893
|
return jsxs(Box, {
|
|
35955
35894
|
id: id,
|
|
35956
35895
|
popover: "manual",
|
|
@@ -35960,10 +35899,42 @@ const Popover = props => {
|
|
|
35960
35899
|
baseClassName: "navi_popover",
|
|
35961
35900
|
pseudoClasses: POPOVER_PSEUDO_CLASSES,
|
|
35962
35901
|
onnavi_request_open: e => {
|
|
35963
|
-
|
|
35902
|
+
const popoverEl = ref.current;
|
|
35903
|
+
if (!popoverEl) {
|
|
35904
|
+
return;
|
|
35905
|
+
}
|
|
35906
|
+
if (openedRef.current) {
|
|
35907
|
+
return;
|
|
35908
|
+
}
|
|
35909
|
+
open(e);
|
|
35964
35910
|
},
|
|
35965
35911
|
onnavi_request_close: e => {
|
|
35966
|
-
|
|
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);
|
|
35967
35938
|
},
|
|
35968
35939
|
children: [jsx("div", {
|
|
35969
35940
|
className: "navi_popover_backdrop",
|
|
@@ -35976,8 +35947,8 @@ const Popover = props => {
|
|
|
35976
35947
|
e.preventDefault();
|
|
35977
35948
|
return;
|
|
35978
35949
|
}
|
|
35979
|
-
|
|
35980
|
-
|
|
35950
|
+
requestClose(e, {
|
|
35951
|
+
isCancel: true
|
|
35981
35952
|
});
|
|
35982
35953
|
}
|
|
35983
35954
|
}), children]
|
|
@@ -36266,8 +36237,7 @@ const PickerCustom = props => {
|
|
|
36266
36237
|
const popupRef = useRef(null);
|
|
36267
36238
|
popupProps.ref = popupRef;
|
|
36268
36239
|
// aria-controls + id
|
|
36269
|
-
const
|
|
36270
|
-
const popupId = `picker_popup_${autoId}`;
|
|
36240
|
+
const popupId = `${props.id}_picker_popup`;
|
|
36271
36241
|
{
|
|
36272
36242
|
Object.assign(pickerProps, {
|
|
36273
36243
|
"aria-controls": popupId
|
|
@@ -36284,7 +36254,7 @@ const PickerCustom = props => {
|
|
|
36284
36254
|
// In "popover" mode, it replaces the current history state (no history entry added).
|
|
36285
36255
|
const pickerNavType = mode === "dialog" ? "push" : "replace";
|
|
36286
36256
|
const expandedRef = useRef(false);
|
|
36287
|
-
const [expanded, enterExpanded, leaveExpanded] = useNavState(popupId,
|
|
36257
|
+
const [expanded, enterExpanded, leaveExpanded] = useNavState(popupId, {
|
|
36288
36258
|
type: pickerNavType,
|
|
36289
36259
|
// onLeave fires only when the state key disappears externally (back button/gesture most of the time).
|
|
36290
36260
|
onLeave: () => {
|
|
@@ -36295,7 +36265,8 @@ const PickerCustom = props => {
|
|
|
36295
36265
|
});
|
|
36296
36266
|
}
|
|
36297
36267
|
});
|
|
36298
|
-
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.
|
|
36299
36270
|
const valueAtOpenRef = useRef(null);
|
|
36300
36271
|
const activeElementAtOpenRef = useRef(null);
|
|
36301
36272
|
const onOpen = e => {
|
|
@@ -36343,7 +36314,9 @@ const PickerCustom = props => {
|
|
|
36343
36314
|
}
|
|
36344
36315
|
}
|
|
36345
36316
|
expandedRef.current = false;
|
|
36346
|
-
leaveExpanded(
|
|
36317
|
+
leaveExpanded({
|
|
36318
|
+
isBack: e.detail.isCancel
|
|
36319
|
+
});
|
|
36347
36320
|
// Reset so the next opening re-evaluates screen size
|
|
36348
36321
|
defaultModeRef.current = null;
|
|
36349
36322
|
restoreFocus(e);
|
|
@@ -36351,7 +36324,7 @@ const PickerCustom = props => {
|
|
|
36351
36324
|
const disableClickFor = useIgnoreClickForMousedown(ref, e => {
|
|
36352
36325
|
debugPopup(e, `click ignored`);
|
|
36353
36326
|
});
|
|
36354
|
-
const requestOpen = e => {
|
|
36327
|
+
const requestOpen = (e, detail) => {
|
|
36355
36328
|
// scroll <button> of the picker into view when opening it
|
|
36356
36329
|
const pickerEl = ref.current;
|
|
36357
36330
|
pickerEl.scrollIntoView({
|
|
@@ -36359,18 +36332,47 @@ const PickerCustom = props => {
|
|
|
36359
36332
|
});
|
|
36360
36333
|
const popupEl = popupRef.current;
|
|
36361
36334
|
return dispatchCustomEvent(popupEl, "navi_request_open", {
|
|
36362
|
-
event: e
|
|
36335
|
+
event: e,
|
|
36336
|
+
...detail
|
|
36363
36337
|
});
|
|
36364
36338
|
};
|
|
36365
|
-
const requestClose = (e = new CustomEvent("programmatic"
|
|
36366
|
-
|
|
36367
|
-
}
|
|
36339
|
+
const requestClose = (e = new CustomEvent("programmatic", {
|
|
36340
|
+
detail: {}
|
|
36341
|
+
}), detail) => {
|
|
36368
36342
|
const popupEl = popupRef.current;
|
|
36369
36343
|
return dispatchCustomEvent(popupEl, "navi_request_close", {
|
|
36370
36344
|
event: e,
|
|
36371
|
-
|
|
36345
|
+
...detail
|
|
36372
36346
|
});
|
|
36373
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]);
|
|
36374
36376
|
const requestInteraction = options => {
|
|
36375
36377
|
dispatchRequestInteraction(ref.current, options);
|
|
36376
36378
|
};
|
|
@@ -36412,16 +36414,9 @@ const PickerCustom = props => {
|
|
|
36412
36414
|
});
|
|
36413
36415
|
Object.assign(popupProps, {
|
|
36414
36416
|
anchorRef: props.ref,
|
|
36415
|
-
|
|
36416
|
-
closeRequestHandler: (requestCloseEvent, closePermission
|
|
36417
|
-
|
|
36418
|
-
} = {}) => {
|
|
36419
|
-
const cancelEvent = findEvent(requestCloseEvent, eInChain => eInChain.type === "navi_request_close" && eInChain.detail.isCancel);
|
|
36420
|
-
// open_prop_change means the parent is driving the open state directly
|
|
36421
|
-
// (e.g. back-button navigation flipped openProp to false before onLeave fires).
|
|
36422
|
-
// Always treat it as cancel — the user's in-progress edit should be discarded.
|
|
36423
|
-
const isPropDrivenClose = requestCloseEvent.type === "open_prop_change";
|
|
36424
|
-
const isCancel = isClickOutside || Boolean(cancelEvent) || isPropDrivenClose;
|
|
36417
|
+
requestClose,
|
|
36418
|
+
closeRequestHandler: (requestCloseEvent, closePermission) => {
|
|
36419
|
+
const isCancel = requestCloseEvent.detail.isCancel;
|
|
36425
36420
|
if (isCancel) {
|
|
36426
36421
|
const pickerEl = ref.current;
|
|
36427
36422
|
const inputEl = getPickerInput(pickerEl);
|