@jsenv/navi 0.28.0 → 0.28.1

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.
@@ -2,7 +2,7 @@
2
2
  * AI reading this file: read ../docs/AI_INSTRUCTIONS.md for context on
3
3
  * using @jsenv/navi as intended.
4
4
  */
5
- import { installImportMetaCssBuild, windowWidthSignal } from "./jsenv_navi_side_effects.js";
5
+ import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal } from "./jsenv_navi_side_effects.js";
6
6
  import { isValidElement, createContext, h, toChildArray, render, Fragment, cloneElement } from "preact";
7
7
  import { useErrorBoundary, useLayoutEffect, useEffect, useContext, useMemo, useRef, useState, useCallback, useId } from "preact/hooks";
8
8
  import { jsxs, jsx, Fragment as Fragment$1 } from "preact/jsx-runtime";
@@ -6821,6 +6821,7 @@ const VISUAL_PROPS = {
6821
6821
  scrollbarWidth: PASS_THROUGH,
6822
6822
  scrollbarGutter: PASS_THROUGH,
6823
6823
  scrollMarginBlock: PASS_THROUGH,
6824
+ scrollMarginInline: PASS_THROUGH,
6824
6825
  scrollMargin: PASS_THROUGH,
6825
6826
  };
6826
6827
  const CONTENT_PROPS = {
@@ -7076,7 +7077,37 @@ const sizeSpacingKeySet = new Set(Object.keys(SIZE_MAP));
7076
7077
  const isSizeSpacingKey = (key) => {
7077
7078
  return sizeSpacingKeySet.has(key);
7078
7079
  };
7080
+ // Viewport-relative units, resolved to pixels here because a JS consumer (popup
7081
+ // positioning) needs an actual number, not a length only CSS can evaluate.
7082
+ // "vvw"/"vvh" are navi's own: the *visual* viewport, which — unlike vw/dvw —
7083
+ // shrinks when the mobile virtual keyboard opens (see layout/responsive.js), so
7084
+ // they are what a popup meant to stay clear of the keyboard should use.
7085
+ const VIEWPORT_UNIT_SIGNALS = {
7086
+ vvw: visualViewportWidthSignal,
7087
+ vvh: visualViewportHeightSignal,
7088
+ vw: windowWidthSignal,
7089
+ vh: windowHeightSignal,
7090
+ dvw: windowWidthSignal,
7091
+ dvh: windowHeightSignal,
7092
+ };
7093
+ const VIEWPORT_LENGTH_REGEX = /^(-?\d+(?:\.\d+)?)(vvw|vvh|dvw|dvh|vw|vh)$/;
7094
+ const resolveViewportLength = (size) => {
7095
+ if (typeof size !== "string") {
7096
+ return null;
7097
+ }
7098
+ const match = VIEWPORT_LENGTH_REGEX.exec(size);
7099
+ if (!match) {
7100
+ return null;
7101
+ }
7102
+ const [, amount, unit] = match;
7103
+ return (parseFloat(amount) / 100) * VIEWPORT_UNIT_SIGNALS[unit].value;
7104
+ };
7105
+
7079
7106
  const resolveSpacingSize = (size, element, property = "padding") => {
7107
+ const viewportLength = resolveViewportLength(size);
7108
+ if (viewportLength !== null) {
7109
+ return viewportLength;
7110
+ }
7080
7111
  return normalizeStyle(SIZE_MAP[size] || size, property, "js", element);
7081
7112
  };
7082
7113
 
@@ -21286,9 +21317,11 @@ const createControlInteraction = (
21286
21317
  // Check managed controls — a non-interactable child blocks the parent,
21287
21318
  // UNLESS the child's failing constraint has `ignoredByParents: true`
21288
21319
  // (e.g. a disabled child inside a group should not prevent the group from acting).
21320
+ // Only the children that are reachable alongside the parent take part: a
21321
+ // picker's popup content is excluded, see getInteractionBlockingControls.
21289
21322
  failingManagedInteraction = null;
21290
21323
  if (!interactionFailedConstraintInfo) {
21291
- for (const mc of controller.getManagedControls()) {
21324
+ for (const mc of controller.getInteractionBlockingControls()) {
21292
21325
  const mci = mc.rules.interaction;
21293
21326
  if (!mci) {
21294
21327
  continue;
@@ -22750,6 +22783,7 @@ const useRenderScope = (init, update) => {
22750
22783
  * props: Object;
22751
22784
  * ref: Ref; // Used to dispatch DOM events
22752
22785
  * getManagedControls(): UIStateController[]; // Returns controls whose validity is managed by this controller
22786
+ * getInteractionBlockingControls(): UIStateController[]; // Subset of the above whose busy state also blocks interacting with this controller
22753
22787
  * }
22754
22788
  * ```
22755
22789
  */
@@ -22867,6 +22901,10 @@ const useUIStateController = (
22867
22901
  }
22868
22902
  return [];
22869
22903
  },
22904
+ // A facade child lives inside the control's own popup, so it is out of
22905
+ // reach until that popup opens. Letting it block interaction would make
22906
+ // a picker whose content is loading impossible to open at all.
22907
+ getInteractionBlockingControls: () => [],
22870
22908
  onUIAction: (e, { skipCommand } = {}) => {
22871
22909
  if (controlType === "button" && controller.controlHostProps.name) {
22872
22910
  const buttonName = controller.controlHostProps.name;
@@ -23798,6 +23836,11 @@ const useUIGroupStateController = (
23798
23836
  if (!cascadeValidationToChildren) return [];
23799
23837
  return childUIStateControllerArray.slice();
23800
23838
  },
23839
+ // Group children sit next to the group itself: a busy one really does
23840
+ // prevent the group from acting as a whole.
23841
+ getInteractionBlockingControls: () => {
23842
+ return controller.getManagedControls();
23843
+ },
23801
23844
  subscribe: subscribeUIState,
23802
23845
  };
23803
23846
  const rules = createControlRules(controller, {
@@ -24033,6 +24076,13 @@ const useUIFacadeStateController = (props, realUIStateController) => {
24033
24076
  }
24034
24077
  return child.getManagedControls();
24035
24078
  },
24079
+ getInteractionBlockingControls: () => {
24080
+ const child = firstChildControllerRef.current;
24081
+ if (!child) {
24082
+ return [];
24083
+ }
24084
+ return child.getInteractionBlockingControls();
24085
+ },
24036
24086
  onChildUIAction: (child, e, { stateChanged, silent = false }) => {
24037
24087
  if (!stateChanged) {
24038
24088
  return;
@@ -24040,6 +24090,18 @@ const useUIFacadeStateController = (props, realUIStateController) => {
24040
24090
  if (child !== firstChildControllerRef.current) {
24041
24091
  return;
24042
24092
  }
24093
+ if (
24094
+ silent &&
24095
+ child.uiState === undefined &&
24096
+ s.realUIStateController.uiState !== undefined
24097
+ ) {
24098
+ // A silent sync means the child's own structure changed (children
24099
+ // mounted/unmounted), not that the user acted. A child that ends up
24100
+ // with no value there is one that currently *cannot* express one —
24101
+ // a <List loading> holds no items yet — which must not read as the
24102
+ // user clearing the picker, nor fire its uiAction.
24103
+ return;
24104
+ }
24043
24105
  updatingRef.current = true;
24044
24106
  // Use a different event type for silent (mount/unmount) syncs so that
24045
24107
  // the picker's setUIState does not fire navi_change or action pipelines.
@@ -28646,6 +28708,7 @@ installImportMetaCssBuild(import.meta);const css$L = /* css */`
28646
28708
  --link-text-decoration-hover: var(--link-text-decoration);
28647
28709
  --link-cursor: pointer;
28648
28710
  --link-loading-outline-size: 1px;
28711
+ --link-outline-width: 2px;
28649
28712
 
28650
28713
  --link-current-indicator-size: 2px;
28651
28714
  --link-current-indicator-spacing: 0;
@@ -28781,6 +28844,10 @@ installImportMetaCssBuild(import.meta);const css$L = /* css */`
28781
28844
  --x-link-color: var(--link-color);
28782
28845
  }
28783
28846
  }
28847
+ &[data-anchor] {
28848
+ /* Usually better to have some spacing between the anchor and the scroll top */
28849
+ scroll-margin-block: calc(1em + var(--link-outline-width) + 1px);
28850
+ }
28784
28851
  /* Hover */
28785
28852
  &[data-hover] {
28786
28853
  --x-link-background: var(--x-link-background-hover);
@@ -28788,7 +28855,7 @@ installImportMetaCssBuild(import.meta);const css$L = /* css */`
28788
28855
  --x-link-text-decoration: var(--x-link-text-decoration-hover);
28789
28856
  }
28790
28857
  &[data-focus-visible] {
28791
- outline-width: 2px;
28858
+ outline-width: var(--link-outline-width);
28792
28859
  }
28793
28860
  /* Pressed */
28794
28861
  &[data-pressed] {
@@ -35947,20 +36014,6 @@ const renderSafe = (value) => {
35947
36014
 
35948
36015
  const PickerContext = createContext();
35949
36016
 
35950
- // The resolved popup mode of the surrounding Picker: "popover" or "dialog"
35951
- // (frozen for the lifetime of an opening — see usePopupMode). Provided around the
35952
- // picker's popup content so that content can render differently per mode.
35953
- const PickerModeContext = createContext(undefined);
35954
-
35955
- /**
35956
- * Read the mode ("popover" | "dialog") of the Picker whose popup this is rendered
35957
- * inside. Only meaningful for a Picker's popup content (its children); returns
35958
- * undefined anywhere else.
35959
- *
35960
- * @returns {"popover" | "dialog" | undefined}
35961
- */
35962
- const usePickerMode = () => useContext(PickerModeContext);
35963
-
35964
36017
  /**
35965
36018
  * Mirrors what browsers do when navigating to a page:
35966
36019
  * 1. Focus the first element with [navi-autofocus] (but not [navi-autofocus="fallback"]) inside the container
@@ -36028,14 +36081,16 @@ const prepareFocusTransfer = (prepareEvent, debugFocus) => {
36028
36081
  }
36029
36082
  }
36030
36083
  if (!target) {
36031
- // querySelector only searches descendants — but the container itself may
36032
- // carry [navi-autofocus="fallback"] (a focusable popup root with nothing
36033
- // else to focus). It's fine to focus the container in that case.
36034
- const naviAutoFocusFallback = containerEl.matches(
36035
- `[navi-autofocus="fallback"]`,
36036
- )
36037
- ? containerEl
36038
- : containerEl.querySelector(`[navi-autofocus="fallback"]`);
36084
+ // A [navi-autofocus="fallback"] INSIDE the container (e.g. a search
36085
+ // input) wins over the container itself. The container is only the
36086
+ // fallback-of-the-fallback: a focusable popup root gets focus solely
36087
+ // when nothing inside it already carries the fallback. (matches() covers
36088
+ // the container-only case since querySelector searches descendants only.)
36089
+ const naviAutoFocusFallback =
36090
+ containerEl.querySelector(`[navi-autofocus="fallback"]`) ||
36091
+ (containerEl.matches(`[navi-autofocus="fallback"]`)
36092
+ ? containerEl
36093
+ : null);
36039
36094
  if (naviAutoFocusFallback) {
36040
36095
  reason = "navi-autofocus fallback";
36041
36096
  target = naviAutoFocusFallback;
@@ -36157,26 +36212,32 @@ const createOpenController = (
36157
36212
  // open() ignores the request — no need to know *which* element triggers
36158
36213
  // it. A bubble-phase listener (runs after everything else, once the click
36159
36214
  // reaches document) clears the flag if nothing consumed it, meaning this
36160
- // click never resulted in an open() call. A microtask is a last-resort
36161
- // safety net in case the click never reaches document at all (e.g. some
36162
- // ancestor called stopPropagation()).
36215
+ // click never resulted in an open() call. A timeout is a last-resort safety
36216
+ // net in case the click never reaches document at all (e.g. some ancestor
36217
+ // called stopPropagation()) — a *task*, never a microtask: a microtask
36218
+ // checkpoint runs between two listeners of the same trusted event dispatch,
36219
+ // so it would clear the flag before the bubble-phase handler this is meant
36220
+ // to block ever runs, which is precisely the case it exists for.
36163
36221
  const armSuppressNextOpenRequest = () => {
36164
36222
  disarmSuppressNextOpenRequest?.();
36223
+ let safetyTimeout = null;
36165
36224
  const onCaptureClick = () => {
36166
36225
  document.removeEventListener("click", onCaptureClick, {
36167
36226
  capture: true,
36168
36227
  });
36169
36228
  suppressNextOpenRequest = true;
36170
36229
  document.addEventListener("click", onBubbleClick);
36171
- queueMicrotask(() => {
36230
+ safetyTimeout = setTimeout(() => {
36172
36231
  suppressNextOpenRequest = false;
36173
36232
  });
36174
36233
  };
36175
36234
  const onBubbleClick = () => {
36176
36235
  document.removeEventListener("click", onBubbleClick);
36236
+ clearTimeout(safetyTimeout);
36177
36237
  suppressNextOpenRequest = false;
36178
36238
  };
36179
36239
  disarmSuppressNextOpenRequest = () => {
36240
+ clearTimeout(safetyTimeout);
36180
36241
  document.removeEventListener("click", onCaptureClick, {
36181
36242
  capture: true,
36182
36243
  });
@@ -36464,6 +36525,61 @@ const useOpenControllerByProps = (props) => {
36464
36525
  return openController;
36465
36526
  };
36466
36527
 
36528
+ /**
36529
+ * Where the "popover or dialog?" answer lives, for both the components that
36530
+ * decide it and the content that renders inside one.
36531
+ *
36532
+ * `useResolvedPopupMode` is the decision (screen size + maxWidth heuristic),
36533
+ * called by whatever renders the popup — `Popup` itself, or `picker_custom.jsx`
36534
+ * which needs the answer for its own mode-dependent history/ARIA handling on
36535
+ * top of picking a renderer. `usePopupMode` is the read side: any content
36536
+ * rendered inside a popup can call it to lay itself out differently in a
36537
+ * dropdown than in a full-screen modal.
36538
+ */
36539
+
36540
+ const PopupModeContext = createContext(undefined);
36541
+
36542
+ /**
36543
+ * Read the mode of the popup this is rendered inside — a `Popup`, or a
36544
+ * `Picker`'s own popup. Returns undefined outside of any popup content.
36545
+ *
36546
+ * @returns {"popover" | "dialog" | undefined}
36547
+ */
36548
+ const usePopupMode = () => useContext(PopupModeContext);
36549
+
36550
+ /**
36551
+ * Resolves which of Popover/Dialog a popup should be. Frozen for the component
36552
+ * instance's lifetime, so a screen resize never switches an already-mounted
36553
+ * popup from one to the other mid-session.
36554
+ *
36555
+ * @param {"dialog"|"popover"} [modeProp] - Forces one mode; `undefined` to
36556
+ * resolve automatically.
36557
+ * @param {string} [maxWidth] - A small enough value is treated as "compact",
36558
+ * staying a popover even on a small screen.
36559
+ * @returns {["dialog"|"popover", () => void]} The resolved mode, and a
36560
+ * `resetMode` function a caller can call (e.g. on close) to force the *next*
36561
+ * call to re-resolve from scratch instead of keeping the frozen value —
36562
+ * `Popup` itself never needs this (it has no notion of open/close of its
36563
+ * own), `picker_custom.jsx` does (re-evaluates screen size on every fresh
36564
+ * open).
36565
+ */
36566
+ const useResolvedPopupMode = (modeProp, maxWidth) => {
36567
+ const defaultModeRef = useRef(null);
36568
+ if (defaultModeRef.current === null) {
36569
+ defaultModeRef.current = resolvePopupMode(modeProp, maxWidth);
36570
+ }
36571
+ const resetMode = () => {
36572
+ defaultModeRef.current = null;
36573
+ };
36574
+ return [defaultModeRef.current, resetMode];
36575
+ };
36576
+ const resolvePopupMode = (modeProp, maxWidth) => {
36577
+ const isSmallScreen = windowWidthSignal.peek() <= 600;
36578
+ const maxWidthPx = parseFloat(maxWidth);
36579
+ const isCompact = isFinite(maxWidthPx) && maxWidthPx < 150;
36580
+ return modeProp ?? (isSmallScreen && !isCompact ? "dialog" : "popover");
36581
+ };
36582
+
36467
36583
  /**
36468
36584
  * Entry/exit animation CSS shared by Popover and Dialog.
36469
36585
  *
@@ -36561,7 +36677,8 @@ const popupCss = /* css */ `
36561
36677
  this list contains (no shared transition-property to clobber, no
36562
36678
  propertyName to filter). */
36563
36679
  &[navi-animation] {
36564
- transition-property: display, overlay, opacity, translate, scale, box-shadow;
36680
+ transition-property:
36681
+ display, overlay, opacity, translate, scale, box-shadow;
36565
36682
  transition-duration:
36566
36683
  var(--popup-animation-duration), var(--popup-animation-duration),
36567
36684
  var(--popup-opacity-duration), var(--popup-translate-duration),
@@ -36924,15 +37041,24 @@ installImportMetaCssBuild(import.meta);/**
36924
37041
  const css$v = /* css */`
36925
37042
  @layer navi {
36926
37043
  .navi_dialog {
36927
- /* min gap between dialog edges and viewport */
36928
- /* not named margin because it's not implemented with margins (which are needed for centering) */
36929
- --dialog-viewport-spacing: 3dvw;
37044
+ /* Min gap between the dialog and the edges of its container. Written
37045
+ from the marginWithContainer prop (see below) hence --x-, not a knob
37046
+ to set from CSS — so the size caps here and the placement can never
37047
+ disagree. The literal is only what a dialog painted before that ever
37048
+ runs falls back to. Not named "margin" because it isn't implemented
37049
+ with margins (those are needed for centering).
37050
+
37051
+ Capping the *size* here rather than only offsetting the position is
37052
+ what makes a centered dialog follow the mobile virtual keyboard for
37053
+ free: --navi-vvw/--navi-vvh track the visual viewport, so the browser
37054
+ reflows the dialog itself as the keyboard opens. */
37055
+ --x-dialog-viewport-spacing: 3vvw;
36930
37056
 
36931
37057
  --dialog-maxmax-width: calc(
36932
- var(--navi-vvw) - 2 * var(--dialog-viewport-spacing)
37058
+ var(--navi-vvw) - 2 * var(--x-dialog-viewport-spacing)
36933
37059
  );
36934
37060
  --dialog-maxmax-height: calc(
36935
- var(--navi-vvh) - 2 * var(--dialog-viewport-spacing)
37061
+ var(--navi-vvh) - 2 * var(--x-dialog-viewport-spacing)
36936
37062
  );
36937
37063
 
36938
37064
  --dialog-border-radius: var(--navi-popup-border-radius);
@@ -37165,8 +37291,14 @@ const css$v = /* css */`
37165
37291
  * `bottom-end`/`bottom-left`/`bottom-right`, `left`/`left-start`/
37166
37292
  * `left-end`, or `center` — optionally wrapped in `inset(...)` (e.g.
37167
37293
  * `inset(top)`) for the overlapping variant.
37168
- * @param {string|number} [props.marginWithContainer=0] - Extra spacing kept
37169
- * between the dialog and the edges of its container.
37294
+ * @param {string|number} [props.marginWithContainer="3vvw"] - Minimum gap kept
37295
+ * between the dialog and the edges of its container, whatever its
37296
+ * `positionArea`: it both caps the dialog's own size (via
37297
+ * `--x-dialog-viewport-spacing`, written from this prop) and offsets a docked
37298
+ * one from the edge it docks to. Accepts a spacing token ("s", "m"…), a
37299
+ * number of pixels, or a viewport length — "vvw"/"vvh" being the visual
37300
+ * viewport, which shrinks when the mobile keyboard opens. Pass 0 for a dialog
37301
+ * meant to sit flush (a side panel).
37170
37302
  * @param {"close"|"cancel"|"capture"|"none"} [props.pointerInteractionOutsideEffect="close"]
37171
37303
  * - `"close"` closes the dialog on an outside click. `"capture"`/`"none"`
37172
37304
  * both just absorb the click without closing (visually dimmed backdrop vs.
@@ -37312,7 +37444,10 @@ const useDialogProps = props => {
37312
37444
  // Same grammar as Popover's own positionArea — see this file's top
37313
37445
  // comment and popup_shared.js's parsePositionArea.
37314
37446
  positionArea = "center",
37315
- marginWithContainer = 0,
37447
+ // A dialog docked against an edge must keep the same gap its own size cap
37448
+ // already guarantees a centered one — so this drives both (see
37449
+ // --x-dialog-viewport-spacing above). Pass 0 to sit flush (side_panel.jsx).
37450
+ marginWithContainer = "3vvw",
37316
37451
  // "close" (default) closes on an outside click. "capture"/"none" both
37317
37452
  // just absorb it without closing — for the via-attribute renderer,
37318
37453
  // showModal() already makes the rest of the page inert, so there's
@@ -37349,6 +37484,21 @@ const useDialogProps = props => {
37349
37484
  const debugFocus = useDebugFocus();
37350
37485
  const debugInteraction = useDebugInteraction();
37351
37486
  const autoFocusProps = useAutoFocus(ref, autoFocus);
37487
+ // positionDialog lives in openEffect's closure — created once, when the
37488
+ // dialog opens. Reading the placement props through a ref instead of that
37489
+ // closure is what lets a change while open take effect on the spot (see the
37490
+ // reposition effect below) rather than only on the next opening.
37491
+ const positionPropsRef = useRef(null);
37492
+ positionPropsRef.current = {
37493
+ positionArea,
37494
+ marginWithContainer
37495
+ };
37496
+ const repositionRef = useRef(null);
37497
+ useEffect(() => {
37498
+ repositionRef.current?.(new CustomEvent("position_props_change", {
37499
+ detail: {}
37500
+ }));
37501
+ }, [positionArea, marginWithContainer]);
37352
37502
  const positionAreaParseResult = parsePositionArea(positionArea);
37353
37503
  if (!positionAreaParseResult) {
37354
37504
  console.warn(`Dialog: invalid positionArea="${positionArea}"`);
@@ -37492,10 +37642,27 @@ const useDialogProps = props => {
37492
37642
  // custom renderer. applyNewPosition sets --container-position-remaining-height/-width
37493
37643
  // from the result, same as popover.jsx.
37494
37644
  const positionDialog = triggerEvent => {
37645
+ const {
37646
+ positionArea,
37647
+ marginWithContainer
37648
+ } = positionPropsRef.current;
37649
+ let marginWithContainerInPixels = resolveSpacingSize(marginWithContainer);
37650
+ if (typeof marginWithContainerInPixels !== "number") {
37651
+ // A value only CSS could evaluate (a spacing token resolving to a var(),
37652
+ // a percentage…) — the placement below needs a real number, and letting
37653
+ // it through would put the dialog at NaN.
37654
+ console.warn(`Dialog: marginWithContainer="${marginWithContainer}" cannot be resolved to pixels. Use a number or a viewport length ("3vvw", "2vvh").`);
37655
+ marginWithContainerInPixels = 0;
37656
+ }
37657
+ // The size caps read the same gap in CSS as the placement below applies
37658
+ // in pixels, so a docked dialog and a centered one keep the same
37659
+ // distance from the edges. Written resolved (not as the raw prop) so a
37660
+ // spacing token stays valid inside the caps' own calc().
37661
+ dialogEl.style.setProperty("--x-dialog-viewport-spacing", `${marginWithContainerInPixels}px`);
37495
37662
  const pickOptions = {
37496
37663
  positionArea,
37497
37664
  container: positionedAncestor,
37498
- marginWithContainer: resolveSpacingSize(marginWithContainer),
37665
+ marginWithContainer: marginWithContainerInPixels,
37499
37666
  event: triggerEvent
37500
37667
  };
37501
37668
  let position = pickPositionRelativeTo(dialogEl, null, pickOptions);
@@ -37541,7 +37708,19 @@ const useDialogProps = props => {
37541
37708
  skipElementResize: true
37542
37709
  });
37543
37710
  rectEffect.observeSize(dialogEl);
37711
+ // Exposed for the placement-props effect below, which needs to re-place an
37712
+ // already-open dialog.
37713
+ repositionRef.current = repositionEvent => {
37714
+ // data-position-*-current pins an open dialog to the side it first
37715
+ // resolved to, so a resize never makes it jump (pickPositionRelativeTo
37716
+ // reads it back and prefers it over the requested area). A new placement
37717
+ // request is precisely the case where that memory must not win.
37718
+ dialogEl.removeAttribute("data-position-x-current");
37719
+ dialogEl.removeAttribute("data-position-y-current");
37720
+ positionDialog(repositionEvent);
37721
+ };
37544
37722
  addCleanup(() => {
37723
+ repositionRef.current = null;
37545
37724
  rectEffect.disconnect();
37546
37725
  });
37547
37726
  // A descendant anchored to something inside this dialog (a Callout, a
@@ -37833,7 +38012,10 @@ let openLocalPopoverCount = 0;
37833
38012
  const css$u = /* css */`
37834
38013
  @layer navi {
37835
38014
  .navi_popover {
37836
- --popover-max-height: 300px; /* soft: user-configurable preferred max-height */
38015
+ /* soft: user-configurable preferred max-height. Kept as a *default*
38016
+ rather than a value so an outer component can bridge its own prop into
38017
+ --popover-max-height without having to restate 300px (see picker). */
38018
+ --popover-max-height-default: 300px;
37837
38019
  --popover-maxmax-height: calc(0.95 * var(--navi-vvh));
37838
38020
  --popover-maxmax-width: calc(0.95 * var(--navi-vvw));
37839
38021
 
@@ -37884,7 +38066,7 @@ const css$u = /* css */`
37884
38066
  var(--popover-maxmax-width)
37885
38067
  );
37886
38068
  --x-popover-max-height: min(
37887
- var(--popover-max-height),
38069
+ var(--popover-max-height, var(--popover-max-height-default)),
37888
38070
  var(--container-position-remaining-height, var(--popover-maxmax-height)),
37889
38071
  var(--popover-maxmax-height)
37890
38072
  );
@@ -38313,6 +38495,24 @@ const usePopoverProps = props => {
38313
38495
  // (see resolveAutoAnimationKind).
38314
38496
  const isAutoAnimation = animation === true || animation === "auto";
38315
38497
  const hasBackdrop = pointerInteractionOutsideEffect !== "none";
38498
+ // positionPopover lives in openEffect's closure — created once, when the
38499
+ // popover opens. Reading the placement props through a ref instead of that
38500
+ // closure is what lets a change while open take effect on the spot (see the
38501
+ // reposition effect below) rather than only on the next opening.
38502
+ const positionPropsRef = useRef(null);
38503
+ positionPropsRef.current = {
38504
+ positionArea,
38505
+ positionAreaFixed,
38506
+ positionAreaWhenAnchorIsInvalid,
38507
+ marginWithAnchor,
38508
+ marginWithContainer
38509
+ };
38510
+ const repositionRef = useRef(null);
38511
+ useEffect(() => {
38512
+ repositionRef.current?.(new CustomEvent("position_props_change", {
38513
+ detail: {}
38514
+ }));
38515
+ }, [positionArea, positionAreaFixed, positionAreaWhenAnchorIsInvalid, marginWithAnchor, marginWithContainer]);
38316
38516
  // The custom renderer's own starting-hidden state is a stylesheet default
38317
38517
  // now (&:not([popover]) { display: none } on .navi_popover/
38318
38518
  // .navi_popover_backdrop above) rather than set here imperatively — a
@@ -38509,6 +38709,13 @@ const usePopoverProps = props => {
38509
38709
  // via-attribute renderer (see its own computation above).
38510
38710
  const effectiveAnchor = hasAnchorElement ? anchorElement : positionedAncestor;
38511
38711
  const positionPopover = positionEvent => {
38712
+ const {
38713
+ positionArea,
38714
+ positionAreaFixed,
38715
+ positionAreaWhenAnchorIsInvalid,
38716
+ marginWithAnchor,
38717
+ marginWithContainer
38718
+ } = positionPropsRef.current;
38512
38719
  let position;
38513
38720
  if (hasAnchorElement) {
38514
38721
  const {
@@ -38676,6 +38883,21 @@ const usePopoverProps = props => {
38676
38883
  // while open (e.g. an expand/collapse toggle inside it) — not just when
38677
38884
  // the anchor itself moves/resizes/re-anchors.
38678
38885
  rectEffect.observeSize(popoverEl);
38886
+ // Exposed for the placement-props effect above, which needs to re-place an
38887
+ // already-open popover.
38888
+ repositionRef.current = repositionEvent => {
38889
+ // data-position-*-current pins an open popover to the side it first
38890
+ // resolved to, so scrolling or a content resize never makes it jump
38891
+ // (pickPositionRelativeTo reads it back and prefers it over the
38892
+ // requested area). A new placement request is precisely the case where
38893
+ // that memory must not win — drop it before re-resolving.
38894
+ popoverEl.removeAttribute("data-position-x-current");
38895
+ popoverEl.removeAttribute("data-position-y-current");
38896
+ positionPopover(repositionEvent);
38897
+ };
38898
+ addCleanup(() => {
38899
+ repositionRef.current = null;
38900
+ });
38679
38901
  // A descendant anchored to something inside this popover (a Callout, a
38680
38902
  // further-nested Popover) needing to know about this popover's own
38681
38903
  // left/top repositioning transition — not just that the target changed
@@ -39002,12 +39224,12 @@ const resolvePositionAreaAndAnimationKind = ({
39002
39224
  installImportMetaCssBuild(import.meta);/**
39003
39225
  * A lightweight version of picker_custom.jsx's own Popover/Dialog switch —
39004
39226
  * no picker concepts (value/action tracking, keyboard letter/arrow-to-open
39005
- * shortcuts, history-driven expanded state, anchor-clone "attached" mode):
39227
+ * shortcuts, history-driven expanded state):
39006
39228
  * just picks between rendering a Popover or a Dialog and applies the shared
39007
39229
  * "popup box" look (padding, background, border-radius, box-shadow) to
39008
39230
  * whichever one it renders.
39009
39231
  *
39010
- * Mode resolution (`usePopupMode` below) is shared with picker_custom.jsx,
39232
+ * Mode resolution (`useResolvedPopupMode`, popup_mode.jsx) is shared with picker_custom.jsx,
39011
39233
  * not just mirrored — the picker needs the resolved mode itself (for its own
39012
39234
  * mode-dependent history/ARIA handling), not just to pick which of Popover/
39013
39235
  * Dialog to render, so it calls the same hook directly instead of
@@ -39150,7 +39372,12 @@ const Popup = props => {
39150
39372
  positionAreaFixed,
39151
39373
  ...rest
39152
39374
  } = props;
39153
- const [mode] = usePopupMode(modeProp, maxWidth);
39375
+ const [mode] = useResolvedPopupMode(modeProp, maxWidth);
39376
+ // So the content can lay itself out per mode — see usePopupMode.
39377
+ const childrenWithMode = jsx(PopupModeContext.Provider, {
39378
+ value: mode,
39379
+ children: children
39380
+ });
39154
39381
  if (mode === "dialog") {
39155
39382
  const expandXResolved = expand || expandX;
39156
39383
  const expandYResolved = expand || expandY;
@@ -39161,7 +39388,7 @@ const Popup = props => {
39161
39388
  className: withPropsClassName("navi_popup", className),
39162
39389
  "data-expand-x": expandXResolved ? "" : undefined,
39163
39390
  "data-expand-y": expandYResolved ? "" : undefined,
39164
- children: children
39391
+ children: childrenWithMode
39165
39392
  });
39166
39393
  }
39167
39394
  return jsx(Popover, {
@@ -39173,54 +39400,10 @@ const Popup = props => {
39173
39400
  focusCapture: focusCapture,
39174
39401
  positionAreaFixed: positionAreaFixed,
39175
39402
  className: withPropsClassName("navi_popup", className),
39176
- children: children
39403
+ children: childrenWithMode
39177
39404
  });
39178
39405
  };
39179
39406
 
39180
- /**
39181
- * Frozen for the component instance's lifetime — mirrors `Popup`'s own
39182
- * mode-resolution timing (a screen resize while already mounted doesn't
39183
- * switch between Popover and Dialog mid-session).
39184
- *
39185
- * @param {"dialog"|"popover"} [modeProp]
39186
- * @param {string} [maxWidth]
39187
- * @returns {["dialog"|"popover", () => void]} The resolved mode, and a
39188
- * `resetMode` function a caller can call (e.g. on close) to force the
39189
- * *next* call to re-resolve from scratch instead of keeping the frozen
39190
- * value — `Popup` itself never needs this (it has no notion of
39191
- * open/close of its own), `picker_custom.jsx` does (re-evaluates screen
39192
- * size on every fresh open).
39193
- */
39194
- const usePopupMode = (modeProp, maxWidth) => {
39195
- const defaultModeRef = useRef(null);
39196
- if (defaultModeRef.current === null) {
39197
- defaultModeRef.current = resolvePopupMode(modeProp, maxWidth);
39198
- }
39199
- const resetMode = () => {
39200
- defaultModeRef.current = null;
39201
- };
39202
- return [defaultModeRef.current, resetMode];
39203
- };
39204
- /**
39205
- * Same small-screen/`maxWidth`-compact heuristic `Popup` uses internally,
39206
- * exported so `picker_custom.jsx` (which needs the resolved mode itself,
39207
- * for its own mode-dependent history/ARIA handling — not just to pick which
39208
- * of Popover/Dialog to render, the way `Popup` only ever needs it) doesn't
39209
- * have to duplicate it.
39210
- *
39211
- * @param {"dialog"|"popover"} [modeProp] - Forces one mode; `undefined` to
39212
- * resolve automatically.
39213
- * @param {string} [maxWidth] - A small enough value is treated as
39214
- * "compact", staying a popover even on a small screen.
39215
- * @returns {"dialog"|"popover"}
39216
- */
39217
- const resolvePopupMode = (modeProp, maxWidth) => {
39218
- const isSmallScreen = windowWidthSignal.peek() <= 600;
39219
- const maxWidthPx = parseFloat(maxWidth);
39220
- const isCompact = isFinite(maxWidthPx) && maxWidthPx < 150;
39221
- return modeProp ?? (isSmallScreen && !isCompact ? "dialog" : "popover");
39222
- };
39223
-
39224
39407
  installImportMetaCssBuild(import.meta);const css$s = /* css */`
39225
39408
  .navi_picker {
39226
39409
  /* Sizing ceilings (maxmax), background, box-shadow, outline, padding,
@@ -39228,8 +39411,7 @@ installImportMetaCssBuild(import.meta);const css$s = /* css */`
39228
39411
  themselves — nothing to redefine here. Only the picker's own look
39229
39412
  (border color/radius/width, background) needs bridging into the vars
39230
39413
  Popover/Dialog actually consume, plus a couple of genuinely
39231
- picker-specific bits below (anchor-width min-width, the anchor clone,
39232
- the nested list). */
39414
+ picker-specific bits below (anchor-width min-width, the nested list). */
39233
39415
 
39234
39416
  /* popover */
39235
39417
  &[aria-haspopup="listbox"] {
@@ -39240,52 +39422,17 @@ installImportMetaCssBuild(import.meta);const css$s = /* css */`
39240
39422
  --popover-background-color: var(--picker-background-color);
39241
39423
  --popover-outline-width: var(--picker-outline-width);
39242
39424
  --popover-outline-color: var(--picker-outline-color);
39425
+ /* No fallback on purpose: when the picker's own popoverMaxHeight prop
39426
+ is unset this declaration is invalid at computed-value time, which
39427
+ leaves --popover-max-height unset and lets the popover fall back to
39428
+ --popover-max-height-default. */
39429
+ --popover-max-height: var(--picker-popover-max-height);
39243
39430
 
39244
39431
  /* At least as wide as the trigger — unless popupWidthFitContent, then
39245
39432
  let the content (e.g. a Wheel) size the popover (see picker.jsx). */
39246
39433
  min-width: var(--picker-popover-min-width, var(--anchor-width, 0px));
39247
39434
  cursor: default; /* Reset pointer cursor within the select */
39248
39435
 
39249
- /* The anchor placeholder is a non-interactive visual clone of the
39250
- trigger. It makes the popover wrap both the trigger area and the list
39251
- under a single border/shadow. CSS order places it before the list
39252
- when the popover is below the trigger, and after when above. */
39253
- .navi_picker_anchor_clone {
39254
- display: flex;
39255
- /* To make clone same height as original we need to force it because context can impact height */
39256
- /* Like siblings with a bigger height in a flex container */
39257
- /* We subtract the border sizes as anchor-height includes borders in the dimensions */
39258
- min-height: var(--anchor-inner-height);
39259
- /* Mirror the trigger's padding so the clone looks identical */
39260
- padding-top: var(--x-picker-padding-top);
39261
- padding-right: var(--x-picker-padding-right);
39262
- padding-bottom: var(--x-picker-padding-bottom);
39263
- padding-left: var(--x-picker-padding-left);
39264
- flex-shrink: 0;
39265
- flex-direction: column;
39266
- justify-content: center;
39267
- gap: var(--navi-s);
39268
- order: -1; /* before the list — popover is below the trigger */
39269
- background: var(--x-picker-background-color);
39270
- border-bottom: var(--picker-border-width) solid
39271
- var(--x-picker-border-color);
39272
-
39273
- &:hover {
39274
- --x-picker-background-color: var(--picker-background-color-hover);
39275
- --x-picker-border-color: var(--picker-border-color-hover);
39276
- }
39277
- }
39278
-
39279
- &[data-position-y-current="top"],
39280
- &[data-position-y-current="inset-bottom"] {
39281
- .navi_picker_anchor_clone {
39282
- order: 1; /* after the list — popover is above the trigger */
39283
- border-top: var(--picker-border-width) solid
39284
- var(--x-picker-border-color);
39285
- border-bottom: none;
39286
- }
39287
- }
39288
-
39289
39436
  /* The list scrolls inside the popover */
39290
39437
  .navi_list_container {
39291
39438
  width: 100%;
@@ -39299,16 +39446,15 @@ installImportMetaCssBuild(import.meta);const css$s = /* css */`
39299
39446
  }
39300
39447
 
39301
39448
  &[aria-expanded="true"] {
39302
- &[navi-popover-mode="overlay"],
39303
- &[navi-popover-mode="attached"] {
39449
+ &[navi-popover-mode="overlay"] {
39304
39450
  /* When sizes uses float AND the border uses border-radius it's possible it's possible to see some pixels
39305
39451
  of the underlying select borders. We hide them to ensure this cannot happen. */
39306
39452
  border-color: transparent;
39307
39453
  }
39308
39454
 
39309
39455
  /* Popover itself has no opinion on its content's own layout (plain
39310
- div, block by default) — the picker's content (anchor clone +
39311
- list) needs to stack vertically. */
39456
+ div, block by default) — the picker's content needs to stack
39457
+ vertically. */
39312
39458
  .navi_popover {
39313
39459
  display: flex;
39314
39460
  flex-direction: column;
@@ -39456,11 +39602,11 @@ const PickerCustom = props => {
39456
39602
  const controlId = useContext(ControlIdContext);
39457
39603
  props.id = props.id || controlId || idDefault;
39458
39604
  // Same small-screen/maxWidth-compact heuristic Popup itself uses (see
39459
- // popup.jsx's own usePopupMode) — frozen for the lifetime of an opening
39605
+ // popup_mode.jsx's own useResolvedPopupMode) — frozen for the lifetime of an opening
39460
39606
  // (computed when closed, stable while open, so a screen resize mid-session
39461
39607
  // doesn't switch between Popover and Dialog), with resetMode called from
39462
39608
  // this picker's own onClose below to re-evaluate on the *next* open.
39463
- const [mode, resetMode] = usePopupMode(modeProp, props.maxWidth);
39609
+ const [mode, resetMode] = useResolvedPopupMode(modeProp, props.maxWidth);
39464
39610
  const pickerProps = {
39465
39611
  ...props
39466
39612
  };
@@ -39801,6 +39947,7 @@ const PickerContentInsidePopup = props => {
39801
39947
  // defaulting the now-correctly-named prop to `true` would be a real,
39802
39948
  // unintended behavior change riding along with the rename.
39803
39949
  focusCapture,
39950
+ positionArea,
39804
39951
  popoverMode = "nearby",
39805
39952
  popoverSpacing = popoverMode === "nearby" ? 5 : 0,
39806
39953
  marginWithContainer = 10,
@@ -39846,11 +39993,11 @@ const PickerContentInsidePopup = props => {
39846
39993
  }
39847
39994
  });
39848
39995
  },
39849
- children: jsxs(Popup, {
39996
+ children: jsx(Popup, {
39850
39997
  ...popupProps,
39851
39998
  mode: mode,
39852
39999
  animation: animation,
39853
- positionArea: isPopover ? popoverMode === "nearby" ? "bottom-start" : "inset(top-left)" : undefined,
40000
+ positionArea: isPopover ? positionArea ?? (popoverMode === "nearby" ? "bottom-start" : "inset(top-left)") : positionArea,
39854
40001
  marginWithAnchor: isPopover ? popoverSpacing : undefined,
39855
40002
  marginWithContainer: isPopover ? marginWithContainer : undefined,
39856
40003
  scrollCapture: scrollCapture === "dialog" ? !isPopover : scrollCapture === "popover" ? isPopover : scrollCapture,
@@ -39858,21 +40005,10 @@ const PickerContentInsidePopup = props => {
39858
40005
  focusCapture: isPopover ? focusCapture : undefined,
39859
40006
  expandX: !isPopover ? expandX : undefined,
39860
40007
  expandY: !isPopover ? expandY : undefined,
39861
- children: [isPopover && popoverMode === "attached" ? jsx("div", {
39862
- className: "navi_picker_anchor_clone",
39863
- onMouseDown: e => {
39864
- if (e.button !== 0) {
39865
- return;
39866
- }
39867
- popupProps.openController.requestClose(e, {
39868
- isCancel: true
39869
- });
39870
- },
39871
- children: props.trigger
39872
- }) : null, jsx(PickerModeContext.Provider, {
40008
+ children: jsx(PopupModeContext.Provider, {
39873
40009
  value: mode,
39874
40010
  children: children
39875
- })]
40011
+ })
39876
40012
  })
39877
40013
  });
39878
40014
  };
@@ -40512,6 +40648,84 @@ const toDate = (value, parseString) => {
40512
40648
  return null;
40513
40649
  };
40514
40650
 
40651
+ const LoadingDotsSvg = () => {
40652
+ return jsxs("svg", {
40653
+ viewBox: "0 0 200 200",
40654
+ width: "100%",
40655
+ height: "100%",
40656
+ xmlns: "http://www.w3.org/2000/svg",
40657
+ children: [jsx("rect", {
40658
+ fill: "currentColor",
40659
+ stroke: "currentColor",
40660
+ "stroke-width": "15",
40661
+ width: "30",
40662
+ height: "30",
40663
+ x: "25",
40664
+ y: "85",
40665
+ children: jsx("animate", {
40666
+ attributeName: "opacity",
40667
+ calcMode: "spline",
40668
+ dur: "2",
40669
+ values: "1;0;1;",
40670
+ keySplines: ".5 0 .5 1;.5 0 .5 1",
40671
+ repeatCount: "indefinite",
40672
+ begin: "-.4"
40673
+ })
40674
+ }), jsx("rect", {
40675
+ fill: "currentColor",
40676
+ stroke: "currentColor",
40677
+ "stroke-width": "15",
40678
+ width: "30",
40679
+ height: "30",
40680
+ x: "85",
40681
+ y: "85",
40682
+ children: jsx("animate", {
40683
+ attributeName: "opacity",
40684
+ calcMode: "spline",
40685
+ dur: "2",
40686
+ values: "1;0;1;",
40687
+ keySplines: ".5 0 .5 1;.5 0 .5 1",
40688
+ repeatCount: "indefinite",
40689
+ begin: "-.2"
40690
+ })
40691
+ }), jsx("rect", {
40692
+ fill: "currentColor",
40693
+ stroke: "currentColor",
40694
+ "stroke-width": "15",
40695
+ width: "30",
40696
+ height: "30",
40697
+ x: "145",
40698
+ y: "85",
40699
+ children: jsx("animate", {
40700
+ attributeName: "opacity",
40701
+ calcMode: "spline",
40702
+ dur: "2",
40703
+ values: "1;0;1;",
40704
+ keySplines: ".5 0 .5 1;.5 0 .5 1",
40705
+ repeatCount: "indefinite",
40706
+ begin: "0"
40707
+ })
40708
+ })]
40709
+ });
40710
+ };
40711
+
40712
+ const LoadingIndicator = ({
40713
+ variant = "circle",
40714
+ ...props
40715
+ }) => {
40716
+ if (variant === "dots") {
40717
+ return jsx(Icon, {
40718
+ ...props,
40719
+ children: jsx(LoadingDotsSvg, {})
40720
+ });
40721
+ }
40722
+ return jsx(Icon, {
40723
+ circle: true,
40724
+ ...props,
40725
+ children: jsx(LoadingIndicatorFluid, {})
40726
+ });
40727
+ };
40728
+
40515
40729
  installImportMetaCssBuild(import.meta);const css$r = /* css */`
40516
40730
  @layer navi {
40517
40731
  .navi_separator {
@@ -41266,6 +41480,11 @@ const ListSelectable = props => {
41266
41480
  "navi-has-selected-background": selectedIndicator === "backgroundColor" ? "" : undefined,
41267
41481
  ...listControlRootProps,
41268
41482
  ...listControlProps,
41483
+ // "loading" is a control prop, so useControlgroupProps consumes it (into
41484
+ // aria-busy / the :-navi-loading pseudo state) and it does not survive
41485
+ // into the props below. ListUI needs it too — it is what makes the list
41486
+ // render skeleton rows instead of its (not yet known) items.
41487
+ loading: props.loading,
41269
41488
  name: undefined,
41270
41489
  value: undefined,
41271
41490
  defaultValue: undefined,
@@ -41789,6 +42008,16 @@ const css$p = /* css */`
41789
42008
  }
41790
42009
  &[data-expand-y] {
41791
42010
  --list-max-height: none;
42011
+
42012
+ /* expandY grows the container to fill its parent (flex-grow, applied by
42013
+ Box). The scroll container must then fill that grown height and take
42014
+ over the internal scroll — flex:1 fills it, min-height:0 lets it shrink
42015
+ below its content so overflow:auto scrolls instead of the content
42016
+ pushing past the container (which overflow:hidden would just clip). */
42017
+ .navi_list_scroll_container {
42018
+ min-height: 0;
42019
+ flex: 1;
42020
+ }
41792
42021
  }
41793
42022
  &[navi-nothing-to-display] {
41794
42023
  display: none;
@@ -41891,7 +42120,11 @@ const css$p = /* css */`
41891
42120
  scroll-margin-bottom: var(--x-list-scroll-spacing-bottom);
41892
42121
  scroll-margin-left: var(--x-list-scroll-spacing-left);
41893
42122
 
41894
- &[aria-hidden="true"] {
42123
+ /* The "invisible_and_inert" search no-match mode keeps items in the DOM
42124
+ (to preserve layout) but hides them — it sets BOTH aria-hidden and inert.
42125
+ Scope to that pair so the presentation placeholders that are only
42126
+ aria-hidden (skeleton rows, the loader) stay visible. */
42127
+ &[aria-hidden="true"][inert] {
41895
42128
  opacity: 0;
41896
42129
  }
41897
42130
 
@@ -42000,6 +42233,39 @@ const css$p = /* css */`
42000
42233
  user-select: none;
42001
42234
  }
42002
42235
  }
42236
+ /* Loading placeholders (see List's loading / loadingIndicator / skeletonTemplate).
42237
+ A skeleton row reuses <Text loading> for the shimmer bar; the loader row
42238
+ centers a spinner. */
42239
+ .navi_list_item_skeleton {
42240
+ pointer-events: none;
42241
+ }
42242
+ .navi_list_loader {
42243
+ display: flex;
42244
+ padding: 12px;
42245
+ align-items: center;
42246
+ justify-content: center;
42247
+ color: light-dark(#888, #aaa);
42248
+ }
42249
+ /* Error state (List error prop): an inline callout describing why the list
42250
+ failed to load, shown in place of the items. */
42251
+ .navi_list_error {
42252
+ display: flex;
42253
+ margin: 8px;
42254
+ padding: 10px 12px;
42255
+ align-items: flex-start;
42256
+ gap: 8px;
42257
+ color: light-dark(#b91c1c, #fca5a5);
42258
+ font-size: 0.9em;
42259
+ line-height: 1.4;
42260
+ background: light-dark(#fef2f2, rgba(127, 29, 29, 0.25));
42261
+ border: 1px solid light-dark(#fecaca, rgba(248, 113, 113, 0.4));
42262
+ border-radius: 6px;
42263
+ }
42264
+ .navi_list_error_icon {
42265
+ flex: none;
42266
+ font-size: 1em;
42267
+ line-height: 1.4;
42268
+ }
42003
42269
  [navi-virtual-filler="after"] {
42004
42270
  /* for some reason preact ends up puttin this element before the list items in some scenarios
42005
42271
  I've noticed that removing the ItemIndexToScrollOnMountRefContext.Provider
@@ -42090,6 +42356,11 @@ const ListUI = props => {
42090
42356
  columns,
42091
42357
  searchText,
42092
42358
  searchNoMatchMode = "remove",
42359
+ loading,
42360
+ loadingIndicator = "skeleton",
42361
+ loadingSkeletonCount = 3,
42362
+ skeletonTemplate,
42363
+ error,
42093
42364
  horizontal,
42094
42365
  spacing,
42095
42366
  ...rest
@@ -42161,9 +42432,63 @@ const ListUI = props => {
42161
42432
  const noMatchCount = tracker.noMatchCountSignal.value;
42162
42433
  const itemCount = tracker.countSignal.value;
42163
42434
  const allNoMatch = noMatchCount > 0 && noMatchCount === itemCount;
42435
+ const searching = Boolean(searchText);
42164
42436
  const fallbackDisabled = fallback !== undefined && !fallback;
42165
42437
  const searchFallbackDisabled = searchFallback !== undefined && !searchFallback;
42166
- const nothingToDisplay = allNoMatch && searchFallbackDisabled && searchNoMatchMode === "remove" || itemCount === 0 && fallbackDisabled;
42438
+ // No item is visible when the list is empty (filtering may happen outside the
42439
+ // list, dropping itemCount to 0) or when a search removed them all — only the
42440
+ // "remove" mode empties the view; "muted"/"below"/… keep items on screen.
42441
+ const noVisibleItems = itemCount === 0 || allNoMatch && searchNoMatchMode === "remove";
42442
+ // Which fallback message actually renders (mirrors SearchFallback / Fallback
42443
+ // below): during a search an empty/no-match result is a "no match" state (the
42444
+ // search fallback), otherwise an empty list is the "empty" state.
42445
+ const searchFallbackShown = (allNoMatch || searching && itemCount === 0) && !searchFallbackDisabled;
42446
+ const emptyFallbackShown = !searching && itemCount === 0 && !fallbackDisabled;
42447
+ // Hide the whole list — border included — when there is genuinely nothing to
42448
+ // show: no visible items AND no fallback message. Never while loading or in
42449
+ // error (the placeholder / error message ARE the content to display).
42450
+ const nothingToDisplay = !loading && !error && noVisibleItems && !searchFallbackShown && !emptyFallbackShown;
42451
+
42452
+ // Placeholder content replaces the real children: an error message when the
42453
+ // load failed (takes precedence), otherwise — while loading — skeleton rows
42454
+ // (default, count loadingSkeletonCount, look skeletonTemplate) or a single
42455
+ // centered loader when loadingIndicator="loader".
42456
+ let content = children;
42457
+ if (error) {
42458
+ content = jsxs(ListItem, {
42459
+ role: "presentation",
42460
+ baseClassName: "navi_list_item navi_list_error",
42461
+ children: [jsx("span", {
42462
+ className: "navi_list_error_icon",
42463
+ "aria-hidden": "true",
42464
+ children: "\u26A0"
42465
+ }), jsx("span", {
42466
+ children: error === true ? "Something went wrong." : error
42467
+ })]
42468
+ });
42469
+ } else if (loading) {
42470
+ if (loadingIndicator === "loader") {
42471
+ content = jsx(ListItem, {
42472
+ role: "presentation",
42473
+ "aria-hidden": "true",
42474
+ baseClassName: "navi_list_item navi_list_loader",
42475
+ children: jsx(LoadingIndicator, {})
42476
+ });
42477
+ } else {
42478
+ const template = skeletonTemplate ?? jsx(ListItem, {
42479
+ skeleton: true
42480
+ });
42481
+ const skeletons = [];
42482
+ let skeletonIndex = 0;
42483
+ while (skeletonIndex < loadingSkeletonCount) {
42484
+ skeletons.push(cloneElement(template, {
42485
+ key: `navi-list-skeleton-${skeletonIndex}`
42486
+ }));
42487
+ skeletonIndex++;
42488
+ }
42489
+ content = skeletons;
42490
+ }
42491
+ }
42167
42492
  return jsx(Box, {
42168
42493
  ...rest,
42169
42494
  ref: ref,
@@ -42177,6 +42502,8 @@ const ListUI = props => {
42177
42502
  expand: expand,
42178
42503
  "navi-zero-match": allNoMatch ? "" : undefined,
42179
42504
  "navi-nothing-to-display": nothingToDisplay ? "" : undefined,
42505
+ "navi-loading": loading ? "" : undefined,
42506
+ "navi-error": error ? "" : undefined,
42180
42507
  styleCSSVars: LIST_STYLE_CSS_VARS,
42181
42508
  pseudoClasses: LIST_PSEUDO_CLASSES,
42182
42509
  hasChildUsingForwardedProps: true,
@@ -42198,6 +42525,9 @@ const ListUI = props => {
42198
42525
  role: role,
42199
42526
  fallback: fallback,
42200
42527
  searchFallback: searchFallback,
42528
+ searching: searching,
42529
+ loading: loading,
42530
+ error: error,
42201
42531
  searchNoMatchMode: searchNoMatchMode,
42202
42532
  separator: separator,
42203
42533
  expandX: expandX || expand,
@@ -42208,7 +42538,7 @@ const ListUI = props => {
42208
42538
  renderWindow: renderWindow,
42209
42539
  virtualItemSizeSignal: virtualItemSizeSignal,
42210
42540
  pendingScrollRef: pendingScrollRef,
42211
- children: children
42541
+ children: content
42212
42542
  })
42213
42543
  });
42214
42544
  };
@@ -42238,6 +42568,11 @@ const ListFirstResolver = props => {
42238
42568
  * searchFallback?: import("ignore:preact").ComponentChildren,
42239
42569
  * searchText?: string,
42240
42570
  * searchNoMatchMode?: "remove" | "invisible_and_inert" | "muted" | "below",
42571
+ * loading?: boolean,
42572
+ * loadingIndicator?: "skeleton" | "loader",
42573
+ * loadingSkeletonCount?: number,
42574
+ * skeletonTemplate?: import("ignore:preact").ComponentChildren,
42575
+ * error?: boolean | import("ignore:preact").ComponentChildren,
42241
42576
  * separator?: boolean | import("ignore:preact").ComponentChildren,
42242
42577
  * lockSize?: boolean,
42243
42578
  * horizontal?: boolean,
@@ -42255,6 +42590,9 @@ const ListContent = ({
42255
42590
  role,
42256
42591
  fallback,
42257
42592
  searchFallback,
42593
+ searching,
42594
+ loading,
42595
+ error,
42258
42596
  searchNoMatchMode,
42259
42597
  separator,
42260
42598
  expandX,
@@ -42274,6 +42612,9 @@ const ListContent = ({
42274
42612
  role: role,
42275
42613
  fallback: fallback,
42276
42614
  searchFallback: searchFallback,
42615
+ searching: searching,
42616
+ loading: loading,
42617
+ error: error,
42277
42618
  searchNoMatchMode: searchNoMatchMode,
42278
42619
  separator: separator === true ? jsx(Separator, {
42279
42620
  margin: "0"
@@ -42772,6 +43113,9 @@ const UnorderedList = ({
42772
43113
  virtualItemSizeSignal,
42773
43114
  fallback,
42774
43115
  searchFallback,
43116
+ searching,
43117
+ loading,
43118
+ error,
42775
43119
  searchNoMatchMode,
42776
43120
  separator,
42777
43121
  horizontal,
@@ -42780,6 +43124,9 @@ const UnorderedList = ({
42780
43124
  children,
42781
43125
  ...rest
42782
43126
  }) => {
43127
+ // No empty/no-match message while loading or in error — the placeholder /
43128
+ // error message is the content, even though no items are tracked yet.
43129
+ const suppressFallback = loading || Boolean(error);
42783
43130
  return jsxs(Box, {
42784
43131
  as: "ul",
42785
43132
  flex: columns ? undefined : horizontal ? "x" : "y",
@@ -42791,11 +43138,13 @@ const UnorderedList = ({
42791
43138
  children: [jsx(BeforeFiller, {
42792
43139
  virtualItemSizeSignal: virtualItemSizeSignal,
42793
43140
  renderWindowStart: renderWindow.start
42794
- }), jsx(SearchFallback, {
43141
+ }), !suppressFallback && jsx(SearchFallback, {
42795
43142
  searchFallback: searchFallback,
43143
+ searching: searching,
42796
43144
  tracker: tracker
42797
- }), jsx(Fallback, {
43145
+ }), !suppressFallback && jsx(Fallback, {
42798
43146
  fallback: fallback,
43147
+ searching: searching,
42799
43148
  tracker: tracker
42800
43149
  }), jsx(SearchNoMatchModeContext.Provider, {
42801
43150
  value: searchNoMatchMode,
@@ -42820,16 +43169,18 @@ const UnorderedList = ({
42820
43169
  });
42821
43170
  };
42822
43171
 
42823
- // Show when all matchable items (those with a match prop) are non-matching.
42824
- // The match prop on List.Item signals participation in a matching system
42825
- // (search, filter, etc.). searchFallback appears when every such item has match=false.
43172
+ // The "no match" message. Shown when a search left nothing to display: either
43173
+ // every matchable item has match=false (in-list filtering), or the list is empty
43174
+ // during an active search (filtering done outside the list, so itemCount is 0).
42826
43175
  const SearchFallback = ({
42827
43176
  tracker,
42828
- searchFallback
43177
+ searchFallback,
43178
+ searching
42829
43179
  }) => {
42830
43180
  const itemCount = tracker.countSignal.value;
42831
43181
  const noMatchCount = tracker.noMatchCountSignal.value;
42832
- const showMatchFallback = noMatchCount > 0 && noMatchCount === itemCount;
43182
+ const allNoMatch = noMatchCount > 0 && noMatchCount === itemCount;
43183
+ const showMatchFallback = allNoMatch || searching && itemCount === 0;
42833
43184
  if (searchFallback === undefined) {
42834
43185
  searchFallback = naviI18n("list.no_match");
42835
43186
  }
@@ -42848,15 +43199,22 @@ const SearchFallback = ({
42848
43199
  children: searchFallback
42849
43200
  });
42850
43201
  };
43202
+ // The "empty list" message. Not shown during a search — an empty search result
43203
+ // is a "no match" state (SearchFallback), not an empty-list state.
42851
43204
  const Fallback = ({
42852
43205
  tracker,
42853
- fallback
43206
+ fallback,
43207
+ searching
42854
43208
  }) => {
42855
43209
  const itemCount = tracker.countSignal.value;
42856
- const showFallback = itemCount === 0;
43210
+ const showFallback = itemCount === 0 && !searching;
42857
43211
  if (fallback === undefined) {
42858
43212
  fallback = naviI18n("list.empty");
42859
43213
  }
43214
+ if (!fallback) {
43215
+ // explicitely disabled by user (<List fallback={false|null|''}>)
43216
+ return null;
43217
+ }
42860
43218
  if (!showFallback) {
42861
43219
  return null;
42862
43220
  }
@@ -42970,11 +43328,57 @@ const ListItemPresentation = props => {
42970
43328
  ...columnsOverrideProps
42971
43329
  });
42972
43330
  };
43331
+ // A <List.Item skeleton> — a non-interactive placeholder row shown while a list
43332
+ // is loading. It is presentation-only (not tracked, not selectable, aria-hidden)
43333
+ // and reuses <Text loading> for the shimmer. Box layout props (padding, spacing…)
43334
+ // pass through so a skeletonTemplate can match the real items' metrics; and when
43335
+ // children are provided they render as-is, so a template can reproduce a
43336
+ // multi-part item (e.g. title + subtitle) out of several <Text loading> bars.
43337
+ const ListItemSkeletonResolver = props => {
43338
+ const Next = useNextResolver();
43339
+ if (props.skeleton) {
43340
+ return jsx(ListItemSkeleton, {
43341
+ ...props
43342
+ });
43343
+ }
43344
+ return jsx(Next, {
43345
+ ...props
43346
+ });
43347
+ };
43348
+ const ListItemSkeleton = props => {
43349
+ // Without vertical padding the bars of consecutive rows touch and read as one
43350
+ // block; "s" is enough air for them to be seen as separate rows.
43351
+ // eslint-disable-next-line no-unused-vars
43352
+ const {
43353
+ skeleton,
43354
+ children,
43355
+ paddingY = "s",
43356
+ ...rest
43357
+ } = props;
43358
+ const columnsOverrideProps = useListItemColumnsOverrideProps(rest.style);
43359
+ return jsx(Box, {
43360
+ as: "li",
43361
+ role: "presentation",
43362
+ "aria-hidden": "true",
43363
+ paddingY: paddingY,
43364
+ ...rest,
43365
+ ...columnsOverrideProps,
43366
+ baseClassName: "navi_list_item navi_list_item_skeleton",
43367
+ children: children ?? jsx(Text, {
43368
+ loading: true
43369
+ })
43370
+ });
43371
+ };
42973
43372
  const ListItemUI = props => {
42974
- if (props.id === undefined) {
43373
+ // A stable id/index only matters when the item's identity must survive
43374
+ // reordering — i.e. it is selectable (selected/pointed state) or participates
43375
+ // in a matching system (search reorders items). A purely presentational,
43376
+ // static list doesn't need either, so don't nag about them there.
43377
+ const identityMatters = props.selectable || Boolean(props.matchInfo) || props.value !== undefined;
43378
+ if (identityMatters && props.id === undefined) {
42975
43379
  console.warn("ListItem is missing an explicit id prop. Provide a stable id so pointed/selected state survives search reordering.");
42976
43380
  }
42977
- if (props.index === undefined) {
43381
+ if (identityMatters && props.index === undefined) {
42978
43382
  console.warn("ListItem is missing an explicit index prop. Provide an index so item ordering is stable regardless of render order.");
42979
43383
  }
42980
43384
  const idDefault = useId();
@@ -42987,6 +43391,13 @@ const ListItemUI = props => {
42987
43391
  // (e.g. useSearchText's getItemMatchInfo(item): { match, matchScore,
42988
43392
  // matchRanges }), so there is exactly one way to wire it up.
42989
43393
  const matchInfo = props.matchInfo;
43394
+ // Expose match on the tracked item: the tracker counts non-matching items via
43395
+ // `item.match === false` (drives noMatchCount → allNoMatch → the searchFallback
43396
+ // / hide-when-empty behavior). Without this a matchInfo-based search would
43397
+ // filter items out but never register them as "no match".
43398
+ if (matchInfo) {
43399
+ props.match = matchInfo.match;
43400
+ }
42990
43401
  // Derive filtered/hidden/muted from matchInfo.match + searchNoMatchMode context.
42991
43402
  if (matchInfo?.match === false) {
42992
43403
  if (searchNoMatchMode === "remove") {
@@ -43160,6 +43571,10 @@ const LIST_ITEM_PSEUDO_ELEMENTS = ["::highlight"];
43160
43571
  * selectable — when true, the item participates in selection (radio or checkbox
43161
43572
  * depending on whether the parent List has `multiple`). Requires
43162
43573
  * `value` and typically a <SelectableInput /> child.
43574
+ * skeleton — render a non-interactive placeholder row (a shimmering bar)
43575
+ * instead of a real item. Used as the List `skeletonTemplate`
43576
+ * while `loading`; Box layout props (padding…) pass through so the
43577
+ * placeholder can match the real items' metrics.
43163
43578
  * value — the JS value emitted by the list's action/uiAction when this item
43164
43579
  * is selected. Can be any type (string, number, object…).
43165
43580
  * selected — controlled selected state. Pass `selected === value` (single) or
@@ -43186,7 +43601,7 @@ const LIST_ITEM_PSEUDO_ELEMENTS = ["::highlight"];
43186
43601
  * CSS Highlight API.
43187
43602
  * ...rest — forwarded to the rendered <li> element
43188
43603
  */
43189
- const ListItem = createComponentResolver([ListItemFirstResolver, ListItemSelectableResolver, ListItemHeaderOrFooterResolver, ListItemPresentationResolver, ListItemUI]);
43604
+ const ListItem = createComponentResolver([ListItemFirstResolver, ListItemSkeletonResolver, ListItemSelectableResolver, ListItemHeaderOrFooterResolver, ListItemPresentationResolver, ListItemUI]);
43190
43605
  List.Item = ListItem;
43191
43606
 
43192
43607
  /**
@@ -44291,7 +44706,8 @@ const PickerButton = props => {
44291
44706
  // --anchor-width). Set true when the CONTENT should dictate the popover width
44292
44707
  // (e.g. a Wheel) instead of being stretched to the trigger — see
44293
44708
  // picker_custom.jsx.
44294
- popupWidthFitContent
44709
+ popupWidthFitContent,
44710
+ error
44295
44711
  } = props;
44296
44712
  const isSingleLine = maxLines === 1;
44297
44713
  const inputRef = useRef(null);
@@ -44308,6 +44724,7 @@ const PickerButton = props => {
44308
44724
  children
44309
44725
  } = inputProps;
44310
44726
  const loading = basePseudoState[":-navi-loading"];
44727
+ usePickerErrorCallout(uiStateController, error);
44311
44728
  return jsxs(Box, {
44312
44729
  as: "div",
44313
44730
  ref: ref,
@@ -44327,6 +44744,7 @@ const PickerButton = props => {
44327
44744
  ui: undefined,
44328
44745
  maxLines: undefined,
44329
44746
  popupWidthFitContent: undefined,
44747
+ error: undefined,
44330
44748
  dayLabel: undefined
44331
44749
  // This wrapper will receive keyboard event bubbling from the picker popup content
44332
44750
  // we re-dispatch on the input (to get escape to close for instance)
@@ -44451,6 +44869,33 @@ const PickerButton = props => {
44451
44869
  const isWithinPickerContent = (el, pickerEl) => {
44452
44870
  return pickerEl.querySelector(".navi_picker_content")?.contains(el);
44453
44871
  };
44872
+ const PICKER_ERROR_TOKEN = createOpenToken();
44873
+ // The `error` prop rides the control's own callout — the same surface already
44874
+ // used for failing constraints — so a caller never has to decide where to put
44875
+ // the message. It shows whether the popup is open or closed, and dismissing it
44876
+ // discards that error: only a new `error` value raises another one.
44877
+ const usePickerErrorCallout = (uiStateController, error) => {
44878
+ useEffect(() => {
44879
+ const {
44880
+ callout
44881
+ } = uiStateController.rules;
44882
+ const closeEvent = new CustomEvent("picker_error_cleared", {
44883
+ detail: {}
44884
+ });
44885
+ if (!error) {
44886
+ callout.removeOpenToken(PICKER_ERROR_TOKEN, closeEvent);
44887
+ return undefined;
44888
+ }
44889
+ callout.addOpenToken(PICKER_ERROR_TOKEN, {
44890
+ message: error === true ? "Something went wrong." : error,
44891
+ status: "error",
44892
+ skipFocus: true
44893
+ });
44894
+ return () => {
44895
+ callout.removeOpenToken(PICKER_ERROR_TOKEN, closeEvent);
44896
+ };
44897
+ }, [error]);
44898
+ };
44454
44899
  const PickerInput = props => {
44455
44900
  const {
44456
44901
  ui,
@@ -44577,10 +45022,14 @@ const PickerFirstResolver = props => {
44577
45022
  * step?: string | number,
44578
45023
  * disabled?: boolean,
44579
45024
  * readOnly?: boolean,
45025
+ * error?: boolean | string,
44580
45026
  * uiAction?: (value: any, event: Event) => void,
44581
45027
  * action?: (value: any, event: Event) => void,
44582
45028
  * children?: import("ignore:preact").ComponentChildren,
44583
45029
  * mode?: "popover" | "dialog",
45030
+ * popoverMode?: "nearby" | "overlay",
45031
+ * positionArea?: string,
45032
+ * popupWidthFitContent?: boolean,
44584
45033
  * variant?: "icon" | "headless",
44585
45034
  * icon?: import("ignore:preact").ComponentChildren,
44586
45035
  * maxLines?: number,
@@ -44593,6 +45042,24 @@ const PickerFirstResolver = props => {
44593
45042
  * ref?: import("ignore:preact").RefObject<HTMLElement>,
44594
45043
  * [key: string]: any,
44595
45044
  * }>}
45045
+ * @param {boolean|string} [error] Something went wrong around this picker (its
45046
+ * content failed to load, its value could not be resolved…). Shown as a
45047
+ * callout on the trigger, open or closed — the caller has nothing to place.
45048
+ * Dismissing it discards that error; a new `error` value raises another one.
45049
+ * @param {"nearby"|"overlay"} [popoverMode="nearby"] "overlay" lays the popover
45050
+ * over the trigger, "nearby" leaves a small gap below it.
45051
+ * @param {string} [positionArea] Where the popup goes — relative to the trigger
45052
+ * in popover mode, relative to the viewport in dialog mode. Same grammar as
45053
+ * Popover/Dialog's own `positionArea` ("top", "right-end", "inset(top-left)",
45054
+ * …). Defaults to "bottom-start" in popover mode ("inset(top-left)" when
45055
+ * popoverMode is "overlay"), and to Dialog's own "center" in dialog mode. A
45056
+ * popover still flips to the opposite side on its own when there isn't
45057
+ * enough room.
45058
+ * @param {boolean} [popupWidthFitContent] By default the popup is at least as
45059
+ * wide as the trigger. Set this to let the content size it instead, so a
45060
+ * popup narrower than the trigger stays narrow.
45061
+ * @param {number|string} [popoverMaxHeight] Soft cap on the popover's height
45062
+ * (default 300px). The popover shrinks below it when space is tight.
44596
45063
  */
44597
45064
  const Picker = createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
44598
45065
  Picker.UI = PickerDefaultUI;
@@ -50764,67 +51231,6 @@ const Address = ({
50764
51231
  });
50765
51232
  };
50766
51233
 
50767
- const LoadingDotsSvg = () => {
50768
- return jsxs("svg", {
50769
- viewBox: "0 0 200 200",
50770
- width: "100%",
50771
- height: "100%",
50772
- xmlns: "http://www.w3.org/2000/svg",
50773
- children: [jsx("rect", {
50774
- fill: "currentColor",
50775
- stroke: "currentColor",
50776
- "stroke-width": "15",
50777
- width: "30",
50778
- height: "30",
50779
- x: "25",
50780
- y: "85",
50781
- children: jsx("animate", {
50782
- attributeName: "opacity",
50783
- calcMode: "spline",
50784
- dur: "2",
50785
- values: "1;0;1;",
50786
- keySplines: ".5 0 .5 1;.5 0 .5 1",
50787
- repeatCount: "indefinite",
50788
- begin: "-.4"
50789
- })
50790
- }), jsx("rect", {
50791
- fill: "currentColor",
50792
- stroke: "currentColor",
50793
- "stroke-width": "15",
50794
- width: "30",
50795
- height: "30",
50796
- x: "85",
50797
- y: "85",
50798
- children: jsx("animate", {
50799
- attributeName: "opacity",
50800
- calcMode: "spline",
50801
- dur: "2",
50802
- values: "1;0;1;",
50803
- keySplines: ".5 0 .5 1;.5 0 .5 1",
50804
- repeatCount: "indefinite",
50805
- begin: "-.2"
50806
- })
50807
- }), jsx("rect", {
50808
- fill: "currentColor",
50809
- stroke: "currentColor",
50810
- "stroke-width": "15",
50811
- width: "30",
50812
- height: "30",
50813
- x: "145",
50814
- y: "85",
50815
- children: jsx("animate", {
50816
- attributeName: "opacity",
50817
- calcMode: "spline",
50818
- dur: "2",
50819
- values: "1;0;1;",
50820
- keySplines: ".5 0 .5 1;.5 0 .5 1",
50821
- repeatCount: "indefinite",
50822
- begin: "0"
50823
- })
50824
- })]
50825
- });
50826
- };
50827
-
50828
51234
  const formatNumber = (value, { lang = languagesSignal.value } = {}) => {
50829
51235
  return new Intl.NumberFormat(lang).format(value);
50830
51236
  };
@@ -52229,23 +52635,6 @@ const Image = ({
52229
52635
  });
52230
52636
  };
52231
52637
 
52232
- const LoadingIndicator = ({
52233
- variant = "circle",
52234
- ...props
52235
- }) => {
52236
- if (variant === "dots") {
52237
- return jsx(Icon, {
52238
- ...props,
52239
- children: jsx(LoadingDotsSvg, {})
52240
- });
52241
- }
52242
- return jsx(Icon, {
52243
- circle: true,
52244
- ...props,
52245
- children: jsx(LoadingIndicatorFluid, {})
52246
- });
52247
- };
52248
-
52249
52638
  const Svg = props => {
52250
52639
  return jsx(Box, {
52251
52640
  ...props,
@@ -52744,7 +53133,12 @@ const SidePanel = ({
52744
53133
  onClose: onClose,
52745
53134
  layer: layer,
52746
53135
  anchorCustomEventDetail: "ignore",
52747
- positionArea: side,
53136
+ positionArea: side
53137
+ // A side panel is flush against the edge it slides in from — none of
53138
+ // Dialog's own default gap with the container.
53139
+ ,
53140
+
53141
+ marginWithContainer: 0,
52748
53142
  animation: animation === true ? `slide-from-${side}` : animation,
52749
53143
  pointerInteractionOutsideEffect: closeOnClickOutside ? "close" : "none",
52750
53144
  focusCapture: closeOnClickOutside,
@@ -52960,5 +53354,5 @@ const UserSvg = () => jsx("svg", {
52960
53354
  })
52961
53355
  });
52962
53356
 
52963
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, Details, Dialog, DialogLayout, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePickerMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
53357
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, Details, Dialog, DialogLayout, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
52964
53358
  //# sourceMappingURL=jsenv_navi.js.map