@ixfx/components 0.7.3 → 0.7.5

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 (141) hide show
  1. package/bundle/index.d.ts +1355 -526
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +3277 -384
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-text.d.ts +1 -1
  6. package/dist/ac-text.js +1 -1
  7. package/dist/{ac-token-TCLAZeVg.d.ts → ac-token--_vCHXWw.d.ts} +2 -2
  8. package/dist/{ac-token-TCLAZeVg.d.ts.map → ac-token--_vCHXWw.d.ts.map} +1 -1
  9. package/dist/ac-token.d.ts +1 -1
  10. package/dist/ac-token.js +2 -2
  11. package/dist/button.d.ts +1 -1
  12. package/dist/button.d.ts.map +1 -1
  13. package/dist/colour-picker.d.ts +1 -1
  14. package/dist/crumbs.d.ts +6 -2
  15. package/dist/crumbs.d.ts.map +1 -1
  16. package/dist/crumbs.js +13 -3
  17. package/dist/crumbs.js.map +1 -1
  18. package/dist/{data-grid-Db4QpnII.d.ts → data-grid-CtJ35v8L.d.ts} +59 -2
  19. package/dist/data-grid-CtJ35v8L.d.ts.map +1 -0
  20. package/dist/data-grid.d.ts +1 -1
  21. package/dist/data-grid.js +268 -14
  22. package/dist/data-grid.js.map +1 -1
  23. package/dist/detail-list-BS5dRDda.d.ts +175 -0
  24. package/dist/detail-list-BS5dRDda.d.ts.map +1 -0
  25. package/dist/detail-list.d.ts +3 -0
  26. package/dist/detail-list.js +1031 -0
  27. package/dist/detail-list.js.map +1 -0
  28. package/dist/grid-list-D-GbghSH.d.ts +321 -0
  29. package/dist/grid-list-D-GbghSH.d.ts.map +1 -0
  30. package/dist/grid-list.d.ts +3 -0
  31. package/dist/grid-list.js +994 -0
  32. package/dist/grid-list.js.map +1 -0
  33. package/dist/{grouped-item-lister-BWlC9S4e.d.ts → grouped-item-lister-fdZHVxFM.d.ts} +3 -3
  34. package/dist/{grouped-item-lister-BWlC9S4e.d.ts.map → grouped-item-lister-fdZHVxFM.d.ts.map} +1 -1
  35. package/dist/grouped-item-lister.d.ts +1 -1
  36. package/dist/{icon-DR_3rSyZ.d.ts → icon-CDGprSdE.d.ts} +2 -2
  37. package/dist/icon-CDGprSdE.d.ts.map +1 -0
  38. package/dist/icons.d.ts +2 -2
  39. package/dist/incr-search-DpbSJtgj.js +962 -0
  40. package/dist/incr-search-DpbSJtgj.js.map +1 -0
  41. package/dist/incr-search.d.ts +2 -2
  42. package/dist/incr-search.js +2 -3
  43. package/dist/{index-C59-JEPl.d.ts → index-9AhaHIT2.d.ts} +3 -3
  44. package/dist/{index-C59-JEPl.d.ts.map → index-9AhaHIT2.d.ts.map} +1 -1
  45. package/dist/index-CBS4z1v-.d.ts +365 -0
  46. package/dist/index-CBS4z1v-.d.ts.map +1 -0
  47. package/dist/{index-DsjpDksO.d.ts → index-CS9yM8L-.d.ts} +2 -2
  48. package/dist/{index-DsjpDksO.d.ts.map → index-CS9yM8L-.d.ts.map} +1 -1
  49. package/dist/{index-BUB5SICW.d.ts → index-CoqtMSd9.d.ts} +2 -2
  50. package/dist/{index-BUB5SICW.d.ts.map → index-CoqtMSd9.d.ts.map} +1 -1
  51. package/dist/{index-CJ5fshD3.d.ts → index-UvnoZUAo.d.ts} +2 -2
  52. package/dist/{index-CJ5fshD3.d.ts.map → index-UvnoZUAo.d.ts.map} +1 -1
  53. package/dist/{index-BOUaDIW-.d.ts → index-tq6f8007.d.ts} +2 -2
  54. package/dist/{index-BOUaDIW-.d.ts.map → index-tq6f8007.d.ts.map} +1 -1
  55. package/dist/index.d.ts +188 -243
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +376 -1004
  58. package/dist/index.js.map +1 -1
  59. package/dist/interaction-Cr13azbM.js +3 -0
  60. package/dist/{labelled-input-base-Dp_9KP0G.d.ts → labelled-input-base-DiTme1JO.d.ts} +2 -2
  61. package/dist/{labelled-input-base-Dp_9KP0G.d.ts.map → labelled-input-base-DiTme1JO.d.ts.map} +1 -1
  62. package/dist/labelled-radial-input.d.ts +2 -2
  63. package/dist/labelled-radial-input.d.ts.map +1 -1
  64. package/dist/labelled-range-input.d.ts +1 -1
  65. package/dist/labelled-slider-input.d.ts +1 -1
  66. package/dist/list-item-animation-DuiwgN3l.js +112 -0
  67. package/dist/list-item-animation-DuiwgN3l.js.map +1 -0
  68. package/dist/list-selection-controller-DTFI40li.js +264 -0
  69. package/dist/list-selection-controller-DTFI40li.js.map +1 -0
  70. package/dist/{list-selection-types-DSuRNWpx.d.ts → list-selection-types-B5ioL-dG.d.ts} +6 -1
  71. package/dist/list-selection-types-B5ioL-dG.d.ts.map +1 -0
  72. package/dist/{menu-DMzjkVnK.js → menu-Bg90Qr0B.js} +3 -3
  73. package/dist/{menu-DMzjkVnK.js.map → menu-Bg90Qr0B.js.map} +1 -1
  74. package/dist/{menu-item-D2S4i8u1.js → menu-item-Bx-Fs7jq.js} +3 -3
  75. package/dist/{menu-item-D2S4i8u1.js.map → menu-item-Bx-Fs7jq.js.map} +1 -1
  76. package/dist/{menu-item-ZmkXNjek.d.ts → menu-item-CZi9vy5O.d.ts} +2 -2
  77. package/dist/{menu-item-ZmkXNjek.d.ts.map → menu-item-CZi9vy5O.d.ts.map} +1 -1
  78. package/dist/menu.d.ts +2 -2
  79. package/dist/menu.js +2 -2
  80. package/dist/miller.d.ts +82 -4
  81. package/dist/miller.d.ts.map +1 -1
  82. package/dist/miller.js +329 -70
  83. package/dist/miller.js.map +1 -1
  84. package/dist/narrowed-text.js +3 -4
  85. package/dist/narrowed-text.js.map +1 -1
  86. package/dist/panel.d.ts +1 -1
  87. package/dist/plots.js +1 -1
  88. package/dist/select-horiz.js +2 -2
  89. package/dist/{split-layout-C5SYbDrq.d.ts → split-layout-JK3xWVX2.d.ts} +2 -2
  90. package/dist/{split-layout-C5SYbDrq.d.ts.map → split-layout-JK3xWVX2.d.ts.map} +1 -1
  91. package/dist/split-layout.d.ts +1 -1
  92. package/dist/{tab-list-DbMIL520.d.ts → tab-list-CGyPt07Z.d.ts} +2 -2
  93. package/dist/{tab-list-DbMIL520.d.ts.map → tab-list-CGyPt07Z.d.ts.map} +1 -1
  94. package/dist/tabs.d.ts +1 -1
  95. package/dist/{tickled-controller-Ds1DMyG7.d.ts → tickled-controller-Bn3YdQsM.d.ts} +12 -1
  96. package/dist/{tickled-controller-Ds1DMyG7.d.ts.map → tickled-controller-Bn3YdQsM.d.ts.map} +1 -1
  97. package/dist/{tickled-controller-h9GmJ_bU.js → tickled-controller-C66DgJHl.js} +16 -1
  98. package/dist/tickled-controller-C66DgJHl.js.map +1 -0
  99. package/dist/{titlebar-B23MrIbI.d.ts → titlebar-Bj5pfdw_.d.ts} +2 -2
  100. package/dist/{titlebar-B23MrIbI.d.ts.map → titlebar-Bj5pfdw_.d.ts.map} +1 -1
  101. package/dist/titlebar.d.ts +1 -1
  102. package/dist/{tree-Ch4W8qXQ.js → tree-DAii2A1g.js} +190 -29
  103. package/dist/tree-DAii2A1g.js.map +1 -0
  104. package/dist/{tree-component-BvV4muxx.d.ts → tree-component-F6qZ0Gdv.d.ts} +9 -1
  105. package/dist/{tree-component-BvV4muxx.d.ts.map → tree-component-F6qZ0Gdv.d.ts.map} +1 -1
  106. package/dist/tree.d.ts +28 -4
  107. package/dist/tree.d.ts.map +1 -1
  108. package/dist/tree.js +1 -1
  109. package/dist/vertical-list-DwtCPy1b.d.ts +143 -0
  110. package/dist/vertical-list-DwtCPy1b.d.ts.map +1 -0
  111. package/dist/vertical-list.d.ts +3 -121
  112. package/dist/vertical-list.js +642 -1
  113. package/dist/vertical-list.js.map +1 -0
  114. package/dist/{xy-axis-DWizs0bt.js → xy-axis-VrX8dmvS.js} +2 -2
  115. package/dist/{xy-axis-DWizs0bt.js.map → xy-axis-VrX8dmvS.js.map} +1 -1
  116. package/docs-user/README.md +1 -0
  117. package/docs-user/data-grid.md +70 -2
  118. package/docs-user/detail-list.md +39 -1
  119. package/docs-user/grid-list.md +298 -0
  120. package/docs-user/incr-search.md +12 -2
  121. package/docs-user/index.json +7 -2
  122. package/docs-user/llms.txt +1 -0
  123. package/docs-user/tree.md +35 -1
  124. package/docs-user/vertical-list.md +58 -2
  125. package/llms.txt +1 -0
  126. package/package.json +17 -2
  127. package/dist/data-grid-Db4QpnII.d.ts.map +0 -1
  128. package/dist/element-search-Y8SRfPGM.js +0 -185
  129. package/dist/element-search-Y8SRfPGM.js.map +0 -1
  130. package/dist/icon-DR_3rSyZ.d.ts.map +0 -1
  131. package/dist/incr-search-vCzuao-2.js +0 -137
  132. package/dist/incr-search-vCzuao-2.js.map +0 -1
  133. package/dist/index-Cu5i8zZY.d.ts +0 -160
  134. package/dist/index-Cu5i8zZY.d.ts.map +0 -1
  135. package/dist/interaction-D5XdKzbR.js +0 -2
  136. package/dist/list-selection-types-DSuRNWpx.d.ts.map +0 -1
  137. package/dist/tickled-controller-h9GmJ_bU.js.map +0 -1
  138. package/dist/tree-Ch4W8qXQ.js.map +0 -1
  139. package/dist/vertical-list-B4w_ksJk.js +0 -814
  140. package/dist/vertical-list-B4w_ksJk.js.map +0 -1
  141. package/dist/vertical-list.d.ts.map +0 -1
package/dist/data-grid.js CHANGED
@@ -1,12 +1,15 @@
1
- import { t as TickledController } from "./tickled-controller-h9GmJ_bU.js";
1
+ import { t as TickledController } from "./tickled-controller-C66DgJHl.js";
2
2
  import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrAHEj1q.js";
3
3
  import { t as themeFallbacks } from "./fallbacks-DNI9xt0F.js";
4
4
  import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
5
- import "./interaction-D5XdKzbR.js";
5
+ import { i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES } from "./incr-search-DpbSJtgj.js";
6
+ import { i as listItemFadeKeyframes } from "./list-item-animation-DuiwgN3l.js";
7
+ import "./interaction-Cr13azbM.js";
6
8
  import { t as createDragGhost } from "./drag-ghost-DtSDvjri.js";
7
- import "./menu-DMzjkVnK.js";
9
+ import "./menu-Bg90Qr0B.js";
8
10
  import { LitElement, css, html, nothing } from "lit";
9
11
  import { property, query, state } from "lit/decorators.js";
12
+ import { createCommandRegistry } from "@clinth/ui-commands";
10
13
  import { repeat } from "lit/directives/repeat.js";
11
14
  //#region src/data-grid/data-grid.ts
12
15
  let DataGridElement = class DataGridElement extends LitElement {
@@ -24,6 +27,7 @@ let DataGridElement = class DataGridElement extends LitElement {
24
27
  this.groupBy = null;
25
28
  this.allowGrouping = false;
26
29
  this.crossGroupSelection = false;
30
+ this.columnMenu = `auto`;
27
31
  this._columnFormatters = /* @__PURE__ */ new Map();
28
32
  this._selectedKeys = /* @__PURE__ */ new Set();
29
33
  this._scrollTop = 0;
@@ -46,10 +50,18 @@ let DataGridElement = class DataGridElement extends LitElement {
46
50
  this._sortedRows = [];
47
51
  this._flat = [];
48
52
  this._flatKeys = [];
53
+ this.#searchKeyByRow = /* @__PURE__ */ new Map();
54
+ this.search = new ListSearchController(this, { adapter: this.#createSearchAdapter() });
55
+ this.#prevFlatKeys = [];
56
+ this.#enteringRowKeys = /* @__PURE__ */ new Set();
57
+ this.#rowReflowTimer = 0;
58
+ this.#dataGridRenderedOnce = false;
49
59
  this._visibleColumns = [];
50
60
  this._colOffsets = [];
51
61
  this._colWidthSum = 0;
52
62
  this._raf = 0;
63
+ this.#appendInProgress = false;
64
+ this.#resortRaf = 0;
53
65
  this._drag = null;
54
66
  this._onFocusOut = (e) => {
55
67
  if (this._suppressBlurCommit) return;
@@ -244,6 +256,8 @@ let DataGridElement = class DataGridElement extends LitElement {
244
256
  themeFallbacks,
245
257
  tickledStyles,
246
258
  tickledItemStyles,
259
+ listItemFadeKeyframes,
260
+ listSearchStyles,
247
261
  css`
248
262
  :host {
249
263
  --data-grid-row-height: 28px;
@@ -252,6 +266,7 @@ let DataGridElement = class DataGridElement extends LitElement {
252
266
  --data-grid-header-bg: var(--surface-2);
253
267
  --data-grid-border: var(--border);
254
268
  --data-grid-header-text: var(--surface-3-text);
269
+ position: relative;
255
270
  display: block;
256
271
  overflow: hidden;
257
272
  border: var(--data-grid-border);
@@ -300,6 +315,22 @@ let DataGridElement = class DataGridElement extends LitElement {
300
315
  .row.item.tickled { background: var(--item-bg-tickled); border: var(--item-border-tickled); color: var(--item-text-tickled); }
301
316
  .row.item.selected { background: var(--item-bg-selected); border: var(--item-border-selected); color: var(--item-text-selected); }
302
317
  .row.item.selected.tickled { background: var(--item-bg-selected-tickled); }
318
+
319
+ /* Insertion / removal animation. Only while [reflowing] — the ~0.3s after
320
+ the row set changes — do rows slide (their top transitions) to make or
321
+ close a gap; a freshly inserted row also fades in. Kept scoped so it
322
+ never fires while scrolling the virtualised list. */
323
+ :host([reflowing]) .row.item,
324
+ :host([reflowing]) .group-row {
325
+ transition: top var(--data-grid-row-move-duration, 200ms) cubic-bezier(0.2, 0, 0, 1);
326
+ }
327
+ .row.item.entering {
328
+ animation: ixfx-list-item-in var(--data-grid-row-move-duration, 200ms) ease both;
329
+ }
330
+ @media (prefers-reduced-motion: reduce) {
331
+ :host([reflowing]) .row.item, :host([reflowing]) .group-row { transition: none; }
332
+ .row.item.entering { animation: none; }
333
+ }
303
334
  .cell {
304
335
  flex-shrink: 0; display: flex; align-items: center; padding: 0 var(--space-s);
305
336
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-right: var(--border-subtle);
@@ -329,17 +360,89 @@ let DataGridElement = class DataGridElement extends LitElement {
329
360
  `
330
361
  ];
331
362
  }
363
+ #searchRegistry;
364
+ #unregisterSearchCommands;
365
+ #searchKeyByRow;
366
+ /** Command registry carrying this grid's `list.search.*` commands. */
367
+ get registry() {
368
+ return this.#searchRegistry;
369
+ }
370
+ #rowSearchText(row) {
371
+ if (this.rowSearchText) return this.rowSearchText(row);
372
+ const rec = row;
373
+ return this._visibleColumns.map((c) => String(rec[c.key] ?? ``)).join(` `);
374
+ }
375
+ #createSearchAdapter() {
376
+ return {
377
+ getItems: () => {
378
+ this.#searchKeyByRow.clear();
379
+ this._sortedRows.forEach((r, i) => this.#searchKeyByRow.set(r, this.getRowKey(r, i)));
380
+ return this._sortedRows;
381
+ },
382
+ getLabel: (row) => this.#rowSearchText(row),
383
+ getKey: (row) => this.#searchKeyByRow.get(row) ?? String(this._sortedRows.indexOf(row)),
384
+ focusItem: (row) => {
385
+ if (row === void 0) return;
386
+ const key = this.#searchKeyByRow.get(row);
387
+ if (key !== void 0) this.tickleRow(key);
388
+ },
389
+ selectItems: (rows) => {
390
+ this.selectKeys(rows.map((r) => this.#searchKeyByRow.get(r)).filter((k) => k !== void 0));
391
+ },
392
+ applyFilter: (matched) => {
393
+ this.filterPredicate = matched === void 0 ? void 0 : (row) => {
394
+ const k = this.#searchKeyByRow.get(row);
395
+ return k === void 0 || matched.has(k);
396
+ };
397
+ }
398
+ };
399
+ }
400
+ #effectiveSearchModes() {
401
+ const raw = this.searchModes?.trim();
402
+ if (raw === void 0) return [...LIST_SEARCH_MODES];
403
+ const requested = new Set(raw.split(/\s+/));
404
+ return LIST_SEARCH_MODES.filter((m) => requested.has(m));
405
+ }
406
+ #syncSearchCommands() {
407
+ if (!this.#searchRegistry) return;
408
+ this.search.setModes(this.#effectiveSearchModes());
409
+ this.#unregisterSearchCommands?.();
410
+ this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
411
+ }
412
+ /** Row-key snapshot from the previous update, for the insertion / removal animation. */
413
+ #prevFlatKeys;
414
+ #enteringRowKeys;
415
+ #rowReflowTimer;
416
+ #dataGridRenderedOnce;
417
+ #appendInProgress;
418
+ #resortRaf;
332
419
  connectedCallback() {
333
420
  super.connectedCallback();
334
421
  if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
335
422
  this.addEventListener(`keydown`, this._onKeyDown);
336
423
  this.addEventListener(`focusout`, this._onFocusOut);
424
+ this.#searchRegistry ??= createCommandRegistry();
425
+ this.#syncSearchCommands();
337
426
  }
338
427
  disconnectedCallback() {
339
428
  super.disconnectedCallback();
340
429
  this.removeEventListener(`keydown`, this._onKeyDown);
341
430
  this.removeEventListener(`focusout`, this._onFocusOut);
342
431
  cancelAnimationFrame(this._raf);
432
+ this.#unregisterSearchCommands?.();
433
+ this.#unregisterSearchCommands = void 0;
434
+ this.#searchRegistry = void 0;
435
+ if (this.#rowReflowTimer) {
436
+ clearTimeout(this.#rowReflowTimer);
437
+ this.#rowReflowTimer = 0;
438
+ }
439
+ }
440
+ /**
441
+ * Dispatch a command to this grid — routes through the command registry so
442
+ * `list.search.*` (and any other registered commands) work.
443
+ */
444
+ dispatch(commandId, args) {
445
+ this.#searchRegistry?.invoke(commandId, args);
343
446
  }
344
447
  firstUpdated() {
345
448
  new ResizeObserver(() => {
@@ -366,8 +469,11 @@ let DataGridElement = class DataGridElement extends LitElement {
366
469
  willUpdate(changed) {
367
470
  super.willUpdate(changed);
368
471
  this.setAttribute(`data-input-mode`, this._tickled.inputMode);
369
- if (changed.has(`rows`) || changed.has(`sortState`) || changed.has(`columns`) || changed.has(`groupBy`) || changed.has(`groupLabel`)) this._sortedRows = this._applySort([...this.rows]);
370
- this._rebuildFlat();
472
+ if (changed.has(`searchModes`)) this.#syncSearchCommands();
473
+ if (!this.#appendInProgress) {
474
+ if (changed.has(`rows`) || changed.has(`sortState`) || changed.has(`columns`) || changed.has(`groupBy`) || changed.has(`groupLabel`)) this._sortedRows = this._applySort([...this.rows]);
475
+ this._rebuildFlat();
476
+ }
371
477
  this._visibleColumns = this.columns.filter((c) => c.visible !== false);
372
478
  let off = 0;
373
479
  this._colOffsets = this._visibleColumns.map((c) => {
@@ -377,6 +483,40 @@ let DataGridElement = class DataGridElement extends LitElement {
377
483
  });
378
484
  this._colWidthSum = off;
379
485
  if (this.interactionMode === `checked`) this._colWidthSum += 28;
486
+ this.#noteFlatMutation();
487
+ }
488
+ /**
489
+ * Diff the row-key list against the previous update. Rows that appeared get an
490
+ * enter fade; any add/remove opens a short `[reflowing]` window so the rows
491
+ * that shift slide (their `top` transitions) instead of snapping. Bounded in
492
+ * time so it never bleeds into scroll virtualisation.
493
+ */
494
+ #noteFlatMutation() {
495
+ const now = this._flatKeys;
496
+ const prev = this.#prevFlatKeys;
497
+ this.#prevFlatKeys = now.slice();
498
+ if (!this.#dataGridRenderedOnce) {
499
+ this.#dataGridRenderedOnce = true;
500
+ return;
501
+ }
502
+ if (prev.length === 0 || now.length === 0) {
503
+ this.#enteringRowKeys.clear();
504
+ return;
505
+ }
506
+ const before = new Set(prev);
507
+ const after = new Set(now);
508
+ const added = now.filter((k) => !before.has(k));
509
+ const removed = prev.filter((k) => !after.has(k));
510
+ if (added.length === now.length || added.length === 0 && removed.length === 0) return;
511
+ this.#enteringRowKeys = new Set(added);
512
+ this.toggleAttribute(`reflowing`, true);
513
+ if (this.#rowReflowTimer) clearTimeout(this.#rowReflowTimer);
514
+ this.#rowReflowTimer = window.setTimeout(() => {
515
+ this.#rowReflowTimer = 0;
516
+ this.#enteringRowKeys.clear();
517
+ this.toggleAttribute(`reflowing`, false);
518
+ this.requestUpdate();
519
+ }, 320);
380
520
  }
381
521
  _getGroupKey(row) {
382
522
  if (!this.groupBy) return null;
@@ -384,18 +524,23 @@ let DataGridElement = class DataGridElement extends LitElement {
384
524
  return String(this.groupBy(row));
385
525
  }
386
526
  _rebuildFlat() {
527
+ const filtered = this.filterPredicate !== void 0;
528
+ const source = filtered ? this._sortedRows.filter((r) => this.filterPredicate(r)) : this._sortedRows;
387
529
  if (!this.groupBy) {
388
- this._flat = this._sortedRows.map((r, i) => ({
389
- type: `row`,
390
- key: this.getRowKey(r, i),
391
- row: r,
392
- idx: i
393
- }));
530
+ this._flat = source.map((r, i) => {
531
+ const idx = filtered ? this._sortedRows.indexOf(r) : i;
532
+ return {
533
+ type: `row`,
534
+ key: this.getRowKey(r, idx),
535
+ row: r,
536
+ idx
537
+ };
538
+ });
394
539
  this._flatKeys = this._flat.map((f) => f.key);
395
540
  return;
396
541
  }
397
542
  const groups = /* @__PURE__ */ new Map();
398
- for (const r of this._sortedRows) {
543
+ for (const r of source) {
399
544
  const k = this._getGroupKey(r) ?? ``;
400
545
  const arr = groups.get(k);
401
546
  if (arr) arr.push(r);
@@ -624,6 +769,102 @@ let DataGridElement = class DataGridElement extends LitElement {
624
769
  _getRowForKey(key) {
625
770
  return this._flat.find((f) => f.key === key)?.row;
626
771
  }
772
+ /** The selected row keys (read-only). */
773
+ get selectedKeys() {
774
+ return this._selectedKeys;
775
+ }
776
+ /** Replace the selection with these row keys (clamped to known rows). */
777
+ set selectedKeys(keys) {
778
+ this.selectKeys(keys);
779
+ }
780
+ /**
781
+ * Replace the selection with the given row keys. Unknown keys are dropped;
782
+ * in a single-select mode only the last surviving key is kept;
783
+ * `selectionMode="none"` clears. Fires `datagrid-select` with the previous
784
+ * set when the selection actually changes.
785
+ */
786
+ selectKeys(keys) {
787
+ const known = new Set(this._flat.filter((f) => f.type === `row`).map((f) => f.key));
788
+ let next = new Set([...keys].filter((k) => known.has(k)));
789
+ const multi = this.selectionMode === `multiple` || this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `vscode`;
790
+ if (this.selectionMode === `none`) next = /* @__PURE__ */ new Set();
791
+ else if (!multi && next.size > 1) {
792
+ const last = [...next].at(-1);
793
+ next = last === void 0 ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([last]);
794
+ }
795
+ const prev = new Set(this._selectedKeys);
796
+ if (prev.size === next.size && [...next].every((k) => prev.has(k))) return;
797
+ this._selectedKeys = next;
798
+ this._anchorKey = [...next].at(-1);
799
+ this.dispatchEvent(new CustomEvent(`datagrid-select`, {
800
+ detail: {
801
+ selectedKeys: new Set(next),
802
+ previous: prev
803
+ },
804
+ bubbles: true,
805
+ composed: true
806
+ }));
807
+ }
808
+ /** The row key under the cursor, or `undefined`. */
809
+ get tickledKey() {
810
+ return this._tickled.tickledId;
811
+ }
812
+ /** Move the cursor to this row key and scroll it into view. */
813
+ tickleRow(key) {
814
+ if (!this._flatKeys.includes(key)) return;
815
+ this._tickled.focus(key);
816
+ this._scrollRowIntoView(key);
817
+ }
818
+ /**
819
+ * Append rows without the full O(n log n) rebuild that reassigning `rows`
820
+ * incurs. `rows` still gets a new array reference (reactivity is preserved),
821
+ * but the derived arrays are extended over the appended slice only. When a
822
+ * sort is active the new rows show unsorted for one frame, then a single
823
+ * coalesced re-sort runs. Scroll position, selection and cursor are kept.
824
+ */
825
+ appendRows(rows) {
826
+ if (rows.length === 0) return;
827
+ const startIdx = this.rows.length;
828
+ this.#appendInProgress = true;
829
+ this.rows = [...this.rows, ...rows];
830
+ const appended = this.rows.slice(startIdx);
831
+ if (this._sortedRows.length === startIdx) this._sortedRows = [...this._sortedRows, ...appended];
832
+ else {
833
+ this._sortedRows = this._applySort([...this.rows]);
834
+ this._rebuildFlat();
835
+ this.#appendInProgress = false;
836
+ this.requestUpdate();
837
+ return;
838
+ }
839
+ this.#appendToFlat(appended, startIdx);
840
+ this.#appendInProgress = false;
841
+ this.requestUpdate();
842
+ if (this.sortState != null) {
843
+ cancelAnimationFrame(this.#resortRaf);
844
+ this.#resortRaf = requestAnimationFrame(() => {
845
+ this._sortedRows = this._applySort([...this.rows]);
846
+ this._rebuildFlat();
847
+ this.requestUpdate();
848
+ });
849
+ }
850
+ }
851
+ #appendToFlat(appended, startIdx) {
852
+ if (this.groupBy) {
853
+ this._rebuildFlat();
854
+ return;
855
+ }
856
+ for (let j = 0; j < appended.length; j++) {
857
+ const idx = startIdx + j;
858
+ const key = this.getRowKey(appended[j], idx);
859
+ this._flat.push({
860
+ type: `row`,
861
+ key,
862
+ row: appended[j],
863
+ idx
864
+ });
865
+ this._flatKeys.push(key);
866
+ }
867
+ }
627
868
  _replaceSelection(key) {
628
869
  if (this.selectionMode === `none`) return;
629
870
  const prev = new Set(this._selectedKeys);
@@ -979,6 +1220,7 @@ let DataGridElement = class DataGridElement extends LitElement {
979
1220
  return last >= 0 ? this._colOffsets[last] + (this._visibleColumns[last].width ?? 150) : 0;
980
1221
  })()}px"></div>` : nothing}
981
1222
  </div>
1223
+ ${this.columnMenu === `none` ? nothing : html`
982
1224
  <ixfx-menu-trigger label="⚙" placement="bottom-end" @menu-command=${(e) => {
983
1225
  const cmd = e.detail.command;
984
1226
  const checked = e.detail.checked;
@@ -1018,7 +1260,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1018
1260
  </ixfx-menu-radio-group>
1019
1261
  ` : nothing}
1020
1262
  </ixfx-menu-container>
1021
- </ixfx-menu-trigger>
1263
+ </ixfx-menu-trigger>`}
1022
1264
  </div>
1023
1265
  <div class="viewport" @scroll=${this._onScroll}>
1024
1266
  <div class="spacer" style="height:${this._flat.length * rh}px; width:${this._colWidthSum}px">
@@ -1035,7 +1277,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1035
1277
  const selected = this._selectedKeys.has(rowKey);
1036
1278
  const tickled = this._tickled.isTickled(rowKey);
1037
1279
  return html`
1038
- <div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``}" style="top:${absIdx * rh}px; height:${rh}px"
1280
+ <div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${this.#enteringRowKeys.has(rowKey) ? `entering` : ``}" style="top:${absIdx * rh}px; height:${rh}px"
1039
1281
  data-key=${rowKey}
1040
1282
  @click=${(e) => {
1041
1283
  this._onRowClick(rowKey, null, e);
@@ -1168,6 +1410,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1168
1410
  })}
1169
1411
  </div>
1170
1412
  </div>
1413
+ ${this.search.renderField()}
1171
1414
  </div>
1172
1415
  `;
1173
1416
  }
@@ -1206,7 +1449,18 @@ __decorate([property({
1206
1449
  attribute: `cross-group-selection`,
1207
1450
  reflect: true
1208
1451
  })], DataGridElement.prototype, "crossGroupSelection", void 0);
1452
+ __decorate([property({
1453
+ type: String,
1454
+ attribute: `column-menu`
1455
+ })], DataGridElement.prototype, "columnMenu", void 0);
1209
1456
  __decorate([property({ attribute: false })], DataGridElement.prototype, "globalFormatter", void 0);
1457
+ __decorate([property({ attribute: false })], DataGridElement.prototype, "filterPredicate", void 0);
1458
+ __decorate([property({ attribute: false })], DataGridElement.prototype, "rowSearchText", void 0);
1459
+ __decorate([property({
1460
+ type: String,
1461
+ attribute: `search-modes`,
1462
+ reflect: true
1463
+ })], DataGridElement.prototype, "searchModes", void 0);
1210
1464
  __decorate([state()], DataGridElement.prototype, "_selectedKeys", void 0);
1211
1465
  __decorate([state()], DataGridElement.prototype, "_scrollTop", void 0);
1212
1466
  __decorate([state()], DataGridElement.prototype, "_scrollLeft", void 0);