@jsenv/navi 0.29.63 → 0.29.65
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 +333 -87
- package/dist/jsenv_navi.js.map +30 -22
- package/docs/AI_INSTRUCTIONS.md +6 -0
- package/docs/popup_open.md +29 -38
- package/docs/testid.md +103 -0
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -693,9 +693,33 @@ const getConfirmParams = (element) => {
|
|
|
693
693
|
if (!element) {
|
|
694
694
|
return undefined;
|
|
695
695
|
}
|
|
696
|
+
if (confirmAnsweredSet.has(element)) {
|
|
697
|
+
return undefined;
|
|
698
|
+
}
|
|
696
699
|
return confirmParamsWeakMap.get(element);
|
|
697
700
|
};
|
|
698
701
|
|
|
702
|
+
const confirmAnsweredSet = new WeakSet();
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* One press asks its question once. What a press sets off can come back through
|
|
706
|
+
* the action path with the same element as requester — clearing a picker sends
|
|
707
|
+
* it, and the send reads the question off whoever asked for it — so whoever
|
|
708
|
+
* already asked says so, for as long as that press lasts.
|
|
709
|
+
*
|
|
710
|
+
* @param {Element} element - The one that was asked.
|
|
711
|
+
* @returns {() => void} Call it when the press is over.
|
|
712
|
+
*/
|
|
713
|
+
const suspendConfirmParams = (element) => {
|
|
714
|
+
if (!element) {
|
|
715
|
+
return () => {};
|
|
716
|
+
}
|
|
717
|
+
confirmAnsweredSet.add(element);
|
|
718
|
+
return () => {
|
|
719
|
+
confirmAnsweredSet.delete(element);
|
|
720
|
+
};
|
|
721
|
+
};
|
|
722
|
+
|
|
699
723
|
/**
|
|
700
724
|
* @param {object} params
|
|
701
725
|
* @param {string|import("preact").ComponentChildren} [params.message]
|
|
@@ -15727,6 +15751,14 @@ const useExecuteAction = (
|
|
|
15727
15751
|
// so the validation message appears on the button, not the form.
|
|
15728
15752
|
const element = elementRef.current;
|
|
15729
15753
|
let target = requester;
|
|
15754
|
+
// A requester that is no longer on the page is not a place to show
|
|
15755
|
+
// anything: the clear cross leaves with the value it optimistically
|
|
15756
|
+
// cleared, and by the time the refusal comes back there is nothing left to
|
|
15757
|
+
// point at — the callout would anchor on a detached node. The control that
|
|
15758
|
+
// ran the action is still there, and the error is about it, so it takes it.
|
|
15759
|
+
if (target && !target.isConnected) {
|
|
15760
|
+
target = undefined;
|
|
15761
|
+
}
|
|
15730
15762
|
let message;
|
|
15731
15763
|
if (errorMapping) {
|
|
15732
15764
|
const errorMappingResult = errorMapping(error);
|
|
@@ -22671,34 +22703,122 @@ registerNaviCommand("--navi-clear", (source, event) => {
|
|
|
22671
22703
|
// moment a value is chosen — has nothing that would commit a clear: its
|
|
22672
22704
|
// action never runs on a ui state change. Left alone, the field goes empty
|
|
22673
22705
|
// while the caller still holds the value it gave, and renders it right back.
|
|
22706
|
+
//
|
|
22707
|
+
// Unless the source committed the clear itself: a clear button given its own
|
|
22708
|
+
// action (`<Button action={remove} command="--navi-clear">`) already made the
|
|
22709
|
+
// request — the ui state is being brought in line with what just happened, and
|
|
22710
|
+
// sending the picker's own action on top would ask twice.
|
|
22711
|
+
const sourceController = source.__uiStateController__;
|
|
22674
22712
|
const fromSendOnlyControl = Boolean(
|
|
22675
|
-
source.closest?.(`[navi-control=picker]`)
|
|
22713
|
+
source.closest?.(`[navi-control=picker]`) &&
|
|
22714
|
+
!sourceController?.props.action,
|
|
22676
22715
|
);
|
|
22677
22716
|
|
|
22678
|
-
|
|
22679
|
-
target,
|
|
22680
|
-
|
|
22681
|
-
|
|
22682
|
-
|
|
22683
|
-
|
|
22684
|
-
|
|
22685
|
-
|
|
22686
|
-
|
|
22687
|
-
|
|
22688
|
-
|
|
22689
|
-
|
|
22690
|
-
|
|
22691
|
-
|
|
22692
|
-
|
|
22717
|
+
const performClear = (clearEvent) => {
|
|
22718
|
+
dispatchRequestInteraction(target, {
|
|
22719
|
+
event: clearEvent,
|
|
22720
|
+
name: "--navi-clear",
|
|
22721
|
+
prevented: () => clearEvent.preventDefault(),
|
|
22722
|
+
allowed: () => {
|
|
22723
|
+
// What the control holds, before it holds nothing: the clear is
|
|
22724
|
+
// optimistic — the field empties now and the send that commits it may
|
|
22725
|
+
// still fail — so what it emptied has to be kept to be put back.
|
|
22726
|
+
const uiStateBefore = getUIStateFromElement(target);
|
|
22727
|
+
dispatchRequestClearUIState(target, clearEvent);
|
|
22728
|
+
if (!fromSendOnlyControl) {
|
|
22729
|
+
return;
|
|
22730
|
+
}
|
|
22731
|
+
// After the clear, never before: the action is bound to the ui
|
|
22732
|
+
// state signal, so this sends the value the control now holds.
|
|
22733
|
+
const actionHost = findControlHost(target) || target;
|
|
22734
|
+
const completion = watchActionCompletion(actionHost, () => {
|
|
22735
|
+
triggerNaviCommand(source, "--navi-send", clearEvent, {
|
|
22736
|
+
optional: true,
|
|
22737
|
+
});
|
|
22738
|
+
});
|
|
22739
|
+
completion.whenSettled(({ error, aborted }) => {
|
|
22740
|
+
if (!error && !aborted) {
|
|
22741
|
+
return;
|
|
22693
22742
|
}
|
|
22694
|
-
|
|
22743
|
+
// The removal did not happen, so the field must stop saying it did.
|
|
22744
|
+
// The error itself stays where the action put it — on the control,
|
|
22745
|
+
// which is still there, unlike the cross that has just gone with the
|
|
22746
|
+
// value it cleared (see addErrorMessage in use_execute_action.js).
|
|
22747
|
+
//
|
|
22748
|
+
// Put back from the inside ("clear_rollback" is an internal event
|
|
22749
|
+
// type, see ui_state_controller.js) rather than asked for the way a
|
|
22750
|
+
// user would: nobody acted, the control is being returned to the
|
|
22751
|
+
// state its caller still holds. Asked from the outside it would be
|
|
22752
|
+
// answered with "this element is busy" — the action is still
|
|
22753
|
+
// settling — over the very error that explains why the value is back.
|
|
22754
|
+
const controller = actionHost.__uiStateController__;
|
|
22755
|
+
if (!controller) {
|
|
22756
|
+
return;
|
|
22757
|
+
}
|
|
22758
|
+
const rollbackEvent = new CustomEvent("clear_rollback", {
|
|
22759
|
+
detail: {},
|
|
22760
|
+
});
|
|
22761
|
+
chainEvent(rollbackEvent, clearEvent);
|
|
22762
|
+
controller.setUIState(uiStateBefore, rollbackEvent);
|
|
22763
|
+
});
|
|
22764
|
+
},
|
|
22765
|
+
});
|
|
22766
|
+
|
|
22767
|
+
if (fromInput) ; else if (
|
|
22768
|
+
// Only what is open: the clear cross of a picker sits on the closed
|
|
22769
|
+
// trigger, and asking that trigger to close is asking it to do nothing —
|
|
22770
|
+
// except that the ask goes through the interaction gate, which turns it
|
|
22771
|
+
// down while the clear it just sent is still running and says so out loud
|
|
22772
|
+
// ("this element is busy"). A clear pressed INSIDE an open popup is the
|
|
22773
|
+
// one this is for: it answers the popup, so the popup goes away.
|
|
22774
|
+
resolveClosestExpandable(source)?.getAttribute("aria-expanded") === "true"
|
|
22775
|
+
) {
|
|
22776
|
+
triggerNaviCommand(source, "--navi-close", clearEvent, {
|
|
22777
|
+
optional: true,
|
|
22695
22778
|
});
|
|
22779
|
+
}
|
|
22780
|
+
};
|
|
22696
22781
|
|
|
22697
|
-
|
|
22698
|
-
|
|
22699
|
-
|
|
22700
|
-
|
|
22782
|
+
return {
|
|
22783
|
+
target,
|
|
22784
|
+
implementation: () => {
|
|
22785
|
+
// "Are you sure?" comes before anything is cleared. It is asked here
|
|
22786
|
+
// rather than by the action the clear ends up sending (which is where a
|
|
22787
|
+
// confirmation is normally asked, see use_execute_action) because that
|
|
22788
|
+
// one only runs AFTER the ui state was emptied: the field would go blank
|
|
22789
|
+
// behind the question, and answering "no" would leave it blank over a
|
|
22790
|
+
// value that was never removed.
|
|
22791
|
+
const confirmParams = getConfirmParams(source);
|
|
22792
|
+
if (!confirmParams) {
|
|
22793
|
+
performClear(event);
|
|
22794
|
+
return;
|
|
22701
22795
|
}
|
|
22796
|
+
requestConfirmation({
|
|
22797
|
+
...confirmParams,
|
|
22798
|
+
anchor: source,
|
|
22799
|
+
}).then((confirmed) => {
|
|
22800
|
+
if (!confirmed) {
|
|
22801
|
+
return;
|
|
22802
|
+
}
|
|
22803
|
+
// A new event, chained to the press: the press itself is over and has
|
|
22804
|
+
// been consumed — the control that took the click cancelled it on its
|
|
22805
|
+
// way out so the region around it would not read it as "unfold me" (see
|
|
22806
|
+
// click_to_expand.js), and navi refuses an interaction on a cancelled
|
|
22807
|
+
// event. What happens now happens BECAUSE of that press, which is what
|
|
22808
|
+
// the chain says, but it is no longer that press.
|
|
22809
|
+
const clearConfirmedEvent = new CustomEvent("navi_clear_confirmed", {
|
|
22810
|
+
detail: {},
|
|
22811
|
+
});
|
|
22812
|
+
chainEvent(clearConfirmedEvent, event);
|
|
22813
|
+
// Answered — and the send that follows must not ask it again: it reads
|
|
22814
|
+
// the same question off this same element (getConfirmParams(requester)).
|
|
22815
|
+
const restoreConfirmParams = suspendConfirmParams(source);
|
|
22816
|
+
try {
|
|
22817
|
+
performClear(clearConfirmedEvent);
|
|
22818
|
+
} finally {
|
|
22819
|
+
restoreConfirmParams();
|
|
22820
|
+
}
|
|
22821
|
+
});
|
|
22702
22822
|
},
|
|
22703
22823
|
};
|
|
22704
22824
|
});
|
|
@@ -23907,6 +24027,10 @@ const useUIStateController = (
|
|
|
23907
24027
|
defaultValue: controlInfo.defaultValue,
|
|
23908
24028
|
|
|
23909
24029
|
facadeChild: null,
|
|
24030
|
+
// Set for the duration of one interaction by whatever wants the
|
|
24031
|
+
// command to wait for something (a button's own action) — see the
|
|
24032
|
+
// command trigger in onUIAction below.
|
|
24033
|
+
commandDeferral: null,
|
|
23910
24034
|
getManagedControls: () => {
|
|
23911
24035
|
if (controller.facadeChild) {
|
|
23912
24036
|
const child = controller.facadeChild;
|
|
@@ -23962,7 +24086,19 @@ const useUIStateController = (
|
|
|
23962
24086
|
debugUIState(
|
|
23963
24087
|
`triggering command "${command}" for "${controlType}"`,
|
|
23964
24088
|
);
|
|
23965
|
-
|
|
24089
|
+
const runCommand = () => {
|
|
24090
|
+
triggerNaviCommand(element, command, e);
|
|
24091
|
+
};
|
|
24092
|
+
// What the press means may not be due yet: a button with an
|
|
24093
|
+
// action of its own runs the work first and lets its command
|
|
24094
|
+
// follow only once that work succeeded (see control_hooks).
|
|
24095
|
+
// The command is handed over rather than run; nobody claiming
|
|
24096
|
+
// it means now.
|
|
24097
|
+
if (controller.commandDeferral) {
|
|
24098
|
+
controller.commandDeferral(runCommand);
|
|
24099
|
+
} else {
|
|
24100
|
+
runCommand();
|
|
24101
|
+
}
|
|
23966
24102
|
}
|
|
23967
24103
|
}
|
|
23968
24104
|
}
|
|
@@ -25455,6 +25591,12 @@ const INTERNAL_EVENT_SET = new Set([
|
|
|
25455
25591
|
// on registration, and group pushing value/defaultValue to children on registerChild.
|
|
25456
25592
|
// Equivalent to defaultValue initialization: no uiAction, no commands, no parent notification.
|
|
25457
25593
|
"initial_state_push",
|
|
25594
|
+
// navi undoing its own optimistic write: the clear cross emptied the control
|
|
25595
|
+
// before the send that commits it, the send failed, and the value it emptied
|
|
25596
|
+
// goes back where it was (see the --navi-clear command). Nothing acted — the
|
|
25597
|
+
// control is being put back on the state the caller still holds — so this
|
|
25598
|
+
// must not fire a uiAction, a command, or a report on the way.
|
|
25599
|
+
"clear_rollback",
|
|
25458
25600
|
]);
|
|
25459
25601
|
const isInternalEvent = (e) => {
|
|
25460
25602
|
return INTERNAL_EVENT_SET.has(e.type);
|
|
@@ -25790,7 +25932,30 @@ const useControlProps = (props, {
|
|
|
25790
25932
|
}
|
|
25791
25933
|
if (controlType === "button") {
|
|
25792
25934
|
const onButtonInteractionAllowed = e => {
|
|
25793
|
-
|
|
25935
|
+
// A command that follows the control's OWN action has to wait for it:
|
|
25936
|
+
// `<Button action={remove} command="--navi-clear">` means "remove it,
|
|
25937
|
+
// then clear the field" — clearing first would empty the field over a
|
|
25938
|
+
// request that can still fail, and a confirm popup refusing the action
|
|
25939
|
+
// would leave the clear standing. Same rule the form counterpart
|
|
25940
|
+
// already has (data-after-send, see resolveAfterSend in commands.js).
|
|
25941
|
+
//
|
|
25942
|
+
// Armed here, around the ui action, because the command fires from
|
|
25943
|
+
// there (see the command trigger in ui_state_controller). A button is
|
|
25944
|
+
// where this arises: its press IS the ui action, the action and the
|
|
25945
|
+
// command, in one breath. A field's ui action and its action are two
|
|
25946
|
+
// different moments (typing, then change/blur), and its command
|
|
25947
|
+
// belongs to the first — there is nothing to wait for.
|
|
25948
|
+
let deferredCommand = null;
|
|
25949
|
+
if (props.action && props.command) {
|
|
25950
|
+
uiStateController.commandDeferral = runCommand => {
|
|
25951
|
+
deferredCommand = runCommand;
|
|
25952
|
+
};
|
|
25953
|
+
}
|
|
25954
|
+
try {
|
|
25955
|
+
triggerUIAction(e);
|
|
25956
|
+
} finally {
|
|
25957
|
+
uiStateController.commandDeferral = null;
|
|
25958
|
+
}
|
|
25794
25959
|
const control = ref.current;
|
|
25795
25960
|
if (!control) {
|
|
25796
25961
|
// What the button just did took the button away: a command that
|
|
@@ -25799,11 +25964,36 @@ const useControlProps = (props, {
|
|
|
25799
25964
|
// press was for has already happened.
|
|
25800
25965
|
return;
|
|
25801
25966
|
}
|
|
25802
|
-
tryActionAfterInteractionAllowed(control, {
|
|
25967
|
+
const completion = watchActionCompletion(control, () => tryActionAfterInteractionAllowed(control, {
|
|
25803
25968
|
event: e,
|
|
25804
25969
|
action: boundAction,
|
|
25805
25970
|
requester: control
|
|
25971
|
+
}));
|
|
25972
|
+
if (!deferredCommand) {
|
|
25973
|
+
return;
|
|
25974
|
+
}
|
|
25975
|
+
if (completion.result === false) {
|
|
25976
|
+
// The action was turned down (a failing constraint, a gate saying
|
|
25977
|
+
// no) — nothing happened, so nothing follows.
|
|
25978
|
+
return;
|
|
25979
|
+
}
|
|
25980
|
+
if (completion.isRunning) {
|
|
25981
|
+
completion.whenSucceeded(deferredCommand);
|
|
25982
|
+
return;
|
|
25983
|
+
}
|
|
25984
|
+
// Synchronous: already settled, and how it ended still decides. An
|
|
25985
|
+
// action that never started (nothing to run) leaves no outcome, and
|
|
25986
|
+
// the command runs as it always did.
|
|
25987
|
+
let succeeded = true;
|
|
25988
|
+
completion.whenSettled(({
|
|
25989
|
+
error,
|
|
25990
|
+
aborted
|
|
25991
|
+
}) => {
|
|
25992
|
+
succeeded = !error && !aborted;
|
|
25806
25993
|
});
|
|
25994
|
+
if (succeeded) {
|
|
25995
|
+
deferredCommand();
|
|
25996
|
+
}
|
|
25807
25997
|
};
|
|
25808
25998
|
return {
|
|
25809
25999
|
keyDown: keyDownDefault,
|
|
@@ -28324,7 +28514,6 @@ const createOpenController = (
|
|
|
28324
28514
|
cancelable: true,
|
|
28325
28515
|
});
|
|
28326
28516
|
chainEvent(requestOpenEvent, e);
|
|
28327
|
-
controller.opened = true;
|
|
28328
28517
|
// we prepare focus transfer before actually opening the popover/dialog
|
|
28329
28518
|
// because opnening dialog makes browser try to transfer focus (which ends up in document.body for instance)
|
|
28330
28519
|
const focusTransfer = prepareFocusTransfer(
|
|
@@ -28372,6 +28561,18 @@ const createOpenController = (
|
|
|
28372
28561
|
// before anything inside the popup can claim it, and before openEffect,
|
|
28373
28562
|
// which measures the popup to place it.
|
|
28374
28563
|
controller.mountContent?.();
|
|
28564
|
+
// Only now — after the content has been built, before openEffect shows
|
|
28565
|
+
// it. Dialog/Popover recompute aria-expanded and navi-hidden from this
|
|
28566
|
+
// flag on every render, and mountContent above renders synchronously:
|
|
28567
|
+
// flipping it any earlier commits an already-open DOM (aria-expanded
|
|
28568
|
+
// "true", navi-hidden gone) before openEffect has run a single
|
|
28569
|
+
// statement, so the "closed" frame it pins to transition from is in
|
|
28570
|
+
// fact the open one and the entrance animation has nothing to play.
|
|
28571
|
+
// It also gives the content it just built the opening it is documented
|
|
28572
|
+
// to observe — mounted while the popup reads as closed, told it opened
|
|
28573
|
+
// right after (see popup_content_mount.js and
|
|
28574
|
+
// use_displayed_layout_effect.js).
|
|
28575
|
+
controller.opened = true;
|
|
28375
28576
|
const openEffectReturnValue =
|
|
28376
28577
|
controller.openEffect(requestOpenEvent) || null;
|
|
28377
28578
|
openEffectCleanup = (closeEvent) => {
|
|
@@ -29696,9 +29897,12 @@ const css$W = /* css */`
|
|
|
29696
29897
|
backdrop-filter: none;
|
|
29697
29898
|
}
|
|
29698
29899
|
|
|
29900
|
+
/* overlay is a no-op here (this backdrop is a plain div, never a
|
|
29901
|
+
top-layer element) but stays in the list so all four transition lists in
|
|
29902
|
+
these files agree — display and overlay always travel together. */
|
|
29699
29903
|
&[navi-animation] {
|
|
29700
29904
|
opacity: 1;
|
|
29701
|
-
transition-property: display, opacity;
|
|
29905
|
+
transition-property: display, overlay, opacity;
|
|
29702
29906
|
transition-duration: var(--popup-animation-duration);
|
|
29703
29907
|
transition-timing-function: ease;
|
|
29704
29908
|
transition-behavior: allow-discrete;
|
|
@@ -29802,6 +30006,11 @@ const css$W = /* css */`
|
|
|
29802
30006
|
* triggered the open (`e.detail.anchor`), if any. A string is resolved via
|
|
29803
30007
|
* `document.getElementById` when the dialog opens — see popover.jsx's own
|
|
29804
30008
|
* `anchor` doc for why (mainly `defaultOpen`).
|
|
30009
|
+
* @param {"override"|"ignore"} [props.anchorCustomEventDetail="override"] -
|
|
30010
|
+
* Whether an explicit `anchor` prop takes precedence over (`"override"`,
|
|
30011
|
+
* default) or is ignored in favor of (`"ignore"`) whatever anchor the
|
|
30012
|
+
* triggering event carried. Same prop as Popover's, applied to the only
|
|
30013
|
+
* thing an anchor does here: sizing (`--anchor-width`/`--anchor-height`).
|
|
29805
30014
|
* @param {string} [props.minWidth] - Maps to `--dialog-min-width`; clamped
|
|
29806
30015
|
* so it can never push the dialog past `--dialog-maxmax-width` (the
|
|
29807
30016
|
* viewport/container-spacing ceiling) regardless of how large a value is
|
|
@@ -30063,6 +30272,10 @@ const useDialogProps = props => {
|
|
|
30063
30272
|
// Only ever affects --anchor-width/--anchor-height (see this file's top
|
|
30064
30273
|
// comment) — Dialog's own positioning is never relative to it.
|
|
30065
30274
|
anchor,
|
|
30275
|
+
// Same meaning as Popover's own prop, applied to the only thing an anchor
|
|
30276
|
+
// does here: sizing. "ignore" is how a dialog that must not inherit its
|
|
30277
|
+
// trigger's width says so (SidePanel does exactly that).
|
|
30278
|
+
anchorCustomEventDetail = "override",
|
|
30066
30279
|
// Makes the dialog itself a valid focus target so
|
|
30067
30280
|
// autoFocus="last-resort" below has somewhere to land when it contains
|
|
30068
30281
|
// nothing focusable of its own — -1 keeps it out of the normal Tab order (it's only ever reached
|
|
@@ -30242,9 +30455,10 @@ const useDialogProps = props => {
|
|
|
30242
30455
|
console.warn(`Dialog: anchor="${anchor}" did not match any element`);
|
|
30243
30456
|
}
|
|
30244
30457
|
} else if (anchor) {
|
|
30245
|
-
// anchor prop is a ref or a DOM element
|
|
30458
|
+
// anchor prop is a ref or a DOM element — always a real anchor,
|
|
30459
|
+
// regardless of anchorCustomEventDetail.
|
|
30246
30460
|
anchorElement = anchor.current ?? anchor;
|
|
30247
|
-
} else if (
|
|
30461
|
+
} else if (anchorCustomEventDetail === "override") {
|
|
30248
30462
|
// e.g. the button that triggered a --navi-toggle/--navi-open command,
|
|
30249
30463
|
// already resolved from detail.anchor/detail.source by the caller
|
|
30250
30464
|
// (see UncontrolledDialog's onnavi_request_open).
|
|
@@ -31094,12 +31308,16 @@ const css$V = /* css */`
|
|
|
31094
31308
|
/* navi-animation mirrors the content popover's own resolved value (set
|
|
31095
31309
|
imperatively in openEffect) — the backdrop only ever fades, regardless
|
|
31096
31310
|
of which kind it is (translate/scale wouldn't mean anything on it).
|
|
31097
|
-
display
|
|
31098
|
-
reason popup_css.js's own .navi_popover rule includes
|
|
31099
|
-
file's top comment.
|
|
31311
|
+
display and overlay are included alongside opacity (+ allow-discrete)
|
|
31312
|
+
for the same reason popup_css.js's own .navi_popover rule includes them
|
|
31313
|
+
— see this file's top comment. They go together: display keeps the
|
|
31314
|
+
backdrop rendered during the exit, overlay keeps it in the top layer
|
|
31315
|
+
(hidePopover() is called synchronously, so without it the backdrop is
|
|
31316
|
+
evicted immediately and, being z-index: unset, loses to FixedBar and to
|
|
31317
|
+
any positioned element later in DOM order for the whole fade). */
|
|
31100
31318
|
&[navi-animation] {
|
|
31101
31319
|
opacity: 1;
|
|
31102
|
-
transition-property: display, opacity;
|
|
31320
|
+
transition-property: display, overlay, opacity;
|
|
31103
31321
|
transition-duration: var(--popup-animation-duration);
|
|
31104
31322
|
transition-timing-function: ease;
|
|
31105
31323
|
transition-behavior: allow-discrete;
|
|
@@ -52746,10 +52964,10 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
52746
52964
|
* the hook's own `resetMode` return value, from its own onClose.
|
|
52747
52965
|
*
|
|
52748
52966
|
* `layer` (shared by both — picks the top-layer vs. local-container rendering
|
|
52749
|
-
* strategy either way) and `anchorCustomEventDetail` (Popover
|
|
52750
|
-
*
|
|
52751
|
-
*
|
|
52752
|
-
*
|
|
52967
|
+
* strategy either way) and `anchorCustomEventDetail` (shared too: Popover
|
|
52968
|
+
* resolves an anchor to position against, Dialog to size itself from) pass
|
|
52969
|
+
* through untouched via `...rest` to whichever of Popover/Dialog actually
|
|
52970
|
+
* renders.
|
|
52753
52971
|
*/
|
|
52754
52972
|
const css$A = /* css */`
|
|
52755
52973
|
@layer navi {
|
|
@@ -52789,12 +53007,14 @@ const css$A = /* css */`
|
|
|
52789
53007
|
* @param {Element|{current: Element}} [props.anchor] - Forwarded as-is —
|
|
52790
53008
|
* sizing-only for `Dialog`, positioning for `Popover` (see each
|
|
52791
53009
|
* component's own doc for what it actually does there).
|
|
52792
|
-
* @param {"override"|"ignore"} [props.anchorCustomEventDetail] -
|
|
52793
|
-
*
|
|
52794
|
-
*
|
|
52795
|
-
*
|
|
52796
|
-
*
|
|
52797
|
-
*
|
|
53010
|
+
* @param {"override"|"ignore"} [props.anchorCustomEventDetail] - Forwarded
|
|
53011
|
+
* as-is to both — what it governs differs (positioning for `Popover`,
|
|
53012
|
+
* sizing for `Dialog`), but "ignore whatever anchor the triggering event
|
|
53013
|
+
* carried" has to mean the same thing in either mode, or the same
|
|
53014
|
+
* `<Popup>` usage silently picks up its trigger's width on small screens.
|
|
53015
|
+
* @param {string} [props.marginWithAnchor] - **Popover-only**, destructured
|
|
53016
|
+
* out so it can't leak onto the real `<dialog>` element as a stray DOM
|
|
53017
|
+
* attribute when `mode="dialog"` is picked.
|
|
52798
53018
|
* @param {boolean} [props.focusCapture] - **Popover-only**, same guard.
|
|
52799
53019
|
* @param {string} [props.positionAreaFixed] - **Popover-only**, same guard.
|
|
52800
53020
|
* @param {string} [props.positionArea] - Forwarded as-is — `Dialog` and
|
|
@@ -52882,7 +53102,6 @@ const Popup = props => {
|
|
|
52882
53102
|
// they're never part of ...rest, and therefore never forwarded to
|
|
52883
53103
|
// Dialog below, where they'd otherwise leak onto the real <dialog>
|
|
52884
53104
|
// element as stray, unrecognized DOM attributes.
|
|
52885
|
-
anchorCustomEventDetail,
|
|
52886
53105
|
marginWithAnchor,
|
|
52887
53106
|
focusCapture,
|
|
52888
53107
|
scrollCapture,
|
|
@@ -52917,7 +53136,6 @@ const Popup = props => {
|
|
|
52917
53136
|
...rest,
|
|
52918
53137
|
maxWidth: maxWidth,
|
|
52919
53138
|
pointerInteractionOutsideEffect: pointerInteractionOutsideEffect,
|
|
52920
|
-
anchorCustomEventDetail: anchorCustomEventDetail,
|
|
52921
53139
|
marginWithAnchor: marginWithAnchor,
|
|
52922
53140
|
focusCapture: focusCapture,
|
|
52923
53141
|
scrollCapture: scrollCapture === "popover" || scrollCapture,
|
|
@@ -60640,6 +60858,11 @@ const PickerButton = props => {
|
|
|
60640
60858
|
variant,
|
|
60641
60859
|
rightSlotIcon,
|
|
60642
60860
|
rightSlotIconSize = "inherit",
|
|
60861
|
+
// What goes in the right slot as-is — no <Icon> around it, so a caller can
|
|
60862
|
+
// put something interactive there. `rightSlotIcon` cannot: it is wrapped in
|
|
60863
|
+
// an <Icon>, which is aria-hidden, and a focusable node under aria-hidden is
|
|
60864
|
+
// invisible to assistive tech while still being reachable by tab.
|
|
60865
|
+
rightSlot,
|
|
60643
60866
|
placeholder,
|
|
60644
60867
|
ui,
|
|
60645
60868
|
maxLines = 1,
|
|
@@ -60652,6 +60875,13 @@ const PickerButton = props => {
|
|
|
60652
60875
|
// the end of an input: a picker holds a value the user chose, and unsetting
|
|
60653
60876
|
// it should not require reopening the popup to hunt for a "none" entry.
|
|
60654
60877
|
clearable,
|
|
60878
|
+
// "Are you sure?" before the cross clears anything. A cross of three
|
|
60879
|
+
// millimetres at the edge of a touch screen, right where the chevron is
|
|
60880
|
+
// aimed at, is the button pressed by accident — and what it removes does
|
|
60881
|
+
// not come back. Asked before the clear, so a "no" leaves the field
|
|
60882
|
+
// untouched (see the --navi-clear command).
|
|
60883
|
+
clearConfirm,
|
|
60884
|
+
clearConfirmPopupContent,
|
|
60655
60885
|
error
|
|
60656
60886
|
} = props;
|
|
60657
60887
|
const isSingleLine = maxLines === 1;
|
|
@@ -60694,6 +60924,9 @@ const PickerButton = props => {
|
|
|
60694
60924
|
variant: undefined,
|
|
60695
60925
|
rightSlotIcon: undefined,
|
|
60696
60926
|
rightSlotIconSize: undefined,
|
|
60927
|
+
rightSlot: undefined,
|
|
60928
|
+
clearConfirm: undefined,
|
|
60929
|
+
clearConfirmPopupContent: undefined,
|
|
60697
60930
|
ui: undefined,
|
|
60698
60931
|
maxLines: undefined,
|
|
60699
60932
|
popupWidthFitContent: undefined,
|
|
@@ -60808,40 +61041,59 @@ const PickerButton = props => {
|
|
|
60808
61041
|
})
|
|
60809
61042
|
}), variant === "headless" || ui === "default" ? null : jsx("span", {
|
|
60810
61043
|
className: "navi_picker_right_slot",
|
|
60811
|
-
children:
|
|
60812
|
-
|
|
60813
|
-
|
|
60814
|
-
|
|
60815
|
-
|
|
60816
|
-
|
|
60817
|
-
|
|
60818
|
-
|
|
60819
|
-
|
|
60820
|
-
|
|
60821
|
-
|
|
60822
|
-
|
|
60823
|
-
|
|
60824
|
-
|
|
60825
|
-
|
|
60826
|
-
|
|
60827
|
-
|
|
60828
|
-
|
|
60829
|
-
|
|
60830
|
-
|
|
60831
|
-
|
|
60832
|
-
|
|
60833
|
-
|
|
60834
|
-
|
|
60835
|
-
|
|
61044
|
+
children: jsx(ControlIdContext.Provider, {
|
|
61045
|
+
value: undefined,
|
|
61046
|
+
children: jsx(ControlNameContext.Provider, {
|
|
61047
|
+
value: undefined,
|
|
61048
|
+
children: clearable && value !== undefined && value !== "" ? jsx(Button, {
|
|
61049
|
+
command: "--navi-clear",
|
|
61050
|
+
commandFor: inputProps.id
|
|
61051
|
+
// The question, asked before the clear rather than by the
|
|
61052
|
+
// action the clear sends — see the --navi-clear command.
|
|
61053
|
+
,
|
|
61054
|
+
|
|
61055
|
+
confirm: clearConfirm,
|
|
61056
|
+
confirmPopupContent: clearConfirmPopupContent,
|
|
61057
|
+
tabIndex: "-1"
|
|
61058
|
+
// No navi-focus-delegate, unlike the identical button inside an
|
|
61059
|
+
// input: handing focus back to the picker's own input is what
|
|
61060
|
+
// opens the popup, and clearing is the opposite intention.
|
|
61061
|
+
,
|
|
61062
|
+
|
|
61063
|
+
icon: true,
|
|
61064
|
+
variant: "discrete"
|
|
61065
|
+
// What is busy once the clear is sent is the picker — the value
|
|
61066
|
+
// being removed is the whole field's, and the picker already
|
|
61067
|
+
// draws the wait around all of it. Two outlines for one wait is
|
|
61068
|
+
// one too many.
|
|
61069
|
+
,
|
|
61070
|
+
|
|
61071
|
+
loadingOutline: false
|
|
61072
|
+
// preventDefault, not just tabIndex="-1": a mousedown focuses
|
|
61073
|
+
// its target before any click happens, and this button should
|
|
61074
|
+
// never hold focus at all — the field keeps it.
|
|
61075
|
+
,
|
|
61076
|
+
|
|
61077
|
+
onMouseDown: e => {
|
|
61078
|
+
e.preventDefault();
|
|
61079
|
+
},
|
|
61080
|
+
flex: true,
|
|
61081
|
+
align: "center",
|
|
61082
|
+
children: jsx(Icon, {
|
|
61083
|
+
size: rightSlotIconSize,
|
|
61084
|
+
lineOverflow: "allow",
|
|
61085
|
+
children: jsx(CloseSvg, {})
|
|
61086
|
+
})
|
|
61087
|
+
}) : rightSlot === undefined ?
|
|
61088
|
+
// lineOverflow: what sits in the slot is an affordance, not a
|
|
61089
|
+
// character — a caller asking for a bigger one wants it bigger,
|
|
61090
|
+
// not capped at the height of the line it sits on
|
|
61091
|
+
jsx(Icon, {
|
|
61092
|
+
size: rightSlotIconSize,
|
|
61093
|
+
lineOverflow: "allow",
|
|
61094
|
+
children: rightSlotIcon === undefined ? jsx(ChevronDownSvg$1, {}) : rightSlotIcon
|
|
61095
|
+
}) : rightSlot
|
|
60836
61096
|
})
|
|
60837
|
-
}) :
|
|
60838
|
-
// lineOverflow: what sits in the slot is an affordance, not a
|
|
60839
|
-
// character — a caller asking for a bigger one wants it bigger,
|
|
60840
|
-
// not capped at the height of the line it sits on
|
|
60841
|
-
jsx(Icon, {
|
|
60842
|
-
size: rightSlotIconSize,
|
|
60843
|
-
lineOverflow: "allow",
|
|
60844
|
-
children: rightSlotIcon === undefined ? jsx(ChevronDownSvg$1, {}) : rightSlotIcon
|
|
60845
61097
|
})
|
|
60846
61098
|
})]
|
|
60847
61099
|
}), jsx(ControlFacadeChildrenWrapper, {
|
|
@@ -72275,19 +72527,13 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
72275
72527
|
* `navi-side`/`data-layer` attributes) rather than computed in JS — read
|
|
72276
72528
|
* the CSS block below instead of expecting a JS equivalent of it here.
|
|
72277
72529
|
*
|
|
72278
|
-
* `anchorCustomEventDetail="ignore"` is required, not cosmetic
|
|
72279
|
-
* Popover
|
|
72280
|
-
* against the edge,
|
|
72530
|
+
* `anchorCustomEventDetail="ignore"` is required, not cosmetic, and in both
|
|
72531
|
+
* modes: without it Popover docks next to whatever triggered the open instead
|
|
72532
|
+
* of flush against the edge, and Dialog takes that trigger's width as its own
|
|
72533
|
+
* `min-width` floor (`--anchor-width`), overriding the `width` prop.
|
|
72281
72534
|
*/
|
|
72282
72535
|
const css = /* css */`
|
|
72283
72536
|
.navi_side_panel {
|
|
72284
|
-
/* Dialog's own \`min-width: var(--anchor-width, 0px)\` exists so a
|
|
72285
|
-
dialog naturally matches whatever triggered it (picker_custom.jsx's
|
|
72286
|
-
dialog mode relies on this) — SidePanel's own "anchor" is just its
|
|
72287
|
-
container, so this would otherwise force min-width to the full
|
|
72288
|
-
container width, overriding \`width\`/\`height\` below entirely. Popover
|
|
72289
|
-
ignores this var. */
|
|
72290
|
-
--anchor-width: 0px;
|
|
72291
72537
|
/* Side panel create a barriere with the content that is full size */
|
|
72292
72538
|
/* So by default they don't have border-radius */
|
|
72293
72539
|
--popup-border-radius: 0px;
|