@jsenv/navi 0.27.52 → 0.27.54

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.
@@ -8891,11 +8891,35 @@ import.meta.css = [/* css */`
8891
8891
  const PSEUDO_CLASSES_DEFAULT = [];
8892
8892
  const PSEUDO_ELEMENTS_DEFAULT = [];
8893
8893
  const STYLE_CSS_VARS_DEFAULT = {};
8894
- const PROPS_CSS_VARS_DEFAULT = {};
8895
8894
  // When only pseudoStateSelector is set (no visualSelector), the box owns its
8896
8895
  // visual identity. Only event handlers and these explicit props are forwarded
8897
8896
  // to the inner semantic/interactive child element.
8898
8897
  const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
8898
+
8899
+ /**
8900
+ * @type {import("ignore:preact").FunctionComponent<{
8901
+ * as?: string,
8902
+ * className?: string,
8903
+ * style?: import("ignore:preact").JSX.CSSProperties & { [pseudo: string]: import("ignore:preact").JSX.CSSProperties },
8904
+ * styleCSSVars?: { [stylePropName: string]: string },
8905
+ * inline?: boolean,
8906
+ * block?: boolean,
8907
+ * flex?: "x" | "y" | boolean,
8908
+ * grid?: boolean,
8909
+ * display?: "inherit",
8910
+ * pseudoState?: { [stateName: string]: boolean },
8911
+ * pseudoClasses?: string[],
8912
+ * pseudoElements?: string[],
8913
+ * visualSelector?: string,
8914
+ * pseudoStateSelector?: string,
8915
+ * hasChildUsingForwardedProps?: boolean,
8916
+ * childPropSet?: Set<string>,
8917
+ * preventInitialTransition?: boolean,
8918
+ * separator?: import("ignore:preact").ComponentChildren | ((index: number) => import("ignore:preact").ComponentChildren),
8919
+ * children?: import("ignore:preact").ComponentChildren,
8920
+ * [key: string]: any,
8921
+ * }>}
8922
+ */
8899
8923
  const Box = props => {
8900
8924
  const {
8901
8925
  ref,
@@ -8906,7 +8930,6 @@ const Box = props => {
8906
8930
  // style management
8907
8931
  style,
8908
8932
  styleCSSVars = STYLE_CSS_VARS_DEFAULT,
8909
- propsCSSVars = PROPS_CSS_VARS_DEFAULT,
8910
8933
  basePseudoState,
8911
8934
  pseudoState,
8912
8935
  // for demo purposes it's possible to control pseudo state from props
@@ -9165,13 +9188,6 @@ const Box = props => {
9165
9188
  addStyle(value, name, styleContext, boxStylesTarget, context);
9166
9189
  return;
9167
9190
  }
9168
- const propCssVar = propsCSSVars[name];
9169
- if (propCssVar) {
9170
- if (value !== undefined) {
9171
- addCSSVar(value, propCssVar, boxStylesTarget);
9172
- }
9173
- return;
9174
- }
9175
9191
  const isPseudoStyle = styleOrigin === "pseudo_style";
9176
9192
  if (isStyleProp(name)) {
9177
9193
  // it's a style prop, we need first to check if we have css var to handle them
@@ -9205,6 +9221,13 @@ const Box = props => {
9205
9221
  }
9206
9222
  return;
9207
9223
  }
9224
+ const cssVarName = styleCSSVars[name];
9225
+ if (cssVarName) {
9226
+ if (value !== undefined) {
9227
+ addCSSVar(value, cssVarName, boxStylesTarget);
9228
+ }
9229
+ return;
9230
+ }
9208
9231
  // not a style prop what do we do with it?
9209
9232
  // When pseudoStateSelector is set, the child element is the semantic/interactive one
9210
9233
  // When both selectors are set the child IS the component (e.g. Button with scale
@@ -17601,11 +17624,32 @@ const createI18n = ({
17601
17624
  return interpolateText(template, values);
17602
17625
  };
17603
17626
 
17627
+ const has = (key, { lang = activeLang } = {}) => {
17628
+ const resolvedLang = lang ? matchLang(lang, languageMap) : null;
17629
+ if (resolvedLang) {
17630
+ const translations = languageMap.get(resolvedLang);
17631
+ if (translations && key in translations) {
17632
+ return true;
17633
+ }
17634
+ }
17635
+ if (fallbackLang) {
17636
+ const resolvedFallbackLang = matchLang(fallbackLang, languageMap);
17637
+ if (resolvedFallbackLang) {
17638
+ const fallbackTranslations = languageMap.get(resolvedFallbackLang);
17639
+ if (fallbackTranslations && key in fallbackTranslations) {
17640
+ return true;
17641
+ }
17642
+ }
17643
+ }
17644
+ return false;
17645
+ };
17646
+
17604
17647
  // The i18n instance is itself a callable function
17605
17648
  const i18n = (key, values, opts) => format(key, values, opts);
17606
17649
  i18n.add = add;
17607
17650
  i18n.addAll = addAll;
17608
17651
  i18n.addLangKeys = addLangKeys;
17652
+ i18n.has = has;
17609
17653
  i18n.format = format;
17610
17654
  i18n.languageMap = languageMap;
17611
17655
 
@@ -27347,57 +27391,74 @@ const shouldInjectSpacingBetween = (left, right) => {
27347
27391
  };
27348
27392
 
27349
27393
  /**
27350
- * Text component for rendering inline or block text with layout-stable style changes.
27351
- *
27352
- * Most props are forwarded to the underlying `Box` component (as, style, bold, noWrap, …).
27353
- * The props listed below are specific to Text.
27354
- *
27355
- * @param {object} props
27356
- *
27357
- * @param {number} [props.maxLines]
27358
- * Truncates overflowing text with an ellipsis after at most N lines.
27359
- * `maxLines={1}` truncates after one line (single-line ellipsis).
27360
- * `maxLines={n}` (n > 1) truncates after n lines (multi-line clamp).
27361
- *
27362
- * @param {string} [props.spacing]
27363
- * Controls the separator injected between child nodes.
27364
- * Accepts a size/spacing scale key, a CSS length, or `"pre"` / `0` to disable.
27365
- * Defaults to a regular space character (or a padding-based fake space when
27366
- * `preventSpaceUnderlines` is active).
27394
+ * @type {import("ignore:preact").FunctionComponent<{
27395
+ * children?: import("ignore:preact").ComponentChildren,
27396
+ * as?: string,
27397
+ * className?: string,
27398
+ * style?: import("ignore:preact").JSX.CSSProperties,
27399
+ * bold?: boolean,
27400
+ * noWrap?: boolean,
27401
+ * maxLines?: number,
27402
+ * spacing?: string | number | import("ignore:preact").ComponentChildren,
27403
+ * loading?: boolean,
27404
+ * skeleton?: boolean,
27405
+ * preventSpaceUnderlines?: boolean,
27406
+ * holdSpaceForStyle?: import("ignore:preact").JSX.CSSProperties,
27407
+ * boldStable?: boolean,
27408
+ * shrinkWrap?: boolean,
27409
+ * capitalize?: boolean,
27410
+ * selectRange?: string | [number, number],
27411
+ * childrenOutsideFlow?: import("ignore:preact").ComponentChildren,
27412
+ * [key: string]: any,
27413
+ * }>}
27367
27414
  *
27368
- * @param {boolean} [props.loading]
27369
- * Renders a shimmer skeleton in place of the text.
27370
- *
27371
- * @param {boolean} [props.skeleton]
27372
- * Same as `loading` but without the shimmer animation.
27373
- *
27374
- * @param {boolean} [props.preventSpaceUnderlines]
27375
- * Replaces real space characters between children with padding-based spaces
27376
- * to avoid the underline browsers draw under spaces inside links.
27377
- *
27378
- * @param {object} [props.holdSpaceForStyle]
27379
- * Prevents layout shifts when text styles change (font-weight, font-size, …).
27380
- * Pass an object of CSS-in-JS style properties representing the "maximum" state of the text.
27381
- * An invisible placeholder is rendered with those styles to reserve the space,
27382
- * and the real visible text is layered on top via `position: absolute`.
27383
- * Only works reliably with single-line (`noWrap`) text.
27384
- * Example: `holdSpaceForStyle={{ fontWeight: "bold", fontSize: "1.5rem" }}`
27385
- *
27386
- * @param {boolean} [props.boldStable]
27387
- * Alternative to `holdSpaceForStyle` for multi-line text.
27388
- * Keeps a consistent visual width regardless of font-weight by painting normal-weight
27389
- * text on top of a bold background using `background-clip: text`.
27390
- * Does not support font-size changes.
27391
- *
27392
- * @param {boolean} [props.capitalize]
27393
- * Applies `text-transform: uppercase` to the first letter via CSS.
27394
- *
27395
- * @param {string|Array} [props.selectRange]
27396
- * Selects a portion of the text on mount. Forwarded to `useInitialTextSelection`.
27397
- *
27398
- * @param {*} [props.childrenOutsideFlow]
27399
- * Rendered after children but outside the text flow (useful for overlays
27400
- * like the skeleton container).
27415
+ * @param {number} [maxLines]
27416
+ * Truncates overflowing text with an ellipsis. `maxLines={1}` produces a
27417
+ * single-line truncation; `maxLines={n}` (n > 1) uses `-webkit-line-clamp`
27418
+ * to allow up to n lines before clipping.
27419
+ *
27420
+ * @param {string|number} [spacing]
27421
+ * Separator injected between child nodes. Accepts a size token (`"s"`, `"m"`, …),
27422
+ * a CSS length string, a number (interpreted as px), or `"pre"` / `0` to
27423
+ * disable spacing entirely. Defaults to a regular space character.
27424
+ *
27425
+ * @param {boolean} [loading]
27426
+ * Renders a shimmer skeleton animation in place of the text content.
27427
+ *
27428
+ * @param {boolean} [skeleton]
27429
+ * Same as `loading` but without the shimmer animation a static grey bar.
27430
+ *
27431
+ * @param {boolean} [preventSpaceUnderlines]
27432
+ * Replaces real space characters between children with padding-based spaces.
27433
+ * Useful inside `<a>` elements where browsers draw an underline under spaces.
27434
+ *
27435
+ * @param {import("ignore:preact").JSX.CSSProperties} [holdSpaceForStyle]
27436
+ * Prevents layout shifts when text styles change (e.g. font-weight, font-size).
27437
+ * Pass the CSS properties representing the "largest" visual state of the text.
27438
+ * An invisible placeholder rendered with those styles reserves the space; the
27439
+ * real visible text is layered on top via `position: absolute`.
27440
+ * Best combined with `noWrap` does not work reliably on multi-line text.
27441
+ *
27442
+ * @param {boolean} [boldStable]
27443
+ * Alternative to `holdSpaceForStyle` for multi-line text. Keeps a consistent
27444
+ * visual width across bold/normal transitions by painting normal-weight text
27445
+ * over a bold background using `background-clip: text`. Does not handle
27446
+ * font-size changes.
27447
+ *
27448
+ * @param {boolean} [shrinkWrap]
27449
+ * Forces the element width to match its longest visual line, preventing the
27450
+ * text block from being wider than its content when inside a flex/grid container.
27451
+ *
27452
+ * @param {boolean} [capitalize]
27453
+ * Uppercases the first letter of the text content via CSS.
27454
+ *
27455
+ * @param {string|[number,number]} [selectRange]
27456
+ * Selects a portion of the text on mount. Pass a substring to search for, or
27457
+ * a `[start, end]` character-offset tuple.
27458
+ *
27459
+ * @param {import("ignore:preact").ComponentChildren} [childrenOutsideFlow]
27460
+ * Rendered after children but outside the text spacing/flow logic. Used
27461
+ * internally for overlays such as the skeleton container.
27401
27462
  */
27402
27463
  const Text = props => {
27403
27464
  const defaultRef = useRef();
@@ -34374,21 +34435,13 @@ const Unit = ({
34374
34435
  let unitText = unit;
34375
34436
  if (label) {
34376
34437
  unitText = label;
34377
- } else {
34438
+ } else if (naviI18n.has(unit, {
34439
+ lang
34440
+ })) {
34378
34441
  const singularText = naviI18n(unit, undefined, {
34379
34442
  lang
34380
34443
  });
34381
- if (singularText === unit) {
34382
- // naviI18n has no translation — try Intl.NumberFormat with style:"unit"
34383
- const intlText = formatIntlUnit(unit, {
34384
- plural,
34385
- lang,
34386
- format
34387
- });
34388
- if (intlText !== null) {
34389
- unitText = intlText;
34390
- }
34391
- } else if (format === "short" || format === "narrow") {
34444
+ if (format === "short" || format === "narrow") {
34392
34445
  const shortKey = `${unit}__short`;
34393
34446
  const shortText = naviI18n(shortKey, undefined, {
34394
34447
  lang
@@ -34404,6 +34457,18 @@ const Unit = ({
34404
34457
  } else {
34405
34458
  unitText = singularText;
34406
34459
  }
34460
+ } else {
34461
+ // naviI18n has no translation — try Intl.NumberFormat with style:"unit"
34462
+ const intlText = formatIntlUnit(unit, {
34463
+ plural,
34464
+ lang,
34465
+ format
34466
+ });
34467
+ if (intlText === null) {
34468
+ unitText = unit;
34469
+ } else {
34470
+ unitText = intlText;
34471
+ }
34407
34472
  }
34408
34473
  return jsx(Text, {
34409
34474
  baseClassName: "navi_unit",
@@ -35510,7 +35575,7 @@ const Dialog = props => {
35510
35575
  focusedBeforeOpen
35511
35576
  });
35512
35577
  };
35513
- const close = e => {
35578
+ const close = (e, detail = {}) => {
35514
35579
  debugPopup(`"${e.type}" on ${getElementSignature(e.target)} -> closeDialog`);
35515
35580
  const dialogEl = ref.current;
35516
35581
  markAutofocusRestoreOnClose(dialogEl);
@@ -35518,7 +35583,8 @@ const Dialog = props => {
35518
35583
  cleanup();
35519
35584
  openedRef.current = false;
35520
35585
  dispatchCustomEvent(dialogEl, "navi_close", {
35521
- event: e
35586
+ event: e,
35587
+ ...detail
35522
35588
  });
35523
35589
  };
35524
35590
  const onRequestOpen = (e, {
@@ -35535,7 +35601,7 @@ const Dialog = props => {
35535
35601
  anchor
35536
35602
  });
35537
35603
  };
35538
- const onRequestClose = e => {
35604
+ const onRequestClose = (e, detail = {}) => {
35539
35605
  const dialogEl = ref.current;
35540
35606
  if (!dialogEl) {
35541
35607
  return;
@@ -35553,15 +35619,15 @@ const Dialog = props => {
35553
35619
  denied = false;
35554
35620
  }
35555
35621
  };
35556
- closeRequestHandler(e, closePermission);
35622
+ closeRequestHandler(e, closePermission, detail);
35557
35623
  if (denied) {
35558
35624
  closePermission.allow = () => {
35559
- close(e);
35625
+ close(e, detail);
35560
35626
  };
35561
35627
  return;
35562
35628
  }
35563
35629
  }
35564
- close(e);
35630
+ close(e, detail);
35565
35631
  };
35566
35632
  return jsx(Box, {
35567
35633
  ...rest,
@@ -35575,7 +35641,9 @@ const Dialog = props => {
35575
35641
  // The <dialog> element covers the full viewport; clicking the backdrop
35576
35642
  // hits the dialog itself (not any child). Close when that happens.
35577
35643
  if (!pointerTrap && e.button === 0 && e.target === ref.current) {
35578
- onRequestClose(e);
35644
+ onRequestClose(e, {
35645
+ isClickOutside: true
35646
+ });
35579
35647
  }
35580
35648
  },
35581
35649
  onCancel: e => {
@@ -35735,7 +35803,7 @@ const Popover = props => {
35735
35803
  focusedBeforeOpen
35736
35804
  });
35737
35805
  };
35738
- const close = e => {
35806
+ const close = (e, detail = {}) => {
35739
35807
  debugPopup(e, `closePopover()`);
35740
35808
  const popoverEl = ref.current;
35741
35809
  markAutofocusRestoreOnClose(popoverEl);
@@ -35744,7 +35812,8 @@ const Popover = props => {
35744
35812
  openedRef.current = false;
35745
35813
  setOpened(false);
35746
35814
  dispatchCustomEvent(popoverEl, "navi_close", {
35747
- event: e
35815
+ event: e,
35816
+ ...detail
35748
35817
  });
35749
35818
  };
35750
35819
  const onRequestOpen = (e, {
@@ -35761,7 +35830,7 @@ const Popover = props => {
35761
35830
  anchor
35762
35831
  });
35763
35832
  };
35764
- const onRequestClose = e => {
35833
+ const onRequestClose = (e, detail = {}) => {
35765
35834
  const popoverEl = ref.current;
35766
35835
  if (!popoverEl) {
35767
35836
  return;
@@ -35779,15 +35848,15 @@ const Popover = props => {
35779
35848
  denied = false;
35780
35849
  }
35781
35850
  };
35782
- closeRequestHandler(e, closePermission);
35851
+ closeRequestHandler(e, closePermission, detail);
35783
35852
  if (denied) {
35784
35853
  closePermission.allow = () => {
35785
- close(e);
35854
+ close(e, detail);
35786
35855
  };
35787
35856
  return;
35788
35857
  }
35789
35858
  }
35790
- close(e);
35859
+ close(e, detail);
35791
35860
  };
35792
35861
  return jsxs(Box, {
35793
35862
  id: id,
@@ -35819,7 +35888,9 @@ const Popover = props => {
35819
35888
  e.preventDefault();
35820
35889
  return;
35821
35890
  }
35822
- onRequestClose(e);
35891
+ onRequestClose(e, {
35892
+ isClickOutside: true
35893
+ });
35823
35894
  }
35824
35895
  }), children]
35825
35896
  });
@@ -35828,6 +35899,15 @@ const POPOVER_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible",
35828
35899
 
35829
35900
  installImportMetaCssBuild(import.meta);const css$p = /* css */`
35830
35901
  .navi_picker {
35902
+ /* Shared by popover and dialog */
35903
+ --picker-popup-background-color: var(--picker-background-color);
35904
+ --picker-popup-border-radius: var(--picker-border-radius);
35905
+ /* Popover */
35906
+ --picker-popover-max-height: 300px;
35907
+ /* Dialog */
35908
+ --picker-dialog-max-width: 95dvw;
35909
+ --picker-dialog-max-height: 95dvh;
35910
+
35831
35911
  /* popover */
35832
35912
  &[aria-haspopup="listbox"] {
35833
35913
  .navi_picker_popover {
@@ -35836,14 +35916,17 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
35836
35916
  min-width: var(--anchor-width, 0px);
35837
35917
  max-width: 95vw;
35838
35918
  /* max-height covers the placeholder + list; the list scrolls internally */
35839
- max-height: var(--space-available, 95dvh);
35919
+ max-height: min(
35920
+ var(--picker-popover-max-height),
35921
+ var(--space-available, 95dvh)
35922
+ );
35840
35923
  margin: 0;
35841
35924
  padding: 0;
35842
- background: var(--picker-background-color);
35925
+ background: var(--picker-popup-background-color);
35843
35926
  border-width: var(--picker-border-width);
35844
35927
  border-style: solid;
35845
35928
  border-color: var(--x-picker-border-color);
35846
- border-radius: var(--picker-border-radius);
35929
+ border-radius: var(--picker-popup-border-radius);
35847
35930
  outline-width: var(--picker-outline-width);
35848
35931
  outline-color: var(--picker-outline-color);
35849
35932
  outline-offset: 0px;
@@ -35851,7 +35934,7 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
35851
35934
  0 4px 8px rgba(0, 0, 0, 0.08),
35852
35935
  0 12px 40px rgba(0, 0, 0, 0.22);
35853
35936
  cursor: default; /* Reset pointer cursor within the select */
35854
- overflow: hidden;
35937
+ overflow: auto;
35855
35938
  overscroll-behavior: none;
35856
35939
 
35857
35940
  /* The anchor placeholder is a non-interactive visual clone of the
@@ -35899,7 +35982,7 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
35899
35982
  width: 100%;
35900
35983
  border-radius: max(
35901
35984
  0px,
35902
- var(--picker-border-radius) - var(--picker-border-width)
35985
+ var(--picker-popup-border-radius) - var(--picker-border-width)
35903
35986
  );
35904
35987
  overflow: auto;
35905
35988
  overscroll-behavior: none;
@@ -35929,11 +36012,12 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
35929
36012
  &[aria-haspopup="dialog"] {
35930
36013
  .navi_picker_dialog {
35931
36014
  min-width: var(--anchor-width, 0px);
35932
- max-height: 95dvh;
36015
+ max-width: var(--picker-dialog-max-width);
36016
+ max-height: var(--picker-dialog-max-height);
35933
36017
  padding: 0;
35934
- background: var(--picker-background-color);
36018
+ background: var(--picker-popup-background-color);
35935
36019
  border: var(--picker-border-width) solid var(--x-picker-border-color);
35936
- border-radius: var(--picker-border-radius);
36020
+ border-radius: var(--picker-popup-border-radius);
35937
36021
  outline-width: var(--picker-outline-width);
35938
36022
  outline-color: var(--picker-outline-color);
35939
36023
  outline-offset: 0;
@@ -35943,6 +36027,13 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
35943
36027
  cursor: default; /* Reset pointer cursor within the select */
35944
36028
  /* overscroll-behavior: contain; */
35945
36029
 
36030
+ &[data-expand-x] {
36031
+ width: var(--picker-dialog-max-width);
36032
+ }
36033
+ &[data-expand-y] {
36034
+ height: var(--picker-dialog-max-height);
36035
+ }
36036
+
35946
36037
  &[open] {
35947
36038
  display: flex;
35948
36039
  flex-direction: column;
@@ -35961,7 +36052,7 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
35961
36052
  width: 100%;
35962
36053
  border-radius: max(
35963
36054
  0px,
35964
- var(--picker-border-radius) - var(--picker-border-width)
36055
+ var(--picker-popup-border-radius) - var(--picker-border-width)
35965
36056
  );
35966
36057
  overflow: auto;
35967
36058
  overscroll-behavior: none;
@@ -36084,15 +36175,9 @@ const PickerCustom = props => {
36084
36175
  expandedRef.current = expanded;
36085
36176
  const valueAtOpenRef = useRef(null);
36086
36177
  const activeElementAtOpenRef = useRef(null);
36087
- // Tracks whether a uiAction has occurred since the last close denial.
36088
- // true = no pending denial (initial state, or user has interacted since)
36089
- // false = close was denied and user hasn't interacted yet
36090
- // When false, a second close attempt is treated as cancel.
36091
- const uiActionSinceDeniedRef = useRef(true);
36092
36178
  const onOpen = e => {
36093
36179
  expandedRef.current = true;
36094
36180
  setExpanded(true);
36095
- uiActionSinceDeniedRef.current = true;
36096
36181
  const focusedBeforeOpen = e.detail.focusedBeforeOpen;
36097
36182
  activeElementAtOpenRef.current = focusedBeforeOpen;
36098
36183
  debugFocus(e, "picked opened, store element focused", focusedBeforeOpen);
@@ -36198,44 +36283,29 @@ const PickerCustom = props => {
36198
36283
  }
36199
36284
  });
36200
36285
  },
36201
- // Intercept uiAction to detect user interaction after a close denial.
36202
36286
  "uiAction": (v, e) => {
36203
- uiActionSinceDeniedRef.current = true;
36204
36287
  uiActionProp?.(v, e);
36205
36288
  },
36206
36289
  children
36207
36290
  });
36208
36291
  Object.assign(popupProps, {
36209
- closeRequestHandler: (requestCloseEvent, closePermission) => {
36292
+ closeRequestHandler: (requestCloseEvent, closePermission, {
36293
+ isClickOutside
36294
+ } = {}) => {
36210
36295
  const cancelEvent = findEvent(requestCloseEvent, eInChain => eInChain.type === "navi_request_close" && eInChain.detail.isCancel);
36211
- const isCancel = Boolean(cancelEvent);
36212
- const pickerEl = ref.current;
36213
- const inputEl = getPickerInput(pickerEl);
36214
- const valueAtOpen = valueAtOpenRef.current;
36296
+ const isCancel = isClickOutside || Boolean(cancelEvent);
36215
36297
  if (isCancel) {
36216
- uiActionSinceDeniedRef.current = true;
36298
+ const pickerEl = ref.current;
36299
+ const inputEl = getPickerInput(pickerEl);
36300
+ const valueAtOpen = valueAtOpenRef.current;
36301
+ debugPopup(requestCloseEvent, `picker cancel, restoring value at open ${JSON.stringify(valueAtOpen)}`);
36217
36302
  dispatchRequestSetUIState(inputEl, valueAtOpen, {
36218
36303
  event: requestCloseEvent
36219
36304
  });
36220
36305
  return;
36221
36306
  }
36222
-
36223
- // If close was previously denied and the user hasn't interacted since,
36224
- // treat this re-attempt as a cancel so they are not trapped.
36225
- if (!uiActionSinceDeniedRef.current) {
36226
- debugPopup(requestCloseEvent, `picker close was denied and user did not interact, treating re-attempt as cancel (restoring ${JSON.stringify(valueAtOpen)})`);
36227
- uiActionSinceDeniedRef.current = true;
36228
- dispatchRequestSetUIState(inputEl, valueAtOpen, {
36229
- event: requestCloseEvent
36230
- });
36231
- return;
36232
- }
36233
- const valueAtClose = getPickerInputUIState(pickerEl);
36234
- if (compareTwoJsValues(valueAtClose, valueAtOpen)) {
36235
- debugPopup(requestCloseEvent, `picker closed with same value as when it opened (${JSON.stringify(valueAtClose)}), no action dispatched`);
36236
- return;
36237
- }
36238
- debugPopup(requestCloseEvent, `picker attempt to close with value (${JSON.stringify(valueAtClose)}) wait for picker action to close picker`);
36307
+ const pickerEl = ref.current;
36308
+ const inputEl = getPickerInput(pickerEl);
36239
36309
  dispatchRequestAction(inputEl, {
36240
36310
  event: requestCloseEvent,
36241
36311
  name: "picker close",
@@ -36246,10 +36316,8 @@ const PickerCustom = props => {
36246
36316
  // user sees what is wrong, even if the picker has no action prop.
36247
36317
  reportOnInvalid: true,
36248
36318
  onInvalid: () => {
36249
- uiActionSinceDeniedRef.current = false;
36250
36319
  closePermission.deny();
36251
- },
36252
- allowed: () => {}
36320
+ }
36253
36321
  });
36254
36322
  },
36255
36323
  onnavi_open: e => {
@@ -36338,7 +36406,9 @@ const PickerCustom = props => {
36338
36406
  if (expandedRef.current) {
36339
36407
  return {
36340
36408
  name: "mousedown to close picker",
36341
- allowed: () => requestClose(e)
36409
+ allowed: () => requestClose(e, {
36410
+ isCancel: true
36411
+ })
36342
36412
  };
36343
36413
  }
36344
36414
  return {
@@ -36431,7 +36501,8 @@ const PickerContentInsidePopover = props => {
36431
36501
  category: "interaction",
36432
36502
  allowed: () => {
36433
36503
  dispatchCustomEvent(popoverEl, "navi_request_close", {
36434
- event: e
36504
+ event: e,
36505
+ isCancel: true
36435
36506
  });
36436
36507
  }
36437
36508
  });
@@ -36457,7 +36528,8 @@ const PickerContentInsidePopover = props => {
36457
36528
  }
36458
36529
  const popupEl = popupProps.ref.current;
36459
36530
  dispatchCustomEvent(popupEl, "navi_request_close", {
36460
- event: e
36531
+ event: e,
36532
+ isCancel: true
36461
36533
  });
36462
36534
  },
36463
36535
  children: props.trigger
@@ -36472,8 +36544,13 @@ const PickerContentInsideDialog = props => {
36472
36544
  children,
36473
36545
  scrollTrap,
36474
36546
  pointerTrap,
36547
+ dialogExpand,
36548
+ dialogExpandX,
36549
+ dialogExpandY,
36475
36550
  ...rest
36476
36551
  } = props;
36552
+ const expandX = dialogExpand || dialogExpandX;
36553
+ const expandY = dialogExpand || dialogExpandY;
36477
36554
  return jsx(Next, {
36478
36555
  "aria-haspopup": "dialog",
36479
36556
  ...rest,
@@ -36484,6 +36561,8 @@ const PickerContentInsideDialog = props => {
36484
36561
  pointerTrap: pointerTrap,
36485
36562
  centerInVisualViewport: true,
36486
36563
  autoFocus: "fallback",
36564
+ "data-expand-x": expandX ? "" : undefined,
36565
+ "data-expand-y": expandY ? "" : undefined,
36487
36566
  children: children
36488
36567
  })
36489
36568
  });
@@ -37600,10 +37679,6 @@ installImportMetaCssBuild(import.meta);const css$n = /* css */`
37600
37679
  }
37601
37680
  }
37602
37681
 
37603
- fieldset.navi_list_container[navi-selectable] {
37604
- margin: 0; /* Reset margin that might come from fieldset */
37605
- padding: 0; /* Reset padding that might come from fieldset */
37606
- }
37607
37682
  .navi_list_container[navi-selectable] {
37608
37683
  &[data-callout] {
37609
37684
  --x-list-border-color: var(--callout-color);
@@ -37847,7 +37922,6 @@ const ListSelectable = props => {
37847
37922
  setCurrentId(initialEl.id);
37848
37923
  }, []);
37849
37924
  const listVnode = jsx(Next, {
37850
- as: "fieldset",
37851
37925
  "navi-selectable": "",
37852
37926
  "navi-has-selected-background": selectedIndicator === "backgroundColor" ? "" : undefined,
37853
37927
  ...listControlRootProps,
@@ -38256,9 +38330,10 @@ installImportMetaCssBuild(import.meta);const ListItemTrackerContext = createCont
38256
38330
  const GroupItemTrackerContext = createContext(null);
38257
38331
  const PendingScrollRefContext = createContext(null);
38258
38332
  // Controls how List.Item behaves when match=false (set via List searchNoMatchMode prop):
38259
- // "remove" — remove from DOM (default)
38333
+ // "remove" — remove from DOM (default)
38260
38334
  // "invisible_and_inert" — keep in DOM, invisible and non-interactive (preserves layout, no content visible)
38261
38335
  // "muted" — keep in DOM, visible but opacified and still interactive
38336
+ // "below" — keep in DOM, fully visible, pushed below matching items via CSS order
38262
38337
  const SearchNoMatchModeContext = createContext("remove");
38263
38338
 
38264
38339
  // When total rendered items exceeds renderBudget, a render window [start, end)
@@ -38285,7 +38360,6 @@ const css$m = /* css */`
38285
38360
  --list-border-width: 1px;
38286
38361
  --list-border-color: light-dark(#ccc, #555);
38287
38362
  --list-background-color: light-dark(#fff, #1e1e1e);
38288
- --list-max-height: 220px;
38289
38363
  }
38290
38364
  .navi_list_item {
38291
38365
  --list-item-padding-x-default: 0px;
@@ -38356,8 +38430,8 @@ const css$m = /* css */`
38356
38430
  .navi_list_scroll_container {
38357
38431
  width: inherit;
38358
38432
  min-width: inherit;
38359
- max-width: inherit;
38360
- max-height: var(--list-max-height);
38433
+ max-width: var(--list-max-width, inherit);
38434
+ max-height: var(--list-max-height, inherit);
38361
38435
  overflow: auto;
38362
38436
  overscroll-behavior: inherit; /* inherit select behavior */
38363
38437
  }
@@ -38365,6 +38439,12 @@ const css$m = /* css */`
38365
38439
  &[data-expand-x] {
38366
38440
  width: 100%;
38367
38441
  }
38442
+ &[data-expand-y] {
38443
+ --list-max-height: none;
38444
+ }
38445
+ &[navi-nothing-to-display] {
38446
+ display: none;
38447
+ }
38368
38448
  &[popover] {
38369
38449
  position: absolute;
38370
38450
  inset: unset;
@@ -38481,6 +38561,8 @@ const css$m = /* css */`
38481
38561
  /* background: pink; */
38482
38562
  }
38483
38563
  &[data-horizontal] {
38564
+ --list-max-height: none;
38565
+
38484
38566
  .navi_list_virtual_filler {
38485
38567
  width: var(--size-to-fill, 0px);
38486
38568
  height: 100%;
@@ -38630,8 +38712,8 @@ const ListUI = props => {
38630
38712
  children,
38631
38713
  popover,
38632
38714
  expandX,
38715
+ expandY,
38633
38716
  expand,
38634
- maxHeight,
38635
38717
  onListVisibleItemsChange,
38636
38718
  virtualItemSize,
38637
38719
  lockSize,
@@ -38696,6 +38778,12 @@ const ListUI = props => {
38696
38778
  const getItemById = itemId => {
38697
38779
  return tracker.itemsSignal.peek().find(item => item.id === itemId);
38698
38780
  };
38781
+ const noMatchCount = tracker.noMatchCountSignal.value;
38782
+ const itemCount = tracker.countSignal.value;
38783
+ const allNoMatch = noMatchCount > 0 && noMatchCount === itemCount;
38784
+ const fallbackDisabled = fallback !== undefined && !fallback;
38785
+ const searchFallbackDisabled = searchFallback !== undefined && !searchFallback;
38786
+ const nothingToDisplay = allNoMatch && searchFallbackDisabled && searchNoMatchMode === "remove" || itemCount === 0 && fallbackDisabled;
38699
38787
  return jsx(Box, {
38700
38788
  ...rest,
38701
38789
  ref: ref,
@@ -38703,9 +38791,12 @@ const ListUI = props => {
38703
38791
  popover: popover,
38704
38792
  "data-horizontal": horizontal ? "" : undefined,
38705
38793
  "data-expand-x": expandX || expand ? "" : undefined,
38794
+ "data-expand-y": expandY || expand ? "" : undefined,
38706
38795
  expandX: expandX,
38796
+ expandY: expandY,
38707
38797
  expand: expand,
38708
- maxHeight: maxHeight,
38798
+ "navi-zero-match": allNoMatch ? "" : undefined,
38799
+ "navi-nothing-to-display": nothingToDisplay ? "" : undefined,
38709
38800
  styleCSSVars: LIST_STYLE_CSS_VARS,
38710
38801
  pseudoClasses: LIST_PSEUDO_CLASSES,
38711
38802
  hasChildUsingForwardedProps: true,
@@ -38730,6 +38821,7 @@ const ListUI = props => {
38730
38821
  searchNoMatchMode: searchNoMatchMode,
38731
38822
  separator: separator,
38732
38823
  expandX: expandX,
38824
+ expandY: expandY,
38733
38825
  expand: expand,
38734
38826
  horizontal: horizontal,
38735
38827
  spacing: spacing,
@@ -38766,14 +38858,14 @@ const ListFirstResolver = props => {
38766
38858
  * fallback?: import("ignore:preact").ComponentChildren,
38767
38859
  * searchFallback?: import("ignore:preact").ComponentChildren,
38768
38860
  * searchText?: string,
38769
- * searchNoMatchMode?: "remove" | "invisible_and_inert" | "muted",
38861
+ * searchNoMatchMode?: "remove" | "invisible_and_inert" | "muted" | "below",
38770
38862
  * separator?: boolean | import("ignore:preact").ComponentChildren,
38771
38863
  * lockSize?: boolean,
38772
38864
  * horizontal?: boolean,
38773
38865
  * spacing?: string,
38774
38866
  * expandX?: boolean,
38867
+ * expandY?: boolean,
38775
38868
  * expand?: boolean,
38776
- * maxHeight?: string | number,
38777
38869
  * children?: import("ignore:preact").ComponentChildren,
38778
38870
  * [key: string]: any,
38779
38871
  * }>}
@@ -38786,6 +38878,7 @@ const ListContent = ({
38786
38878
  searchNoMatchMode,
38787
38879
  separator,
38788
38880
  expandX,
38881
+ expandY,
38789
38882
  expand,
38790
38883
  horizontal,
38791
38884
  spacing,
@@ -38807,6 +38900,7 @@ const ListContent = ({
38807
38900
  margin: "0"
38808
38901
  }) : separator,
38809
38902
  expandX: expandX || expand,
38903
+ expandY: expandY || expand,
38810
38904
  horizontal: horizontal,
38811
38905
  spacing: spacing,
38812
38906
  ...listProps,
@@ -38822,6 +38916,7 @@ const ListContent = ({
38822
38916
  };
38823
38917
  const LIST_STYLE_CSS_VARS = {
38824
38918
  maxHeight: "--list-max-height",
38919
+ maxWidth: "--list-max-width",
38825
38920
  borderColor: "--list-border-color",
38826
38921
  borderRadius: "--list-border-radius",
38827
38922
  borderWidth: "--list-border-width"
@@ -39240,7 +39335,6 @@ const UnorderedList = ({
39240
39335
  return jsxs(Box, {
39241
39336
  as: "ul",
39242
39337
  flex: horizontal ? "x" : "y",
39243
- flexWrap: true,
39244
39338
  ...rest,
39245
39339
  spacing: spacing,
39246
39340
  baseClassName: "navi_list",
@@ -40672,38 +40766,6 @@ installImportMetaCssBuild(import.meta);const css$i = /* css */`
40672
40766
  }
40673
40767
  }
40674
40768
  `;
40675
-
40676
- /**
40677
- * A button-like trigger that opens a picker when clicked.
40678
- *
40679
- * Use the `type` prop to choose what kind of picker to open:
40680
- * "date" — calendar day
40681
- * "month" — year + month
40682
- * "week" — ISO week
40683
- * "time" — hours + minutes
40684
- * "datetime" — date + time
40685
- * "color" — color chooser
40686
- * "hour" — fixed time slots (derived from min/max/step)
40687
- *
40688
- * When `children` are provided, the picker opens a popover or dialog instead
40689
- * of the browser-native picker. On small screens a dialog is used automatically;
40690
- * on larger screens a popover is used. Pass `mode="dialog"` or `mode="popover"`
40691
- * to force one. The children are rendered inside the popup.
40692
- *
40693
- * Props:
40694
- * type — picker variant (see above)
40695
- * value — controlled value
40696
- * uiAction — called with the new value when the user picks one
40697
- * name — form field name
40698
- * placeholder — shown when no value is selected
40699
- * required — marks the field as required
40700
- * min — minimum allowed value; accepts a Date or a raw string
40701
- * max — maximum allowed value; accepts a Date or a raw string
40702
- * step — step interval
40703
- * disabled — disables the picker
40704
- * children — content to display inside the popup (enables popover/dialog mode)
40705
- * mode — "popover" or "dialog"; auto-detected from screen size when omitted
40706
- */
40707
40769
  const PickerButton = props => {
40708
40770
  import.meta.css = [css$i, "@jsenv/navi/src/control/picker/picker.jsx"];
40709
40771
  if (typeof props.maxLines === "string") {
@@ -40914,6 +40976,9 @@ const PickerStyleCSSVars = {
40914
40976
  "outlineWidth": "--picker-outline-width",
40915
40977
  "borderWidth": "--picker-border-width",
40916
40978
  "borderRadius": "--picker-border-radius",
40979
+ "popoverMaxHeight": "--picker-popover-max-height",
40980
+ "popupBackgroundColor": "--picker-popup-background-color",
40981
+ "popupBorderRadius": "--picker-popup-border-radius",
40917
40982
  "padding": "--picker-padding",
40918
40983
  "paddingX": "--picker-padding-x",
40919
40984
  "paddingY": "--picker-padding-y",
@@ -40961,6 +41026,43 @@ const PickerFirstResolver = props => {
40961
41026
  ...props
40962
41027
  });
40963
41028
  };
41029
+
41030
+ /**
41031
+ * Button-like trigger that opens a picker (native or custom popup) when clicked.
41032
+ *
41033
+ * Without `children`, opens the browser-native picker for the given `type`.
41034
+ * With `children`, opens a popover (desktop) or dialog (mobile) containing the children.
41035
+ * Pass `mode="popover"` or `mode="dialog"` to override the automatic choice.
41036
+ *
41037
+ * @type {import("ignore:preact").FunctionComponent<{
41038
+ * type?: "date" | "month" | "week" | "time" | "datetime" | "color" | "hour" | "navi_time" | "navi_number" | "navi_percentage",
41039
+ * value?: any,
41040
+ * defaultValue?: any,
41041
+ * name?: string,
41042
+ * placeholder?: import("ignore:preact").ComponentChildren,
41043
+ * required?: boolean,
41044
+ * min?: Date | string | number,
41045
+ * max?: Date | string | number,
41046
+ * step?: string | number,
41047
+ * disabled?: boolean,
41048
+ * readOnly?: boolean,
41049
+ * uiAction?: (value: any, event: Event) => void,
41050
+ * action?: (value: any, event: Event) => void,
41051
+ * children?: import("ignore:preact").ComponentChildren,
41052
+ * mode?: "popover" | "dialog",
41053
+ * variant?: "icon" | "headless",
41054
+ * icon?: import("ignore:preact").ComponentChildren,
41055
+ * maxLines?: number,
41056
+ * popoverMaxHeight?: number | string,
41057
+ * popupBackgroundColor?: string,
41058
+ * popupBorderRadius?: number | string,
41059
+ * dialogExpand?: boolean,
41060
+ * dialogExpandX?: boolean,
41061
+ * dialogExpandY?: boolean,
41062
+ * ref?: import("ignore:preact").RefObject<HTMLElement>,
41063
+ * [key: string]: any,
41064
+ * }>}
41065
+ */
40964
41066
  const Picker = createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
40965
41067
  Picker.UI = PickerDefaultUI;
40966
41068
  Picker.UI.Date = PickerDateUI;
@@ -45832,6 +45934,26 @@ installImportMetaCssBuild(import.meta);const css$7 = /* css */`
45832
45934
  }
45833
45935
  }
45834
45936
  `;
45937
+
45938
+ /**
45939
+ * @type {import("ignore:preact").FunctionComponent<{
45940
+ * children?: number | string,
45941
+ * unit?: string,
45942
+ * unitPosition?: "right" | "bottom",
45943
+ * unitSize?: string,
45944
+ * unitSizeRatio?: number,
45945
+ * unitColor?: string,
45946
+ * label?: string,
45947
+ * size?: string,
45948
+ * lang?: string,
45949
+ * integer?: boolean,
45950
+ * loading?: boolean,
45951
+ * readOnly?: boolean,
45952
+ * disabled?: boolean,
45953
+ * bold?: boolean,
45954
+ * [key: string]: any,
45955
+ * }>}
45956
+ */
45835
45957
  const Quantity = ({
45836
45958
  children,
45837
45959
  unit,
@@ -45859,7 +45981,7 @@ const Quantity = ({
45859
45981
  return jsxs(Text, {
45860
45982
  baseClassName: "navi_quantity",
45861
45983
  "data-unit-bottom": unitBottom ? "" : undefined,
45862
- propsCSSVars: QuantityPropsCSSVars,
45984
+ styleCSSVars: QuantityStyleCSSVars,
45863
45985
  basePseudoState: {
45864
45986
  ":read-only": readOnly,
45865
45987
  ":disabled": disabled,
@@ -45893,7 +46015,7 @@ const Quantity = ({
45893
46015
  })]
45894
46016
  });
45895
46017
  };
45896
- const QuantityPropsCSSVars = {
46018
+ const QuantityStyleCSSVars = {
45897
46019
  unitColor: "--unit-color"
45898
46020
  };
45899
46021
  const QuantityPseudoClasses = [":hover", ":active", ":read-only", ":disabled", ":-navi-loading"];
@@ -46773,7 +46895,7 @@ const SidePanel = ({
46773
46895
  value: onClose,
46774
46896
  children: jsxs(Box, {
46775
46897
  baseClassName: "navi_side_panel",
46776
- propsCSSVars: SidePanelStyleCSSVars,
46898
+ styleCSSVars: SidePanelStyleCSSVars,
46777
46899
  width: width,
46778
46900
  "data-opening": phase === "opening" ? "" : undefined,
46779
46901
  "data-closing": phase === "closing" ? "" : undefined,