@jsenv/navi 0.27.50 → 0.27.51

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.
@@ -8869,11 +8869,11 @@ import.meta.css = [/* css */`
8869
8869
  }
8870
8870
  }
8871
8871
  /*
8872
-
8873
- It's very common to declare a display on component as follow
8872
+ To set display on component, code usually do something like:
8874
8873
  .component_class { display: component_display; }
8875
8874
 
8876
- This kill the default behavior of [hidden] attribute and we need to explicitly handle it with:
8875
+ It overrides the default behavior of [hidden] attribute!
8876
+ This needs to be explicitly handled with:
8877
8877
  .component_class[hidden] { display: none; }
8878
8878
 
8879
8879
  To avoid this extra work and potential mistakes we force the default behavior of [hidden] attribute.
@@ -37257,7 +37257,7 @@ const createItemTracker = (onChange) => {
37257
37257
  const visibleItemsSignal = signal([]);
37258
37258
  const countSignal = signal(0);
37259
37259
  const visibleCountSignal = signal(0);
37260
- const matchCountSignal = signal(0);
37260
+ const noMatchCountSignal = signal(0);
37261
37261
 
37262
37262
  let notifyScheduled = false;
37263
37263
  const runNotify = () => {
@@ -37279,7 +37279,7 @@ const createItemTracker = (onChange) => {
37279
37279
  let visibleItemsChanged = false;
37280
37280
  const allItems = [];
37281
37281
  const visibleItems = [];
37282
- let newMatchCount = 0;
37282
+ let newNoMatchCount = 0;
37283
37283
  for (let i = 0; i < allOrderedKeys.length; i++) {
37284
37284
  const key = allOrderedKeys[i];
37285
37285
  const item = allRegistrations.get(key);
@@ -37288,12 +37288,12 @@ const createItemTracker = (onChange) => {
37288
37288
  if (!allItemsChanged && item !== prevAllItems[i]) {
37289
37289
  allItemsChanged = true;
37290
37290
  }
37291
- if (!item.filtered && !item.hidden) {
37291
+ if (item.match === false) {
37292
+ newNoMatchCount++;
37293
+ }
37294
+ if (!item.hidden) {
37292
37295
  const visibleIdx = visibleItems.length;
37293
37296
  visibleItems.push(item);
37294
- if (item.matchScore > 0) {
37295
- newMatchCount++;
37296
- }
37297
37297
  if (!visibleItemsChanged && item !== prevVisibleItems[visibleIdx]) {
37298
37298
  visibleItemsChanged = true;
37299
37299
  }
@@ -37315,9 +37315,9 @@ const createItemTracker = (onChange) => {
37315
37315
  visibleItemsSignal.value = visibleItems;
37316
37316
  someChange = true;
37317
37317
  }
37318
- const matchCountModified = matchCountSignal.peek() !== newMatchCount;
37319
- if (matchCountModified) {
37320
- matchCountSignal.value = newMatchCount;
37318
+ const noMatchCountModified = noMatchCountSignal.peek() !== newNoMatchCount;
37319
+ if (noMatchCountModified) {
37320
+ noMatchCountSignal.value = newNoMatchCount;
37321
37321
  someChange = true;
37322
37322
  }
37323
37323
  if (someChange) {
@@ -37516,7 +37516,7 @@ const createItemTracker = (onChange) => {
37516
37516
  visibleItemsSignal,
37517
37517
  countSignal,
37518
37518
  visibleCountSignal,
37519
- matchCountSignal,
37519
+ noMatchCountSignal,
37520
37520
  _flushSync,
37521
37521
  };
37522
37522
  };
@@ -37583,8 +37583,8 @@ installImportMetaCssBuild(import.meta);const css$n = /* css */`
37583
37583
  --list-item-outline-color: var(--navi-focus-outline-color);
37584
37584
  /* Focus outline end */
37585
37585
  --list-item-border-color: var(--navi-control-border-color);
37586
- --list-item-padding-x-default: var(--navi-control-padding-x-default);
37587
- --list-item-padding-y-default: var(--navi-control-padding-y-default);
37586
+ --list-item-padding-x-default: var(--navi-list-item-padding-x-default);
37587
+ --list-item-padding-y-default: var(--navi-list-item-padding-y-default);
37588
37588
 
37589
37589
  /* Hover (mouse) */
37590
37590
  --list-item-background-color-hover: light-dark(#f5f5f5, #2a2a2a);
@@ -37633,6 +37633,14 @@ installImportMetaCssBuild(import.meta);const css$n = /* css */`
37633
37633
  }
37634
37634
  }
37635
37635
 
37636
+ .navi_list_container[navi-selectable] {
37637
+ .navi_list_fallback,
37638
+ .navi_list_no_match_fallback {
37639
+ --list-item-padding-x-default: inherit;
37640
+ --list-item-padding-y-default: inherit;
37641
+ }
37642
+ }
37643
+
37636
37644
  .navi_list_item[navi-selectable] {
37637
37645
  --list-item-padding-x-default: inherit;
37638
37646
  --list-item-padding-y-default: inherit;
@@ -37642,6 +37650,13 @@ installImportMetaCssBuild(import.meta);const css$n = /* css */`
37642
37650
  outline-offset: var(--list-item-outline-offset);
37643
37651
  cursor: var(--x-list-item-cursor);
37644
37652
 
37653
+ .navi_checkbox {
37654
+ --margin: 0;
37655
+ }
37656
+ .navi_radio {
37657
+ --margin: 0;
37658
+ }
37659
+
37645
37660
  &[navi-selectable] {
37646
37661
  user-select: none;
37647
37662
  }
@@ -38253,6 +38268,11 @@ const applySearchHighlight = (el, highlight) => {
38253
38268
  installImportMetaCssBuild(import.meta);const ListItemTrackerContext = createContext(null);
38254
38269
  const GroupItemTrackerContext = createContext(null);
38255
38270
  const PendingScrollRefContext = createContext(null);
38271
+ // Controls how List.Item behaves when match=false (set via List searchNoMatchMode prop):
38272
+ // "remove" — remove from DOM (default)
38273
+ // "invisible_and_inert" — keep in DOM, invisible and non-interactive (preserves layout, no content visible)
38274
+ // "muted" — keep in DOM, visible but opacified and still interactive
38275
+ const SearchNoMatchModeContext = createContext("remove");
38256
38276
 
38257
38277
  // When total rendered items exceeds renderBudget, a render window [start, end)
38258
38278
  // is activated to cap the number of DOM nodes. Items outside the window return
@@ -38454,6 +38474,14 @@ const css$m = /* css */`
38454
38474
  scroll-margin-right: var(--x-list-scroll-spacing-right);
38455
38475
  scroll-margin-bottom: var(--x-list-scroll-spacing-bottom);
38456
38476
  scroll-margin-left: var(--x-list-scroll-spacing-left);
38477
+
38478
+ &[aria-hidden="true"] {
38479
+ opacity: 0;
38480
+ }
38481
+
38482
+ &[navi-muted] {
38483
+ opacity: 0.35;
38484
+ }
38457
38485
  }
38458
38486
 
38459
38487
  /* Virtual scroll fillers — must remain invisible.
@@ -38479,10 +38507,10 @@ const css$m = /* css */`
38479
38507
  Using order ensures fallbacks always appear after items regardless of DOM order.
38480
38508
  matchFallback intentionally shares the same order as fallback so it appears
38481
38509
  at the same visual position — after an input if present but before any items
38482
- still displayed (non-matching items remain in DOM with hidden prop):
38510
+ still displayed (non-matching items remain in DOM, invisible_and_inert or muted):
38483
38511
  1. Input (sticky header, order: -2)
38484
- 2. matchFallback (order: -1)
38485
- 3. hidden items (regular order, after DOM flow)
38512
+ 2. searchFallback (order: -1)
38513
+ 3. invisible/dim items (regular order, after DOM flow)
38486
38514
  4. HOT FIX OF THE DEAD for bottom filler + preact issue: order: 1
38487
38515
  5. sticky footer (order: 2)
38488
38516
  */
@@ -38496,12 +38524,39 @@ const css$m = /* css */`
38496
38524
  order: -2;
38497
38525
  }
38498
38526
  .navi_list_fallback,
38499
- .navi_list_no_match_fallback {
38527
+ .navi_list_search_fallback {
38500
38528
  order: -1;
38501
38529
  color: light-dark(#888, #aaa);
38502
38530
  &[navi-default] {
38503
38531
  display: inline;
38504
- padding: var(--list-item-padding);
38532
+ padding-top: var(
38533
+ --list-item-padding-top,
38534
+ var(
38535
+ --list-item-padding-y,
38536
+ var(--list-item-padding, var(--list-item-padding-y-default))
38537
+ )
38538
+ );
38539
+ padding-right: var(
38540
+ --list-item-padding-right,
38541
+ var(
38542
+ --list-item-padding-x,
38543
+ var(--list-item-padding, var(--list-item-padding-x-default))
38544
+ )
38545
+ );
38546
+ padding-bottom: var(
38547
+ --list-item-padding-bottom,
38548
+ var(
38549
+ --list-item-padding-y,
38550
+ var(--list-item-padding, var(--list-item-padding-y-default))
38551
+ )
38552
+ );
38553
+ padding-left: var(
38554
+ --list-item-padding-left,
38555
+ var(
38556
+ --list-item-padding-x,
38557
+ var(--list-item-padding, var(--list-item-padding-x-default))
38558
+ )
38559
+ );
38505
38560
  text-align: center;
38506
38561
  user-select: none;
38507
38562
  }
@@ -38575,37 +38630,6 @@ const css$m = /* css */`
38575
38630
  }
38576
38631
  }
38577
38632
  `;
38578
-
38579
- /**
38580
- * List — generic virtualized scroll container.
38581
- *
38582
- * Renders children inside a scrollable container with an optional render budget
38583
- * for virtual scrolling. Items must use <ListItem> to participate in tracking.
38584
- *
38585
- * Props:
38586
- * keyboardInteractions — when true, attaches arrow/enter/escape keyboard shortcuts
38587
- * that dispatch navi_list_nav / navi_list_confirm / navi_list_clear
38588
- * to the list container. Pair with uiAction for a full keyboard-
38589
- * navigable list.
38590
- * uiAction — called with the selected value on confirm. When provided
38591
- * the list becomes interactive: tracks hover and keyboard-
38592
- * pointed state, handles navi_list_nav / navi_list_clear /
38593
- * navi_list_confirm custom events via ListInteractionContext.
38594
- * popover — when true, renders as a managed popover positioned near
38595
- * an anchor element via navi_list_open / navi_list_close events.
38596
- * renderBudget — max items in DOM at once (default 100, virtual scroll when exceeded)
38597
- * virtualItemSize — fixed px size per item (width if horizontal, height otherwise) when all items have the same size.
38598
- * Enables precise virtual-scroll filler sizing without a DOM
38599
- * measurement pass. Required when renderBudget is active and
38600
- * item height is known up-front.
38601
- * fallback — content shown when no items exist at all
38602
- * matchFallback — content shown when items exist but all are hidden (e.g. no search match)
38603
- * separator — element or function(index, { previousItem, currentItem }) inserted between visible items
38604
- * lockSize — when true, captures the container's dimensions on first render
38605
- * (always in unfiltered state). Those values become min-width/
38606
- * min-height so filtering cannot collapse the layout.
38607
- * ...rest — forwarded to the outer scroll container <Box>
38608
- */
38609
38633
  const ListUI = props => {
38610
38634
  import.meta.css = [css$m, "@jsenv/navi/src/control/list/list.jsx"];
38611
38635
  const {
@@ -38614,7 +38638,7 @@ const ListUI = props => {
38614
38638
  renderBudgetSkipCheck,
38615
38639
  role,
38616
38640
  fallback,
38617
- noMatchFallback,
38641
+ searchFallback,
38618
38642
  separator,
38619
38643
  children,
38620
38644
  popover,
@@ -38625,6 +38649,7 @@ const ListUI = props => {
38625
38649
  virtualItemSize,
38626
38650
  lockSize,
38627
38651
  searchText,
38652
+ searchNoMatchMode = "remove",
38628
38653
  horizontal,
38629
38654
  spacing,
38630
38655
  ...rest
@@ -38714,8 +38739,8 @@ const ListUI = props => {
38714
38739
  children: jsx(ListContent, {
38715
38740
  role: role,
38716
38741
  fallback: fallback,
38717
- noMatchFallback: noMatchFallback,
38718
- searchText: searchText,
38742
+ searchFallback: searchFallback,
38743
+ searchNoMatchMode: searchNoMatchMode,
38719
38744
  separator: separator,
38720
38745
  expandX: expandX,
38721
38746
  expand: expand,
@@ -38739,12 +38764,48 @@ const ListFirstResolver = props => {
38739
38764
  ...props
38740
38765
  });
38741
38766
  };
38767
+ /**
38768
+ * 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>.
38802
+ */
38742
38803
  const List = createComponentResolver([ListFirstResolver, ListSelectableResolver, ListUI]);
38743
38804
  const ListContent = ({
38744
38805
  role,
38745
38806
  fallback,
38746
- noMatchFallback,
38747
- searchText,
38807
+ searchFallback,
38808
+ searchNoMatchMode,
38748
38809
  separator,
38749
38810
  expandX,
38750
38811
  expand,
@@ -38762,8 +38823,8 @@ const ListContent = ({
38762
38823
  children: jsx(UnorderedList, {
38763
38824
  role: role,
38764
38825
  fallback: fallback,
38765
- noMatchFallback: noMatchFallback,
38766
- searchText: searchText,
38826
+ searchFallback: searchFallback,
38827
+ searchNoMatchMode: searchNoMatchMode,
38767
38828
  separator: separator === true ? jsx(Separator, {
38768
38829
  margin: "0"
38769
38830
  }) : separator,
@@ -39190,8 +39251,8 @@ const UnorderedList = ({
39190
39251
  renderWindow,
39191
39252
  virtualItemSizeSignal,
39192
39253
  fallback,
39193
- noMatchFallback,
39194
- searchText,
39254
+ searchFallback,
39255
+ searchNoMatchMode,
39195
39256
  separator,
39196
39257
  horizontal,
39197
39258
  spacing,
@@ -39208,20 +39269,22 @@ const UnorderedList = ({
39208
39269
  children: [jsx(BeforeFiller, {
39209
39270
  virtualItemSizeSignal: virtualItemSizeSignal,
39210
39271
  renderWindowStart: renderWindow.start
39211
- }), jsx(NoMatchFallback, {
39212
- noMatchFallback: noMatchFallback,
39213
- tracker: tracker,
39214
- searchText: searchText
39272
+ }), jsx(SearchFallback, {
39273
+ searchFallback: searchFallback,
39274
+ tracker: tracker
39215
39275
  }), jsx(Fallback, {
39216
39276
  fallback: fallback,
39217
39277
  tracker: tracker
39218
- }), jsx(RenderWindowContext.Provider, {
39219
- value: renderWindow,
39220
- children: jsx(SeparatorContext.Provider, {
39221
- value: separator ?? null,
39222
- children: jsx(ListItemTrackerContext.Provider, {
39223
- value: tracker,
39224
- children: children
39278
+ }), jsx(SearchNoMatchModeContext.Provider, {
39279
+ value: searchNoMatchMode,
39280
+ children: jsx(RenderWindowContext.Provider, {
39281
+ value: renderWindow,
39282
+ children: jsx(SeparatorContext.Provider, {
39283
+ value: separator ?? null,
39284
+ children: jsx(ListItemTrackerContext.Provider, {
39285
+ value: tracker,
39286
+ children: children
39287
+ })
39225
39288
  })
39226
39289
  })
39227
39290
  }), jsx(AfterFiller, {
@@ -39231,31 +39294,33 @@ const UnorderedList = ({
39231
39294
  })]
39232
39295
  });
39233
39296
  };
39234
- const NoMatchFallback = ({
39297
+
39298
+ // Show when all matchable items (those with a match prop) are non-matching.
39299
+ // The match prop on List.Item signals participation in a matching system
39300
+ // (search, filter, etc.). searchFallback appears when every such item has match=false.
39301
+ const SearchFallback = ({
39235
39302
  tracker,
39236
- noMatchFallback,
39237
- searchText
39303
+ searchFallback
39238
39304
  }) => {
39239
39305
  const itemCount = tracker.countSignal.value;
39240
- const visibleItemCount = tracker.visibleCountSignal.value;
39241
- const matchCount = tracker.matchCountSignal.value;
39242
- // Show when all items are filtered out (filtered prop), or when search is
39243
- // active but no visible item has a positive match score.
39244
- const allHidden = itemCount > 0 && visibleItemCount === 0;
39245
- const noneMatch = searchText && visibleItemCount > 0 && matchCount === 0;
39246
- const showMatchFallback = allHidden || noneMatch;
39247
- if (noMatchFallback === undefined) {
39248
- noMatchFallback = allHidden ? naviI18n("list.no_match") : naviI18n("list.no_match_rest_shown");
39306
+ const noMatchCount = tracker.noMatchCountSignal.value;
39307
+ const showMatchFallback = noMatchCount > 0 && noMatchCount === itemCount;
39308
+ if (searchFallback === undefined) {
39309
+ searchFallback = naviI18n("list.no_match");
39310
+ }
39311
+ if (!searchFallback) {
39312
+ // explicitely disabled by user (<List searchFallback={false|null|''}>)
39313
+ return null;
39249
39314
  }
39250
39315
  if (!showMatchFallback) {
39251
39316
  return null;
39252
39317
  }
39253
39318
  return jsx(ListItem, {
39254
39319
  role: "presentation",
39255
- className: "navi_list_item navi_list_no_match_fallback",
39320
+ className: "navi_list_item navi_list_search_fallback",
39256
39321
  hidden: !showMatchFallback,
39257
- "navi-default": typeof noMatchFallback === "string" ? "" : undefined,
39258
- children: noMatchFallback
39322
+ "navi-default": typeof searchFallback === "string" ? "" : undefined,
39323
+ children: searchFallback
39259
39324
  });
39260
39325
  };
39261
39326
  const Fallback = ({
@@ -39360,6 +39425,18 @@ const ListItemUI = props => {
39360
39425
  props.id = props.id || idDefault;
39361
39426
  const renderWindow = useContext(RenderWindowContext);
39362
39427
  const tracker = useContext(ListItemTrackerContext);
39428
+ const searchNoMatchMode = useContext(SearchNoMatchModeContext);
39429
+ // Derive filtered/hidden/nonMatching from the `match` prop + searchNoMatchMode context.
39430
+ // The `match` prop replaces the older `filtered`/`hidden` per-item props.
39431
+ if (props.match === false) {
39432
+ if (searchNoMatchMode === "remove") {
39433
+ props.filtered = true;
39434
+ } else if (searchNoMatchMode === "invisible_and_inert") {
39435
+ props.hidden = true;
39436
+ } else if (searchNoMatchMode === "muted") {
39437
+ props.muted = true;
39438
+ }
39439
+ }
39363
39440
  const item = props;
39364
39441
  const visibleIndex = tracker.useTrackItem(item);
39365
39442
  const groupTracker = useContext(GroupItemTrackerContext);
@@ -39419,6 +39496,7 @@ const ListItemReal = props => {
39419
39496
  ref,
39420
39497
  id,
39421
39498
  hidden,
39499
+ muted,
39422
39500
  highlight,
39423
39501
  children,
39424
39502
  ...rest
@@ -39451,7 +39529,15 @@ const ListItemReal = props => {
39451
39529
  index: undefined,
39452
39530
  selected: undefined,
39453
39531
  matchScore: undefined,
39454
- hidden: hidden,
39532
+ match: undefined
39533
+ // We use aria-hidden and not hidden because hidden would be forced to
39534
+ // display: none while here we want to keep it in the DOM to avoid layout shift
39535
+ // but visually hidden
39536
+ ,
39537
+
39538
+ "aria-hidden": hidden,
39539
+ inert: hidden ? true : undefined,
39540
+ "navi-muted": muted ? "" : undefined,
39455
39541
  ref: ref,
39456
39542
  children: children
39457
39543
  });