@ixfx/components 0.5.19 → 0.6.0

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 (198) hide show
  1. package/bundle/index.d.ts +462 -210
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +8027 -6493
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-text.js +3 -3
  6. package/dist/ac-token-DVbmHmky.d.ts +107 -0
  7. package/dist/ac-token-DVbmHmky.d.ts.map +1 -0
  8. package/dist/ac-token.d.ts +2 -0
  9. package/dist/ac-token.js +646 -0
  10. package/dist/ac-token.js.map +1 -0
  11. package/dist/{button-CphdEaZd.js → button-Bn0BuLpO.js} +2 -3
  12. package/dist/{button-CphdEaZd.js.map → button-Bn0BuLpO.js.map} +1 -1
  13. package/dist/button.d.ts +1 -1
  14. package/dist/button.d.ts.map +1 -1
  15. package/dist/button.js +1 -1
  16. package/dist/checkbox.js +1 -1
  17. package/dist/{colour-picker-37NyVenk.js → colour-picker-0stHB90J.js} +18 -8
  18. package/dist/colour-picker-0stHB90J.js.map +1 -0
  19. package/dist/colour-picker.d.ts +1 -1
  20. package/dist/colour-picker.js +1 -1
  21. package/dist/crumbs.d.ts +2 -2
  22. package/dist/crumbs.js +1152 -1
  23. package/dist/crumbs.js.map +1 -0
  24. package/dist/data-controller-DOlmqNKb.js +171 -0
  25. package/dist/data-controller-DOlmqNKb.js.map +1 -0
  26. package/dist/data-display.js +1 -1
  27. package/dist/data-grid-BeAkORz2.d.ts +215 -0
  28. package/dist/data-grid-BeAkORz2.d.ts.map +1 -0
  29. package/dist/data-grid.d.ts +2 -0
  30. package/dist/data-grid.js +1225 -0
  31. package/dist/data-grid.js.map +1 -0
  32. package/dist/data-provider-BAn_-oR1.d.ts +61 -0
  33. package/dist/data-provider-BAn_-oR1.d.ts.map +1 -0
  34. package/dist/{defaults-BkWCPJFL.js → defaults-BCiDhZbF.js} +20 -3
  35. package/dist/defaults-BCiDhZbF.js.map +1 -0
  36. package/dist/drag-ghost-DtSDvjri.js +85 -0
  37. package/dist/drag-ghost-DtSDvjri.js.map +1 -0
  38. package/dist/editable-label.js +1 -1
  39. package/dist/{incr-search-2pz21xtR.js → element-search-Y8SRfPGM.js} +2 -133
  40. package/dist/element-search-Y8SRfPGM.js.map +1 -0
  41. package/dist/fallbacks-DEE1nCTt.js +406 -0
  42. package/dist/fallbacks-DEE1nCTt.js.map +1 -0
  43. package/dist/form-CsXJX15p.js +64 -0
  44. package/dist/form-CsXJX15p.js.map +1 -0
  45. package/dist/form.d.ts +15 -0
  46. package/dist/form.d.ts.map +1 -0
  47. package/dist/form.js +2 -0
  48. package/dist/grouped-item-lister-CNLMgdIr.d.ts +121 -0
  49. package/dist/grouped-item-lister-CNLMgdIr.d.ts.map +1 -0
  50. package/dist/grouped-item-lister.d.ts +2 -0
  51. package/dist/grouped-item-lister.js +177 -0
  52. package/dist/grouped-item-lister.js.map +1 -0
  53. package/dist/{icon-DG9Lm3LT.js → icons-Bm2ByQ3s.js} +6 -3
  54. package/dist/icons-Bm2ByQ3s.js.map +1 -0
  55. package/dist/icons.js +2 -8
  56. package/dist/incr-search-vCzuao-2.js +137 -0
  57. package/dist/incr-search-vCzuao-2.js.map +1 -0
  58. package/dist/incr-search.d.ts +1 -1
  59. package/dist/incr-search.js +2 -1
  60. package/dist/{index-BKAlRgbq.d.ts → index-BOUaDIW-.d.ts} +4 -1
  61. package/dist/index-BOUaDIW-.d.ts.map +1 -0
  62. package/dist/{index-D9u_Q7-b.d.ts → index-Cu5i8zZY.d.ts} +3 -3
  63. package/dist/{index-D9u_Q7-b.d.ts.map → index-Cu5i8zZY.d.ts.map} +1 -1
  64. package/dist/{index-DkhBGHgF.d.ts → index-ovq_GTdl.d.ts} +2 -2
  65. package/dist/{index-DkhBGHgF.d.ts.map → index-ovq_GTdl.d.ts.map} +1 -1
  66. package/dist/index.d.ts +54 -572
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +1898 -4253
  69. package/dist/index.js.map +1 -1
  70. package/dist/labelled-radial-input.d.ts +1 -1
  71. package/dist/labelled-radial-input.js +1 -2
  72. package/dist/labelled-radial-input.js.map +1 -1
  73. package/dist/led.d.ts +0 -1
  74. package/dist/led.d.ts.map +1 -1
  75. package/dist/led.js +2 -4
  76. package/dist/led.js.map +1 -1
  77. package/dist/menu-DIfU70AA.js +1609 -0
  78. package/dist/menu-DIfU70AA.js.map +1 -0
  79. package/dist/menu-item-BG5w7fi4.d.ts.map +1 -1
  80. package/dist/menu.d.ts +1 -1
  81. package/dist/menu.js +1 -2
  82. package/dist/miller.d.ts +3 -3
  83. package/dist/miller.js +3 -3
  84. package/dist/narrowed-text.d.ts.map +1 -1
  85. package/dist/narrowed-text.js +4 -4
  86. package/dist/narrowed-text.js.map +1 -1
  87. package/dist/notification-pill-DyZydXoZ.d.ts +68 -0
  88. package/dist/notification-pill-DyZydXoZ.d.ts.map +1 -0
  89. package/dist/notifier.d.ts +2 -0
  90. package/dist/notifier.js +146 -0
  91. package/dist/notifier.js.map +1 -0
  92. package/dist/panel.js +1 -2
  93. package/dist/panel.js.map +1 -1
  94. package/dist/plots.js +1 -1
  95. package/dist/polar-pad.d.ts +1 -1
  96. package/dist/polar-pad.js +1 -1
  97. package/dist/{radial-input-BhZeyyP-.d.ts → radial-input-DXMN55Nz.d.ts} +2 -2
  98. package/dist/{radial-input-BhZeyyP-.d.ts.map → radial-input-DXMN55Nz.d.ts.map} +1 -1
  99. package/dist/{radial-input-CVxGH-Cp.js → radial-input-Dk8Wydp5.js} +4 -4
  100. package/dist/radial-input-Dk8Wydp5.js.map +1 -0
  101. package/dist/radial-input.d.ts +1 -1
  102. package/dist/radial-input.js +1 -1
  103. package/dist/range-input.d.ts +1 -1
  104. package/dist/range-input.js +1 -1
  105. package/dist/range.d.ts +1 -1
  106. package/dist/range.js +1 -1
  107. package/dist/scroll-fade-CAs1EabA.js +70 -0
  108. package/dist/scroll-fade-CAs1EabA.js.map +1 -0
  109. package/dist/select-horiz.d.ts +50 -0
  110. package/dist/select-horiz.d.ts.map +1 -0
  111. package/dist/select-horiz.js +263 -0
  112. package/dist/select-horiz.js.map +1 -0
  113. package/dist/{slider-input-CzmSVgm7.js → slider-input-CT4gcF-o.js} +2 -2
  114. package/dist/{slider-input-CzmSVgm7.js.map → slider-input-CT4gcF-o.js.map} +1 -1
  115. package/dist/{slider-input-TidGhvXt.d.ts → slider-input-XppH3vcy.d.ts} +2 -2
  116. package/dist/{slider-input-TidGhvXt.d.ts.map → slider-input-XppH3vcy.d.ts.map} +1 -1
  117. package/dist/slider-input.d.ts +1 -1
  118. package/dist/slider-input.js +1 -1
  119. package/dist/snackbar.d.ts +58 -0
  120. package/dist/snackbar.d.ts.map +1 -0
  121. package/dist/snackbar.js +265 -0
  122. package/dist/snackbar.js.map +1 -0
  123. package/dist/split-layout.js +1 -1
  124. package/dist/swipe.d.ts +1 -1
  125. package/dist/swipe.js +1 -1
  126. package/dist/tabs.js +533 -1
  127. package/dist/tabs.js.map +1 -0
  128. package/dist/{tickled-controller-BF2Ea0DE.d.ts → tickled-controller-BtevrRLV.d.ts} +8 -6
  129. package/dist/tickled-controller-BtevrRLV.d.ts.map +1 -0
  130. package/dist/{tickled-styles-fNDdqf6l.js → tickled-controller-DkAU-0qL.js} +19 -105
  131. package/dist/tickled-controller-DkAU-0qL.js.map +1 -0
  132. package/dist/tickled-styles-BrAHEj1q.js +89 -0
  133. package/dist/tickled-styles-BrAHEj1q.js.map +1 -0
  134. package/dist/transitory-label.d.ts +30 -0
  135. package/dist/transitory-label.d.ts.map +1 -0
  136. package/dist/transitory-label.js +249 -0
  137. package/dist/transitory-label.js.map +1 -0
  138. package/dist/{tree-BtiiDX1a.js → tree-BO6BXI2b.js} +4 -4
  139. package/dist/{tree-BtiiDX1a.js.map → tree-BO6BXI2b.js.map} +1 -1
  140. package/dist/{tree-component-DQ9vzdy5.d.ts → tree-component-BvV4muxx.d.ts} +2 -2
  141. package/dist/{tree-component-DQ9vzdy5.d.ts.map → tree-component-BvV4muxx.d.ts.map} +1 -1
  142. package/dist/tree.d.ts +3 -3
  143. package/dist/tree.js +1 -1
  144. package/dist/{types-DO3gt5vg.d.ts → types-Bc23SI94.d.ts} +3 -61
  145. package/dist/types-Bc23SI94.d.ts.map +1 -0
  146. package/dist/types-DlVNJez_.d.ts +41 -0
  147. package/dist/types-DlVNJez_.d.ts.map +1 -0
  148. package/dist/vertical-list.d.ts +122 -0
  149. package/dist/vertical-list.d.ts.map +1 -0
  150. package/dist/vertical-list.js +737 -0
  151. package/dist/vertical-list.js.map +1 -0
  152. package/dist/wheel-nudge-BlSCe_hD.js.map +1 -1
  153. package/dist/{wheel-nudge-DgMTRYlh.d.ts → wheel-nudge-DGrNzWuE.d.ts} +3 -3
  154. package/dist/wheel-nudge-DGrNzWuE.d.ts.map +1 -0
  155. package/dist/{xy-axis-CxHC8z5E.js → xy-axis-B6yMjckW.js} +3 -109
  156. package/dist/xy-axis-B6yMjckW.js.map +1 -0
  157. package/dist/xy-pad.d.ts +1 -1
  158. package/dist/xy-pad.js +1 -1
  159. package/docs-user/README.md +3 -2
  160. package/docs-user/ac-token.md +7 -1
  161. package/docs-user/colour-picker.md +8 -4
  162. package/docs-user/data-grid.md +248 -0
  163. package/docs-user/index.json +217 -0
  164. package/docs-user/llms.txt +57 -0
  165. package/docs-user/menu.md +11 -8
  166. package/docs-user/rating.md +102 -0
  167. package/llms.txt +57 -0
  168. package/package.json +21 -11
  169. package/dist/colour-picker-37NyVenk.js.map +0 -1
  170. package/dist/crumb-navigation-BrB0r14u.js +0 -1220
  171. package/dist/crumb-navigation-BrB0r14u.js.map +0 -1
  172. package/dist/defaults-BkWCPJFL.js.map +0 -1
  173. package/dist/fallbacks-DPgPEqo8.js +0 -218
  174. package/dist/fallbacks-DPgPEqo8.js.map +0 -1
  175. package/dist/icon-DG9Lm3LT.js.map +0 -1
  176. package/dist/icons.js.map +0 -1
  177. package/dist/incr-search-2pz21xtR.js.map +0 -1
  178. package/dist/index-BKAlRgbq.d.ts.map +0 -1
  179. package/dist/interaction-D6efrBLo.js +0 -1
  180. package/dist/menu-CdwSe_XN.js +0 -825
  181. package/dist/menu-CdwSe_XN.js.map +0 -1
  182. package/dist/menu-item-CrmUwIoJ.js +0 -782
  183. package/dist/menu-item-CrmUwIoJ.js.map +0 -1
  184. package/dist/radial-input-CVxGH-Cp.js.map +0 -1
  185. package/dist/registry-CkW7q09Q.js +0 -21
  186. package/dist/registry-CkW7q09Q.js.map +0 -1
  187. package/dist/selecthorizontal.d.ts +0 -15
  188. package/dist/selecthorizontal.d.ts.map +0 -1
  189. package/dist/selecthorizontal.js +0 -43
  190. package/dist/selecthorizontal.js.map +0 -1
  191. package/dist/tab-list-mAoD6L0e.js +0 -615
  192. package/dist/tab-list-mAoD6L0e.js.map +0 -1
  193. package/dist/tickled-controller-BF2Ea0DE.d.ts.map +0 -1
  194. package/dist/tickled-styles-fNDdqf6l.js.map +0 -1
  195. package/dist/types-DO3gt5vg.d.ts.map +0 -1
  196. package/dist/wheel-nudge-DgMTRYlh.d.ts.map +0 -1
  197. package/dist/xy-axis-CxHC8z5E.js.map +0 -1
  198. package/docs-user/selecthorizontal.md +0 -32
@@ -0,0 +1,1225 @@
1
+ import { t as TickledController } from "./tickled-controller-DkAU-0qL.js";
2
+ import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrAHEj1q.js";
3
+ import { t as themeFallbacks } from "./fallbacks-DEE1nCTt.js";
4
+ import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
5
+ import { t as createDragGhost } from "./drag-ghost-DtSDvjri.js";
6
+ import { LitElement, css, html, nothing } from "lit";
7
+ import { property, query, state } from "lit/decorators.js";
8
+ import { repeat } from "lit/directives/repeat.js";
9
+ //#region src/data-grid/data-grid.ts
10
+ let DataGridElement = class DataGridElement extends LitElement {
11
+ constructor(..._args) {
12
+ super(..._args);
13
+ this.columns = [];
14
+ this.rows = [];
15
+ this.selectionMode = `single`;
16
+ this.interactionMode = `standard`;
17
+ this.sortState = null;
18
+ this.rowHeight = 28;
19
+ this.getRowKey = (r, i) => r.key ?? r.id ?? String(i);
20
+ this.editable = false;
21
+ this.editability = `user`;
22
+ this.groupBy = null;
23
+ this.allowGrouping = false;
24
+ this.crossGroupSelection = false;
25
+ this._columnFormatters = /* @__PURE__ */ new Map();
26
+ this._selectedKeys = /* @__PURE__ */ new Set();
27
+ this._scrollTop = 0;
28
+ this._scrollLeft = 0;
29
+ this._viewportH = 300;
30
+ this._viewportW = 600;
31
+ this._collapsedGroups = /* @__PURE__ */ new Set();
32
+ this._editing = null;
33
+ this._editingRow = null;
34
+ this._lastClick = null;
35
+ this._lastClickedCell = null;
36
+ this._editValue = ``;
37
+ this._tickled = new TickledController(this, {
38
+ getItems: () => this._flatKeys.map((k) => ({
39
+ id: k,
40
+ enabled: true
41
+ })),
42
+ onStateChange: () => this.requestUpdate()
43
+ });
44
+ this._sortedRows = [];
45
+ this._flat = [];
46
+ this._flatKeys = [];
47
+ this._visibleColumns = [];
48
+ this._colOffsets = [];
49
+ this._colWidthSum = 0;
50
+ this._raf = 0;
51
+ this._drag = null;
52
+ this._onFocusOut = (e) => {
53
+ if (this._suppressBlurCommit) return;
54
+ if (this.editability === `immediate`) return;
55
+ if (!this._editing && !this._editingRow) return;
56
+ const next = e.relatedTarget;
57
+ if (next && this.contains(next)) return;
58
+ this._editing = null;
59
+ this._editingRow = null;
60
+ this.requestUpdate();
61
+ };
62
+ this._onScroll = (e) => {
63
+ const el = e.target;
64
+ cancelAnimationFrame(this._raf);
65
+ this._raf = requestAnimationFrame(() => {
66
+ this._scrollTop = el.scrollTop;
67
+ this._scrollLeft = el.scrollLeft;
68
+ if (this._headerScrollEl) this._headerScrollEl.scrollLeft = el.scrollLeft;
69
+ this.requestUpdate();
70
+ });
71
+ };
72
+ this._onKeyDown = (e) => {
73
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === `a`) {
74
+ if (this.selectionMode === `multiple` || this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `vscode` || this.interactionMode === `standard`) {
75
+ e.preventDefault();
76
+ const prev = new Set(this._selectedKeys);
77
+ if (this.groupBy && !this.crossGroupSelection && this._anchorKey) {
78
+ const g = this._getGroupKey(this._getRowForKey(this._anchorKey) ?? { key: this._anchorKey });
79
+ this._selectedKeys = new Set(this._flat.filter((f) => f.type === `row` && this._getGroupKey(f.row) === g).map((f) => f.key));
80
+ } else this._selectedKeys = new Set(this._flat.filter((f) => f.type === `row`).map((f) => f.key));
81
+ this.dispatchEvent(new CustomEvent(`datagrid-select`, {
82
+ detail: {
83
+ selectedKeys: new Set(this._selectedKeys),
84
+ previous: prev
85
+ },
86
+ bubbles: true,
87
+ composed: true
88
+ }));
89
+ }
90
+ return;
91
+ }
92
+ if (this._editing && (e.key === `ArrowDown` || e.key === `ArrowUp`)) {
93
+ if ((this.shadowRoot?.activeElement)?.tagName === `SELECT`) return;
94
+ e.preventDefault();
95
+ const cur = this._editing;
96
+ const dir = e.key === `ArrowDown` ? 1 : -1;
97
+ const colKey = cur.colKey;
98
+ this._suppressBlurCommit = true;
99
+ this._commitEdit();
100
+ const rowKeys = this._flat.filter((f) => f.type === `row`).map((f) => f.key);
101
+ const idx = rowKeys.indexOf(cur.rowKey);
102
+ let nextIdx = idx + dir;
103
+ while (nextIdx >= 0 && nextIdx < rowKeys.length) {
104
+ const nk = rowKeys[nextIdx];
105
+ const row = this._sortedRows.find((r, i) => this.getRowKey(r, i) === nk);
106
+ const col = this._visibleColumns.find((c) => c.key === colKey);
107
+ if (row && col && this._isEditable(row, col)) {
108
+ this._startEdit(nk, colKey);
109
+ this._tickled.handleKeyboardNavigation(nextIdx > idx ? `next` : `previous`);
110
+ this._scrollRowIntoView(nk);
111
+ break;
112
+ }
113
+ nextIdx += dir;
114
+ }
115
+ if (nextIdx < 0 || nextIdx >= rowKeys.length) {
116
+ this._tickled.handleKeyboardNavigation(dir > 0 ? `next` : `previous`);
117
+ if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
118
+ }
119
+ requestAnimationFrame(() => {
120
+ this._suppressBlurCommit = false;
121
+ });
122
+ return;
123
+ }
124
+ if (e.key === `ArrowDown` || e.key === `ArrowUp` || e.key === `Home` || e.key === `End`) {
125
+ e.preventDefault();
126
+ this._tickled.setInputMode(`keyboard`);
127
+ }
128
+ switch (e.key) {
129
+ case `ArrowDown`:
130
+ this._tickled.handleKeyboardNavigation(`next`);
131
+ if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
132
+ if (this.interactionMode === `implicit` && this._tickled.tickledId) this._replaceSelection(this._tickled.tickledId);
133
+ if (e.shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`) && this._tickled.tickledId) this._rangeSelectTo(this._tickled.tickledId);
134
+ break;
135
+ case `ArrowUp`:
136
+ this._tickled.handleKeyboardNavigation(`previous`);
137
+ if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
138
+ if (this.interactionMode === `implicit` && this._tickled.tickledId) this._replaceSelection(this._tickled.tickledId);
139
+ if (e.shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`) && this._tickled.tickledId) this._rangeSelectTo(this._tickled.tickledId);
140
+ break;
141
+ case `Home`:
142
+ this._tickled.handleKeyboardNavigation(`first`);
143
+ if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
144
+ break;
145
+ case `End`:
146
+ this._tickled.handleKeyboardNavigation(`last`);
147
+ if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
148
+ break;
149
+ case `Enter`: {
150
+ const key = this._tickled.tickledId;
151
+ if (!key) break;
152
+ e.preventDefault();
153
+ if (!this._editing && this.editability === `user`) {
154
+ const idx = this._sortedRows.findIndex((r, i) => this.getRowKey(r, i) === key);
155
+ if (idx >= 0) {
156
+ const row = this._sortedRows[idx];
157
+ if (this._lastClickedCell?.rowKey === key) {
158
+ const col = this._visibleColumns.find((c) => c.key === this._lastClickedCell.colKey);
159
+ if (col && this._isEditable(row, col)) {
160
+ this._startEdit(key, col.key);
161
+ break;
162
+ }
163
+ }
164
+ const firstEditable = this._visibleColumns.find((c) => this._isEditable(row, c));
165
+ if (firstEditable) {
166
+ this._startEdit(key, firstEditable.key);
167
+ break;
168
+ }
169
+ }
170
+ }
171
+ if (!this._editing) {
172
+ const idx = this._sortedRows.findIndex((r, i) => this.getRowKey(r, i) === key);
173
+ if (this.interactionMode !== `manual` && this.interactionMode !== `checked`) this._replaceSelection(key);
174
+ this.dispatchEvent(new CustomEvent(`datagrid-cell-click`, {
175
+ detail: {
176
+ rowKey: key,
177
+ colKey: ``,
178
+ rowIndex: idx,
179
+ colIndex: -1
180
+ },
181
+ bubbles: true,
182
+ composed: true
183
+ }));
184
+ }
185
+ break;
186
+ }
187
+ case ` `: {
188
+ if (this._editing) {
189
+ if (this.columns.find((c) => c.key === this._editing.colKey)?.editor === `boolean`) {
190
+ e.preventDefault();
191
+ const isImmediate = this.editability === `immediate`;
192
+ const rowKey = this._editing.rowKey;
193
+ const colKey = this._editing.colKey;
194
+ const active = this.shadowRoot?.activeElement;
195
+ let next;
196
+ if (active?.type === `checkbox`) {
197
+ next = !active.checked;
198
+ active.checked = next;
199
+ } else {
200
+ const row = this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey);
201
+ next = !(this._editValue !== `` ? Boolean(this._editValue) : Boolean(row?.[colKey]));
202
+ }
203
+ if (isImmediate) this._commitImmediate(rowKey, colKey, next);
204
+ else {
205
+ this._editValue = next;
206
+ this._commitEdit();
207
+ }
208
+ }
209
+ break;
210
+ }
211
+ const key = this._tickled.tickledId;
212
+ if (!key) break;
213
+ e.preventDefault();
214
+ if (this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `standard` || this.interactionMode === `vscode` || this.interactionMode === `manual`) this._toggleSelection(key);
215
+ else this._replaceSelection(key);
216
+ break;
217
+ }
218
+ case `Escape`: if (this._editing || this._editingRow) {
219
+ e.preventDefault();
220
+ this._editing = null;
221
+ this._editingRow = null;
222
+ this.requestUpdate();
223
+ } else if (this._selectedKeys.size) {
224
+ e.preventDefault();
225
+ const prev = new Set(this._selectedKeys);
226
+ this._selectedKeys = /* @__PURE__ */ new Set();
227
+ this.dispatchEvent(new CustomEvent(`datagrid-select`, {
228
+ detail: {
229
+ selectedKeys: /* @__PURE__ */ new Set(),
230
+ previous: prev
231
+ },
232
+ bubbles: true,
233
+ composed: true
234
+ }));
235
+ }
236
+ }
237
+ };
238
+ this._suppressBlurCommit = false;
239
+ }
240
+ static {
241
+ this.styles = [
242
+ themeFallbacks,
243
+ tickledStyles,
244
+ tickledItemStyles,
245
+ css`
246
+ :host {
247
+ --data-grid-row-height: 28px;
248
+ --data-grid-header-height: 32px;
249
+ --data-grid-bg: var(--surface-3);
250
+ --data-grid-header-bg: var(--surface-2);
251
+ --data-grid-border: var(--border);
252
+ --data-grid-header-text: var(--surface-3-text);
253
+ display: block;
254
+ overflow: hidden;
255
+ border: var(--data-grid-border);
256
+ border-radius: var(--radius-m);
257
+ background: var(--data-grid-bg);
258
+ color: var(--surface-3-text);
259
+ font-family: var(--font-family);
260
+ font-size: var(--text-m);
261
+ }
262
+ :host(:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
263
+ .grid { display: flex; flex-direction: column; height: 100%; min-height: 200px; position: relative; }
264
+ .header-bar {
265
+ display: flex; align-items: stretch; height: var(--data-grid-header-height);
266
+ background: var(--data-grid-header-bg); border-bottom: var(--data-grid-border);
267
+ overflow: visible; flex-shrink: 0; position: relative;
268
+ }
269
+ .header-scroll {
270
+ display: flex; flex: 1; overflow: hidden;
271
+ }
272
+ .header-cell {
273
+ display: flex; align-items: center; gap: var(--space-xs);
274
+ padding: 0 var(--space-s); border-right: var(--border-subtle);
275
+ flex-shrink: 0; user-select: none; cursor: pointer; position: relative;
276
+ background: var(--data-grid-header-bg);
277
+ box-sizing: border-box;
278
+ }
279
+ .header-cell[data-tickled] { background: var(--surface-5); }
280
+ .header-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
281
+ .sort-icon { font-size: 10px; opacity: 0.7; }
282
+ .resize-handle { position: absolute; right: 0; top: 0; bottom: 0; width: 8px; cursor: col-resize; z-index: 1; }
283
+ .reorder-handle { cursor: grab; opacity: 0.5; padding: 0 2px; }
284
+ .flyout-trigger { flex-shrink: 0; width: 36px; display: flex; align-items: center; justify-content: center; cursor: pointer; border-left: var(--border-subtle); background: var(--data-grid-header-bg); }
285
+ .viewport { flex: 1; overflow: auto; position: relative; }
286
+ .spacer { position: relative; }
287
+ .row.item {
288
+ position: absolute; left: 0; right: 0; display: flex; align-items: stretch;
289
+ gap: 0;
290
+ height: var(--data-grid-row-height);
291
+ border: none;
292
+ box-shadow: inset 0 0 0 1px var(--item-border-color, transparent);
293
+ background: var(--item-bg);
294
+ border-radius: var(--item-border-radius);
295
+ padding: 0;
296
+ box-sizing: border-box;
297
+ }
298
+ .row.item.tickled { background: var(--item-bg-tickled); border: var(--item-border-tickled); color: var(--item-text-tickled); }
299
+ .row.item.selected { background: var(--item-bg-selected); border: var(--item-border-selected); color: var(--item-text-selected); }
300
+ .row.item.selected.tickled { background: var(--item-bg-selected-tickled); }
301
+ .cell {
302
+ flex-shrink: 0; display: flex; align-items: center; padding: 0 var(--space-s);
303
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-right: var(--border-subtle);
304
+ box-sizing: border-box;
305
+ font-variant-numeric: tabular-nums;
306
+ }
307
+ .cell.editable-text { cursor: text; }
308
+ .cell input, .cell select { width: 100%; font: inherit; }
309
+ .checkbox-col { width: 28px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
310
+ ixfx-menu-trigger { flex-shrink: 0; border-left: var(--border-subtle); background: var(--data-grid-header-bg); display: flex; align-items: center; --ixfx-button-padding: var(--space-s) var(--space-m); min-width: 44px; min-height: var(--data-grid-header-height); justify-content: center; }
311
+ .header-cell[drag-source] { opacity: 0.4; }
312
+ .drop-caret {
313
+ position: absolute; top: 0; bottom: 0; width: 2px;
314
+ background: var(--accent); border-radius: 1px; pointer-events: none; z-index: 2;
315
+ }
316
+ .group-row {
317
+ position: absolute; left: 0; right: 0; display: flex; align-items: center; gap: var(--space-s);
318
+ height: var(--data-grid-row-height); background: transparent; color: var(--surface-3-text);
319
+ border-bottom: 1px solid var(--border); font-weight: 600; padding: 0 var(--space-s);
320
+ cursor: pointer; user-select: none;
321
+ }
322
+ .group-row .caret { transition: transform 0.15s; display: inline-flex; }
323
+ .group-row.collapsed .caret { transform: rotate(-90deg); }
324
+ .group-row .divider { flex: 1; height: 1px; background: var(--border); margin-left: var(--space-s); }
325
+ .group-row .count { opacity: 0.6; font-weight: 400; font-size: 0.9em; }
326
+
327
+ `
328
+ ];
329
+ }
330
+ connectedCallback() {
331
+ super.connectedCallback();
332
+ if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
333
+ this.addEventListener(`keydown`, this._onKeyDown);
334
+ this.addEventListener(`focusout`, this._onFocusOut);
335
+ }
336
+ disconnectedCallback() {
337
+ super.disconnectedCallback();
338
+ this.removeEventListener(`keydown`, this._onKeyDown);
339
+ this.removeEventListener(`focusout`, this._onFocusOut);
340
+ cancelAnimationFrame(this._raf);
341
+ }
342
+ firstUpdated() {
343
+ new ResizeObserver(() => {
344
+ if (!this._viewportEl) return;
345
+ this._viewportH = this._viewportEl.clientHeight;
346
+ this._viewportW = this._viewportEl.clientWidth;
347
+ this.requestUpdate();
348
+ }).observe(this._viewportEl);
349
+ this._viewportH = this._viewportEl.clientHeight;
350
+ this._viewportW = this._viewportEl.clientWidth;
351
+ }
352
+ updated(changed) {
353
+ super.updated(changed);
354
+ if (changed.has(`_editing`) || changed.has(`_editingRow`)) requestAnimationFrame(() => {
355
+ let sel = null;
356
+ 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`;
357
+ else if (this._editingRow) sel = `.row[data-key="${this._editingRow}"] .cell input, .row[data-key="${this._editingRow}"] .cell select`;
358
+ if (!sel) return;
359
+ const el = this.renderRoot.querySelector(sel);
360
+ el?.focus();
361
+ if (el instanceof HTMLInputElement && el.type === `text`) el.select();
362
+ });
363
+ }
364
+ willUpdate(changed) {
365
+ super.willUpdate(changed);
366
+ this.setAttribute(`data-input-mode`, this._tickled.inputMode);
367
+ if (changed.has(`rows`) || changed.has(`sortState`) || changed.has(`columns`) || changed.has(`groupBy`) || changed.has(`groupLabel`)) this._sortedRows = this._applySort([...this.rows]);
368
+ this._rebuildFlat();
369
+ this._visibleColumns = this.columns.filter((c) => c.visible !== false);
370
+ let off = 0;
371
+ this._colOffsets = this._visibleColumns.map((c) => {
372
+ const o = off;
373
+ off += c.width ?? 150;
374
+ return o;
375
+ });
376
+ this._colWidthSum = off;
377
+ if (this.interactionMode === `checked`) this._colWidthSum += 28;
378
+ }
379
+ _getGroupKey(row) {
380
+ if (!this.groupBy) return null;
381
+ if (typeof this.groupBy === `string`) return String(row[this.groupBy] ?? ``);
382
+ return String(this.groupBy(row));
383
+ }
384
+ _rebuildFlat() {
385
+ if (!this.groupBy) {
386
+ this._flat = this._sortedRows.map((r, i) => ({
387
+ type: `row`,
388
+ key: this.getRowKey(r, i),
389
+ row: r,
390
+ idx: i
391
+ }));
392
+ this._flatKeys = this._flat.map((f) => f.key);
393
+ return;
394
+ }
395
+ const groups = /* @__PURE__ */ new Map();
396
+ for (const r of this._sortedRows) {
397
+ const k = this._getGroupKey(r) ?? ``;
398
+ const arr = groups.get(k);
399
+ if (arr) arr.push(r);
400
+ else groups.set(k, [r]);
401
+ }
402
+ const flat = [];
403
+ const keys = [];
404
+ for (const [gKey, rows] of groups) {
405
+ const label = this.groupLabel ? this.groupLabel(gKey, rows) : gKey;
406
+ const gId = `__group__${gKey}`;
407
+ flat.push({
408
+ type: `group`,
409
+ key: gId,
410
+ label,
411
+ count: rows.length
412
+ });
413
+ keys.push(gId);
414
+ if (this._collapsedGroups.has(gKey)) continue;
415
+ for (const r of rows) {
416
+ const idx = this._sortedRows.indexOf(r);
417
+ const rk = this.getRowKey(r, idx);
418
+ flat.push({
419
+ type: `row`,
420
+ key: rk,
421
+ row: r,
422
+ idx
423
+ });
424
+ keys.push(rk);
425
+ }
426
+ }
427
+ this._flat = flat;
428
+ this._flatKeys = keys;
429
+ }
430
+ _toggleGroup(gKey) {
431
+ const next = new Set(this._collapsedGroups);
432
+ if (next.has(gKey)) next.delete(gKey);
433
+ else next.add(gKey);
434
+ this._collapsedGroups = next;
435
+ }
436
+ _applySort(rows) {
437
+ if (!this.sortState) return rows;
438
+ const col = this.columns.find((c) => c.key === this.sortState.key);
439
+ if (!col) return rows;
440
+ const dir = this.sortState.direction === `asc` ? 1 : -1;
441
+ if (col.sortFn) return rows.sort((a, b) => dir * col.sortFn(a, b));
442
+ return rows.sort((a, b) => {
443
+ const av = a[col.key];
444
+ const bv = b[col.key];
445
+ if (av === bv) return 0;
446
+ if (typeof av === `number` && typeof bv === `number`) return dir * (av - bv);
447
+ return dir * String(av).localeCompare(String(bv));
448
+ });
449
+ }
450
+ _onHeaderClick(col) {
451
+ if (col.sortable === false) return;
452
+ const prev = this.sortState;
453
+ let next = null;
454
+ if (!prev || prev.key !== col.key) next = {
455
+ key: col.key,
456
+ direction: `asc`
457
+ };
458
+ else if (prev.direction === `asc`) next = {
459
+ key: col.key,
460
+ direction: `desc`
461
+ };
462
+ else next = null;
463
+ this.sortState = next;
464
+ this.dispatchEvent(new CustomEvent(`datagrid-sort`, {
465
+ detail: {
466
+ sort: next,
467
+ previous: prev
468
+ },
469
+ bubbles: true,
470
+ composed: true
471
+ }));
472
+ }
473
+ _onRowClick(rowKey, col, e) {
474
+ if (col && col.selectable === false) return;
475
+ if (this.selectionMode === `none` && this.interactionMode !== `checked` && this.interactionMode !== `sticky`) return;
476
+ if (this.interactionMode === `manual`) return;
477
+ if (this.interactionMode === `implicit`) {
478
+ this._replaceSelection(rowKey);
479
+ return;
480
+ }
481
+ if (this.interactionMode === `checked`) return;
482
+ if (this.interactionMode === `sticky`) {
483
+ this._toggleSelection(rowKey);
484
+ return;
485
+ }
486
+ if (e.ctrlKey || e.metaKey) {
487
+ if (this.selectionMode === `multiple` || this.interactionMode === `vscode`) this._toggleSelection(rowKey);
488
+ } else if (e.shiftKey && (this.selectionMode === `multiple` || this.interactionMode === `vscode`)) this._rangeSelectTo(rowKey);
489
+ else this._replaceSelection(rowKey);
490
+ }
491
+ _onCheckboxClick(rowKey, e) {
492
+ e.stopPropagation();
493
+ this._onRowClick(rowKey, null, e);
494
+ }
495
+ _onVisibilityToggle(col, checked) {
496
+ col.visible = checked;
497
+ this.columns = [...this.columns];
498
+ this.dispatchEvent(new CustomEvent(`datagrid-column-visibility`, {
499
+ detail: {
500
+ key: col.key,
501
+ visible: checked
502
+ },
503
+ bubbles: true,
504
+ composed: true
505
+ }));
506
+ }
507
+ _beginHeaderDrag(e, col, idx) {
508
+ e.stopPropagation();
509
+ const anchor = e.currentTarget.closest(`.header-cell`) ?? e.currentTarget;
510
+ this._drag = {
511
+ pointerId: e.pointerId,
512
+ from: idx,
513
+ startX: e.clientX,
514
+ startY: e.clientY,
515
+ active: false,
516
+ ghost: null,
517
+ gap: void 0,
518
+ sourceCol: col
519
+ };
520
+ const onMove = (ev) => {
521
+ if (!this._drag || ev.pointerId !== this._drag.pointerId) return;
522
+ const dx = ev.clientX - this._drag.startX;
523
+ const dy = ev.clientY - this._drag.startY;
524
+ if (!this._drag.active) {
525
+ if (Math.hypot(dx, dy) < 5) return;
526
+ this._drag.active = true;
527
+ const content = document.createElement(`span`);
528
+ content.textContent = col.label;
529
+ try {
530
+ this._drag.ghost = createDragGhost({
531
+ anchor,
532
+ content
533
+ });
534
+ this._drag.ghost.move(ev.clientX, ev.clientY);
535
+ } catch {}
536
+ try {
537
+ this._headerScrollEl?.setPointerCapture(ev.pointerId);
538
+ } catch {}
539
+ }
540
+ this._drag.ghost?.move(ev.clientX, ev.clientY);
541
+ const x = ev.clientX - (this._headerScrollEl?.getBoundingClientRect().left ?? 0) + (this._headerScrollEl?.scrollLeft ?? 0);
542
+ let gap = this._drag.from;
543
+ let bestDist = Infinity;
544
+ for (let i = 0; i < this._visibleColumns.length; i++) {
545
+ if (i === this._drag.from) continue;
546
+ const rectLeft = this._colOffsets[i];
547
+ const rectRight = rectLeft + (this._visibleColumns[i].width ?? 150);
548
+ const mid = (rectLeft + rectRight) / 2;
549
+ const gapPos = x < mid ? rectLeft : rectRight;
550
+ const gapIdx = x < mid ? i : i + 1;
551
+ const d = Math.abs(x - gapPos);
552
+ if (d < bestDist) {
553
+ bestDist = d;
554
+ gap = gapIdx > this._drag.from ? gapIdx - 1 : gapIdx;
555
+ }
556
+ }
557
+ if (this._visibleColumns.length > 0) {
558
+ const lastOff = this._colOffsets[this._colOffsets.length - 1] + (this._visibleColumns[this._visibleColumns.length - 1].width ?? 150);
559
+ const d = Math.abs(x - lastOff);
560
+ if (d < bestDist) {
561
+ bestDist = d;
562
+ gap = this._visibleColumns.length - 1;
563
+ if (this._drag.from === this._visibleColumns.length - 1) gap = this._drag.from;
564
+ else if (gap >= this._drag.from) gap = this._visibleColumns.length - 1;
565
+ }
566
+ }
567
+ const siblings = this._visibleColumns.length - 1;
568
+ let normalizedGap = gap;
569
+ if (normalizedGap < 0) normalizedGap = 0;
570
+ if (normalizedGap > siblings) normalizedGap = siblings;
571
+ this._drag.gap = normalizedGap;
572
+ this.requestUpdate();
573
+ };
574
+ const onUp = (ev) => {
575
+ if (!this._drag || ev.pointerId !== this._drag.pointerId) return;
576
+ const active = this._drag.active;
577
+ const from = this._drag.from;
578
+ const gap = this._drag.gap;
579
+ this._drag.ghost?.destroy();
580
+ try {
581
+ this._headerScrollEl?.releasePointerCapture(ev.pointerId);
582
+ } catch {}
583
+ window.removeEventListener(`pointermove`, onMove);
584
+ window.removeEventListener(`pointerup`, onUp);
585
+ window.removeEventListener(`pointercancel`, onUp);
586
+ this._drag = null;
587
+ this.requestUpdate();
588
+ if (active && gap !== void 0 && gap !== from) this._onReorderDrag(from, gap);
589
+ };
590
+ window.addEventListener(`pointermove`, onMove);
591
+ window.addEventListener(`pointerup`, onUp);
592
+ window.addEventListener(`pointercancel`, onUp);
593
+ }
594
+ _onReorderDrag(fromIdx, toIdx) {
595
+ if (fromIdx === toIdx) return;
596
+ const cols = [...this.columns];
597
+ const vis = cols.filter((c) => c.visible !== false);
598
+ const hidden = cols.filter((c) => c.visible === false);
599
+ const [moved] = vis.splice(fromIdx, 1);
600
+ vis.splice(toIdx, 0, moved);
601
+ this.columns = [...vis, ...hidden];
602
+ this.dispatchEvent(new CustomEvent(`datagrid-column-reorder`, {
603
+ detail: {
604
+ from: fromIdx,
605
+ to: toIdx,
606
+ columns: [...this.columns]
607
+ },
608
+ bubbles: true,
609
+ composed: true
610
+ }));
611
+ }
612
+ _scrollRowIntoView(key) {
613
+ const idx = this._flat.findIndex((f) => f.key === key);
614
+ if (idx < 0 || !this._viewportEl) return;
615
+ const top = idx * this.rowHeight;
616
+ const bottom = top + this.rowHeight;
617
+ const vt = this._viewportEl.scrollTop;
618
+ const vb = vt + this._viewportEl.clientHeight;
619
+ if (top < vt) this._viewportEl.scrollTop = top;
620
+ else if (bottom > vb) this._viewportEl.scrollTop = bottom - this._viewportEl.clientHeight;
621
+ }
622
+ _getRowForKey(key) {
623
+ return this._flat.find((f) => f.key === key)?.row;
624
+ }
625
+ _replaceSelection(key) {
626
+ if (this.selectionMode === `none`) return;
627
+ const prev = new Set(this._selectedKeys);
628
+ if (this.groupBy && !this.crossGroupSelection) {
629
+ const g = this._getGroupKey(this._getRowForKey(key) ?? { key });
630
+ [...prev].filter((k) => this._getGroupKey(this._getRowForKey(k) ?? { key: k }) === g);
631
+ this._selectedKeys = /* @__PURE__ */ new Set([key]);
632
+ } else this._selectedKeys = /* @__PURE__ */ new Set([key]);
633
+ this._anchorKey = key;
634
+ this.dispatchEvent(new CustomEvent(`datagrid-select`, {
635
+ detail: {
636
+ selectedKeys: new Set(this._selectedKeys),
637
+ previous: prev
638
+ },
639
+ bubbles: true,
640
+ composed: true
641
+ }));
642
+ }
643
+ _toggleSelection(key) {
644
+ const prev = new Set(this._selectedKeys);
645
+ const next = new Set(this._selectedKeys);
646
+ if (next.has(key)) next.delete(key);
647
+ else {
648
+ if (this.groupBy && !this.crossGroupSelection) {
649
+ const g = this._getGroupKey(this._getRowForKey(key) ?? { key });
650
+ for (const k of [...next]) if (this._getGroupKey(this._getRowForKey(k) ?? { key: k }) !== g) next.delete(k);
651
+ }
652
+ if (this.selectionMode !== `multiple` && this.interactionMode !== `checked` && this.interactionMode !== `sticky`) next.clear();
653
+ next.add(key);
654
+ }
655
+ this._selectedKeys = next;
656
+ this._anchorKey = key;
657
+ this.dispatchEvent(new CustomEvent(`datagrid-select`, {
658
+ detail: {
659
+ selectedKeys: new Set(this._selectedKeys),
660
+ previous: prev
661
+ },
662
+ bubbles: true,
663
+ composed: true
664
+ }));
665
+ }
666
+ _rangeSelectTo(key) {
667
+ if (!this._anchorKey) {
668
+ this._replaceSelection(key);
669
+ return;
670
+ }
671
+ const keys = this._flat.filter((f) => f.type === `row`).map((f) => f.key);
672
+ const a = keys.indexOf(this._anchorKey);
673
+ const b = keys.indexOf(key);
674
+ if (a < 0 || b < 0) {
675
+ this._replaceSelection(key);
676
+ return;
677
+ }
678
+ if (this.groupBy && !this.crossGroupSelection) {
679
+ const anchorGroup = this._getGroupKey(this._getRowForKey(this._anchorKey) ?? { key: this._anchorKey });
680
+ if (anchorGroup !== this._getGroupKey(this._getRowForKey(key) ?? { key })) {
681
+ this._replaceSelection(key);
682
+ return;
683
+ }
684
+ const groupKeys = keys.filter((k) => this._getGroupKey(this._getRowForKey(k) ?? { key: k }) === anchorGroup);
685
+ const ga = groupKeys.indexOf(this._anchorKey);
686
+ const gb = groupKeys.indexOf(key);
687
+ if (ga < 0 || gb < 0) {
688
+ this._replaceSelection(key);
689
+ return;
690
+ }
691
+ const lo = Math.min(ga, gb);
692
+ const hi = Math.max(ga, gb);
693
+ const prev = new Set(this._selectedKeys);
694
+ this._selectedKeys = new Set(groupKeys.slice(lo, hi + 1));
695
+ this.dispatchEvent(new CustomEvent(`datagrid-select`, {
696
+ detail: {
697
+ selectedKeys: new Set(this._selectedKeys),
698
+ previous: prev
699
+ },
700
+ bubbles: true,
701
+ composed: true
702
+ }));
703
+ return;
704
+ }
705
+ const lo = Math.min(a, b);
706
+ const hi = Math.max(a, b);
707
+ const prev = new Set(this._selectedKeys);
708
+ this._selectedKeys = new Set(keys.slice(lo, hi + 1));
709
+ this.dispatchEvent(new CustomEvent(`datagrid-select`, {
710
+ detail: {
711
+ selectedKeys: new Set(this._selectedKeys),
712
+ previous: prev
713
+ },
714
+ bubbles: true,
715
+ composed: true
716
+ }));
717
+ }
718
+ _isEditable(row, col) {
719
+ if (this.editability === `disabled`) return false;
720
+ if (typeof this.editable === `function`) return this.editable(row, col);
721
+ if (this.editable === true) return col.editable !== false;
722
+ return !!col.editable;
723
+ }
724
+ _isTextEditor(col) {
725
+ const e = col.editor;
726
+ return !e || e === `text` || e === `number`;
727
+ }
728
+ _isCellEditing(rowKey, colKey) {
729
+ 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;
730
+ if (this._editingRow === rowKey) return this._isEditable(this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey) ?? {}, this.columns.find((c) => c.key === colKey));
731
+ return this._editing?.rowKey === rowKey && this._editing?.colKey === colKey;
732
+ }
733
+ _startEdit(rowKey, colKey) {
734
+ if (this.editability === `disabled`) return;
735
+ const row = this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey);
736
+ if (!row) return;
737
+ const col = this.columns.find((c) => c.key === colKey);
738
+ if (!col || !this._isEditable(row, col)) return;
739
+ this._editing = {
740
+ rowKey,
741
+ colKey
742
+ };
743
+ this._editingRow = null;
744
+ this._editValue = row[colKey];
745
+ }
746
+ /** Programmatic: put a single cell in edit mode. Works in manual/user/immediate (no-op if disabled or not editable). */
747
+ startEdit(rowKey, colKey) {
748
+ this._startEdit(rowKey, colKey);
749
+ }
750
+ /** Programmatic: put all editable cells of a row in edit mode. */
751
+ startRowEdit(rowKey) {
752
+ if (this.editability === `disabled`) return;
753
+ if (!this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey)) return;
754
+ this._editing = null;
755
+ this._editingRow = rowKey;
756
+ this.requestUpdate();
757
+ }
758
+ stopEdit() {
759
+ this._editing = null;
760
+ this._editingRow = null;
761
+ }
762
+ setColumnFormatter(columnKey, formatter, options) {
763
+ if (!formatter) {
764
+ this._columnFormatters.delete(columnKey);
765
+ this.requestUpdate();
766
+ return;
767
+ }
768
+ if (typeof formatter === `string`) {
769
+ const digits = options?.digits ?? (formatter === `integer` ? 0 : 2);
770
+ if (formatter === `fixed`) this._columnFormatters.set(columnKey, (v) => typeof v === `number` && Number.isFinite(v) ? v.toFixed(digits) : String(v ?? ``));
771
+ else if (formatter === `percentage`) this._columnFormatters.set(columnKey, (v) => typeof v === `number` && Number.isFinite(v) ? `${v.toFixed(digits)}%` : String(v ?? ``));
772
+ else if (formatter === `integer`) this._columnFormatters.set(columnKey, (v) => typeof v === `number` && Number.isFinite(v) ? Math.round(v).toString() : String(v ?? ``));
773
+ } else this._columnFormatters.set(columnKey, formatter);
774
+ this.requestUpdate();
775
+ }
776
+ _formatValue(value, col, row) {
777
+ const perCol = this._columnFormatters.get(col.key);
778
+ if (perCol) return perCol(value);
779
+ if (col.formatter) return col.formatter(value, row, col);
780
+ if (this.globalFormatter) return this.globalFormatter(value, col.key, row);
781
+ return value;
782
+ }
783
+ _handleCellSecondClick(rowKey, col) {
784
+ if (this.editability !== `user`) return;
785
+ const now = Date.now();
786
+ if (this._lastClick && this._lastClick.key === rowKey && this._lastClick.col === col.key && now - this._lastClick.time < 700 && now - this._lastClick.time > 150) {
787
+ this._startEdit(rowKey, col.key);
788
+ this._lastClick = null;
789
+ } else this._lastClick = {
790
+ key: rowKey,
791
+ col: col.key,
792
+ time: now
793
+ };
794
+ }
795
+ _commitEdit(fromImmediate = false) {
796
+ if (!this._editing && !fromImmediate) return;
797
+ const target = this._editing;
798
+ if (!target) return;
799
+ const { rowKey, colKey } = target;
800
+ const row = this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey);
801
+ if (!row) {
802
+ this._editing = null;
803
+ return;
804
+ }
805
+ const oldValue = row[colKey];
806
+ const newValue = this._editValue;
807
+ const col = this.columns.find((c) => c.key === colKey);
808
+ if (col?.editor && typeof col.editor === `function`) {} else {
809
+ row[colKey] = newValue;
810
+ this.rows = [...this.rows];
811
+ }
812
+ this._editing = null;
813
+ this.dispatchEvent(new CustomEvent(`datagrid-cell-edit`, {
814
+ detail: {
815
+ rowKey,
816
+ colKey,
817
+ oldValue,
818
+ newValue
819
+ },
820
+ bubbles: true,
821
+ composed: true
822
+ }));
823
+ }
824
+ _tabToNext(rowKey, curColKey, dir) {
825
+ const row = this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey);
826
+ if (!row) return;
827
+ const cols = this._visibleColumns;
828
+ let next = cols.findIndex((c) => c.key === curColKey) + dir;
829
+ while (next >= 0 && next < cols.length) {
830
+ if (this._isEditable(row, cols[next])) {
831
+ if (this._editing) this._commitEdit();
832
+ this._suppressBlurCommit = true;
833
+ this._startEdit(rowKey, cols[next].key);
834
+ requestAnimationFrame(() => {
835
+ this._suppressBlurCommit = false;
836
+ });
837
+ return;
838
+ }
839
+ next += dir;
840
+ }
841
+ if (this._editing) this._commitEdit();
842
+ this._editing = null;
843
+ this.requestUpdate();
844
+ }
845
+ _commitImmediate(rowKey, colKey, value) {
846
+ const row = this._sortedRows.find((r, i) => this.getRowKey(r, i) === rowKey);
847
+ if (!row) return;
848
+ const oldValue = row[colKey];
849
+ if (oldValue === value) return;
850
+ row[colKey] = value;
851
+ this.rows = [...this.rows];
852
+ this.dispatchEvent(new CustomEvent(`datagrid-cell-edit`, {
853
+ detail: {
854
+ rowKey,
855
+ colKey,
856
+ oldValue,
857
+ newValue: value
858
+ },
859
+ bubbles: true,
860
+ composed: true
861
+ }));
862
+ }
863
+ _autoSizeColumn(col) {
864
+ const cs = getComputedStyle(this);
865
+ const font = `${cs.fontSize} ${cs.fontFamily}`;
866
+ const ctx = document.createElement(`canvas`).getContext(`2d`);
867
+ const cellPad = 32;
868
+ const headerExtra = 40;
869
+ const measure = (text) => {
870
+ if (!ctx) return text.length * 8;
871
+ ctx.font = font;
872
+ return Math.ceil(ctx.measureText(text).width);
873
+ };
874
+ let maxW = measure(col.label) + headerExtra;
875
+ for (const r of this.rows) {
876
+ const v = r[col.key];
877
+ if (v === null || v === void 0 || v === ``) continue;
878
+ const w = measure(String(v)) + cellPad;
879
+ if (w > maxW) maxW = w;
880
+ }
881
+ const min = col.minWidth ?? 40;
882
+ const max = col.maxWidth ?? 600;
883
+ const w = Math.max(min, Math.min(max, maxW));
884
+ col.width = w;
885
+ this.requestUpdate();
886
+ this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
887
+ detail: {
888
+ key: col.key,
889
+ width: w
890
+ },
891
+ bubbles: true,
892
+ composed: true
893
+ }));
894
+ }
895
+ _onResizePointerDown(e, col) {
896
+ e.preventDefault();
897
+ e.stopPropagation();
898
+ const startX = e.clientX;
899
+ const startW = col.width ?? 150;
900
+ const onMove = (ev) => {
901
+ col.width = Math.max(col.minWidth ?? 40, Math.min(col.maxWidth ?? 600, startW + ev.clientX - startX));
902
+ this.requestUpdate();
903
+ };
904
+ const onUp = (ev) => {
905
+ window.removeEventListener(`pointermove`, onMove);
906
+ window.removeEventListener(`pointerup`, onUp);
907
+ const w = Math.max(col.minWidth ?? 40, Math.min(col.maxWidth ?? 600, startW + ev.clientX - startX));
908
+ this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
909
+ detail: {
910
+ key: col.key,
911
+ width: w
912
+ },
913
+ bubbles: true,
914
+ composed: true
915
+ }));
916
+ };
917
+ window.addEventListener(`pointermove`, onMove);
918
+ window.addEventListener(`pointerup`, onUp);
919
+ }
920
+ getVisibleRange() {
921
+ const rh = this.rowHeight;
922
+ return {
923
+ startRow: Math.max(0, Math.floor(this._scrollTop / rh) - 2),
924
+ endRow: Math.min(this._flat.length, Math.ceil((this._scrollTop + this._viewportH) / rh) + 2),
925
+ startCol: this._findFirstVisibleCol(this._scrollLeft),
926
+ endCol: this._findLastVisibleCol(this._scrollLeft + this._viewportW)
927
+ };
928
+ }
929
+ _findFirstVisibleCol(scrollLeft) {
930
+ let lo = 0;
931
+ let hi = this._colOffsets.length - 1;
932
+ let ans = 0;
933
+ while (lo <= hi) {
934
+ const mid = lo + hi >> 1;
935
+ if (this._colOffsets[mid] <= scrollLeft) {
936
+ ans = mid;
937
+ lo = mid + 1;
938
+ } else hi = mid - 1;
939
+ }
940
+ return ans;
941
+ }
942
+ _findLastVisibleCol(right) {
943
+ for (let i = 0; i < this._colOffsets.length; i++) if (this._colOffsets[i] > right) return i;
944
+ return this._colOffsets.length;
945
+ }
946
+ render() {
947
+ const rh = this.rowHeight;
948
+ const startRow = Math.max(0, Math.floor(this._scrollTop / rh) - 2);
949
+ const endRow = Math.min(this._flat.length, Math.ceil((this._scrollTop + this._viewportH) / rh) + 2);
950
+ const visibleFlat = this._flat.slice(startRow, endRow);
951
+ const showCheckbox = this.interactionMode === `checked`;
952
+ return html`
953
+ <div class="grid" style="height:400px">
954
+ <div class="header-bar">
955
+ <div class="header-scroll">
956
+ ${showCheckbox ? html`<div class="header-cell checkbox-col"></div>` : nothing}
957
+ ${repeat(this._visibleColumns, (c) => c.key, (col, idx) => html`
958
+ <div class="header-cell" style="width:${col.width ?? 150}px" ?drag-source=${this._drag?.active && this._drag.sourceCol.key === col.key}
959
+ @click=${() => this._onHeaderClick(col)}
960
+ @pointerenter=${() => this._tickled.handlePointerEnter(col.key)}>
961
+ ${col.reorderable !== false ? html`<span class="reorder-handle" draggable="false"
962
+ @pointerdown=${(e) => this._beginHeaderDrag(e, col, idx)}
963
+ >⠿</span>` : nothing}
964
+ <span class="header-label">${col.label}</span>
965
+ ${this.sortState?.key === col.key ? html`<span class="sort-icon">${this.sortState.direction === `asc` ? `▲` : `▼`}</span>` : nothing}
966
+ ${col.resizable !== false ? html`<span class="resize-handle" @pointerdown=${(e) => this._onResizePointerDown(e, col)} @click=${(e) => e.stopPropagation()} @dblclick=${(e) => {
967
+ e.preventDefault();
968
+ e.stopPropagation();
969
+ this._autoSizeColumn(col);
970
+ }}></span>` : nothing}
971
+ </div>
972
+ `)}
973
+ ${this._drag?.active && this._drag.gap !== void 0 ? html`<div class="drop-caret" style="left:${(() => {
974
+ const gap = this._drag.gap;
975
+ if (gap < this._colOffsets.length) return this._colOffsets[gap];
976
+ const last = this._colOffsets.length - 1;
977
+ return last >= 0 ? this._colOffsets[last] + (this._visibleColumns[last].width ?? 150) : 0;
978
+ })()}px"></div>` : nothing}
979
+ </div>
980
+ <ixfx-menu-trigger label="⚙" placement="bottom-end" @menu-command=${(e) => {
981
+ const cmd = e.detail.command;
982
+ const checked = e.detail.checked;
983
+ const value = e.detail.value;
984
+ if (cmd?.startsWith(`col:`)) {
985
+ const key = cmd.slice(4);
986
+ const col = this.columns.find((c) => c.key === key);
987
+ if (col) this._onVisibilityToggle(col, !!checked);
988
+ e.stopPropagation();
989
+ return;
990
+ }
991
+ if (cmd === `groupBy`) {
992
+ const v = value ?? ``;
993
+ this.groupBy = v || null;
994
+ this._collapsedGroups = /* @__PURE__ */ new Set();
995
+ this.dispatchEvent(new CustomEvent(`datagrid-group-change`, {
996
+ detail: { groupBy: this.groupBy },
997
+ bubbles: true,
998
+ composed: true
999
+ }));
1000
+ e.stopPropagation();
1001
+ }
1002
+ }}>
1003
+ <ixfx-menu-container slot="menu">
1004
+ <ixfx-menu-header label="Columns"></ixfx-menu-header>
1005
+ ${repeat(this.columns, (c) => c.key, (col) => html`
1006
+ <ixfx-menu-check-item command="col:${col.key}" label=${col.label} ?checked=${col.visible !== false}></ixfx-menu-check-item>
1007
+ `)}
1008
+ ${this.allowGrouping ? html`
1009
+ <ixfx-menu-separator></ixfx-menu-separator>
1010
+ <ixfx-menu-header label="Group by"></ixfx-menu-header>
1011
+ <ixfx-menu-radio-group name="groupBy" value=${typeof this.groupBy === `string` ? this.groupBy : ``}>
1012
+ <ixfx-menu-radio-item value="" label="None"></ixfx-menu-radio-item>
1013
+ ${this.columns.filter((c) => c.groupable !== false && (!this.groupableColumns || this.groupableColumns.includes(c.key))).map((col) => html`
1014
+ <ixfx-menu-radio-item value=${col.key} label=${col.label}></ixfx-menu-radio-item>
1015
+ `)}
1016
+ </ixfx-menu-radio-group>
1017
+ ` : nothing}
1018
+ </ixfx-menu-container>
1019
+ </ixfx-menu-trigger>
1020
+ </div>
1021
+ <div class="viewport" @scroll=${this._onScroll}>
1022
+ <div class="spacer" style="height:${this._flat.length * rh}px; width:${this._colWidthSum}px">
1023
+ ${repeat(visibleFlat, (f) => f.key, (entry, i) => {
1024
+ const absIdx = startRow + i;
1025
+ if (entry.type === `group`) {
1026
+ const gKey = entry.key.replace(/^__group__/, ``);
1027
+ const collapsed = this._collapsedGroups.has(gKey);
1028
+ return html`<div class="group-row ${collapsed ? `collapsed` : ``}" style="top:${absIdx * rh}px; height:${rh}px" @click=${() => this._toggleGroup(gKey)}><span class="caret">▼</span><span>${entry.label}</span><span class="count">(${entry.count})</span><span class="divider"></span></div>`;
1029
+ }
1030
+ const row = entry.row;
1031
+ const absIdx2 = entry.idx;
1032
+ const rowKey = entry.key;
1033
+ const selected = this._selectedKeys.has(rowKey);
1034
+ const tickled = this._tickled.isTickled(rowKey);
1035
+ return html`
1036
+ <div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``}" style="top:${absIdx * rh}px; height:${rh}px"
1037
+ data-key=${rowKey}
1038
+ @click=${(e) => {
1039
+ this._onRowClick(rowKey, null, e);
1040
+ this.dispatchEvent(new CustomEvent(`datagrid-cell-click`, {
1041
+ detail: {
1042
+ rowKey,
1043
+ colKey: ``,
1044
+ rowIndex: absIdx2,
1045
+ colIndex: -1
1046
+ },
1047
+ bubbles: true,
1048
+ composed: true
1049
+ }));
1050
+ }}
1051
+ @pointerenter=${() => this._tickled.handlePointerEnter(rowKey)}
1052
+ @pointerleave=${() => this._tickled.clearTickled()}
1053
+ @dblclick=${() => {
1054
+ if (this._visibleColumns[0] && this._isEditable(row, this._visibleColumns[0])) this._startEdit(rowKey, this._visibleColumns[0].key);
1055
+ }}>
1056
+ ${showCheckbox ? html`<div class="checkbox-col"><input type="checkbox" .checked=${selected} @click=${(e) => this._onCheckboxClick(rowKey, e)} /></div>` : nothing}
1057
+ ${repeat(this._visibleColumns, (c) => c.key, (col) => {
1058
+ const val = row[col.key];
1059
+ if (this._isCellEditing(rowKey, col.key)) {
1060
+ const immediate = this.editability === `immediate` || this._editingRow === rowKey;
1061
+ const curVal = immediate ? val : this._editValue ?? val;
1062
+ if (col.editor === `boolean` || col.editor === `boolean`) 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) => {
1063
+ const v = e.target.checked;
1064
+ if (immediate) this._commitImmediate(rowKey, col.key, v);
1065
+ else {
1066
+ this._editValue = v;
1067
+ this._commitEdit();
1068
+ }
1069
+ }} /></div>`;
1070
+ if (col.editor === `select`) {
1071
+ const opts = col.editorOptions ?? [];
1072
+ return html`<div class="cell" data-col=${col.key} style="width:${col.width ?? 150}px"><select @change=${(e) => {
1073
+ const v = e.target.value;
1074
+ if (immediate) this._commitImmediate(rowKey, col.key, v);
1075
+ else {
1076
+ this._editValue = v;
1077
+ this._commitEdit();
1078
+ }
1079
+ }} @keydown=${(e) => {
1080
+ if (e.key === `Tab`) {
1081
+ e.preventDefault();
1082
+ e.stopPropagation();
1083
+ this._tabToNext(rowKey, col.key, e.shiftKey ? -1 : 1);
1084
+ }
1085
+ if (e.key === `Escape`) {
1086
+ this._editing = null;
1087
+ this._editingRow = null;
1088
+ }
1089
+ }} @blur=${() => {
1090
+ if (this._suppressBlurCommit) return;
1091
+ if (!immediate) this._commitEdit();
1092
+ }}><option value=""></option>${opts.map((o) => {
1093
+ const v = typeof o === `string` ? o : o.value;
1094
+ const l = typeof o === `string` ? o : o.label;
1095
+ return html`<option value=${v} ?selected=${String(curVal) === v}>${l}</option>`;
1096
+ })}</select></div>`;
1097
+ }
1098
+ if (typeof col.editor === `function`) {
1099
+ const factory = col.editor;
1100
+ const ctx = {
1101
+ row,
1102
+ column: col,
1103
+ rowIndex: absIdx2,
1104
+ colIndex: 0,
1105
+ value: curVal,
1106
+ commit: (v) => {
1107
+ if (immediate) this._commitImmediate(rowKey, col.key, v);
1108
+ else {
1109
+ this._editValue = v;
1110
+ this._commitEdit();
1111
+ }
1112
+ },
1113
+ cancel: () => {
1114
+ this._editing = null;
1115
+ this._editingRow = null;
1116
+ }
1117
+ };
1118
+ return html`<div class="cell" data-col=${col.key} style="width:${col.width ?? 150}px">${factory(ctx)}</div>`;
1119
+ }
1120
+ return html`<div class="cell" data-col=${col.key} style="width:${col.width ?? 150}px"><input .value=${String(curVal ?? ``)} @input=${(e) => this._editValue = e.target.value} @keydown=${(e) => {
1121
+ if (e.key === `Enter`) this._commitEdit();
1122
+ if (e.key === `Escape`) {
1123
+ this._editing = null;
1124
+ this._editingRow = null;
1125
+ }
1126
+ if (e.key === `Tab`) {
1127
+ e.preventDefault();
1128
+ e.stopPropagation();
1129
+ this._tabToNext(rowKey, col.key, e.shiftKey ? -1 : 1);
1130
+ }
1131
+ }} @blur=${() => {
1132
+ if (this._suppressBlurCommit) return;
1133
+ if (!immediate) this._commitEdit();
1134
+ }} autofocus /></div>`;
1135
+ }
1136
+ const displayVal = this._formatValue(val, col, row);
1137
+ const rendered = col.renderer ? col.renderer(row, col, absIdx2) : displayVal;
1138
+ const cellContent = col.renderer ? rendered : displayVal;
1139
+ const textEditable = this._isEditable(row, col) && this._isTextEditor(col);
1140
+ return html`<div class="cell ${textEditable ? `editable-text` : ``}" data-col=${col.key} style="width:${col.width ?? 150}px; justify-content:${col.align === `right` ? `flex-end` : col.align === `center` ? `center` : `flex-start`}"
1141
+ @click=${(e) => {
1142
+ this._lastClickedCell = {
1143
+ rowKey,
1144
+ colKey: col.key
1145
+ };
1146
+ if (col.selectable !== false) {
1147
+ e.stopPropagation();
1148
+ this._onRowClick(rowKey, col, e);
1149
+ this._handleCellSecondClick(rowKey, col);
1150
+ }
1151
+ this.dispatchEvent(new CustomEvent(`datagrid-cell-click`, {
1152
+ detail: {
1153
+ rowKey,
1154
+ colKey: col.key,
1155
+ rowIndex: absIdx2,
1156
+ colIndex: this.columns.indexOf(col)
1157
+ },
1158
+ bubbles: true,
1159
+ composed: true
1160
+ }));
1161
+ }}
1162
+ >${cellContent}</div>`;
1163
+ })}
1164
+ </div>
1165
+ `;
1166
+ })}
1167
+ </div>
1168
+ </div>
1169
+ </div>
1170
+ `;
1171
+ }
1172
+ };
1173
+ __decorate([property({ type: Array })], DataGridElement.prototype, "columns", void 0);
1174
+ __decorate([property({ type: Array })], DataGridElement.prototype, "rows", void 0);
1175
+ __decorate([property({
1176
+ type: String,
1177
+ attribute: `selection-mode`
1178
+ })], DataGridElement.prototype, "selectionMode", void 0);
1179
+ __decorate([property({
1180
+ type: String,
1181
+ attribute: `interaction-mode`
1182
+ })], DataGridElement.prototype, "interactionMode", void 0);
1183
+ __decorate([property({ attribute: false })], DataGridElement.prototype, "sortState", void 0);
1184
+ __decorate([property({
1185
+ type: Number,
1186
+ attribute: `row-height`
1187
+ })], DataGridElement.prototype, "rowHeight", void 0);
1188
+ __decorate([property({ attribute: false })], DataGridElement.prototype, "getRowKey", void 0);
1189
+ __decorate([property({ attribute: false })], DataGridElement.prototype, "checkboxRenderer", void 0);
1190
+ __decorate([property({ attribute: false })], DataGridElement.prototype, "editable", void 0);
1191
+ __decorate([property({
1192
+ type: String,
1193
+ reflect: true
1194
+ })], DataGridElement.prototype, "editability", void 0);
1195
+ __decorate([property({ attribute: false })], DataGridElement.prototype, "groupBy", void 0);
1196
+ __decorate([property({
1197
+ type: Boolean,
1198
+ attribute: `allow-grouping`
1199
+ })], DataGridElement.prototype, "allowGrouping", void 0);
1200
+ __decorate([property({ attribute: false })], DataGridElement.prototype, "groupLabel", void 0);
1201
+ __decorate([property({ attribute: false })], DataGridElement.prototype, "groupableColumns", void 0);
1202
+ __decorate([property({
1203
+ type: Boolean,
1204
+ attribute: `cross-group-selection`,
1205
+ reflect: true
1206
+ })], DataGridElement.prototype, "crossGroupSelection", void 0);
1207
+ __decorate([property({ attribute: false })], DataGridElement.prototype, "globalFormatter", void 0);
1208
+ __decorate([state()], DataGridElement.prototype, "_selectedKeys", void 0);
1209
+ __decorate([state()], DataGridElement.prototype, "_scrollTop", void 0);
1210
+ __decorate([state()], DataGridElement.prototype, "_scrollLeft", void 0);
1211
+ __decorate([state()], DataGridElement.prototype, "_viewportH", void 0);
1212
+ __decorate([state()], DataGridElement.prototype, "_viewportW", void 0);
1213
+ __decorate([state()], DataGridElement.prototype, "_collapsedGroups", void 0);
1214
+ __decorate([state()], DataGridElement.prototype, "_editing", void 0);
1215
+ __decorate([state()], DataGridElement.prototype, "_editingRow", void 0);
1216
+ __decorate([state()], DataGridElement.prototype, "_lastClick", void 0);
1217
+ __decorate([state()], DataGridElement.prototype, "_lastClickedCell", void 0);
1218
+ __decorate([state()], DataGridElement.prototype, "_editValue", void 0);
1219
+ __decorate([query(`.viewport`)], DataGridElement.prototype, "_viewportEl", void 0);
1220
+ __decorate([query(`.header-scroll`)], DataGridElement.prototype, "_headerScrollEl", void 0);
1221
+ DataGridElement = __decorate([safeCustomElement(`ixfx-data-grid`)], DataGridElement);
1222
+ //#endregion
1223
+ export { DataGridElement };
1224
+
1225
+ //# sourceMappingURL=data-grid.js.map