@jsenv/navi 0.27.60 → 0.27.62
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 +217 -216
- package/dist/jsenv_navi.js.map +19 -25
- 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 });
|
|
@@ -32222,6 +32244,7 @@ const getNowHoursRoundedToStep = (stepMinutes, offsetMinutes = 0) => {
|
|
|
32222
32244
|
// Numeric signal types must not fall through to the native type="number"
|
|
32223
32245
|
// (which adds spinner buttons and has poor UX) — they map to navi_number instead.
|
|
32224
32246
|
const VALIDITY_TYPE_TO_INPUT_TYPE = {
|
|
32247
|
+
boolean: "checkbox",
|
|
32225
32248
|
number: "navi_number",
|
|
32226
32249
|
integer: "navi_number",
|
|
32227
32250
|
percentage: "navi_percentage",
|
|
@@ -33080,7 +33103,9 @@ const InputSearchUI = ({
|
|
|
33080
33103
|
const InputEmail = props => {
|
|
33081
33104
|
const Next = useNextResolver();
|
|
33082
33105
|
return jsx(Next, {
|
|
33083
|
-
ui: jsx(InputEmailUI, {
|
|
33106
|
+
ui: jsx(InputEmailUI, {
|
|
33107
|
+
icon: props.icon
|
|
33108
|
+
}),
|
|
33084
33109
|
...props
|
|
33085
33110
|
});
|
|
33086
33111
|
};
|
|
@@ -33630,6 +33655,19 @@ const css$x = /* css */`
|
|
|
33630
33655
|
outline: none;
|
|
33631
33656
|
-webkit-tap-highlight-color: var(--navi-control-tap-highlight-color);
|
|
33632
33657
|
|
|
33658
|
+
&::placeholder {
|
|
33659
|
+
color: var(--x-placeholder-color);
|
|
33660
|
+
}
|
|
33661
|
+
&:-webkit-autofill {
|
|
33662
|
+
-webkit-box-shadow: 0 0 0 1000px var(--x-background-color) inset;
|
|
33663
|
+
}
|
|
33664
|
+
&:-internal-autofill-selected {
|
|
33665
|
+
/* Webkit is putting some nasty styles after automplete that look as follow */
|
|
33666
|
+
/* input:-internal-autofill-selected { color: FieldText !important; } */
|
|
33667
|
+
/* Fortunately we can override it as follow */
|
|
33668
|
+
-webkit-text-fill-color: var(--x-color) !important;
|
|
33669
|
+
}
|
|
33670
|
+
|
|
33633
33671
|
&[type="search"] {
|
|
33634
33672
|
-webkit-appearance: textfield;
|
|
33635
33673
|
|
|
@@ -33747,16 +33785,6 @@ const css$x = /* css */`
|
|
|
33747
33785
|
}
|
|
33748
33786
|
}
|
|
33749
33787
|
}
|
|
33750
|
-
|
|
33751
|
-
.navi_input .navi_control_input::placeholder {
|
|
33752
|
-
color: var(--x-placeholder-color);
|
|
33753
|
-
}
|
|
33754
|
-
.navi_input .navi_control_input:-internal-autofill-selected {
|
|
33755
|
-
/* Webkit is putting some nasty styles after automplete that look as follow */
|
|
33756
|
-
/* input:-internal-autofill-selected { color: FieldText !important; } */
|
|
33757
|
-
/* Fortunately we can override it as follow */
|
|
33758
|
-
-webkit-text-fill-color: var(--x-color) !important;
|
|
33759
|
-
}
|
|
33760
33788
|
`;
|
|
33761
33789
|
const InputHeadlessResolver = props => {
|
|
33762
33790
|
const Next = useNextResolver();
|
|
@@ -35557,7 +35585,8 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
|
|
|
35557
35585
|
const Dialog = props => {
|
|
35558
35586
|
import.meta.css = [css$r, "@jsenv/navi/src/popup/dialog.jsx"];
|
|
35559
35587
|
const {
|
|
35560
|
-
|
|
35588
|
+
// requestOpen,
|
|
35589
|
+
requestClose,
|
|
35561
35590
|
anchorRef,
|
|
35562
35591
|
children,
|
|
35563
35592
|
scrollTrap,
|
|
@@ -35635,7 +35664,7 @@ const Dialog = props => {
|
|
|
35635
35664
|
focusedBeforeOpen
|
|
35636
35665
|
});
|
|
35637
35666
|
};
|
|
35638
|
-
const close =
|
|
35667
|
+
const close = e => {
|
|
35639
35668
|
debugPopup(`"${e.type}" on ${getElementSignature(e.target)} -> closeDialog`);
|
|
35640
35669
|
const dialogEl = ref.current;
|
|
35641
35670
|
markAutofocusRestoreOnClose(dialogEl);
|
|
@@ -35643,70 +35672,9 @@ const Dialog = props => {
|
|
|
35643
35672
|
cleanup();
|
|
35644
35673
|
openedRef.current = false;
|
|
35645
35674
|
dispatchCustomEvent(dialogEl, "navi_close", {
|
|
35646
|
-
event: e
|
|
35647
|
-
...detail
|
|
35675
|
+
event: e
|
|
35648
35676
|
});
|
|
35649
35677
|
};
|
|
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
35678
|
return jsx(Box, {
|
|
35711
35679
|
...rest,
|
|
35712
35680
|
...autoFocusProps,
|
|
@@ -35723,20 +35691,57 @@ const Dialog = props => {
|
|
|
35723
35691
|
const rect = ref.current.getBoundingClientRect();
|
|
35724
35692
|
const isBackdrop = e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom;
|
|
35725
35693
|
if (isBackdrop) {
|
|
35726
|
-
|
|
35727
|
-
|
|
35694
|
+
requestClose(e, {
|
|
35695
|
+
isCancel: true
|
|
35728
35696
|
});
|
|
35729
35697
|
}
|
|
35730
35698
|
}
|
|
35731
35699
|
},
|
|
35732
35700
|
onCancel: e => {
|
|
35733
|
-
|
|
35701
|
+
requestClose(e, {
|
|
35702
|
+
isCancel: true
|
|
35703
|
+
});
|
|
35734
35704
|
},
|
|
35735
35705
|
onnavi_request_open: e => {
|
|
35736
|
-
|
|
35706
|
+
const dialogEl = ref.current;
|
|
35707
|
+
if (!dialogEl) {
|
|
35708
|
+
return;
|
|
35709
|
+
}
|
|
35710
|
+
if (openedRef.current) {
|
|
35711
|
+
return;
|
|
35712
|
+
}
|
|
35713
|
+
open(e);
|
|
35737
35714
|
},
|
|
35738
35715
|
onnavi_request_close: e => {
|
|
35739
|
-
|
|
35716
|
+
const dialogEl = ref.current;
|
|
35717
|
+
if (!dialogEl) {
|
|
35718
|
+
return;
|
|
35719
|
+
}
|
|
35720
|
+
if (!openedRef.current) {
|
|
35721
|
+
return;
|
|
35722
|
+
}
|
|
35723
|
+
if (closeRequestHandler) {
|
|
35724
|
+
let denied = false;
|
|
35725
|
+
const closePermission = {
|
|
35726
|
+
deny: () => {
|
|
35727
|
+
denied = true;
|
|
35728
|
+
},
|
|
35729
|
+
allow: () => {
|
|
35730
|
+
denied = false;
|
|
35731
|
+
}
|
|
35732
|
+
};
|
|
35733
|
+
closeRequestHandler(e, closePermission);
|
|
35734
|
+
if (denied) {
|
|
35735
|
+
if (e.type === "cancel") {
|
|
35736
|
+
e.preventDefault();
|
|
35737
|
+
}
|
|
35738
|
+
closePermission.allow = () => {
|
|
35739
|
+
close(e);
|
|
35740
|
+
};
|
|
35741
|
+
return;
|
|
35742
|
+
}
|
|
35743
|
+
}
|
|
35744
|
+
close(e);
|
|
35740
35745
|
},
|
|
35741
35746
|
children: children
|
|
35742
35747
|
});
|
|
@@ -35768,7 +35773,7 @@ installImportMetaCssBuild(import.meta);const css$q = /* css */`
|
|
|
35768
35773
|
const Popover = props => {
|
|
35769
35774
|
import.meta.css = [css$q, "@jsenv/navi/src/popup/popover.jsx"];
|
|
35770
35775
|
const {
|
|
35771
|
-
|
|
35776
|
+
requestClose,
|
|
35772
35777
|
anchorRef,
|
|
35773
35778
|
scrollTrap,
|
|
35774
35779
|
pointerTrap,
|
|
@@ -35880,7 +35885,7 @@ const Popover = props => {
|
|
|
35880
35885
|
focusedBeforeOpen
|
|
35881
35886
|
});
|
|
35882
35887
|
};
|
|
35883
|
-
const close =
|
|
35888
|
+
const close = e => {
|
|
35884
35889
|
debugPopup(e, `closePopover()`);
|
|
35885
35890
|
const popoverEl = ref.current;
|
|
35886
35891
|
markAutofocusRestoreOnClose(popoverEl);
|
|
@@ -35888,69 +35893,9 @@ const Popover = props => {
|
|
|
35888
35893
|
cleanup();
|
|
35889
35894
|
openedRef.current = false;
|
|
35890
35895
|
dispatchCustomEvent(popoverEl, "navi_close", {
|
|
35891
|
-
event: e
|
|
35892
|
-
...detail
|
|
35896
|
+
event: e
|
|
35893
35897
|
});
|
|
35894
35898
|
};
|
|
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
35899
|
return jsxs(Box, {
|
|
35955
35900
|
id: id,
|
|
35956
35901
|
popover: "manual",
|
|
@@ -35960,10 +35905,42 @@ const Popover = props => {
|
|
|
35960
35905
|
baseClassName: "navi_popover",
|
|
35961
35906
|
pseudoClasses: POPOVER_PSEUDO_CLASSES,
|
|
35962
35907
|
onnavi_request_open: e => {
|
|
35963
|
-
|
|
35908
|
+
const popoverEl = ref.current;
|
|
35909
|
+
if (!popoverEl) {
|
|
35910
|
+
return;
|
|
35911
|
+
}
|
|
35912
|
+
if (openedRef.current) {
|
|
35913
|
+
return;
|
|
35914
|
+
}
|
|
35915
|
+
open(e);
|
|
35964
35916
|
},
|
|
35965
35917
|
onnavi_request_close: e => {
|
|
35966
|
-
|
|
35918
|
+
const popoverEl = ref.current;
|
|
35919
|
+
if (!popoverEl) {
|
|
35920
|
+
return;
|
|
35921
|
+
}
|
|
35922
|
+
if (!openedRef.current) {
|
|
35923
|
+
return;
|
|
35924
|
+
}
|
|
35925
|
+
if (closeRequestHandler) {
|
|
35926
|
+
let denied = false;
|
|
35927
|
+
const closePermission = {
|
|
35928
|
+
deny: () => {
|
|
35929
|
+
denied = true;
|
|
35930
|
+
},
|
|
35931
|
+
allow: () => {
|
|
35932
|
+
denied = false;
|
|
35933
|
+
}
|
|
35934
|
+
};
|
|
35935
|
+
closeRequestHandler(e, closePermission);
|
|
35936
|
+
if (denied) {
|
|
35937
|
+
closePermission.allow = () => {
|
|
35938
|
+
close(e);
|
|
35939
|
+
};
|
|
35940
|
+
return;
|
|
35941
|
+
}
|
|
35942
|
+
}
|
|
35943
|
+
close(e);
|
|
35967
35944
|
},
|
|
35968
35945
|
children: [jsx("div", {
|
|
35969
35946
|
className: "navi_popover_backdrop",
|
|
@@ -35976,8 +35953,8 @@ const Popover = props => {
|
|
|
35976
35953
|
e.preventDefault();
|
|
35977
35954
|
return;
|
|
35978
35955
|
}
|
|
35979
|
-
|
|
35980
|
-
|
|
35956
|
+
requestClose(e, {
|
|
35957
|
+
isCancel: true
|
|
35981
35958
|
});
|
|
35982
35959
|
}
|
|
35983
35960
|
}), children]
|
|
@@ -36266,8 +36243,7 @@ const PickerCustom = props => {
|
|
|
36266
36243
|
const popupRef = useRef(null);
|
|
36267
36244
|
popupProps.ref = popupRef;
|
|
36268
36245
|
// aria-controls + id
|
|
36269
|
-
const
|
|
36270
|
-
const popupId = `picker_popup_${autoId}`;
|
|
36246
|
+
const popupId = `${props.id}_picker_popup`;
|
|
36271
36247
|
{
|
|
36272
36248
|
Object.assign(pickerProps, {
|
|
36273
36249
|
"aria-controls": popupId
|
|
@@ -36284,7 +36260,7 @@ const PickerCustom = props => {
|
|
|
36284
36260
|
// In "popover" mode, it replaces the current history state (no history entry added).
|
|
36285
36261
|
const pickerNavType = mode === "dialog" ? "push" : "replace";
|
|
36286
36262
|
const expandedRef = useRef(false);
|
|
36287
|
-
const [expanded, enterExpanded, leaveExpanded] = useNavState(popupId,
|
|
36263
|
+
const [expanded, enterExpanded, leaveExpanded] = useNavState(popupId, {
|
|
36288
36264
|
type: pickerNavType,
|
|
36289
36265
|
// onLeave fires only when the state key disappears externally (back button/gesture most of the time).
|
|
36290
36266
|
onLeave: () => {
|
|
@@ -36295,7 +36271,8 @@ const PickerCustom = props => {
|
|
|
36295
36271
|
});
|
|
36296
36272
|
}
|
|
36297
36273
|
});
|
|
36298
|
-
expandedRef
|
|
36274
|
+
// expandedRef tracks actual open/close state, updated only by onOpen/onClose.
|
|
36275
|
+
// NOT synced to expanded on every render so the useLayoutEffect guard below works.
|
|
36299
36276
|
const valueAtOpenRef = useRef(null);
|
|
36300
36277
|
const activeElementAtOpenRef = useRef(null);
|
|
36301
36278
|
const onOpen = e => {
|
|
@@ -36343,7 +36320,9 @@ const PickerCustom = props => {
|
|
|
36343
36320
|
}
|
|
36344
36321
|
}
|
|
36345
36322
|
expandedRef.current = false;
|
|
36346
|
-
leaveExpanded(
|
|
36323
|
+
leaveExpanded({
|
|
36324
|
+
isBack: e.detail.isCancel
|
|
36325
|
+
});
|
|
36347
36326
|
// Reset so the next opening re-evaluates screen size
|
|
36348
36327
|
defaultModeRef.current = null;
|
|
36349
36328
|
restoreFocus(e);
|
|
@@ -36351,7 +36330,7 @@ const PickerCustom = props => {
|
|
|
36351
36330
|
const disableClickFor = useIgnoreClickForMousedown(ref, e => {
|
|
36352
36331
|
debugPopup(e, `click ignored`);
|
|
36353
36332
|
});
|
|
36354
|
-
const requestOpen = e => {
|
|
36333
|
+
const requestOpen = (e, detail) => {
|
|
36355
36334
|
// scroll <button> of the picker into view when opening it
|
|
36356
36335
|
const pickerEl = ref.current;
|
|
36357
36336
|
pickerEl.scrollIntoView({
|
|
@@ -36359,18 +36338,47 @@ const PickerCustom = props => {
|
|
|
36359
36338
|
});
|
|
36360
36339
|
const popupEl = popupRef.current;
|
|
36361
36340
|
return dispatchCustomEvent(popupEl, "navi_request_open", {
|
|
36362
|
-
event: e
|
|
36341
|
+
event: e,
|
|
36342
|
+
...detail
|
|
36363
36343
|
});
|
|
36364
36344
|
};
|
|
36365
|
-
const requestClose = (e = new CustomEvent("programmatic"
|
|
36366
|
-
|
|
36367
|
-
}
|
|
36345
|
+
const requestClose = (e = new CustomEvent("programmatic", {
|
|
36346
|
+
detail: {}
|
|
36347
|
+
}), detail) => {
|
|
36368
36348
|
const popupEl = popupRef.current;
|
|
36369
36349
|
return dispatchCustomEvent(popupEl, "navi_request_close", {
|
|
36370
36350
|
event: e,
|
|
36371
|
-
|
|
36351
|
+
...detail
|
|
36372
36352
|
});
|
|
36373
36353
|
};
|
|
36354
|
+
const open = Boolean(expanded);
|
|
36355
|
+
useLayoutEffect(() => {
|
|
36356
|
+
if (open === undefined) {
|
|
36357
|
+
return;
|
|
36358
|
+
}
|
|
36359
|
+
// Skip when the popup is already in the desired state.
|
|
36360
|
+
// expandedRef tracks actual open/close (updated by onOpen/onClose, not by renders)
|
|
36361
|
+
// so it is the authoritative check against feedback loops.
|
|
36362
|
+
if (open === expandedRef.current) {
|
|
36363
|
+
return;
|
|
36364
|
+
}
|
|
36365
|
+
// open_prop_change means the parent is driving the open state directly
|
|
36366
|
+
// (e.g. back-button navigation flipped openProp to false before onLeave fires).
|
|
36367
|
+
// Always treat it as cancel — the user's in-progress edit should be discarded.
|
|
36368
|
+
if (open) {
|
|
36369
|
+
requestOpen(new CustomEvent("open_by_prop", {
|
|
36370
|
+
detail: {}
|
|
36371
|
+
}), {
|
|
36372
|
+
isCancel: true
|
|
36373
|
+
});
|
|
36374
|
+
} else {
|
|
36375
|
+
requestClose(new CustomEvent("close_by_prop", {
|
|
36376
|
+
detail: {}
|
|
36377
|
+
}), {
|
|
36378
|
+
isCancel: true
|
|
36379
|
+
});
|
|
36380
|
+
}
|
|
36381
|
+
}, [open]);
|
|
36374
36382
|
const requestInteraction = options => {
|
|
36375
36383
|
dispatchRequestInteraction(ref.current, options);
|
|
36376
36384
|
};
|
|
@@ -36412,16 +36420,9 @@ const PickerCustom = props => {
|
|
|
36412
36420
|
});
|
|
36413
36421
|
Object.assign(popupProps, {
|
|
36414
36422
|
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;
|
|
36423
|
+
requestClose,
|
|
36424
|
+
closeRequestHandler: (requestCloseEvent, closePermission) => {
|
|
36425
|
+
const isCancel = requestCloseEvent.detail.isCancel;
|
|
36425
36426
|
if (isCancel) {
|
|
36426
36427
|
const pickerEl = ref.current;
|
|
36427
36428
|
const inputEl = getPickerInput(pickerEl);
|