@jsenv/navi 0.27.51 → 0.27.53

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.
@@ -22459,7 +22459,7 @@ const useUIStateController = (
22459
22459
  const currentUIState = uiStateController.uiState;
22460
22460
  const stateIsTheSame = compareTwoJsValues(newUIState, currentUIState);
22461
22461
  if (stateIsTheSame) {
22462
- if (controlType === "button") {
22462
+ if (controlType === "button" || controlType === "link") {
22463
22463
  if (!isInternalEvent(e)) {
22464
22464
  uiStateController.onUIAction(e);
22465
22465
  }
@@ -23071,6 +23071,9 @@ const useUIGroupStateController = (
23071
23071
  if (childUIStateController.controlType === "button") {
23072
23072
  return false;
23073
23073
  }
23074
+ if (childUIStateController.controlType === "link") {
23075
+ return false;
23076
+ }
23074
23077
  return true;
23075
23078
  };
23076
23079
 
@@ -23367,6 +23370,9 @@ const useUIGroupStateController = (
23367
23370
  if (childUIStateController.controlType === "button") {
23368
23371
  continue;
23369
23372
  }
23373
+ if (childUIStateController.controlType === "link") {
23374
+ continue;
23375
+ }
23370
23376
  childUIStateController.clearUIState(propagateDownClearEvent);
23371
23377
  }
23372
23378
  onChange(e, { notifyExternal: true });
@@ -23465,6 +23471,9 @@ const useUIFacadeStateController = (props, realUIStateController) => {
23465
23471
  if (childController.controlType === "button") {
23466
23472
  return false;
23467
23473
  }
23474
+ if (childController.controlType === "link") {
23475
+ return false;
23476
+ }
23468
23477
  if (childController.controlType === "facade") {
23469
23478
  return false;
23470
23479
  }
@@ -32091,8 +32100,12 @@ const getNowHoursRoundedToStep = (stepMinutes, offsetMinutes = 0) => {
32091
32100
  return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
32092
32101
  };
32093
32102
 
32094
- // Maps validity type names → navi input type names
32103
+ // Maps validity type names → navi input type names.
32104
+ // Numeric signal types must not fall through to the native type="number"
32105
+ // (which adds spinner buttons and has poor UX) — they map to navi_number instead.
32095
32106
  const VALIDITY_TYPE_TO_INPUT_TYPE = {
32107
+ number: "navi_number",
32108
+ integer: "navi_number",
32096
32109
  percentage: "navi_percentage",
32097
32110
  };
32098
32111
 
@@ -35497,7 +35510,7 @@ const Dialog = props => {
35497
35510
  focusedBeforeOpen
35498
35511
  });
35499
35512
  };
35500
- const close = e => {
35513
+ const close = (e, detail = {}) => {
35501
35514
  debugPopup(`"${e.type}" on ${getElementSignature(e.target)} -> closeDialog`);
35502
35515
  const dialogEl = ref.current;
35503
35516
  markAutofocusRestoreOnClose(dialogEl);
@@ -35505,7 +35518,8 @@ const Dialog = props => {
35505
35518
  cleanup();
35506
35519
  openedRef.current = false;
35507
35520
  dispatchCustomEvent(dialogEl, "navi_close", {
35508
- event: e
35521
+ event: e,
35522
+ ...detail
35509
35523
  });
35510
35524
  };
35511
35525
  const onRequestOpen = (e, {
@@ -35522,7 +35536,7 @@ const Dialog = props => {
35522
35536
  anchor
35523
35537
  });
35524
35538
  };
35525
- const onRequestClose = e => {
35539
+ const onRequestClose = (e, detail = {}) => {
35526
35540
  const dialogEl = ref.current;
35527
35541
  if (!dialogEl) {
35528
35542
  return;
@@ -35540,15 +35554,15 @@ const Dialog = props => {
35540
35554
  denied = false;
35541
35555
  }
35542
35556
  };
35543
- closeRequestHandler(e, closePermission);
35557
+ closeRequestHandler(e, closePermission, detail);
35544
35558
  if (denied) {
35545
35559
  closePermission.allow = () => {
35546
- close(e);
35560
+ close(e, detail);
35547
35561
  };
35548
35562
  return;
35549
35563
  }
35550
35564
  }
35551
- close(e);
35565
+ close(e, detail);
35552
35566
  };
35553
35567
  return jsx(Box, {
35554
35568
  ...rest,
@@ -35562,7 +35576,9 @@ const Dialog = props => {
35562
35576
  // The <dialog> element covers the full viewport; clicking the backdrop
35563
35577
  // hits the dialog itself (not any child). Close when that happens.
35564
35578
  if (!pointerTrap && e.button === 0 && e.target === ref.current) {
35565
- onRequestClose(e);
35579
+ onRequestClose(e, {
35580
+ isClickOutside: true
35581
+ });
35566
35582
  }
35567
35583
  },
35568
35584
  onCancel: e => {
@@ -35722,7 +35738,7 @@ const Popover = props => {
35722
35738
  focusedBeforeOpen
35723
35739
  });
35724
35740
  };
35725
- const close = e => {
35741
+ const close = (e, detail = {}) => {
35726
35742
  debugPopup(e, `closePopover()`);
35727
35743
  const popoverEl = ref.current;
35728
35744
  markAutofocusRestoreOnClose(popoverEl);
@@ -35731,7 +35747,8 @@ const Popover = props => {
35731
35747
  openedRef.current = false;
35732
35748
  setOpened(false);
35733
35749
  dispatchCustomEvent(popoverEl, "navi_close", {
35734
- event: e
35750
+ event: e,
35751
+ ...detail
35735
35752
  });
35736
35753
  };
35737
35754
  const onRequestOpen = (e, {
@@ -35748,7 +35765,7 @@ const Popover = props => {
35748
35765
  anchor
35749
35766
  });
35750
35767
  };
35751
- const onRequestClose = e => {
35768
+ const onRequestClose = (e, detail = {}) => {
35752
35769
  const popoverEl = ref.current;
35753
35770
  if (!popoverEl) {
35754
35771
  return;
@@ -35766,15 +35783,15 @@ const Popover = props => {
35766
35783
  denied = false;
35767
35784
  }
35768
35785
  };
35769
- closeRequestHandler(e, closePermission);
35786
+ closeRequestHandler(e, closePermission, detail);
35770
35787
  if (denied) {
35771
35788
  closePermission.allow = () => {
35772
- close(e);
35789
+ close(e, detail);
35773
35790
  };
35774
35791
  return;
35775
35792
  }
35776
35793
  }
35777
- close(e);
35794
+ close(e, detail);
35778
35795
  };
35779
35796
  return jsxs(Box, {
35780
35797
  id: id,
@@ -35806,7 +35823,9 @@ const Popover = props => {
35806
35823
  e.preventDefault();
35807
35824
  return;
35808
35825
  }
35809
- onRequestClose(e);
35826
+ onRequestClose(e, {
35827
+ isClickOutside: true
35828
+ });
35810
35829
  }
35811
35830
  }), children]
35812
35831
  });
@@ -35823,7 +35842,7 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
35823
35842
  min-width: var(--anchor-width, 0px);
35824
35843
  max-width: 95vw;
35825
35844
  /* max-height covers the placeholder + list; the list scrolls internally */
35826
- max-height: var(--space-available, 95dvh);
35845
+ max-height: min(var(--picker-popup-max-height, 300px), var(--space-available, 95dvh));
35827
35846
  margin: 0;
35828
35847
  padding: 0;
35829
35848
  background: var(--picker-background-color);
@@ -35915,8 +35934,12 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
35915
35934
  /* dialog */
35916
35935
  &[aria-haspopup="dialog"] {
35917
35936
  .navi_picker_dialog {
35937
+ --dialog-max-width: 95dvw;
35938
+ --dialog-max-height: 95dvh;
35939
+
35918
35940
  min-width: var(--anchor-width, 0px);
35919
- max-height: 95dvh;
35941
+ max-width: var(--dialog-max-width);
35942
+ max-height: var(--dialog-max-height);
35920
35943
  padding: 0;
35921
35944
  background: var(--picker-background-color);
35922
35945
  border: var(--picker-border-width) solid var(--x-picker-border-color);
@@ -35930,6 +35953,13 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
35930
35953
  cursor: default; /* Reset pointer cursor within the select */
35931
35954
  /* overscroll-behavior: contain; */
35932
35955
 
35956
+ &[data-expand-x] {
35957
+ width: var(--dialog-max-width);
35958
+ }
35959
+ &[data-expand-y] {
35960
+ height: var(--dialog-max-height);
35961
+ }
35962
+
35933
35963
  &[open] {
35934
35964
  display: flex;
35935
35965
  flex-direction: column;
@@ -36071,15 +36101,9 @@ const PickerCustom = props => {
36071
36101
  expandedRef.current = expanded;
36072
36102
  const valueAtOpenRef = useRef(null);
36073
36103
  const activeElementAtOpenRef = useRef(null);
36074
- // Tracks whether a uiAction has occurred since the last close denial.
36075
- // true = no pending denial (initial state, or user has interacted since)
36076
- // false = close was denied and user hasn't interacted yet
36077
- // When false, a second close attempt is treated as cancel.
36078
- const uiActionSinceDeniedRef = useRef(true);
36079
36104
  const onOpen = e => {
36080
36105
  expandedRef.current = true;
36081
36106
  setExpanded(true);
36082
- uiActionSinceDeniedRef.current = true;
36083
36107
  const focusedBeforeOpen = e.detail.focusedBeforeOpen;
36084
36108
  activeElementAtOpenRef.current = focusedBeforeOpen;
36085
36109
  debugFocus(e, "picked opened, store element focused", focusedBeforeOpen);
@@ -36185,44 +36209,29 @@ const PickerCustom = props => {
36185
36209
  }
36186
36210
  });
36187
36211
  },
36188
- // Intercept uiAction to detect user interaction after a close denial.
36189
36212
  "uiAction": (v, e) => {
36190
- uiActionSinceDeniedRef.current = true;
36191
36213
  uiActionProp?.(v, e);
36192
36214
  },
36193
36215
  children
36194
36216
  });
36195
36217
  Object.assign(popupProps, {
36196
- closeRequestHandler: (requestCloseEvent, closePermission) => {
36218
+ closeRequestHandler: (requestCloseEvent, closePermission, {
36219
+ isClickOutside
36220
+ } = {}) => {
36197
36221
  const cancelEvent = findEvent(requestCloseEvent, eInChain => eInChain.type === "navi_request_close" && eInChain.detail.isCancel);
36198
- const isCancel = Boolean(cancelEvent);
36199
- const pickerEl = ref.current;
36200
- const inputEl = getPickerInput(pickerEl);
36201
- const valueAtOpen = valueAtOpenRef.current;
36222
+ const isCancel = isClickOutside || Boolean(cancelEvent);
36202
36223
  if (isCancel) {
36203
- uiActionSinceDeniedRef.current = true;
36224
+ const pickerEl = ref.current;
36225
+ const inputEl = getPickerInput(pickerEl);
36226
+ const valueAtOpen = valueAtOpenRef.current;
36227
+ debugPopup(requestCloseEvent, `picker cancel, restoring value at open ${JSON.stringify(valueAtOpen)}`);
36204
36228
  dispatchRequestSetUIState(inputEl, valueAtOpen, {
36205
36229
  event: requestCloseEvent
36206
36230
  });
36207
36231
  return;
36208
36232
  }
36209
-
36210
- // If close was previously denied and the user hasn't interacted since,
36211
- // treat this re-attempt as a cancel so they are not trapped.
36212
- if (!uiActionSinceDeniedRef.current) {
36213
- debugPopup(requestCloseEvent, `picker close was denied and user did not interact, treating re-attempt as cancel (restoring ${JSON.stringify(valueAtOpen)})`);
36214
- uiActionSinceDeniedRef.current = true;
36215
- dispatchRequestSetUIState(inputEl, valueAtOpen, {
36216
- event: requestCloseEvent
36217
- });
36218
- return;
36219
- }
36220
- const valueAtClose = getPickerInputUIState(pickerEl);
36221
- if (compareTwoJsValues(valueAtClose, valueAtOpen)) {
36222
- debugPopup(requestCloseEvent, `picker closed with same value as when it opened (${JSON.stringify(valueAtClose)}), no action dispatched`);
36223
- return;
36224
- }
36225
- debugPopup(requestCloseEvent, `picker attempt to close with value (${JSON.stringify(valueAtClose)}) wait for picker action to close picker`);
36233
+ const pickerEl = ref.current;
36234
+ const inputEl = getPickerInput(pickerEl);
36226
36235
  dispatchRequestAction(inputEl, {
36227
36236
  event: requestCloseEvent,
36228
36237
  name: "picker close",
@@ -36233,10 +36242,8 @@ const PickerCustom = props => {
36233
36242
  // user sees what is wrong, even if the picker has no action prop.
36234
36243
  reportOnInvalid: true,
36235
36244
  onInvalid: () => {
36236
- uiActionSinceDeniedRef.current = false;
36237
36245
  closePermission.deny();
36238
- },
36239
- allowed: () => {}
36246
+ }
36240
36247
  });
36241
36248
  },
36242
36249
  onnavi_open: e => {
@@ -36318,10 +36325,16 @@ const PickerCustom = props => {
36318
36325
  Object.assign(pickerProps, {
36319
36326
  eventReactionDefinitions: {
36320
36327
  mouseDown: e => {
36328
+ const popupEl = popupRef.current;
36329
+ if (popupEl && popupEl.contains(e.target)) {
36330
+ return null;
36331
+ }
36321
36332
  if (expandedRef.current) {
36322
36333
  return {
36323
36334
  name: "mousedown to close picker",
36324
- allowed: () => requestClose(e)
36335
+ allowed: () => requestClose(e, {
36336
+ isCancel: true
36337
+ })
36325
36338
  };
36326
36339
  }
36327
36340
  return {
@@ -36334,6 +36347,10 @@ const PickerCustom = props => {
36334
36347
  };
36335
36348
  },
36336
36349
  click: e => {
36350
+ const popupEl = popupRef.current;
36351
+ if (popupEl && popupEl.contains(e.target)) {
36352
+ return null;
36353
+ }
36337
36354
  // When a label is clicked it transfers focus to the select
36338
36355
  // in that case we want to open it (otherwise we have already opened on mousedown interaction)
36339
36356
  return {
@@ -36352,40 +36369,6 @@ const PickerCustom = props => {
36352
36369
  }
36353
36370
  }
36354
36371
  });
36355
- Object.assign(popupProps, {
36356
- onMouseDown: e => {
36357
- if (e.button !== 0) {
36358
- return;
36359
- }
36360
- // mousedown inside popover should not bubble to the select (would re-open it if that mousedown closes it)
36361
- debugPopup(e, `"mousedown" received on popup -> prevent bubbling to picker (e.stopPropagation())`);
36362
- e.stopPropagation();
36363
- },
36364
- onClick: e => {
36365
- if (e.button !== 0) {
36366
- return;
36367
- }
36368
- // click inside popover should not bubble to the picker (would re-open it if that click closes it)
36369
- debugPopup(e, `"click" received on popup -> prevent bubbling to picker (e.stopPropagation())`);
36370
- e.stopPropagation();
36371
- // Here we can't preventDefault because the click might be needed to check a radio for instance.
36372
- // As a result we have to let it go through which means it could trigger form submission
36373
- // but we've put type="button" on the picker to ensure it can't submit the form
36374
- // so browser won't submit eventual form for clicks inside the popover/dialog
36375
- // e.preventDefault();
36376
- },
36377
- onKeyDown: e => {
36378
- // some keys pressed inside popover should not reach the picker button
36379
- // (like enter that would try to request action of closest form otherwise for instance)
36380
- if (e.key === "Enter") {
36381
- debugPopup(e, `"enter" received on popup -> prevent bubbling to picker (e.stopPropagation())`);
36382
- e.stopPropagation();
36383
- // preventDefault prevents the browser from dispatching a "click" on the
36384
- // picker button when focus moves to it synchronously during enterEffect
36385
- e.preventDefault();
36386
- }
36387
- }
36388
- });
36389
36372
  }
36390
36373
  }
36391
36374
  if (mode === "popover") {
@@ -36444,7 +36427,8 @@ const PickerContentInsidePopover = props => {
36444
36427
  category: "interaction",
36445
36428
  allowed: () => {
36446
36429
  dispatchCustomEvent(popoverEl, "navi_request_close", {
36447
- event: e
36430
+ event: e,
36431
+ isCancel: true
36448
36432
  });
36449
36433
  }
36450
36434
  });
@@ -36470,7 +36454,8 @@ const PickerContentInsidePopover = props => {
36470
36454
  }
36471
36455
  const popupEl = popupProps.ref.current;
36472
36456
  dispatchCustomEvent(popupEl, "navi_request_close", {
36473
- event: e
36457
+ event: e,
36458
+ isCancel: true
36474
36459
  });
36475
36460
  },
36476
36461
  children: props.trigger
@@ -36485,8 +36470,13 @@ const PickerContentInsideDialog = props => {
36485
36470
  children,
36486
36471
  scrollTrap,
36487
36472
  pointerTrap,
36473
+ dialogExpand,
36474
+ dialogExpandX,
36475
+ dialogExpandY,
36488
36476
  ...rest
36489
36477
  } = props;
36478
+ const expandX = dialogExpand || dialogExpandX;
36479
+ const expandY = dialogExpand || dialogExpandY;
36490
36480
  return jsx(Next, {
36491
36481
  "aria-haspopup": "dialog",
36492
36482
  ...rest,
@@ -36497,6 +36487,8 @@ const PickerContentInsideDialog = props => {
36497
36487
  pointerTrap: pointerTrap,
36498
36488
  centerInVisualViewport: true,
36499
36489
  autoFocus: "fallback",
36490
+ "data-expand-x": expandX ? "" : undefined,
36491
+ "data-expand-y": expandY ? "" : undefined,
36500
36492
  children: children
36501
36493
  })
36502
36494
  });
@@ -37613,10 +37605,6 @@ installImportMetaCssBuild(import.meta);const css$n = /* css */`
37613
37605
  }
37614
37606
  }
37615
37607
 
37616
- fieldset.navi_list_container[navi-selectable] {
37617
- margin: 0; /* Reset margin that might come from fieldset */
37618
- padding: 0; /* Reset padding that might come from fieldset */
37619
- }
37620
37608
  .navi_list_container[navi-selectable] {
37621
37609
  &[data-callout] {
37622
37610
  --x-list-border-color: var(--callout-color);
@@ -37860,7 +37848,6 @@ const ListSelectable = props => {
37860
37848
  setCurrentId(initialEl.id);
37861
37849
  }, []);
37862
37850
  const listVnode = jsx(Next, {
37863
- as: "fieldset",
37864
37851
  "navi-selectable": "",
37865
37852
  "navi-has-selected-background": selectedIndicator === "backgroundColor" ? "" : undefined,
37866
37853
  ...listControlRootProps,
@@ -38269,9 +38256,10 @@ installImportMetaCssBuild(import.meta);const ListItemTrackerContext = createCont
38269
38256
  const GroupItemTrackerContext = createContext(null);
38270
38257
  const PendingScrollRefContext = createContext(null);
38271
38258
  // Controls how List.Item behaves when match=false (set via List searchNoMatchMode prop):
38272
- // "remove" — remove from DOM (default)
38259
+ // "remove" — remove from DOM (default)
38273
38260
  // "invisible_and_inert" — keep in DOM, invisible and non-interactive (preserves layout, no content visible)
38274
38261
  // "muted" — keep in DOM, visible but opacified and still interactive
38262
+ // "below" — keep in DOM, fully visible, pushed below matching items via CSS order
38275
38263
  const SearchNoMatchModeContext = createContext("remove");
38276
38264
 
38277
38265
  // When total rendered items exceeds renderBudget, a render window [start, end)
@@ -38298,7 +38286,6 @@ const css$m = /* css */`
38298
38286
  --list-border-width: 1px;
38299
38287
  --list-border-color: light-dark(#ccc, #555);
38300
38288
  --list-background-color: light-dark(#fff, #1e1e1e);
38301
- --list-max-height: 220px;
38302
38289
  }
38303
38290
  .navi_list_item {
38304
38291
  --list-item-padding-x-default: 0px;
@@ -38369,8 +38356,8 @@ const css$m = /* css */`
38369
38356
  .navi_list_scroll_container {
38370
38357
  width: inherit;
38371
38358
  min-width: inherit;
38372
- max-width: inherit;
38373
- max-height: var(--list-max-height);
38359
+ max-width: var(--list-max-width, inherit);
38360
+ max-height: var(--list-max-height, inherit);
38374
38361
  overflow: auto;
38375
38362
  overscroll-behavior: inherit; /* inherit select behavior */
38376
38363
  }
@@ -38378,6 +38365,12 @@ const css$m = /* css */`
38378
38365
  &[data-expand-x] {
38379
38366
  width: 100%;
38380
38367
  }
38368
+ &[data-expand-y] {
38369
+ --list-max-height: none;
38370
+ }
38371
+ &[navi-nothing-to-display] {
38372
+ display: none;
38373
+ }
38381
38374
  &[popover] {
38382
38375
  position: absolute;
38383
38376
  inset: unset;
@@ -38494,6 +38487,8 @@ const css$m = /* css */`
38494
38487
  /* background: pink; */
38495
38488
  }
38496
38489
  &[data-horizontal] {
38490
+ --list-max-height: none;
38491
+
38497
38492
  .navi_list_virtual_filler {
38498
38493
  width: var(--size-to-fill, 0px);
38499
38494
  height: 100%;
@@ -38643,8 +38638,8 @@ const ListUI = props => {
38643
38638
  children,
38644
38639
  popover,
38645
38640
  expandX,
38641
+ expandY,
38646
38642
  expand,
38647
- maxHeight,
38648
38643
  onListVisibleItemsChange,
38649
38644
  virtualItemSize,
38650
38645
  lockSize,
@@ -38709,6 +38704,12 @@ const ListUI = props => {
38709
38704
  const getItemById = itemId => {
38710
38705
  return tracker.itemsSignal.peek().find(item => item.id === itemId);
38711
38706
  };
38707
+ const noMatchCount = tracker.noMatchCountSignal.value;
38708
+ const itemCount = tracker.countSignal.value;
38709
+ const allNoMatch = noMatchCount > 0 && noMatchCount === itemCount;
38710
+ const fallbackDisabled = fallback !== undefined && !fallback;
38711
+ const searchFallbackDisabled = searchFallback !== undefined && !searchFallback;
38712
+ const nothingToDisplay = allNoMatch && searchFallbackDisabled && searchNoMatchMode === "remove" || itemCount === 0 && fallbackDisabled;
38712
38713
  return jsx(Box, {
38713
38714
  ...rest,
38714
38715
  ref: ref,
@@ -38716,9 +38717,12 @@ const ListUI = props => {
38716
38717
  popover: popover,
38717
38718
  "data-horizontal": horizontal ? "" : undefined,
38718
38719
  "data-expand-x": expandX || expand ? "" : undefined,
38720
+ "data-expand-y": expandY || expand ? "" : undefined,
38719
38721
  expandX: expandX,
38722
+ expandY: expandY,
38720
38723
  expand: expand,
38721
- maxHeight: maxHeight,
38724
+ "navi-zero-match": allNoMatch ? "" : undefined,
38725
+ "navi-nothing-to-display": nothingToDisplay ? "" : undefined,
38722
38726
  styleCSSVars: LIST_STYLE_CSS_VARS,
38723
38727
  pseudoClasses: LIST_PSEUDO_CLASSES,
38724
38728
  hasChildUsingForwardedProps: true,
@@ -38743,6 +38747,7 @@ const ListUI = props => {
38743
38747
  searchNoMatchMode: searchNoMatchMode,
38744
38748
  separator: separator,
38745
38749
  expandX: expandX,
38750
+ expandY: expandY,
38746
38751
  expand: expand,
38747
38752
  horizontal: horizontal,
38748
38753
  spacing: spacing,
@@ -38764,41 +38769,32 @@ const ListFirstResolver = props => {
38764
38769
  ...props
38765
38770
  });
38766
38771
  };
38772
+
38767
38773
  /**
38768
38774
  * List — generic virtualized scroll container.
38769
- *
38770
- * Renders children inside a scrollable container with an optional render budget
38771
- * for virtual scrolling. Items must use <List.Item> to participate in tracking.
38772
- *
38773
- * Props:
38774
- * selectable — enables selection: items get aria roles, action/uiAction callbacks,
38775
- * and keyboard navigation (arrow keys, enter, escape).
38776
- * action — called with the selected value when the user confirms a selection.
38777
- * uiAction — called on every interaction (hover, keyboard navigation, confirm).
38778
- * When provided the list tracks hovered/pointed state via ListInteractionContext.
38779
- * popover — when true, renders as a managed popover positioned near
38780
- * an anchor element via navi_list_open / navi_list_close events.
38781
- * renderBudget — max items in DOM at once (default 100, virtual scroll when exceeded).
38782
- * virtualItemSize — fixed px size per item (width if horizontal, height otherwise).
38783
- * Enables precise virtual-scroll filler sizing without a DOM
38784
- * measurement pass. Useful when renderBudget is active and
38785
- * all items have the same known height.
38786
- * fallback — content shown when the list has no items at all.
38787
- * searchFallback — content shown when every matchable item (those with a match prop)
38788
- * has match=false. Defaults to a "no results" message.
38789
- * Pass false/null/'' to disable.
38790
- * searchText — current search string, used to trigger scroll-to-top when
38791
- * search becomes active and to drive search highlight.
38792
- * searchNoMatchMode — controls how List.Item behaves when match=false (default "remove"):
38793
- * "remove" — remove from DOM
38794
- * "invisible_and_inert" — keep in DOM, invisible and non-interactive (preserves layout)
38795
- * "muted" — keep in DOM, visible but opacified and still interactive
38796
- * separator — element or function(index, { previousItem, currentItem }) inserted between visible items.
38797
- * lockSize — captures the container's dimensions on first render so filtering
38798
- * cannot collapse the layout (sets min-width/min-height).
38799
- * horizontal — lays items out in a row instead of a column.
38800
- * spacing — gap between items (forwarded to Box spacing prop).
38801
- * ...rest — forwarded to the outer container <Box>.
38775
+ * Items must use <List.Item> to participate in tracking.
38776
+ *
38777
+ * @type {import("ignore:preact").FunctionComponent<{
38778
+ * selectable?: boolean,
38779
+ * action?: (value: any) => void,
38780
+ * uiAction?: (value: any) => void,
38781
+ * popover?: boolean,
38782
+ * renderBudget?: number,
38783
+ * virtualItemSize?: number,
38784
+ * fallback?: import("ignore:preact").ComponentChildren,
38785
+ * searchFallback?: import("ignore:preact").ComponentChildren,
38786
+ * searchText?: string,
38787
+ * searchNoMatchMode?: "remove" | "invisible_and_inert" | "muted" | "below",
38788
+ * separator?: boolean | import("ignore:preact").ComponentChildren,
38789
+ * lockSize?: boolean,
38790
+ * horizontal?: boolean,
38791
+ * spacing?: string,
38792
+ * expandX?: boolean,
38793
+ * expandY?: boolean,
38794
+ * expand?: boolean,
38795
+ * children?: import("ignore:preact").ComponentChildren,
38796
+ * [key: string]: any,
38797
+ * }>}
38802
38798
  */
38803
38799
  const List = createComponentResolver([ListFirstResolver, ListSelectableResolver, ListUI]);
38804
38800
  const ListContent = ({
@@ -38808,6 +38804,7 @@ const ListContent = ({
38808
38804
  searchNoMatchMode,
38809
38805
  separator,
38810
38806
  expandX,
38807
+ expandY,
38811
38808
  expand,
38812
38809
  horizontal,
38813
38810
  spacing,
@@ -38829,6 +38826,7 @@ const ListContent = ({
38829
38826
  margin: "0"
38830
38827
  }) : separator,
38831
38828
  expandX: expandX || expand,
38829
+ expandY: expandY || expand,
38832
38830
  horizontal: horizontal,
38833
38831
  spacing: spacing,
38834
38832
  ...listProps,
@@ -38844,6 +38842,7 @@ const ListContent = ({
38844
38842
  };
38845
38843
  const LIST_STYLE_CSS_VARS = {
38846
38844
  maxHeight: "--list-max-height",
38845
+ maxWidth: "--list-max-width",
38847
38846
  borderColor: "--list-border-color",
38848
38847
  borderRadius: "--list-border-radius",
38849
38848
  borderWidth: "--list-border-width"
@@ -39262,7 +39261,6 @@ const UnorderedList = ({
39262
39261
  return jsxs(Box, {
39263
39262
  as: "ul",
39264
39263
  flex: horizontal ? "x" : "y",
39265
- flexWrap: true,
39266
39264
  ...rest,
39267
39265
  spacing: spacing,
39268
39266
  baseClassName: "navi_list",
@@ -40694,38 +40692,6 @@ installImportMetaCssBuild(import.meta);const css$i = /* css */`
40694
40692
  }
40695
40693
  }
40696
40694
  `;
40697
-
40698
- /**
40699
- * A button-like trigger that opens a picker when clicked.
40700
- *
40701
- * Use the `type` prop to choose what kind of picker to open:
40702
- * "date" — calendar day
40703
- * "month" — year + month
40704
- * "week" — ISO week
40705
- * "time" — hours + minutes
40706
- * "datetime" — date + time
40707
- * "color" — color chooser
40708
- * "hour" — fixed time slots (derived from min/max/step)
40709
- *
40710
- * When `children` are provided, the picker opens a popover or dialog instead
40711
- * of the browser-native picker. On small screens a dialog is used automatically;
40712
- * on larger screens a popover is used. Pass `mode="dialog"` or `mode="popover"`
40713
- * to force one. The children are rendered inside the popup.
40714
- *
40715
- * Props:
40716
- * type — picker variant (see above)
40717
- * value — controlled value
40718
- * uiAction — called with the new value when the user picks one
40719
- * name — form field name
40720
- * placeholder — shown when no value is selected
40721
- * required — marks the field as required
40722
- * min — minimum allowed value; accepts a Date or a raw string
40723
- * max — maximum allowed value; accepts a Date or a raw string
40724
- * step — step interval
40725
- * disabled — disables the picker
40726
- * children — content to display inside the popup (enables popover/dialog mode)
40727
- * mode — "popover" or "dialog"; auto-detected from screen size when omitted
40728
- */
40729
40695
  const PickerButton = props => {
40730
40696
  import.meta.css = [css$i, "@jsenv/navi/src/control/picker/picker.jsx"];
40731
40697
  if (typeof props.maxLines === "string") {
@@ -40983,6 +40949,40 @@ const PickerFirstResolver = props => {
40983
40949
  ...props
40984
40950
  });
40985
40951
  };
40952
+
40953
+ /**
40954
+ * Button-like trigger that opens a picker (native or custom popup) when clicked.
40955
+ *
40956
+ * Without `children`, opens the browser-native picker for the given `type`.
40957
+ * With `children`, opens a popover (desktop) or dialog (mobile) containing the children.
40958
+ * Pass `mode="popover"` or `mode="dialog"` to override the automatic choice.
40959
+ *
40960
+ * @type {import("ignore:preact").FunctionComponent<{
40961
+ * type?: "date" | "month" | "week" | "time" | "datetime" | "color" | "hour" | "navi_time" | "navi_number" | "navi_percentage",
40962
+ * value?: any,
40963
+ * defaultValue?: any,
40964
+ * name?: string,
40965
+ * placeholder?: import("ignore:preact").ComponentChildren,
40966
+ * required?: boolean,
40967
+ * min?: Date | string | number,
40968
+ * max?: Date | string | number,
40969
+ * step?: string | number,
40970
+ * disabled?: boolean,
40971
+ * readOnly?: boolean,
40972
+ * uiAction?: (value: any, event: Event) => void,
40973
+ * action?: (value: any, event: Event) => void,
40974
+ * children?: import("ignore:preact").ComponentChildren,
40975
+ * mode?: "popover" | "dialog",
40976
+ * variant?: "icon" | "headless",
40977
+ * icon?: import("ignore:preact").ComponentChildren,
40978
+ * maxLines?: number,
40979
+ * dialogExpand?: boolean,
40980
+ * dialogExpandX?: boolean,
40981
+ * dialogExpandY?: boolean,
40982
+ * ref?: import("ignore:preact").RefObject<HTMLElement>,
40983
+ * [key: string]: any,
40984
+ * }>}
40985
+ */
40986
40986
  const Picker = createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
40987
40987
  Picker.UI = PickerDefaultUI;
40988
40988
  Picker.UI.Date = PickerDateUI;