@ixfx/components 0.16.0 → 0.16.2

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.
Files changed (90) hide show
  1. package/bundle/index.d.ts +290 -66
  2. package/bundle/index.js +412 -327
  3. package/dist/ac-text.d.ts +1 -1
  4. package/dist/{ac-token-qEs3FsQv.d.ts → ac-token-Cq7lsYf-.d.ts} +1 -1
  5. package/dist/ac-token.d.ts +1 -1
  6. package/dist/ac-token.js +1 -1
  7. package/dist/{button-N92DKoWb.d.ts → button-D1sPDUCM.d.ts} +1 -1
  8. package/dist/button.d.ts +1 -1
  9. package/dist/colour-picker.d.ts +1 -1
  10. package/dist/{crumb-navigation-YnFVFqrx.js → crumb-navigation-D3ndPgFg.js} +2 -2
  11. package/dist/{crumb-navigation-BbqfmkYs.d.ts → crumb-navigation-nnr881n-.d.ts} +1 -1
  12. package/dist/crumbs.d.ts +1 -1
  13. package/dist/crumbs.js +1 -1
  14. package/dist/{data-grid-CIG2DVip.d.ts → data-grid-BAEpfTkb.d.ts} +32 -5
  15. package/dist/{data-grid-rMkLX2qo.js → data-grid-CaPLWt5E.js} +159 -60
  16. package/dist/data-grid.d.ts +2 -2
  17. package/dist/data-grid.js +1 -1
  18. package/dist/{detail-list-C4pDSoI8.js → detail-list-C3XKTRmE.js} +86 -21
  19. package/dist/{detail-list-6iMTNP4N.d.ts → detail-list-DK49TsJd.d.ts} +26 -4
  20. package/dist/detail-list.d.ts +2 -2
  21. package/dist/detail-list.js +1 -1
  22. package/dist/{flat-list-JPgQSmLW.d.ts → flat-list-Bv4-qfjp.d.ts} +9 -4
  23. package/dist/flat-list.d.ts +1 -1
  24. package/dist/flat-list.js +6 -3
  25. package/dist/{grid-list-CdEkScn-.js → grid-list-51XsR2X2.js} +56 -18
  26. package/dist/{grid-list-D6n7WH8l.d.ts → grid-list-DaeSHv9P.d.ts} +58 -14
  27. package/dist/grid-list.d.ts +2 -2
  28. package/dist/grid-list.js +1 -1
  29. package/dist/{grouped-item-lister-DETte0Dh.d.ts → grouped-item-lister-DZIjo9nO.d.ts} +2 -2
  30. package/dist/grouped-item-lister.d.ts +1 -1
  31. package/dist/{icon-CDGprSdE.d.ts → icon-DR_3rSyZ.d.ts} +1 -1
  32. package/dist/icons.d.ts +2 -2
  33. package/dist/{incr-search-JIhwB49y.js → incr-search-2YaxUUA9.js} +150 -11
  34. package/dist/incr-search.d.ts +1 -1
  35. package/dist/incr-search.js +1 -1
  36. package/dist/{index-CoqtMSd9.d.ts → index-BUB5SICW.d.ts} +1 -1
  37. package/dist/{index-Dr80Zdl6.d.ts → index-BjRoPrwj.d.ts} +31 -4
  38. package/dist/{index-DN2BWctP.d.ts → index-Bz3loIAf.d.ts} +1 -1
  39. package/dist/{index-UvnoZUAo.d.ts → index-CJ5fshD3.d.ts} +1 -1
  40. package/dist/{index-DxyyVqzA.d.ts → index-DM9OLgTH.d.ts} +3 -2
  41. package/dist/{index-CYRc6Dah.d.ts → index-DXDz60XH.d.ts} +20 -1
  42. package/dist/{index-CYndvTWH.d.ts → index-garxxqgV.d.ts} +1 -1
  43. package/dist/{index-Bx0BXPxI.d.ts → index-vdbbGE7N.d.ts} +1 -1
  44. package/dist/index.d.ts +57 -28
  45. package/dist/index.js +58 -35
  46. package/dist/item-appearance-Bl6rMIID.js +60 -0
  47. package/dist/{labelled-input-base-DiTme1JO.d.ts → labelled-input-base-Dp_9KP0G.d.ts} +1 -1
  48. package/dist/labelled-radial-input.d.ts +2 -2
  49. package/dist/labelled-range-input.d.ts +1 -1
  50. package/dist/labelled-slider-input.d.ts +1 -1
  51. package/dist/list-group-header.d.ts +1 -1
  52. package/dist/{list-selection-controller-5L0Nj9tR.js → list-selection-controller-CJDdjH7E.js} +192 -54
  53. package/dist/list-selection-types-hj9Njei3.d.ts +83 -0
  54. package/dist/{menu-CU9KAm8s.js → menu-BX1FHxY8.js} +16 -1
  55. package/dist/{menu-item-bF6narIz.d.ts → menu-item-B0zqw2xp.d.ts} +1 -1
  56. package/dist/{menu-item-BUv8An2k.js → menu-item-oxU1VKiJ.js} +34 -9
  57. package/dist/menu.d.ts +2 -2
  58. package/dist/menu.js +2 -2
  59. package/dist/{miller-BFRfn0ve.js → miller-BfrTcujx.js} +142 -18
  60. package/dist/miller.d.ts +1 -1
  61. package/dist/miller.js +1 -1
  62. package/dist/narrowed-text.js +1 -1
  63. package/dist/panel.d.ts +1 -1
  64. package/dist/{split-layout-CophkNeU.d.ts → split-layout-ChZ0Glrs.d.ts} +1 -1
  65. package/dist/split-layout.d.ts +1 -1
  66. package/dist/sunburst-tree.d.ts +1 -1
  67. package/dist/sunburst-tree.js +1 -1
  68. package/dist/{sunbursttree-list-B_VxOHlo.js → sunbursttree-list-906G33ME.js} +1 -1
  69. package/dist/{sunbursttree-list-D6j5BxVQ.d.ts → sunbursttree-list-BAS0KrDG.d.ts} +1 -1
  70. package/dist/{tab-list-Df371rqw.d.ts → tab-list-wC0xNz1b.d.ts} +1 -1
  71. package/dist/tabs.d.ts +1 -1
  72. package/dist/{titlebar-Tqu-VvMU.d.ts → titlebar-B5zSTZnF.d.ts} +1 -1
  73. package/dist/titlebar.d.ts +1 -1
  74. package/dist/{tree-Crdpl4nc.js → tree-C_H1COTf.js} +1 -1
  75. package/dist/tree.d.ts +1 -1
  76. package/dist/tree.js +1 -1
  77. package/dist/{vertical-list-BPDmZx6T.d.ts → vertical-list-od4050Bo.d.ts} +11 -2
  78. package/dist/{vertical-list-1V1Vg2O5.js → vertical-list-qMRIxZIT.js} +54 -20
  79. package/dist/vertical-list.d.ts +2 -2
  80. package/dist/vertical-list.js +1 -1
  81. package/docs-user/data-grid.md +23 -7
  82. package/docs-user/detail-list.md +16 -1
  83. package/docs-user/dock.md +12 -3
  84. package/docs-user/grid-list.md +9 -3
  85. package/docs-user/index.json +2 -2
  86. package/docs-user/menu.md +11 -1
  87. package/docs-user/vertical-list.md +9 -1
  88. package/package.json +2 -2
  89. package/dist/item-appearance-BjcMyauw.js +0 -29
  90. package/dist/list-selection-types-PjKdhF-a.d.ts +0 -45
@@ -1,13 +1,13 @@
1
1
  import { t as TickledController } from "./tickled-controller-D-HZymaZ.js";
2
2
  import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
3
3
  import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
4
- import { c as createElementListSearchAdapter, i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, u as keyForElement } from "./incr-search-JIhwB49y.js";
4
+ import { c as createElementListSearchAdapter, d as keysOfElements, i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, u as keyForElement } from "./incr-search-2YaxUUA9.js";
5
5
  import { i as animateListItemOut, r as animateListItemIn, t as ListNativeDragController } from "./list-native-drag-controller-CcXoS0YX.js";
6
- import { r as ListDragSelectController, t as ListSelectionController } from "./list-selection-controller-5L0Nj9tR.js";
6
+ import { i as ListDragSelectController, n as gestureOf, t as ListSelectionController } from "./list-selection-controller-CJDdjH7E.js";
7
7
  import { l as keysToArray } from "./pending-BGidHppL.js";
8
- import { a as restoreElementScroll, n as anchorFromRow, o as scrollToOffset, r as captureElementScroll, t as ViewStateController } from "./view-state-controller-BdyXWUUM.js";
8
+ import { a as isPartialKey, i as applyPartialAttributes, n as appearanceColor, o as sameKeySet, t as appearanceAbsent } from "./item-appearance-Bl6rMIID.js";
9
9
  import { t as scrollFadeStyles } from "./scroll-fade-Xx28D7mH.js";
10
- import { n as appearanceColor, t as appearanceAbsent } from "./item-appearance-BjcMyauw.js";
10
+ import { a as restoreElementScroll, n as anchorFromRow, o as scrollToOffset, r as captureElementScroll, t as ViewStateController } from "./view-state-controller-BdyXWUUM.js";
11
11
  import "./editable-label.js";
12
12
  import { LitElement, css, html, render } from "lit";
13
13
  import { property } from "lit/decorators.js";
@@ -153,13 +153,15 @@ let VerticalListElement = class VerticalListElement extends LitElement {
153
153
  interactionMode: this.interactionMode
154
154
  }),
155
155
  getKey: keyForElement,
156
+ onIntent: (intent) => this._dispatchCancelable(`list-select-intent`, intent) === false,
156
157
  onChange: (selected, previous) => {
157
158
  this._syncItemStates();
158
159
  this.#viewState.notify();
159
160
  this._dispatch(`list-select`, {
160
161
  selected,
161
162
  previous,
162
- selectedKeys: this.selection.selectedKeys
163
+ selectedKeys: this.selection.selectedKeys,
164
+ previousKeys: keysOfElements(previous)
163
165
  });
164
166
  }
165
167
  });
@@ -245,6 +247,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
245
247
  getAllItems: () => this.#selectionOrder(),
246
248
  observeContainer: false
247
249
  }) });
250
+ this.#partialKeys = /* @__PURE__ */ new Set();
248
251
  this.#onScroll = () => {
249
252
  if (!this.#viewState.pendingScroll) this.#viewState.notify();
250
253
  if (this.#scrollRaf !== 0) return;
@@ -275,7 +278,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
275
278
  if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
276
279
  if (this.selection.canSelectAll()) {
277
280
  e.preventDefault();
278
- this.selection.selectAll();
281
+ this.selection.selectAll(`keyboard`);
279
282
  }
280
283
  return;
281
284
  }
@@ -284,18 +287,18 @@ let VerticalListElement = class VerticalListElement extends LitElement {
284
287
  e.preventDefault();
285
288
  this.tickled.handleKeyboardNavigation(`next`);
286
289
  this.#revealCursor();
287
- if (e.shiftKey && this.interactionMode === `vscode`) {
290
+ if (e.shiftKey && (this.interactionMode === `vscode` || this.interactionMode === `manual`)) {
288
291
  const cur = this.tickled.tickledId;
289
- if (cur) this.selection.rangeTo(cur);
292
+ if (cur) this.selection.handleExtend(cur);
290
293
  }
291
294
  break;
292
295
  case `ArrowUp`:
293
296
  e.preventDefault();
294
297
  this.tickled.handleKeyboardNavigation(`previous`);
295
298
  this.#revealCursor();
296
- if (e.shiftKey && this.interactionMode === `vscode`) {
299
+ if (e.shiftKey && (this.interactionMode === `vscode` || this.interactionMode === `manual`)) {
297
300
  const cur = this.tickled.tickledId;
298
- if (cur) this.selection.rangeTo(cur);
301
+ if (cur) this.selection.handleExtend(cur);
299
302
  }
300
303
  break;
301
304
  case `Home`:
@@ -321,7 +324,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
321
324
  if (id) {
322
325
  e.preventDefault();
323
326
  this._dispatch(`list-item-click`, { item: id });
324
- this._activateItem(id);
327
+ this._activateItem(id, `keyboard`);
325
328
  }
326
329
  break;
327
330
  }
@@ -333,7 +336,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
333
336
  }
334
337
  break;
335
338
  }
336
- case `Escape`: this.selection.clear();
339
+ case `Escape`: this.selection.handleClear(`keyboard`);
337
340
  }
338
341
  };
339
342
  this._onClick = (e) => {
@@ -342,7 +345,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
342
345
  const item = this._findListItem(e.composedPath());
343
346
  if (!item) return;
344
347
  this._dispatch(`list-item-click`, { item });
345
- this.selection.handleClick(item, e);
348
+ this.selection.handleClick(item, gestureOf(e));
346
349
  };
347
350
  this._onDblClick = (e) => {
348
351
  if (this.#isEditEvent(e)) return;
@@ -773,6 +776,23 @@ let VerticalListElement = class VerticalListElement extends LitElement {
773
776
  get selectedKeys() {
774
777
  return this.selection.selectedKeys;
775
778
  }
779
+ #partialKeys;
780
+ /**
781
+ * Rows shown as *partially* selected (a part of the resource is selected):
782
+ * `data-partial`, an indeterminate checkbox in `checked` mode, and an
783
+ * accessible description. Independent of `selectedKeys` — a key in both
784
+ * counts as selected. Replace-on-set; keys without a row are kept for when
785
+ * the row appears.
786
+ */
787
+ get partialKeys() {
788
+ return this.#partialKeys;
789
+ }
790
+ set partialKeys(keys) {
791
+ const next = new Set(keys);
792
+ if (sameKeySet(next, this.#partialKeys)) return;
793
+ this.#partialKeys = next;
794
+ this._syncItemStates();
795
+ }
776
796
  /** The key of the item under the cursor, or `undefined`. */
777
797
  get tickledKey() {
778
798
  const id = this.tickled.tickledId;
@@ -1468,6 +1488,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
1468
1488
  const item = this.#customWrappers.get(el) ?? el;
1469
1489
  el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));
1470
1490
  el.toggleAttribute(`data-selected`, this.selection.selected.has(el));
1491
+ applyPartialAttributes(el, isPartialKey(keyForElement(el), this.#partialKeys, this.selection.selected.has(el)));
1471
1492
  if (el.hasAttribute(`data-aria-role`)) el.setAttribute(`aria-selected`, String(this.selection.selected.has(el)));
1472
1493
  if (this.interactionMode === `checked`) el.toggleAttribute(`data-checked`, !item.hasAttribute(`not-checked`) && this.selection.selected.has(el));
1473
1494
  else el.removeAttribute(`data-checked`);
@@ -1532,16 +1553,22 @@ let VerticalListElement = class VerticalListElement extends LitElement {
1532
1553
  el.prepend(cb);
1533
1554
  }
1534
1555
  const checked = this.selection.selected.has(el);
1556
+ const partial = isPartialKey(keyForElement(el), this.#partialKeys, checked);
1535
1557
  if (wantsCustom) {
1536
- cb.setAttribute(`aria-checked`, String(checked));
1558
+ cb.setAttribute(`aria-checked`, partial ? `mixed` : String(checked));
1537
1559
  cb.toggleAttribute(`data-checked`, checked);
1560
+ cb.toggleAttribute(`data-partial`, partial);
1538
1561
  if (this.checkboxRenderer) {
1539
1562
  const renderer = typeof this.checkboxRenderer === `function` ? this.checkboxRenderer : this.checkboxRenderer.renderer;
1540
1563
  render(renderer(checked), cb);
1541
1564
  }
1542
1565
  } else {
1543
1566
  const input = cb.querySelector(`input.ixfx-list-checkbox`);
1544
- if (input) input.checked = checked;
1567
+ if (input) {
1568
+ input.checked = checked;
1569
+ input.indeterminate = partial;
1570
+ }
1571
+ cb.setAttribute(`aria-checked`, partial ? `mixed` : String(checked));
1545
1572
  }
1546
1573
  }
1547
1574
  }
@@ -1560,7 +1587,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
1560
1587
  cell.setAttribute(`part`, `checkbox`);
1561
1588
  cell.addEventListener(`click`, (e) => {
1562
1589
  e.stopPropagation();
1563
- this.selection.toggle(el);
1590
+ this.selection.handleCheckbox(el);
1564
1591
  });
1565
1592
  const input = document.createElement(`input`);
1566
1593
  input.type = `checkbox`;
@@ -1582,7 +1609,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
1582
1609
  span.setAttribute(`part`, `checkbox`);
1583
1610
  span.addEventListener(`click`, (e) => {
1584
1611
  e.stopPropagation();
1585
- this.selection.toggle(el);
1612
+ this.selection.handleCheckbox(el);
1586
1613
  });
1587
1614
  return span;
1588
1615
  }
@@ -1608,13 +1635,13 @@ let VerticalListElement = class VerticalListElement extends LitElement {
1608
1635
  cancelable: true
1609
1636
  }));
1610
1637
  }
1611
- _activateItem(item) {
1612
- if (this.interactionMode !== `manual`) this.selection.replace(item);
1638
+ _activateItem(item, source = `pointer`) {
1639
+ this.selection.handleReplace(item, source);
1613
1640
  this._dispatch(`list-activate`, { item });
1614
1641
  }
1615
1642
  activateKey(key) {
1616
1643
  const item = this.#itemForKey(key);
1617
- if (item) this._activateItem(item);
1644
+ if (item) this._activateItem(item, `api`);
1618
1645
  }
1619
1646
  /** The row (or `undefined` for empty space) the last `list-drag-over` was fired for. */
1620
1647
  #dragOver;
@@ -1862,6 +1889,13 @@ let VerticalListElement = class VerticalListElement extends LitElement {
1862
1889
  outline: 1px solid transparent;
1863
1890
  }
1864
1891
 
1892
+ /* Partially selected: a dashed edge, lighter than a full selection. Hook: --item-bg-partial. */
1893
+ ::slotted([data-partial]) {
1894
+ background: var(--item-bg-partial, color-mix(in srgb, var(--item-bg-selected) 35%, transparent));
1895
+ outline: 1px dashed var(--item-border-partial, var(--border));
1896
+ outline-offset: -1px;
1897
+ }
1898
+
1865
1899
  ::slotted([data-selected][data-tickled]) {
1866
1900
  background: var(--item-bg-selected-tickled);
1867
1901
  outline: 2px dashed var(--surface-3);
@@ -1,5 +1,5 @@
1
1
  import { B as ListNativeDragStartDetail, F as ListDragPreview, I as ListDragPreviewContext, L as ListDragPreviewRenderer, P as ListDragEntry, V as ListNativeDragStartEvent, z as ListDragSource } from "./tree-component-DgfkxmVY.js";
2
2
  import { n as ListViewportChangeEvent, t as ListViewportChangeDetail } from "./list-viewport-types-BRZsLcvp.js";
3
- import { a as ListItemClickDetail, c as ListSelectEvent, d as ListTickleEvent, i as ListInteractionMode, l as ListSelectionMode, n as ListActivateEvent, o as ListItemClickEvent, r as ListElementEventMap, s as ListSelectDetail, t as ListActivateDetail, u as ListTickleDetail } from "./list-selection-types-PjKdhF-a.js";
4
- import { a as VerticalListViewState, c as ListDragLeaveDetail, d as ListDropDetail, f as ListDropEvent, g as ListEditCommitEvent, h as ListEditCommitDetail, i as VerticalListElementEventMap, l as ListDragLeaveEvent, m as ListEditCancelEvent, n as CustomCheckboxOptions, o as ListContextMenuDetail, p as ListEditCancelDetail, r as VerticalListElement, s as ListContextMenuEvent, t as AddItemOptions, u as ListDragOverEvent } from "./vertical-list-BPDmZx6T.js";
3
+ import { a as ListItemClickDetail, c as ListSelectEvent, g as ListTickleEvent, h as ListTickleDetail, i as ListInteractionMode, m as ListSelectionMode, n as ListActivateEvent, o as ListItemClickEvent, r as ListElementEventMap, s as ListSelectDetail, t as ListActivateDetail } from "./list-selection-types-hj9Njei3.js";
4
+ import { a as VerticalListViewState, c as ListDragLeaveDetail, d as ListDropDetail, f as ListDropEvent, g as ListEditCommitEvent, h as ListEditCommitDetail, i as VerticalListElementEventMap, l as ListDragLeaveEvent, m as ListEditCancelEvent, n as CustomCheckboxOptions, o as ListContextMenuDetail, p as ListEditCancelDetail, r as VerticalListElement, s as ListContextMenuEvent, t as AddItemOptions, u as ListDragOverEvent } from "./vertical-list-od4050Bo.js";
5
5
  export { AddItemOptions, CustomCheckboxOptions, type ListActivateDetail, type ListActivateEvent, type ListContextMenuDetail, type ListContextMenuEvent, type ListDragEntry, type ListDragLeaveDetail, type ListDragLeaveEvent, type ListDragOverEvent, type ListDragPreview, type ListDragPreviewContext, type ListDragPreviewRenderer, type ListDragSource, type ListDropDetail, type ListDropEvent, type ListEditCancelDetail, type ListEditCancelEvent, type ListEditCommitDetail, type ListEditCommitEvent, type ListElementEventMap, type ListInteractionMode, type ListItemClickDetail, type ListItemClickEvent, type ListNativeDragStartDetail, type ListNativeDragStartEvent, type ListSelectDetail, type ListSelectEvent, type ListSelectionMode, type ListTickleDetail, type ListTickleEvent, type ListViewportChangeDetail, type ListViewportChangeEvent, VerticalListElement, VerticalListElementEventMap, type VerticalListViewState };
@@ -1,2 +1,2 @@
1
- import { t as VerticalListElement } from "./vertical-list-1V1Vg2O5.js";
1
+ import { t as VerticalListElement } from "./vertical-list-qMRIxZIT.js";
2
2
  export { VerticalListElement };
@@ -222,9 +222,16 @@ grid.tickleRow('r7'); // move the row cursor + scroll it into view
222
222
  grid.tickledKey; // row key under the cursor, or undefined
223
223
  ```
224
224
 
225
- The setter fires `datagrid-select` `{ selectedKeys, previous }` only when the
226
- selection actually changes. Selected rows that leave `rows` are deselected
227
- silently, and reselected if they return. See [View state](#view-state).
225
+ The setter fires `datagrid-select` `{ selectedKeys, previous }` (and `list-select`
226
+ `{ selectedKeys, previousKeys, previous }`, the name the other list components use)
227
+ only when the selection actually changes. A cancelable `list-select-intent` fires
228
+ before every selection gesture, and `partialKeys` marks partially selected rows
229
+ (`data-partial`, indeterminate checkbox in `checked` mode).
230
+ See [Selection intents, partial rows, unloaded keys](../../docs/infra-selection-intents.md): `list-select-intent`, `partialKeys`, and keys that arrive later.
231
+
232
+ Selected rows that leave `rows` are deselected silently, and reselected if they
233
+ return. See [View state](#view-state). In `checked` mode the row checkbox toggles
234
+ the row.
228
235
 
229
236
  ---
230
237
 
@@ -506,10 +513,19 @@ rows dropped from the flat list) modes. No default shortcut:
506
513
  grid.registry.invoke('list.search.filter'); // or .navigate / .select
507
514
  ```
508
515
 
509
- A row is matched against `rowSearchText(row)` — by default its visible cells'
510
- values joined by a space; override with the `rowSearchText` property. `select`
511
- mode needs `selection-mode="multiple"` to hold more than one row. Character
512
- highlights are not drawn (a match position spans multiple cells). `filterPredicate`
516
+ A row is matched against the displayed (formatted) text of one column: the
517
+ `search-column` attribute (`searchColumn` property) names it, defaulting to the
518
+ first visible column. Searching every column makes dates and sizes match
519
+ queries like `12`, which is rarely what Find is for. Set the `rowSearchText`
520
+ property, `(row) => string`, to match against something else, e.g. several
521
+ columns joined. `select` mode needs `selection-mode="multiple"` to hold more
522
+ than one row.
523
+
524
+ Matched characters in the search column are highlighted under the
525
+ `data-grid-search` key, coloured by `--search-highlight-bg` /
526
+ `--search-highlight-color` (default `--accent` / `--accent-text`). Matches
527
+ against a custom `rowSearchText` aren't highlighted, since they can't be
528
+ mapped onto a cell, and neither are cells drawn by a column `renderer`. `filterPredicate`
513
529
  (a `(row) => boolean` property) filters rows directly. `search-modes` limits the
514
530
  offered modes. Full model: [`docs/infra-incremental.md`](../../docs/infra-incremental.md).
515
531
 
@@ -162,6 +162,7 @@ list.keyProperty = 'name'; // Preserves selection across rebuilds
162
162
  | `textColorProperty` | `string` | Property name for the caption's CSS colour |
163
163
  | `badgeProperty` | `string` | Property name for a Finder-style badge |
164
164
  | `formatter` | `(item: unknown, index: number) => string \| Node` | Custom renderer |
165
+ | `searchText` | `(item: unknown, index: number) => string` | Text incremental search matches (default: the caption if a string, else the row's whole text) |
165
166
 
166
167
  ```typescript
167
168
  list.formatter = (item, index) => {
@@ -177,6 +178,18 @@ put, and the new row animates in like `addItem()`. Pass new objects (or change
177
178
  `displayProperty`, `formatter`, …) to rebuild rows. With a `formatter`, a row
178
179
  is only kept at the same index, since the formatter receives the index.
179
180
 
181
+ Because reused rows aren't re-run through the `formatter`, state it reads from
182
+ outside the item (sizes, fit mode, …) doesn't reach existing rows when the same
183
+ objects are reassigned. Call `list.reformat()` to rebuild every row, or assign a
184
+ new `formatter` function (a new identity counts as a shape change).
185
+
186
+ When `formatter` returns a `Node`, search falls back to the row's whole text
187
+ (sublines, sizes, dates). Set `searchText` to match on just the name:
188
+
189
+ ```typescript
190
+ list.searchText = item => (item as { name: string }).name;
191
+ ```
192
+
180
193
  Retrieve the original data for an element:
181
194
 
182
195
  ```typescript
@@ -297,7 +310,7 @@ the Element API is unchanged, the key API is additive. Object items built from
297
310
  `items` / a provider get their key stamped automatically from `keyProperty`.
298
311
 
299
312
  ```typescript
300
- list.selectKeys(['row-3', 'row-7']); // replace selection; unknown keys ignored
313
+ list.selectKeys(['row-3', 'row-7']); // replace selection; keys with no row yet apply when the row appears
301
314
  list.addSelectedKeys(['row-9']); // multi-select modes only
302
315
  list.removeSelectedKeys(['row-3']);
303
316
  list.selectedKeys; // ReadonlySet<string>
@@ -459,6 +472,8 @@ Search matches against `data-search-label` (or `textContent` as fallback). Use `
459
472
  <li data-search-label="document a alpha">Document A</li>
460
473
  ```
461
474
 
475
+ For object items, `searchText` sets `data-search-label` per row (see [Object items](#object-items)).
476
+
462
477
  ### Highlight styling
463
478
 
464
479
  Matched characters are highlighted with the `detail-list-search` highlight key. Style matches with:
package/docs-user/dock.md CHANGED
@@ -228,7 +228,9 @@ The host consumes `ixfx-split-layout` events: `ixfx-split-resize` /
228
228
  ```ts
229
229
  getViewState(options?): DockHostViewState // { v, kind: 'dock-host', layout }; folds live split weights
230
230
  setViewState(state: Partial<DockHostViewState>): void
231
- clearPendingViewState(): void // no-op: placeholders live in the layout tree
231
+ clearPendingViewState(): void // drops placeholders (panels the host doesn't hold)
232
+ saveLayout(): DockLayout // deprecated alias of getViewState().layout
233
+ loadLayout(layout: DockLayout): void // deprecated alias of setViewState({ layout })
232
234
  addPanel(panel: DockPanelElement | DockPanelInit, target?: DockAddPanelTarget): boolean
233
235
  type DockAddPanelTarget = {
234
236
  container?: string; // named stack leaf — takes precedence over `stack`
@@ -259,6 +261,7 @@ getNeighbours(leaf: DockLeafRef): DockNeighbours | null
259
261
  moveLeaf(leaf: DockLeafRef, direction: 'up' | 'down' | 'left' | 'right'): boolean
260
262
  moveLeafToEdge(leaf: DockLeafRef, edge: 'top' | 'bottom' | 'left' | 'right'): boolean
261
263
  canAccept?: (panelId: string, op: DockOperation) => boolean
264
+ placeholderLabel?: (panelId: string) => string // tab label for placeholders (default: the id)
262
265
 
263
266
  // Cross-window hooks (serializable in/out; no element handoff)
264
267
  getPanelSnapshot(panelId: string): DockPanelSnapshot | null
@@ -456,13 +459,19 @@ The dock layout is close to structure, so the saved state *is* the tree
456
459
  a save → drag → save round-trip preserves user adjustments.
457
460
  - **Panels not in the host yet.** `setViewState()` keeps every panel id the
458
461
  layout names, held or not. One the host doesn't hold is a *placeholder*:
459
- it keeps its place in the tree (shown as a tab labelled with its id) and
462
+ it keeps its place in the tree (shown as a tab labelled by the
463
+ `placeholderLabel(panelId)` property, or its id when unset) and
460
464
  the panel slots into that place when it arrives — appended as a light-DOM
461
465
  child, `addPanel()` **without** a target, `acceptExternalPanel()` without
462
466
  `at`, or a cross-host adoption. An explicit `addPanel()` target still wins.
463
467
  Placeholders are saved by `getViewState()`; `includePending: false` leaves
464
468
  them out (stacks they empty are dropped unless named). Remove one with
465
- `removePanel(id)`; `clearPendingViewState()` is a no-op.
469
+ `removePanel(id)`, or all of them with `clearPendingViewState()`.
470
+ - **Persisting with the default keeps stale ids.** A placeholder for a panel
471
+ that no longer exists is saved again on every `getViewState()`, so it never
472
+ goes away. Apps whose panels can disappear between save and restore should
473
+ call `clearPendingViewState()` once they have added the panels they hold,
474
+ or save with `includePending: false`.
466
475
  - Pooled panels the layout doesn't name are appended to the first stack
467
476
  admitting their kind.
468
477
  - A panel that is closed / removed (`removePanel`, close button, removed from
@@ -183,7 +183,7 @@ See `docs-user/grid-list.md`.
183
183
  item, drive selection by key instead — additive, the Element API is unchanged.
184
184
 
185
185
  ```typescript
186
- grid.selectKeys(['a', 'c']); // replace; unknown keys ignored
186
+ grid.selectKeys(['a', 'c']); // replace; keys with no cell yet apply when the cell appears
187
187
  grid.addSelectedKeys(['b']); // multi-select modes only
188
188
  grid.removeSelectedKeys(['a']);
189
189
  grid.selectedKeys; // ReadonlySet<string>
@@ -193,6 +193,9 @@ grid.addEventListener('list-select', ({ detail }) => {
193
193
  });
194
194
  ```
195
195
 
196
+ Caller-owned selection: a cancelable `list-select-intent` fires before every gesture, and `partialKeys` marks partially selected cells (`data-partial`, `part="item partial"`).
197
+ See [Selection intents, partial rows, unloaded keys](../../docs/infra-selection-intents.md): `list-select-intent`, `partialKeys`, and keys that arrive later.
198
+
196
199
  Restore across a reload:
197
200
 
198
201
  ```typescript
@@ -311,8 +314,11 @@ grid.registry.invoke('list.search.filter'); // or .navigate / .select
311
314
 
312
315
  Search matches `data-label` (or `[data-grid-list-caption]` text, then
313
316
  `data-search-label`, then `textContent`). Filter mode genuinely removes items
314
- from the virtualised layout. Add a page-level highlight rule to see matched
315
- characters:
317
+ from the virtualised layout. Matched characters are highlighted under the
318
+ `grid-list-search` key. In data mode (`itemAdapter`) the captions live in the
319
+ shadow root and the grid styles them itself (`--search-highlight-bg` /
320
+ `--search-highlight-color`, default `--accent` / `--accent-text`). In element
321
+ mode the items are light DOM, so add a page-level rule:
316
322
 
317
323
  ```css
318
324
  ::highlight(grid-list-search) { background: var(--accent); color: var(--accent-text); }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
- "version": "0.16.0",
4
- "generated": "2026-10-23T13:15:45.063Z",
3
+ "version": "0.16.2",
4
+ "generated": "2026-11-02T19:16:08.207Z",
5
5
  "components": [
6
6
  {
7
7
  "name": "ac-text",
package/docs-user/menu.md CHANGED
@@ -6,7 +6,7 @@ A custom menu system built on native HTML popovers with keyboard navigation, tic
6
6
 
7
7
  | Component | Description |
8
8
  |------------|-------------|
9
- | `ixfx-menu-bar` | Horizontal container for menu triggers; handles arrow-key navigation between top-level menus |
9
+ | `ixfx-menu-bar` | Horizontal container for menu triggers; handles arrow-key navigation between top-level menus; while a menu is open, hovering another top-level trigger switches to its menu |
10
10
  | `ixfx-menu-trigger` | Button or slot that opens a menu popover. Modes: `trigger` (button), `dropdown` (slot-only), `context` (right-click) |
11
11
  | `ixfx-menu-container` | The popover panel holding menu items; manages keyboard focus and tickled state |
12
12
  | `ixfx-menu-item` | Basic menu item that dispatches a `command` event |
@@ -254,6 +254,16 @@ The trigger button and menu bar additionally expose a couple of underscore-prefi
254
254
  | `--_trigger-hover-bg` | `var(--surface-4)` | Trigger button hover background |
255
255
  | `--_trigger-hover-opacity` | `1` | Trigger button hover opacity |
256
256
 
257
+ Public layout variables:
258
+
259
+ | Variable | Default | Description |
260
+ |----------|---------|-------------|
261
+ | `--ixfx-button-padding` | `var(--space-s) var(--space-m)` | Padding of the default trigger button |
262
+ | `--menu-max-height` | `min(60vh, 400px)` | Max height of a menu panel; taller menus scroll, with an edge fade |
263
+ | `--menu-popup-offset` | `var(--space-xs)` | Distance between a trigger and its menu popup |
264
+ | `--menu-bar-gap` | `var(--space-xs)` | Gap between top-level triggers in `ixfx-menu-bar` |
265
+ | `--menu-bar-padding` | `var(--space-xs)` | Padding around the items in `ixfx-menu-bar` |
266
+
257
267
  The popup surface uses these from `popupSurfaceStyles`:
258
268
 
259
269
  | Variable | Description |
@@ -441,7 +441,7 @@ unchanged — the key API is additive.
441
441
  ```
442
442
 
443
443
  ```typescript
444
- list.selectKeys(['a', 'c']); // replace selection; unknown keys ignored
444
+ list.selectKeys(['a', 'c']); // replace selection; keys with no row yet apply when the row appears
445
445
  list.addSelectedKeys(['b']); // multi-select modes only
446
446
  list.removeSelectedKeys(['a']);
447
447
  list.selectedKeys; // ReadonlySet<string>, e.g. Set { 'b', 'c' }
@@ -512,6 +512,7 @@ All events **bubble** and are **composed** (they cross shadow-DOM boundaries).
512
512
  | Event | Detail type | When fired |
513
513
  |---|---|---|
514
514
  | `list-select` | `ListSelectDetail` | Selection changes by any means |
515
+ | `list-select-intent` | `ListSelectIntentDetail` | Cancelable; a gesture is about to change the selection (see below) |
515
516
  | `list-tickle` | `ListTickleDetail` | An item enters cursor focus (hover or keyboard navigation) |
516
517
  | `list-activate` | `ListActivateDetail` | User explicitly activates an item: Enter key or double-click |
517
518
  | `list-item-click` | `ListItemClickDetail` | Raw click or Enter on any item |
@@ -524,6 +525,8 @@ All events **bubble** and are **composed** (they cross shadow-DOM boundaries).
524
525
  type ListSelectDetail = {
525
526
  readonly selected: ReadonlySet<Element>; // Current full selection
526
527
  readonly previous: ReadonlySet<Element>; // Selection before this change
528
+ readonly selectedKeys: ReadonlySet<string>;
529
+ readonly previousKeys: ReadonlySet<string>;
527
530
  };
528
531
 
529
532
  type ListTickleDetail = { readonly item: Element };
@@ -531,6 +534,11 @@ type ListActivateDetail = { readonly item: Element };
531
534
  type ListItemClickDetail = { readonly item: Element };
532
535
  ```
533
536
 
537
+ ### Caller-owned selection: `list-select-intent` and `partialKeys`
538
+
539
+ `list-select-intent` is fired before any gesture changes the selection (in every `interactionMode`, including `manual`); `preventDefault()` means "I'll apply it". `partialKeys` marks rows as partially selected (`data-partial`, indeterminate checkbox in `checked` mode). Both are shared across the list-like components.
540
+ See [Selection intents, partial rows, unloaded keys](../../docs/infra-selection-intents.md): `list-select-intent`, `partialKeys`, and keys that arrive later.
541
+
534
542
  ### Usage example
535
543
 
536
544
  ```typescript
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
3
  "type": "module",
4
- "version": "0.16.0",
4
+ "version": "0.16.2",
5
5
  "packageManager": "pnpm@10.0.0",
6
6
  "description": "Web components for ixfx — Lit-based UI toolkit",
7
7
  "author": "Clint Heyer",
@@ -247,7 +247,7 @@
247
247
  "lit": { "optional": false }
248
248
  },
249
249
  "dependencies": {
250
- "@clinth/ui-commands": "1.1.0",
250
+ "@clinth/ui-commands": "1.1.1",
251
251
  "@clinth/vcanvas": "0.0.5",
252
252
  "@ixfx/arrays": "0.56.14",
253
253
  "@ixfx/collections": "0.56.14",
@@ -1,29 +0,0 @@
1
- //#region src/common/item-appearance.ts
2
- /**
3
- * Shared helpers for the list components' per-item appearance hooks — the
4
- * per-item icon colour / text colour / badge that miller-list, detail-list,
5
- * grid-list, data-grid and vertical-list all accept.
6
- *
7
- * Every component takes these three features in its own idiom (plain fields
8
- * on a `TreeItem`, a property-name lookup, an adapter function, a nominated
9
- * column) but they all agree on what "not supplied" means, and on never
10
- * emitting an empty slot for an absent value.
11
- */
12
- /**
13
- * Whether a per-item appearance value counts as "not supplied". `false` and
14
- * `''` are included so a consumer can clear a feature without rebuilding the
15
- * collection — e.g. `badge: flags && tagChip(flags.colour)`.
16
- */
17
- function appearanceAbsent(value) {
18
- return value === void 0 || value === null || value === false || value === ``;
19
- }
20
- /**
21
- * A usable CSS colour from a per-item value, or `undefined`. Non-strings and
22
- * empty strings are ignored rather than stringified, so a stray number or
23
- * `0` never lands in a `style` attribute.
24
- */
25
- function appearanceColor(value) {
26
- return typeof value === `string` && value.length > 0 ? value : void 0;
27
- }
28
- //#endregion
29
- export { appearanceColor as n, appearanceAbsent as t };
@@ -1,45 +0,0 @@
1
- //#region src/interaction/list-selection-types.d.ts
2
- type ListSelectionMode = `none` | `single` | `multiple`;
3
- /**
4
- * Controls how pointer and keyboard gestures map to selection changes.
5
- *
6
- * - `implicit` — click always replaces selection (no modifier support)
7
- * - `standard` — click replaces; Ctrl/Cmd+click toggles; Shift+click range-selects (multiple only)
8
- * - `manual` — no auto-selection; drive state via select()/deselect()/clearSelection()
9
- * - `checked` — every click toggles; implies multiple selection regardless of selectionMode
10
- * - `sticky` — every tap/click toggles (no checkbox column); implies multiple selection, for touchscreens
11
- * - `vscode` — like standard for clicks; Shift+Arrow extends range from the keyboard anchor
12
- *
13
- * Switching interactionMode clears the current selection.
14
- */
15
- type ListInteractionMode = `implicit` | `standard` | `manual` | `checked` | `sticky` | `vscode`;
16
- type ListSelectDetail = {
17
- readonly selected: ReadonlySet<Element>;
18
- readonly previous: ReadonlySet<Element>;
19
- /**
20
- * The selection as stable key strings (`data-key` / `data-value`). Empty
21
- * when items carry no key. Additive to `selected`.
22
- */
23
- readonly selectedKeys: ReadonlySet<string>;
24
- };
25
- type ListTickleDetail = {
26
- readonly item: Element;
27
- };
28
- type ListActivateDetail = {
29
- readonly item: Element;
30
- };
31
- type ListItemClickDetail = {
32
- readonly item: Element;
33
- };
34
- type ListSelectEvent = CustomEvent<ListSelectDetail>;
35
- type ListTickleEvent = CustomEvent<ListTickleDetail>;
36
- type ListActivateEvent = CustomEvent<ListActivateDetail>;
37
- type ListItemClickEvent = CustomEvent<ListItemClickDetail>;
38
- interface ListElementEventMap {
39
- 'list-select': ListSelectEvent;
40
- 'list-tickle': ListTickleEvent;
41
- 'list-activate': ListActivateEvent;
42
- 'list-item-click': ListItemClickEvent;
43
- }
44
- //#endregion
45
- export { ListItemClickDetail as a, ListSelectEvent as c, ListTickleEvent as d, ListInteractionMode as i, ListSelectionMode as l, ListActivateEvent as n, ListItemClickEvent as o, ListElementEventMap as r, ListSelectDetail as s, ListActivateDetail as t, ListTickleDetail as u };