@ixfx/components 0.13.2 → 0.13.3

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 (76) hide show
  1. package/bundle/index.d.ts +97 -14
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +2183 -636
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-token.js +1 -1
  6. package/dist/{data-grid-Ck1Qx2H1.d.ts → data-grid-DHWJN66w.d.ts} +7 -5
  7. package/dist/{data-grid-Ck1Qx2H1.d.ts.map → data-grid-DHWJN66w.d.ts.map} +1 -1
  8. package/dist/data-grid.d.ts +1 -1
  9. package/dist/data-grid.js +210 -120
  10. package/dist/data-grid.js.map +1 -1
  11. package/dist/detail-list-C96B5Txz.js +2001 -0
  12. package/dist/detail-list-C96B5Txz.js.map +1 -0
  13. package/dist/{detail-list-C1xzr1z7.d.ts → detail-list-dX7jLO6b.d.ts} +12 -3
  14. package/dist/{detail-list-C1xzr1z7.d.ts.map → detail-list-dX7jLO6b.d.ts.map} +1 -1
  15. package/dist/detail-list.d.ts +2 -2
  16. package/dist/detail-list.js +1 -1680
  17. package/dist/{flat-list-Ohxqsy0g.d.ts → flat-list-Y98IqH00.d.ts} +2 -2
  18. package/dist/{flat-list-Ohxqsy0g.d.ts.map → flat-list-Y98IqH00.d.ts.map} +1 -1
  19. package/dist/flat-list.d.ts +1 -1
  20. package/dist/flat-list.js +5 -1
  21. package/dist/flat-list.js.map +1 -1
  22. package/dist/{grid-list-pefLORIr.d.ts → grid-list-CoT9e1jB.d.ts} +9 -2
  23. package/dist/{grid-list-pefLORIr.d.ts.map → grid-list-CoT9e1jB.d.ts.map} +1 -1
  24. package/dist/{grid-list-DjIl910A.js → grid-list-DeqAd9-h.js} +189 -40
  25. package/dist/grid-list-DeqAd9-h.js.map +1 -0
  26. package/dist/grid-list.d.ts +1 -1
  27. package/dist/grid-list.js +1 -1
  28. package/dist/{incr-search-D91M4Ozq.js → incr-search-JIhwB49y.js} +27 -5
  29. package/dist/incr-search-JIhwB49y.js.map +1 -0
  30. package/dist/incr-search.d.ts +1 -1
  31. package/dist/incr-search.js +1 -1
  32. package/dist/index-CnQPCywE.d.ts +325 -0
  33. package/dist/index-CnQPCywE.d.ts.map +1 -0
  34. package/dist/{index-CxZ61DO1.d.ts → index-Dlah7Zfi.d.ts} +8 -1
  35. package/dist/{index-CxZ61DO1.d.ts.map → index-Dlah7Zfi.d.ts.map} +1 -1
  36. package/dist/index.d.ts +9 -9
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +46 -25
  39. package/dist/index.js.map +1 -1
  40. package/dist/miller.d.ts +1 -1
  41. package/dist/miller.d.ts.map +1 -1
  42. package/dist/miller.js +146 -100
  43. package/dist/miller.js.map +1 -1
  44. package/dist/narrowed-text.js +1 -1
  45. package/dist/split-layout-C5SYbDrq.d.ts.map +1 -1
  46. package/dist/{split-layout-CXotkdbx.js → split-layout-Deoxy47d.js} +6 -1
  47. package/dist/{split-layout-CXotkdbx.js.map → split-layout-Deoxy47d.js.map} +1 -1
  48. package/dist/split-layout.js +1 -1
  49. package/dist/{tab-list-v8mKX7Fu.d.ts → tab-list-D_dmImJM.d.ts} +2 -3
  50. package/dist/tab-list-D_dmImJM.d.ts.map +1 -0
  51. package/dist/tabs.d.ts +1 -1
  52. package/dist/tabs.js +22 -0
  53. package/dist/tabs.js.map +1 -1
  54. package/dist/{tree-DGveys4K.js → tree-DD8OaqsK.js} +1010 -230
  55. package/dist/tree-DD8OaqsK.js.map +1 -0
  56. package/dist/tree.d.ts +4 -267
  57. package/dist/tree.js +1 -1
  58. package/dist/{vertical-list-CqBc-t8w.d.ts → vertical-list-lj_19DCx.d.ts} +3 -2
  59. package/dist/{vertical-list-CqBc-t8w.d.ts.map → vertical-list-lj_19DCx.d.ts.map} +1 -1
  60. package/dist/vertical-list.d.ts +1 -1
  61. package/dist/vertical-list.js +113 -24
  62. package/dist/vertical-list.js.map +1 -1
  63. package/docs-user/data-grid.md +74 -4
  64. package/docs-user/detail-list.md +65 -0
  65. package/docs-user/grid-list.md +81 -4
  66. package/docs-user/index.json +1 -1
  67. package/docs-user/miller.md +59 -3
  68. package/docs-user/tree.md +63 -2
  69. package/docs-user/vertical-list.md +9 -1
  70. package/package.json +6 -1
  71. package/dist/detail-list.js.map +0 -1
  72. package/dist/grid-list-DjIl910A.js.map +0 -1
  73. package/dist/incr-search-D91M4Ozq.js.map +0 -1
  74. package/dist/tab-list-v8mKX7Fu.d.ts.map +0 -1
  75. package/dist/tree-DGveys4K.js.map +0 -1
  76. package/dist/tree.d.ts.map +0 -1
package/dist/data-grid.js CHANGED
@@ -3,7 +3,7 @@ import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrA
3
3
  import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
4
4
  import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
5
5
  import "./icon-1gpwtPx8.js";
6
- import { i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES } from "./incr-search-D91M4Ozq.js";
6
+ import { i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES } from "./incr-search-JIhwB49y.js";
7
7
  import { t as handleStyles } from "./handle-DAXs7kbJ.js";
8
8
  import { t as ListNativeDragController } from "./list-native-drag-controller-B9xO0XWn.js";
9
9
  import { i as listItemFadeKeyframes } from "./list-item-animation-BUoEKi5s.js";
@@ -30,6 +30,10 @@ function sameKeys(a, b) {
30
30
  for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
31
31
  return true;
32
32
  }
33
+ /** Element-wise reference equality, treating `undefined` (never computed) as different. */
34
+ function sameRefs(a, b) {
35
+ return a !== void 0 && a.length === b.length && a.every((v, i) => v === b[i]);
36
+ }
33
37
  let DataGridElement = class DataGridElement extends LitElement {
34
38
  constructor(..._args) {
35
39
  super(..._args);
@@ -47,7 +51,7 @@ let DataGridElement = class DataGridElement extends LitElement {
47
51
  this.groupBy = null;
48
52
  this.allowGrouping = false;
49
53
  this.crossGroupSelection = false;
50
- this.columnMenu = `auto`;
54
+ this.columnMenu = `none`;
51
55
  this.textColorColumn = ``;
52
56
  this.iconColumn = ``;
53
57
  this.badgeColumn = ``;
@@ -68,9 +72,9 @@ let DataGridElement = class DataGridElement extends LitElement {
68
72
  this._lastClickedCell = null;
69
73
  this._editValue = ``;
70
74
  this._tickled = new TickledController(this, {
71
- getItems: () => this._flatKeys.map((k) => ({
72
- id: k,
73
- enabled: true
75
+ getItems: () => this.#tickledItems ??= this._flat.map((f) => ({
76
+ id: f.key,
77
+ enabled: f.type === `row`
74
78
  })),
75
79
  onStateChange: () => this.requestUpdate(),
76
80
  hoverDisabled: () => this.#nativeDrag.isDragging
@@ -117,6 +121,9 @@ let DataGridElement = class DataGridElement extends LitElement {
117
121
  this._sortedRows = [];
118
122
  this._flat = [];
119
123
  this._flatKeys = [];
124
+ this.#flatIndexByKey = /* @__PURE__ */ new Map();
125
+ this.#flatVersion = 0;
126
+ this.#notedFlatVersion = -1;
120
127
  this.#searchKeyByRow = /* @__PURE__ */ new Map();
121
128
  this.search = new ListSearchController(this, { adapter: this.#createSearchAdapter() });
122
129
  this.#prevFlatKeys = [];
@@ -128,7 +135,6 @@ let DataGridElement = class DataGridElement extends LitElement {
128
135
  this._colWidthSum = 0;
129
136
  this._raf = 0;
130
137
  this.#viewportChangeRaf = 0;
131
- this.#appendInProgress = false;
132
138
  this.#resortRaf = 0;
133
139
  this._drag = null;
134
140
  this._onFocusOut = (e) => {
@@ -145,10 +151,12 @@ let DataGridElement = class DataGridElement extends LitElement {
145
151
  const el = e.target;
146
152
  cancelAnimationFrame(this._raf);
147
153
  this._raf = requestAnimationFrame(() => {
154
+ const before = this.#windowRows(2);
148
155
  this._scrollTop = el.scrollTop;
149
156
  this._scrollLeft = el.scrollLeft;
150
157
  if (this._headerScrollEl) this._headerScrollEl.scrollLeft = el.scrollLeft;
151
- this.requestUpdate();
158
+ const after = this.#windowRows(2);
159
+ if (before.start !== after.start || before.end !== after.end) this.requestUpdate();
152
160
  cancelAnimationFrame(this.#viewportChangeRaf);
153
161
  this.#viewportChangeRaf = 0;
154
162
  this.#emitViewportChangeIfChanged();
@@ -187,7 +195,7 @@ let DataGridElement = class DataGridElement extends LitElement {
187
195
  let nextIdx = idx + dir;
188
196
  while (nextIdx >= 0 && nextIdx < rowKeys.length) {
189
197
  const nk = rowKeys[nextIdx];
190
- const row = this._sortedRows.find((r, i) => this.getRowKey(r, i) === nk);
198
+ const row = this.#rowEntry(nk)?.row;
191
199
  const col = this._visibleColumns.find((c) => c.key === colKey);
192
200
  if (row && col && this._isEditable(row, col)) {
193
201
  this._startEdit(nk, colKey);
@@ -236,9 +244,9 @@ let DataGridElement = class DataGridElement extends LitElement {
236
244
  if (!key) break;
237
245
  e.preventDefault();
238
246
  if (!this._editing && this.editability === `user`) {
239
- const idx = this._sortedRows.findIndex((r, i) => this.getRowKey(r, i) === key);
240
- if (idx >= 0) {
241
- const row = this._sortedRows[idx];
247
+ const entry = this.#rowEntry(key);
248
+ if (entry) {
249
+ const row = entry.row;
242
250
  if (this._lastClickedCell?.rowKey === key) {
243
251
  const col = this._visibleColumns.find((c) => c.key === this._lastClickedCell.colKey);
244
252
  if (col && this._isEditable(row, col)) {
@@ -254,9 +262,10 @@ let DataGridElement = class DataGridElement extends LitElement {
254
262
  }
255
263
  }
256
264
  if (!this._editing) {
257
- const idx = this._sortedRows.findIndex((r, i) => this.getRowKey(r, i) === key);
265
+ const entry = this.#rowEntry(key);
266
+ const idx = entry?.idx ?? -1;
258
267
  if (this._selectedKeys.has(key)) {
259
- this._activateRow(key, idx >= 0 ? this._sortedRows[idx] : void 0, idx);
268
+ this._activateRow(key, entry?.row, idx);
260
269
  break;
261
270
  }
262
271
  if (this.interactionMode !== `manual` && this.interactionMode !== `checked`) this._replaceSelection(key);
@@ -286,7 +295,7 @@ let DataGridElement = class DataGridElement extends LitElement {
286
295
  next = !active.checked;
287
296
  active.checked = next;
288
297
  } else {
289
- const row = this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey);
298
+ const row = this.#rowEntry(rowKey)?.row;
290
299
  next = !(this._editValue !== `` ? Boolean(this._editValue) : Boolean(row?.[colKey]));
291
300
  }
292
301
  if (isImmediate) this._commitImmediate(rowKey, colKey, next);
@@ -555,6 +564,15 @@ let DataGridElement = class DataGridElement extends LitElement {
555
564
  this.#postDragSuppressTimer = 0;
556
565
  }, 0);
557
566
  }
567
+ #flatIndexByKey;
568
+ /** Bumped whenever `_flat` changes; drives `#noteFlatMutation`. */
569
+ #flatVersion;
570
+ #notedFlatVersion;
571
+ /** Inputs `_sortedRows` / `_flat` were last derived from — see `#syncDerivedRows`. */
572
+ #sortInputs;
573
+ #flatInputs;
574
+ /** `_flat` projected for the tickled controller, cached until `_flat` changes. */
575
+ #tickledItems;
558
576
  #searchRegistry;
559
577
  #unregisterSearchCommands;
560
578
  #searchKeyByRow;
@@ -613,7 +631,6 @@ let DataGridElement = class DataGridElement extends LitElement {
613
631
  /** Key sets as of the last `datagrid-viewport-change` dispatch, for dedupe. */
614
632
  #lastViewportRenderedKeys;
615
633
  #lastViewportVisibleKeys;
616
- #appendInProgress;
617
634
  #resortRaf;
618
635
  connectedCallback() {
619
636
  super.connectedCallback();
@@ -686,7 +703,7 @@ let DataGridElement = class DataGridElement extends LitElement {
686
703
  updated(changed) {
687
704
  super.updated(changed);
688
705
  if (changed.has(`rows`) || changed.has(`sortState`) || changed.has(`columns`) || changed.has(`groupBy`) || changed.has(`groupLabel`) || changed.has(`filterPredicate`) || changed.has(`rowHeight`)) this.#scheduleViewportChangeEmit();
689
- if (changed.has(`_editing`) || changed.has(`_editingRow`)) requestAnimationFrame(() => {
706
+ if (changed.has(`_editing`) || changed.has(`_editingRow`)) this.updateComplete.then(() => {
690
707
  let sel = null;
691
708
  if (this._editing) sel = `.row[data-key="${this._editing.rowKey}"] .cell[data-col="${this._editing.colKey}"] input, .row[data-key="${this._editing.rowKey}"] .cell[data-col="${this._editing.colKey}"] select`;
692
709
  else if (this._editingRow) sel = `.row[data-key="${this._editingRow}"] .cell input, .row[data-key="${this._editingRow}"] .cell select`;
@@ -700,10 +717,7 @@ let DataGridElement = class DataGridElement extends LitElement {
700
717
  super.willUpdate(changed);
701
718
  this.setAttribute(`data-input-mode`, this._tickled.inputMode);
702
719
  if (changed.has(`searchModes`)) this.#syncSearchCommands();
703
- if (!this.#appendInProgress) {
704
- if (changed.has(`rows`) || changed.has(`sortState`) || changed.has(`columns`) || changed.has(`groupBy`) || changed.has(`groupLabel`)) this._sortedRows = this._applySort([...this.rows]);
705
- this._rebuildFlat();
706
- }
720
+ this.#syncDerivedRows();
707
721
  this._visibleColumns = this.columns.filter((c) => c.visible !== false);
708
722
  let off = 0;
709
723
  this._colOffsets = this._visibleColumns.map((c) => {
@@ -713,7 +727,53 @@ let DataGridElement = class DataGridElement extends LitElement {
713
727
  });
714
728
  this._colWidthSum = off;
715
729
  if (this.interactionMode === `checked`) this._colWidthSum += 28;
716
- this.#noteFlatMutation();
730
+ if (this.#flatVersion !== this.#notedFlatVersion) {
731
+ this.#notedFlatVersion = this.#flatVersion;
732
+ this.#noteFlatMutation();
733
+ }
734
+ }
735
+ /**
736
+ * Re-derive `_sortedRows` / `_flat` only when one of their inputs changed
737
+ * identity. Scroll, hover, selection and edit-state updates touch none of
738
+ * them, so those frames cost O(window) instead of O(rows).
739
+ */
740
+ #syncDerivedRows() {
741
+ const sortInputs = [
742
+ this.rows,
743
+ this.sortState,
744
+ this.columns
745
+ ];
746
+ const flatInputs = [
747
+ this.filterPredicate,
748
+ this.groupBy,
749
+ this.groupLabel,
750
+ this._collapsedGroups,
751
+ this.getRowKey
752
+ ];
753
+ const resort = !sameRefs(this.#sortInputs, sortInputs);
754
+ if (resort) {
755
+ this.#sortInputs = sortInputs;
756
+ this._sortedRows = this._applySort([...this.rows]);
757
+ }
758
+ if (resort || !sameRefs(this.#flatInputs, flatInputs)) {
759
+ this.#flatInputs = flatInputs;
760
+ this._rebuildFlat();
761
+ }
762
+ }
763
+ /** Mark the derived rows as matching the current inputs (after an incremental update). */
764
+ #markDerivedRowsSynced() {
765
+ this.#sortInputs = [
766
+ this.rows,
767
+ this.sortState,
768
+ this.columns
769
+ ];
770
+ this.#flatInputs = [
771
+ this.filterPredicate,
772
+ this.groupBy,
773
+ this.groupLabel,
774
+ this._collapsedGroups,
775
+ this.getRowKey
776
+ ];
717
777
  }
718
778
  /**
719
779
  * Diff the row-key list against the previous update. Rows that appeared get an
@@ -734,10 +794,9 @@ let DataGridElement = class DataGridElement extends LitElement {
734
794
  return;
735
795
  }
736
796
  const before = new Set(prev);
737
- const after = new Set(now);
738
797
  const added = now.filter((k) => !before.has(k));
739
- const removed = prev.filter((k) => !after.has(k));
740
- if (added.length === now.length || added.length === 0 && removed.length === 0) return;
798
+ const removedCount = prev.length - (now.length - added.length);
799
+ if (added.length === now.length || added.length === 0 && removedCount === 0) return;
741
800
  this.#enteringRowKeys = new Set(added);
742
801
  this.toggleAttribute(`reflowing`, true);
743
802
  if (this.#rowReflowTimer) clearTimeout(this.#rowReflowTimer);
@@ -754,55 +813,64 @@ let DataGridElement = class DataGridElement extends LitElement {
754
813
  return String(this.groupBy(row));
755
814
  }
756
815
  _rebuildFlat() {
757
- const filtered = this.filterPredicate !== void 0;
758
- const source = filtered ? this._sortedRows.filter((r) => this.filterPredicate(r)) : this._sortedRows;
759
- if (!this.groupBy) {
760
- this._flat = source.map((r, i) => {
761
- const idx = filtered ? this._sortedRows.indexOf(r) : i;
762
- return {
763
- type: `row`,
764
- key: this.getRowKey(r, idx),
765
- row: r,
766
- idx
767
- };
768
- });
769
- this._flatKeys = this._flat.map((f) => f.key);
770
- return;
771
- }
772
- const groups = /* @__PURE__ */ new Map();
773
- for (const r of source) {
774
- const k = this._getGroupKey(r) ?? ``;
775
- const arr = groups.get(k);
776
- if (arr) arr.push(r);
777
- else groups.set(k, [r]);
778
- }
816
+ const pred = this.filterPredicate;
817
+ const sourceIdx = [];
818
+ for (let i = 0; i < this._sortedRows.length; i++) if (!pred || pred(this._sortedRows[i])) sourceIdx.push(i);
779
819
  const flat = [];
780
820
  const keys = [];
781
- for (const [gKey, rows] of groups) {
782
- const label = this.groupLabel ? this.groupLabel(gKey, rows) : gKey;
783
- const gId = `__group__${gKey}`;
821
+ const pushRow = (idx) => {
822
+ const row = this._sortedRows[idx];
823
+ const key = this.getRowKey(row, idx);
784
824
  flat.push({
785
- type: `group`,
786
- key: gId,
787
- label,
788
- count: rows.length
825
+ type: `row`,
826
+ key,
827
+ row,
828
+ idx
789
829
  });
790
- keys.push(gId);
791
- if (this._collapsedGroups.has(gKey)) continue;
792
- for (const r of rows) {
793
- const idx = this._sortedRows.indexOf(r);
794
- const rk = this.getRowKey(r, idx);
830
+ keys.push(key);
831
+ };
832
+ if (!this.groupBy) sourceIdx.forEach(pushRow);
833
+ else {
834
+ const groups = /* @__PURE__ */ new Map();
835
+ for (const idx of sourceIdx) {
836
+ const k = this._getGroupKey(this._sortedRows[idx]) ?? ``;
837
+ const arr = groups.get(k);
838
+ if (arr) arr.push(idx);
839
+ else groups.set(k, [idx]);
840
+ }
841
+ for (const [gKey, idxs] of groups) {
842
+ const label = this.groupLabel ? this.groupLabel(gKey, idxs.map((i) => this._sortedRows[i])) : gKey;
843
+ const gId = `__group__${gKey}`;
795
844
  flat.push({
796
- type: `row`,
797
- key: rk,
798
- row: r,
799
- idx
845
+ type: `group`,
846
+ key: gId,
847
+ label,
848
+ count: idxs.length
800
849
  });
801
- keys.push(rk);
850
+ keys.push(gId);
851
+ if (!this._collapsedGroups.has(gKey)) idxs.forEach(pushRow);
802
852
  }
803
853
  }
804
854
  this._flat = flat;
805
855
  this._flatKeys = keys;
856
+ this.#rebuildFlatIndex();
857
+ }
858
+ #rebuildFlatIndex() {
859
+ this.#flatIndexByKey.clear();
860
+ this._flat.forEach((f, i) => this.#flatIndexByKey.set(f.key, i));
861
+ this.#flatVersion++;
862
+ this.#tickledItems = void 0;
863
+ }
864
+ /** Flat entry for a row key, or `undefined` for unknown keys and group headers. */
865
+ #rowEntry(key) {
866
+ const i = this.#flatIndexByKey.get(key);
867
+ const entry = i === void 0 ? void 0 : this._flat[i];
868
+ if (entry?.type === `row`) return entry;
869
+ const idx = this._sortedRows.findIndex((r, j) => this.getRowKey(r, j) === key);
870
+ return idx < 0 ? void 0 : {
871
+ row: this._sortedRows[idx],
872
+ idx
873
+ };
806
874
  }
807
875
  _toggleGroup(gKey) {
808
876
  const next = new Set(this._collapsedGroups);
@@ -1032,17 +1100,26 @@ let DataGridElement = class DataGridElement extends LitElement {
1032
1100
  }));
1033
1101
  }
1034
1102
  _scrollRowIntoView(key) {
1035
- const idx = this._flat.findIndex((f) => f.key === key);
1036
- if (idx < 0 || !this._viewportEl) return;
1037
- const top = idx * this.rowHeight;
1038
- const bottom = top + this.rowHeight;
1103
+ const idx = this.#flatIndexByKey.get(key);
1104
+ if (idx === void 0 || !this._viewportEl) return;
1105
+ const rh = this.rowHeight;
1106
+ const top = (idx > 0 && this._flat[idx - 1].type === `group` ? idx - 1 : idx) * rh;
1107
+ const bottom = (idx + 1) * rh;
1039
1108
  const vt = this._viewportEl.scrollTop;
1040
- const vb = vt + this._viewportEl.clientHeight;
1041
- if (top < vt) this._viewportEl.scrollTop = top;
1042
- else if (bottom > vb) this._viewportEl.scrollTop = bottom - this._viewportEl.clientHeight;
1109
+ const vh = this._viewportEl.clientHeight;
1110
+ let next = vt;
1111
+ if (top < vt) next = top;
1112
+ else if (bottom > vt + vh) next = bottom - vh;
1113
+ if (next !== vt) {
1114
+ this._viewportEl.scrollTop = next;
1115
+ this._scrollTop = next;
1116
+ this.requestUpdate();
1117
+ }
1043
1118
  }
1044
1119
  _getRowForKey(key) {
1045
- return this._flat.find((f) => f.key === key)?.row;
1120
+ const i = this.#flatIndexByKey.get(key);
1121
+ const entry = i === void 0 ? void 0 : this._flat[i];
1122
+ return entry?.type === `row` ? entry.row : void 0;
1046
1123
  }
1047
1124
  /** The selected row keys (read-only). */
1048
1125
  get selectedKeys() {
@@ -1059,8 +1136,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1059
1136
  * set when the selection actually changes.
1060
1137
  */
1061
1138
  selectKeys(keys) {
1062
- const known = new Set(this._flat.filter((f) => f.type === `row`).map((f) => f.key));
1063
- let next = new Set([...keys].filter((k) => known.has(k)));
1139
+ let next = new Set([...keys].filter((k) => this._getRowForKey(k) !== void 0));
1064
1140
  const multi = this.selectionMode === `multiple` || this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `vscode`;
1065
1141
  if (this.selectionMode === `none`) next = /* @__PURE__ */ new Set();
1066
1142
  else if (!multi && next.size > 1) {
@@ -1086,7 +1162,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1086
1162
  }
1087
1163
  /** Move the cursor to this row key and scroll it into view. */
1088
1164
  tickleRow(key) {
1089
- if (!this._flatKeys.includes(key)) return;
1165
+ if (!this.#flatIndexByKey.has(key) || key.startsWith(`__group__`)) return;
1090
1166
  this._tickled.focus(key);
1091
1167
  this._scrollRowIntoView(key);
1092
1168
  }
@@ -1100,25 +1176,21 @@ let DataGridElement = class DataGridElement extends LitElement {
1100
1176
  appendRows(rows) {
1101
1177
  if (rows.length === 0) return;
1102
1178
  const startIdx = this.rows.length;
1103
- this.#appendInProgress = true;
1179
+ const inSync = this._sortedRows.length === startIdx && sameRefs(this.#sortInputs, [
1180
+ this.rows,
1181
+ this.sortState,
1182
+ this.columns
1183
+ ]);
1104
1184
  this.rows = [...this.rows, ...rows];
1185
+ if (!inSync) return;
1105
1186
  const appended = this.rows.slice(startIdx);
1106
- if (this._sortedRows.length === startIdx) this._sortedRows = [...this._sortedRows, ...appended];
1107
- else {
1108
- this._sortedRows = this._applySort([...this.rows]);
1109
- this._rebuildFlat();
1110
- this.#appendInProgress = false;
1111
- this.requestUpdate();
1112
- return;
1113
- }
1187
+ this._sortedRows = [...this._sortedRows, ...appended];
1114
1188
  this.#appendToFlat(appended, startIdx);
1115
- this.#appendInProgress = false;
1116
- this.requestUpdate();
1189
+ this.#markDerivedRowsSynced();
1117
1190
  if (this.sortState != null) {
1118
1191
  cancelAnimationFrame(this.#resortRaf);
1119
1192
  this.#resortRaf = requestAnimationFrame(() => {
1120
- this._sortedRows = this._applySort([...this.rows]);
1121
- this._rebuildFlat();
1193
+ this.#sortInputs = void 0;
1122
1194
  this.requestUpdate();
1123
1195
  });
1124
1196
  }
@@ -1130,6 +1202,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1130
1202
  }
1131
1203
  for (let j = 0; j < appended.length; j++) {
1132
1204
  const idx = startIdx + j;
1205
+ if (this.filterPredicate && !this.filterPredicate(appended[j])) continue;
1133
1206
  const key = this.getRowKey(appended[j], idx);
1134
1207
  this._flat.push({
1135
1208
  type: `row`,
@@ -1138,7 +1211,10 @@ let DataGridElement = class DataGridElement extends LitElement {
1138
1211
  idx
1139
1212
  });
1140
1213
  this._flatKeys.push(key);
1214
+ this.#flatIndexByKey.set(key, this._flat.length - 1);
1141
1215
  }
1216
+ this.#flatVersion++;
1217
+ this.#tickledItems = void 0;
1142
1218
  }
1143
1219
  _replaceSelection(key) {
1144
1220
  if (this.selectionMode === `none`) return;
@@ -1244,8 +1320,8 @@ let DataGridElement = class DataGridElement extends LitElement {
1244
1320
  return !e || e === `auto`;
1245
1321
  }
1246
1322
  _isCellEditing(rowKey, colKey) {
1247
- if (this.editability === `immediate` && this._isEditable(this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey) ?? {}, this.columns.find((c) => c.key === colKey))) return true;
1248
- if (this._editingRow === rowKey) return this._isEditable(this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey) ?? {}, this.columns.find((c) => c.key === colKey));
1323
+ if (this.editability === `immediate` && this._isEditable(this.#rowEntry(rowKey)?.row ?? {}, this.columns.find((c) => c.key === colKey))) return true;
1324
+ if (this._editingRow === rowKey) return this._isEditable(this.#rowEntry(rowKey)?.row ?? {}, this.columns.find((c) => c.key === colKey));
1249
1325
  return this._editing?.rowKey === rowKey && this._editing?.colKey === colKey;
1250
1326
  }
1251
1327
  _activateRow(rowKey, row, rowIndex) {
@@ -1261,7 +1337,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1261
1337
  }
1262
1338
  _startEdit(rowKey, colKey) {
1263
1339
  if (this.editability === `disabled`) return;
1264
- const row = this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey);
1340
+ const row = this.#rowEntry(rowKey)?.row;
1265
1341
  if (!row) return;
1266
1342
  const col = this.columns.find((c) => c.key === colKey);
1267
1343
  if (!col || !this._isEditable(row, col)) return;
@@ -1279,7 +1355,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1279
1355
  /** Programmatic: put all editable cells of a row in edit mode. */
1280
1356
  startRowEdit(rowKey) {
1281
1357
  if (this.editability === `disabled`) return;
1282
- if (!this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey)) return;
1358
+ if (!this.#rowEntry(rowKey)?.row) return;
1283
1359
  this._editing = null;
1284
1360
  this._editingRow = rowKey;
1285
1361
  this.requestUpdate();
@@ -1347,7 +1423,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1347
1423
  const target = this._editing;
1348
1424
  if (!target) return;
1349
1425
  const { rowKey, colKey } = target;
1350
- const row = this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey);
1426
+ const row = this.#rowEntry(rowKey)?.row;
1351
1427
  if (!row) {
1352
1428
  this._editing = null;
1353
1429
  return;
@@ -1372,7 +1448,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1372
1448
  }));
1373
1449
  }
1374
1450
  _tabToNext(rowKey, curColKey, dir) {
1375
- const row = this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey);
1451
+ const row = this.#rowEntry(rowKey)?.row;
1376
1452
  if (!row) return;
1377
1453
  const cols = this._visibleColumns;
1378
1454
  let next = cols.findIndex((c) => c.key === curColKey) + dir;
@@ -1393,7 +1469,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1393
1469
  this.requestUpdate();
1394
1470
  }
1395
1471
  _commitImmediate(rowKey, colKey, value) {
1396
- const row = this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey);
1472
+ const row = this.#rowEntry(rowKey)?.row;
1397
1473
  if (!row) return;
1398
1474
  const oldValue = row[colKey];
1399
1475
  if (oldValue === value) return;
@@ -1516,11 +1592,19 @@ let DataGridElement = class DataGridElement extends LitElement {
1516
1592
  window.addEventListener(`pointermove`, onMove);
1517
1593
  window.addEventListener(`pointerup`, onUp);
1518
1594
  }
1519
- getVisibleRange() {
1595
+ /** Flat-index window `[start, end)` intersecting the viewport, widened by `overscan` rows each side. */
1596
+ #windowRows(overscan) {
1520
1597
  const rh = this.rowHeight;
1521
1598
  return {
1522
- startRow: Math.max(0, Math.floor(this._scrollTop / rh) - 2),
1523
- endRow: Math.min(this._flat.length, Math.ceil((this._scrollTop + this._viewportH) / rh) + 2),
1599
+ start: Math.max(0, Math.floor(this._scrollTop / rh) - overscan),
1600
+ end: Math.min(this._flat.length, Math.ceil((this._scrollTop + this._viewportH) / rh) + overscan)
1601
+ };
1602
+ }
1603
+ getVisibleRange() {
1604
+ const { start: startRow, end: endRow } = this.#windowRows(2);
1605
+ return {
1606
+ startRow,
1607
+ endRow,
1524
1608
  startCol: this._findFirstVisibleCol(this._scrollLeft),
1525
1609
  endCol: this._findLastVisibleCol(this._scrollLeft + this._viewportW)
1526
1610
  };
@@ -1535,10 +1619,8 @@ let DataGridElement = class DataGridElement extends LitElement {
1535
1619
  * `ixfx-grid-list.visibleKeys()` for the `datagrid-viewport-change` detail.
1536
1620
  */
1537
1621
  #visibleKeys() {
1538
- const rh = this.rowHeight;
1539
- const startRow = Math.max(0, Math.floor(this._scrollTop / rh));
1540
- const endRow = Math.min(this._flat.length, Math.ceil((this._scrollTop + this._viewportH) / rh));
1541
- return this._flat.slice(startRow, endRow).filter((f) => f.type === `row`).map((f) => f.key);
1622
+ const { start, end } = this.#windowRows(0);
1623
+ return this._flat.slice(start, end).filter((f) => f.type === `row`).map((f) => f.key);
1542
1624
  }
1543
1625
  /** Coalesce viewport-change dispatches to at most one per animation frame. */
1544
1626
  #scheduleViewportChangeEmit() {
@@ -1587,17 +1669,28 @@ let DataGridElement = class DataGridElement extends LitElement {
1587
1669
  }
1588
1670
  render() {
1589
1671
  const rh = this.rowHeight;
1590
- const startRow = Math.max(0, Math.floor(this._scrollTop / rh) - 2);
1591
- const endRow = Math.min(this._flat.length, Math.ceil((this._scrollTop + this._viewportH) / rh) + 2);
1592
- const visibleFlat = this._flat.slice(startRow, endRow);
1672
+ const { start: startRow, end: endRow } = this.#windowRows(2);
1673
+ const visibleFlat = this._flat.slice(startRow, endRow).map((entry, i) => ({
1674
+ entry,
1675
+ absIdx: startRow + i
1676
+ }));
1677
+ const pinnedKey = this._editing?.rowKey ?? this._editingRow;
1678
+ const pinnedIdx = pinnedKey ? this.#flatIndexByKey.get(pinnedKey) : void 0;
1679
+ if (pinnedIdx !== void 0) {
1680
+ if (pinnedIdx >= startRow && pinnedIdx < endRow) visibleFlat.splice(pinnedIdx - startRow, 1);
1681
+ visibleFlat.unshift({
1682
+ entry: this._flat[pinnedIdx],
1683
+ absIdx: pinnedIdx
1684
+ });
1685
+ }
1593
1686
  const showCheckbox = this.interactionMode === `checked`;
1594
1687
  return html`
1595
- <div class="grid">
1688
+ <div class="grid" role="grid" aria-rowcount=${this._flat.length + 1} aria-colcount=${this._visibleColumns.length} aria-multiselectable=${this.selectionMode === `multiple` ? `true` : `false`}>
1596
1689
  <div class="header-bar">
1597
- <div class="header-scroll scroll-fade-x">
1598
- ${showCheckbox ? html`<div class="header-cell checkbox-col"></div>` : nothing}
1690
+ <div class="header-scroll scroll-fade-x" role="row" aria-rowindex="1">
1691
+ ${showCheckbox ? html`<div class="header-cell checkbox-col" role="columnheader"></div>` : nothing}
1599
1692
  ${repeat(this._visibleColumns, (c) => c.key, (col, idx) => html`
1600
- <div class="header-cell ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" style="width:${col.width ?? 150}px" ?drag-source=${this._drag?.active && this._drag.sourceCol.key === col.key}
1693
+ <div class="header-cell ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" role="columnheader" aria-sort=${this.sortState?.key === col.key ? this.sortState.direction === `asc` ? `ascending` : `descending` : `none`} style="width:${col.width ?? 150}px" ?drag-source=${this._drag?.active && this._drag.sourceCol.key === col.key}
1601
1694
  @click=${(e) => this._onHeaderClick(col, e)}
1602
1695
  @pointerenter=${() => this._tickled.handlePointerEnter(col.key)}>
1603
1696
  ${col.reorderable !== false ? html`<span class="reorder-handle" draggable="false"
@@ -1620,7 +1713,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1620
1713
  return last >= 0 ? this._colOffsets[last] + (this._visibleColumns[last].width ?? 150) : 0;
1621
1714
  })()}px"></div>` : nothing}
1622
1715
  </div>
1623
- ${this.columnMenu === `none` ? nothing : html`
1716
+ ${this.columnMenu !== `auto` ? nothing : html`
1624
1717
  <ixfx-menu-trigger label="⚙" placement="bottom-end" @menu-command=${(e) => {
1625
1718
  const cmd = e.detail.command;
1626
1719
  const checked = e.detail.checked;
@@ -1664,12 +1757,11 @@ let DataGridElement = class DataGridElement extends LitElement {
1664
1757
  </div>
1665
1758
  <div class="viewport scroll-fade-y" @scroll=${this._onScroll}>
1666
1759
  <div class="spacer" style="height:${this._flat.length * rh}px; width:${this._colWidthSum}px">
1667
- ${repeat(visibleFlat, (f) => f.key, (entry, i) => {
1668
- const absIdx = startRow + i;
1760
+ ${repeat(visibleFlat, (f) => f.entry.key, ({ entry, absIdx }) => {
1669
1761
  if (entry.type === `group`) {
1670
1762
  const gKey = entry.key.replace(/^__group__/, ``);
1671
1763
  const collapsed = this._collapsedGroups.has(gKey);
1672
- return html`<div class="group-row ${collapsed ? `collapsed` : ``}" style="top:${absIdx * rh}px; height:${rh}px" @click=${() => this._toggleGroup(gKey)}><span class="caret"><ixfx-icon name="chevron-down" size="12"></ixfx-icon></span><span>${entry.label}</span><span class="count">(${entry.count})</span><span class="divider"></span></div>`;
1764
+ return html`<div class="group-row ${collapsed ? `collapsed` : ``}" role="row" aria-rowindex=${absIdx + 2} style="top:${absIdx * rh}px; height:${rh}px" @click=${() => this._toggleGroup(gKey)}><span class="caret"><ixfx-icon name="chevron-down" size="12"></ixfx-icon></span><span role="rowheader" aria-expanded=${collapsed ? `false` : `true`}>${entry.label}</span><span class="count">(${entry.count})</span><span class="divider"></span></div>`;
1673
1765
  }
1674
1766
  const row = entry.row;
1675
1767
  const absIdx2 = entry.idx;
@@ -1677,7 +1769,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1677
1769
  const selected = this._selectedKeys.has(rowKey);
1678
1770
  const tickled = this._tickled.isTickled(rowKey);
1679
1771
  return html`
1680
- <div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${this.zebraStripes && absIdx2 % 2 === 1 ? `zebra-odd` : ``} ${this.#enteringRowKeys.has(rowKey) ? `entering` : ``} ${this.#nativeDragging && rowKey === this.#dragOriginKey ? `dragging-origin` : ``}" style="top:${absIdx * rh}px; height:${rh}px"
1772
+ <div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${this.zebraStripes && absIdx2 % 2 === 1 ? `zebra-odd` : ``} ${this.#enteringRowKeys.has(rowKey) ? `entering` : ``} ${this.#nativeDragging && rowKey === this.#dragOriginKey ? `dragging-origin` : ``}" role="row" aria-rowindex=${absIdx + 2} aria-selected=${selected ? `true` : `false`} style="top:${absIdx * rh}px; height:${rh}px"
1681
1773
  data-key=${rowKey}
1682
1774
  draggable=${this.#nativeDrag.draggableAttr}
1683
1775
  @click=${(e) => {
@@ -1706,7 +1798,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1706
1798
  const immediate = this.editability === `immediate` || this._editingRow === rowKey;
1707
1799
  const curVal = immediate ? val : this._editValue ?? val;
1708
1800
  const effectiveDataType = col.dataType ?? `text`;
1709
- if (effectiveDataType === `boolean` && (col.editor === `auto` || col.editor === `select` || !col.editor)) return html`<div class="cell" data-col=${col.key} style="width:${col.width ?? 150}px;justify-content:center;align-items:center"><input type="checkbox" .checked=${Boolean(curVal)} @change=${(e) => {
1801
+ if (effectiveDataType === `boolean` && (col.editor === `auto` || col.editor === `select` || !col.editor)) return html`<div class="cell" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px;justify-content:center;align-items:center"><input type="checkbox" .checked=${Boolean(curVal)} @change=${(e) => {
1710
1802
  const v = e.target.checked;
1711
1803
  if (immediate) this._commitImmediate(rowKey, col.key, v);
1712
1804
  else {
@@ -1716,7 +1808,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1716
1808
  }} /></div>`;
1717
1809
  if (col.editor === `select`) {
1718
1810
  const opts = col.editorOptions ?? [];
1719
- return html`<div class="cell" data-col=${col.key} style="width:${col.width ?? 150}px"><select @change=${(e) => {
1811
+ return html`<div class="cell" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px"><select @change=${(e) => {
1720
1812
  const v = e.target.value;
1721
1813
  if (immediate) this._commitImmediate(rowKey, col.key, v);
1722
1814
  else {
@@ -1762,11 +1854,11 @@ let DataGridElement = class DataGridElement extends LitElement {
1762
1854
  this._editingRow = null;
1763
1855
  }
1764
1856
  };
1765
- return html`<div class="cell" data-col=${col.key} style="width:${col.width ?? 150}px">${factory(ctx)}</div>`;
1857
+ return html`<div class="cell" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px">${factory(ctx)}</div>`;
1766
1858
  }
1767
1859
  const inputType = effectiveDataType === `number` ? `number` : `text`;
1768
1860
  const inputValue = effectiveDataType === `number` ? String(curVal ?? ``) : String(curVal ?? ``);
1769
- return html`<div class="cell" data-col=${col.key} style="width:${col.width ?? 150}px"><input type=${inputType} .value=${inputValue} @input=${(e) => this._editValue = e.target.value} @keydown=${(e) => {
1861
+ return html`<div class="cell" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px"><input type=${inputType} .value=${inputValue} @input=${(e) => this._editValue = e.target.value} @keydown=${(e) => {
1770
1862
  if (e.key === `Enter`) this._commitEdit();
1771
1863
  if (e.key === `Escape`) {
1772
1864
  this._editing = null;
@@ -1790,7 +1882,7 @@ let DataGridElement = class DataGridElement extends LitElement {
1790
1882
  const canPan = this.cellOverflow === `pan-text` && !col.renderer && (selected || tickled);
1791
1883
  const look = this._cellLook(row, col.key);
1792
1884
  const cellStyle = look.textColor === void 0 ? `` : `color:${look.textColor};`;
1793
- return html`<div class="cell ${textEditable ? `editable-text` : ``} ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" data-col=${col.key} style="width:${col.width ?? 150}px; justify-content:${col.align === `right` ? `flex-end` : col.align === `center` ? `center` : `flex-start`}; ${indent} ${cellStyle}"
1885
+ return html`<div class="cell ${textEditable ? `editable-text` : ``} ${this._animatingColKeys.has(col.key) ? `width-animating` : ``}" role="gridcell" data-col=${col.key} style="width:${col.width ?? 150}px; justify-content:${col.align === `right` ? `flex-end` : col.align === `center` ? `center` : `flex-start`}; ${indent} ${cellStyle}"
1794
1886
  @click=${(e) => {
1795
1887
  this._lastClickedCell = {
1796
1888
  rowKey,
@@ -1905,8 +1997,6 @@ __decorate([property({
1905
1997
  __decorate([state()], DataGridElement.prototype, "_resizingColKey", void 0);
1906
1998
  __decorate([state()], DataGridElement.prototype, "_animatingColKeys", void 0);
1907
1999
  __decorate([state()], DataGridElement.prototype, "_selectedKeys", void 0);
1908
- __decorate([state()], DataGridElement.prototype, "_scrollTop", void 0);
1909
- __decorate([state()], DataGridElement.prototype, "_scrollLeft", void 0);
1910
2000
  __decorate([state()], DataGridElement.prototype, "_viewportH", void 0);
1911
2001
  __decorate([state()], DataGridElement.prototype, "_viewportW", void 0);
1912
2002
  __decorate([state()], DataGridElement.prototype, "_collapsedGroups", void 0);