@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.
@@ -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
- return {
22679
- target,
22680
- implementation: () => {
22681
- dispatchRequestInteraction(target, {
22682
- event,
22683
- name: "--navi-clear",
22684
- prevented: () => event.preventDefault(),
22685
- allowed: () => {
22686
- dispatchRequestClearUIState(target, event);
22687
- if (fromSendOnlyControl) {
22688
- // After the clear, never before: the action is bound to the ui
22689
- // state signal, so this sends the value the control now holds.
22690
- triggerNaviCommand(source, "--navi-send", event, {
22691
- optional: true,
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
- if (fromInput) ; else {
22698
- triggerNaviCommand(source, "--navi-close", event, {
22699
- optional: true,
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
- triggerNaviCommand(element, command, e);
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
- triggerUIAction(e);
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 (e.detail.anchor) {
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-only, Dialog
52750
- * ignores it Dialog never resolves an anchor for positioning purposes)
52751
- * pass through untouched via `...rest` to whichever of Popover/Dialog
52752
- * actually renders.
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
- * **Popover-only** (`Dialog` never resolves an anchor for positioning)
52794
- * never forwarded to `Dialog`, so it can't leak onto the real `<dialog>`
52795
- * element as a stray DOM attribute when `mode="dialog"` is picked.
52796
- * @param {string} [props.marginWithAnchor] - **Popover-only**, same
52797
- * Dialog-leak guard as `anchorCustomEventDetail` above.
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: clearable && value !== undefined && value !== "" ? jsx(Button, {
60812
- command: "--navi-clear",
60813
- commandFor: inputProps.id,
60814
- tabIndex: "-1"
60815
- // No navi-focus-delegate, unlike the identical button inside an
60816
- // input: handing focus back to the picker's own input is what
60817
- // opens the popup, and clearing is the opposite intention.
60818
- ,
60819
-
60820
- icon: true,
60821
- variant: "discrete"
60822
- // preventDefault, not just tabIndex="-1": a mousedown focuses
60823
- // its target before any click happens, and this button should
60824
- // never hold focus at all — the field keeps it.
60825
- ,
60826
-
60827
- onMouseDown: e => {
60828
- e.preventDefault();
60829
- },
60830
- flex: true,
60831
- align: "center",
60832
- children: jsx(Icon, {
60833
- size: rightSlotIconSize,
60834
- lineOverflow: "allow",
60835
- children: jsx(CloseSvg, {})
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: without it,
72279
- * Popover would dock next to whatever triggered the open instead of flush
72280
- * against the edge, defeating the point of a side panel.
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;