@jsenv/navi 0.29.63 → 0.29.64
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 +321 -82
- package/dist/jsenv_navi.js.map +29 -21
- package/docs/AI_INSTRUCTIONS.md +6 -0
- package/docs/popup_open.md +29 -38
- package/docs/testid.md +103 -0
- package/package.json +1 -1
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) => {
|
|
@@ -29802,6 +30003,11 @@ const css$W = /* css */`
|
|
|
29802
30003
|
* triggered the open (`e.detail.anchor`), if any. A string is resolved via
|
|
29803
30004
|
* `document.getElementById` when the dialog opens — see popover.jsx's own
|
|
29804
30005
|
* `anchor` doc for why (mainly `defaultOpen`).
|
|
30006
|
+
* @param {"override"|"ignore"} [props.anchorCustomEventDetail="override"] -
|
|
30007
|
+
* Whether an explicit `anchor` prop takes precedence over (`"override"`,
|
|
30008
|
+
* default) or is ignored in favor of (`"ignore"`) whatever anchor the
|
|
30009
|
+
* triggering event carried. Same prop as Popover's, applied to the only
|
|
30010
|
+
* thing an anchor does here: sizing (`--anchor-width`/`--anchor-height`).
|
|
29805
30011
|
* @param {string} [props.minWidth] - Maps to `--dialog-min-width`; clamped
|
|
29806
30012
|
* so it can never push the dialog past `--dialog-maxmax-width` (the
|
|
29807
30013
|
* viewport/container-spacing ceiling) regardless of how large a value is
|
|
@@ -30063,6 +30269,10 @@ const useDialogProps = props => {
|
|
|
30063
30269
|
// Only ever affects --anchor-width/--anchor-height (see this file's top
|
|
30064
30270
|
// comment) — Dialog's own positioning is never relative to it.
|
|
30065
30271
|
anchor,
|
|
30272
|
+
// Same meaning as Popover's own prop, applied to the only thing an anchor
|
|
30273
|
+
// does here: sizing. "ignore" is how a dialog that must not inherit its
|
|
30274
|
+
// trigger's width says so (SidePanel does exactly that).
|
|
30275
|
+
anchorCustomEventDetail = "override",
|
|
30066
30276
|
// Makes the dialog itself a valid focus target so
|
|
30067
30277
|
// autoFocus="last-resort" below has somewhere to land when it contains
|
|
30068
30278
|
// nothing focusable of its own — -1 keeps it out of the normal Tab order (it's only ever reached
|
|
@@ -30242,9 +30452,10 @@ const useDialogProps = props => {
|
|
|
30242
30452
|
console.warn(`Dialog: anchor="${anchor}" did not match any element`);
|
|
30243
30453
|
}
|
|
30244
30454
|
} else if (anchor) {
|
|
30245
|
-
// anchor prop is a ref or a DOM element
|
|
30455
|
+
// anchor prop is a ref or a DOM element — always a real anchor,
|
|
30456
|
+
// regardless of anchorCustomEventDetail.
|
|
30246
30457
|
anchorElement = anchor.current ?? anchor;
|
|
30247
|
-
} else if (
|
|
30458
|
+
} else if (anchorCustomEventDetail === "override") {
|
|
30248
30459
|
// e.g. the button that triggered a --navi-toggle/--navi-open command,
|
|
30249
30460
|
// already resolved from detail.anchor/detail.source by the caller
|
|
30250
30461
|
// (see UncontrolledDialog's onnavi_request_open).
|
|
@@ -52746,10 +52957,10 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
52746
52957
|
* the hook's own `resetMode` return value, from its own onClose.
|
|
52747
52958
|
*
|
|
52748
52959
|
* `layer` (shared by both — picks the top-layer vs. local-container rendering
|
|
52749
|
-
* strategy either way) and `anchorCustomEventDetail` (Popover
|
|
52750
|
-
*
|
|
52751
|
-
*
|
|
52752
|
-
*
|
|
52960
|
+
* strategy either way) and `anchorCustomEventDetail` (shared too: Popover
|
|
52961
|
+
* resolves an anchor to position against, Dialog to size itself from) pass
|
|
52962
|
+
* through untouched via `...rest` to whichever of Popover/Dialog actually
|
|
52963
|
+
* renders.
|
|
52753
52964
|
*/
|
|
52754
52965
|
const css$A = /* css */`
|
|
52755
52966
|
@layer navi {
|
|
@@ -52789,12 +53000,14 @@ const css$A = /* css */`
|
|
|
52789
53000
|
* @param {Element|{current: Element}} [props.anchor] - Forwarded as-is —
|
|
52790
53001
|
* sizing-only for `Dialog`, positioning for `Popover` (see each
|
|
52791
53002
|
* component's own doc for what it actually does there).
|
|
52792
|
-
* @param {"override"|"ignore"} [props.anchorCustomEventDetail] -
|
|
52793
|
-
*
|
|
52794
|
-
*
|
|
52795
|
-
*
|
|
52796
|
-
*
|
|
52797
|
-
*
|
|
53003
|
+
* @param {"override"|"ignore"} [props.anchorCustomEventDetail] - Forwarded
|
|
53004
|
+
* as-is to both — what it governs differs (positioning for `Popover`,
|
|
53005
|
+
* sizing for `Dialog`), but "ignore whatever anchor the triggering event
|
|
53006
|
+
* carried" has to mean the same thing in either mode, or the same
|
|
53007
|
+
* `<Popup>` usage silently picks up its trigger's width on small screens.
|
|
53008
|
+
* @param {string} [props.marginWithAnchor] - **Popover-only**, destructured
|
|
53009
|
+
* out so it can't leak onto the real `<dialog>` element as a stray DOM
|
|
53010
|
+
* attribute when `mode="dialog"` is picked.
|
|
52798
53011
|
* @param {boolean} [props.focusCapture] - **Popover-only**, same guard.
|
|
52799
53012
|
* @param {string} [props.positionAreaFixed] - **Popover-only**, same guard.
|
|
52800
53013
|
* @param {string} [props.positionArea] - Forwarded as-is — `Dialog` and
|
|
@@ -52882,7 +53095,6 @@ const Popup = props => {
|
|
|
52882
53095
|
// they're never part of ...rest, and therefore never forwarded to
|
|
52883
53096
|
// Dialog below, where they'd otherwise leak onto the real <dialog>
|
|
52884
53097
|
// element as stray, unrecognized DOM attributes.
|
|
52885
|
-
anchorCustomEventDetail,
|
|
52886
53098
|
marginWithAnchor,
|
|
52887
53099
|
focusCapture,
|
|
52888
53100
|
scrollCapture,
|
|
@@ -52917,7 +53129,6 @@ const Popup = props => {
|
|
|
52917
53129
|
...rest,
|
|
52918
53130
|
maxWidth: maxWidth,
|
|
52919
53131
|
pointerInteractionOutsideEffect: pointerInteractionOutsideEffect,
|
|
52920
|
-
anchorCustomEventDetail: anchorCustomEventDetail,
|
|
52921
53132
|
marginWithAnchor: marginWithAnchor,
|
|
52922
53133
|
focusCapture: focusCapture,
|
|
52923
53134
|
scrollCapture: scrollCapture === "popover" || scrollCapture,
|
|
@@ -60640,6 +60851,11 @@ const PickerButton = props => {
|
|
|
60640
60851
|
variant,
|
|
60641
60852
|
rightSlotIcon,
|
|
60642
60853
|
rightSlotIconSize = "inherit",
|
|
60854
|
+
// What goes in the right slot as-is — no <Icon> around it, so a caller can
|
|
60855
|
+
// put something interactive there. `rightSlotIcon` cannot: it is wrapped in
|
|
60856
|
+
// an <Icon>, which is aria-hidden, and a focusable node under aria-hidden is
|
|
60857
|
+
// invisible to assistive tech while still being reachable by tab.
|
|
60858
|
+
rightSlot,
|
|
60643
60859
|
placeholder,
|
|
60644
60860
|
ui,
|
|
60645
60861
|
maxLines = 1,
|
|
@@ -60652,6 +60868,13 @@ const PickerButton = props => {
|
|
|
60652
60868
|
// the end of an input: a picker holds a value the user chose, and unsetting
|
|
60653
60869
|
// it should not require reopening the popup to hunt for a "none" entry.
|
|
60654
60870
|
clearable,
|
|
60871
|
+
// "Are you sure?" before the cross clears anything. A cross of three
|
|
60872
|
+
// millimetres at the edge of a touch screen, right where the chevron is
|
|
60873
|
+
// aimed at, is the button pressed by accident — and what it removes does
|
|
60874
|
+
// not come back. Asked before the clear, so a "no" leaves the field
|
|
60875
|
+
// untouched (see the --navi-clear command).
|
|
60876
|
+
clearConfirm,
|
|
60877
|
+
clearConfirmPopupContent,
|
|
60655
60878
|
error
|
|
60656
60879
|
} = props;
|
|
60657
60880
|
const isSingleLine = maxLines === 1;
|
|
@@ -60694,6 +60917,9 @@ const PickerButton = props => {
|
|
|
60694
60917
|
variant: undefined,
|
|
60695
60918
|
rightSlotIcon: undefined,
|
|
60696
60919
|
rightSlotIconSize: undefined,
|
|
60920
|
+
rightSlot: undefined,
|
|
60921
|
+
clearConfirm: undefined,
|
|
60922
|
+
clearConfirmPopupContent: undefined,
|
|
60697
60923
|
ui: undefined,
|
|
60698
60924
|
maxLines: undefined,
|
|
60699
60925
|
popupWidthFitContent: undefined,
|
|
@@ -60808,40 +61034,59 @@ const PickerButton = props => {
|
|
|
60808
61034
|
})
|
|
60809
61035
|
}), variant === "headless" || ui === "default" ? null : jsx("span", {
|
|
60810
61036
|
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
|
-
|
|
61037
|
+
children: jsx(ControlIdContext.Provider, {
|
|
61038
|
+
value: undefined,
|
|
61039
|
+
children: jsx(ControlNameContext.Provider, {
|
|
61040
|
+
value: undefined,
|
|
61041
|
+
children: clearable && value !== undefined && value !== "" ? jsx(Button, {
|
|
61042
|
+
command: "--navi-clear",
|
|
61043
|
+
commandFor: inputProps.id
|
|
61044
|
+
// The question, asked before the clear rather than by the
|
|
61045
|
+
// action the clear sends — see the --navi-clear command.
|
|
61046
|
+
,
|
|
61047
|
+
|
|
61048
|
+
confirm: clearConfirm,
|
|
61049
|
+
confirmPopupContent: clearConfirmPopupContent,
|
|
61050
|
+
tabIndex: "-1"
|
|
61051
|
+
// No navi-focus-delegate, unlike the identical button inside an
|
|
61052
|
+
// input: handing focus back to the picker's own input is what
|
|
61053
|
+
// opens the popup, and clearing is the opposite intention.
|
|
61054
|
+
,
|
|
61055
|
+
|
|
61056
|
+
icon: true,
|
|
61057
|
+
variant: "discrete"
|
|
61058
|
+
// What is busy once the clear is sent is the picker — the value
|
|
61059
|
+
// being removed is the whole field's, and the picker already
|
|
61060
|
+
// draws the wait around all of it. Two outlines for one wait is
|
|
61061
|
+
// one too many.
|
|
61062
|
+
,
|
|
61063
|
+
|
|
61064
|
+
loadingOutline: false
|
|
61065
|
+
// preventDefault, not just tabIndex="-1": a mousedown focuses
|
|
61066
|
+
// its target before any click happens, and this button should
|
|
61067
|
+
// never hold focus at all — the field keeps it.
|
|
61068
|
+
,
|
|
61069
|
+
|
|
61070
|
+
onMouseDown: e => {
|
|
61071
|
+
e.preventDefault();
|
|
61072
|
+
},
|
|
61073
|
+
flex: true,
|
|
61074
|
+
align: "center",
|
|
61075
|
+
children: jsx(Icon, {
|
|
61076
|
+
size: rightSlotIconSize,
|
|
61077
|
+
lineOverflow: "allow",
|
|
61078
|
+
children: jsx(CloseSvg, {})
|
|
61079
|
+
})
|
|
61080
|
+
}) : rightSlot === undefined ?
|
|
61081
|
+
// lineOverflow: what sits in the slot is an affordance, not a
|
|
61082
|
+
// character — a caller asking for a bigger one wants it bigger,
|
|
61083
|
+
// not capped at the height of the line it sits on
|
|
61084
|
+
jsx(Icon, {
|
|
61085
|
+
size: rightSlotIconSize,
|
|
61086
|
+
lineOverflow: "allow",
|
|
61087
|
+
children: rightSlotIcon === undefined ? jsx(ChevronDownSvg$1, {}) : rightSlotIcon
|
|
61088
|
+
}) : rightSlot
|
|
60836
61089
|
})
|
|
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
61090
|
})
|
|
60846
61091
|
})]
|
|
60847
61092
|
}), jsx(ControlFacadeChildrenWrapper, {
|
|
@@ -72275,19 +72520,13 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
72275
72520
|
* `navi-side`/`data-layer` attributes) rather than computed in JS — read
|
|
72276
72521
|
* the CSS block below instead of expecting a JS equivalent of it here.
|
|
72277
72522
|
*
|
|
72278
|
-
* `anchorCustomEventDetail="ignore"` is required, not cosmetic
|
|
72279
|
-
* Popover
|
|
72280
|
-
* against the edge,
|
|
72523
|
+
* `anchorCustomEventDetail="ignore"` is required, not cosmetic, and in both
|
|
72524
|
+
* modes: without it Popover docks next to whatever triggered the open instead
|
|
72525
|
+
* of flush against the edge, and Dialog takes that trigger's width as its own
|
|
72526
|
+
* `min-width` floor (`--anchor-width`), overriding the `width` prop.
|
|
72281
72527
|
*/
|
|
72282
72528
|
const css = /* css */`
|
|
72283
72529
|
.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
72530
|
/* Side panel create a barriere with the content that is full size */
|
|
72292
72531
|
/* So by default they don't have border-radius */
|
|
72293
72532
|
--popup-border-radius: 0px;
|