stimulus_grid_rails 0.1.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.
@@ -0,0 +1,1547 @@
1
+ var B = Object.defineProperty;
2
+ var O = (n, l, e) => l in n ? B(n, l, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[l] = e;
3
+ var m = (n, l, e) => O(n, typeof l != "symbol" ? l + "" : l, e);
4
+ import { Controller as S, Application as z } from "@hotwired/stimulus";
5
+ function b(n, l) {
6
+ return typeof l.valueGetter == "function" ? l.valueGetter(n) : n?.[l.field];
7
+ }
8
+ function C(n, l) {
9
+ const e = b(n, l);
10
+ return typeof l.valueFormatter == "function" ? l.valueFormatter(e, n) : e == null ? "" : l.type === "date" && e instanceof Date ? e.toLocaleDateString() : l.type === "boolean" ? e ? "✓" : "" : String(e);
11
+ }
12
+ const k = {
13
+ contains: (n, l) => String(n ?? "").toLowerCase().includes(String(l ?? "").toLowerCase()),
14
+ notContains: (n, l) => !String(n ?? "").toLowerCase().includes(String(l ?? "").toLowerCase()),
15
+ equals: (n, l) => String(n ?? "").toLowerCase() === String(l ?? "").toLowerCase(),
16
+ notEqual: (n, l) => String(n ?? "").toLowerCase() !== String(l ?? "").toLowerCase(),
17
+ startsWith: (n, l) => String(n ?? "").toLowerCase().startsWith(String(l ?? "").toLowerCase()),
18
+ endsWith: (n, l) => String(n ?? "").toLowerCase().endsWith(String(l ?? "").toLowerCase()),
19
+ blank: (n) => n == null || n === "",
20
+ notBlank: (n) => n != null && n !== ""
21
+ }, K = {
22
+ equals: (n, l) => Number(n) === Number(l),
23
+ notEqual: (n, l) => Number(n) !== Number(l),
24
+ lessThan: (n, l) => Number(n) < Number(l),
25
+ lessThanOrEqual: (n, l) => Number(n) <= Number(l),
26
+ greaterThan: (n, l) => Number(n) > Number(l),
27
+ greaterThanOrEqual: (n, l) => Number(n) >= Number(l),
28
+ inRange: (n, l, e) => Number(n) >= Number(l) && Number(n) <= Number(e),
29
+ blank: (n) => n == null || n === "",
30
+ notBlank: (n) => n != null && n !== ""
31
+ };
32
+ function _(n) {
33
+ if (n == null || n === "") return null;
34
+ if (n instanceof Date) return n;
35
+ const l = new Date(n);
36
+ return Number.isNaN(l.valueOf()) ? null : l;
37
+ }
38
+ const G = {
39
+ equals: (n, l) => _(n)?.toDateString() === _(l)?.toDateString(),
40
+ notEqual: (n, l) => _(n)?.toDateString() !== _(l)?.toDateString(),
41
+ lessThan: (n, l) => (_(n)?.valueOf() ?? -1 / 0) < (_(l)?.valueOf() ?? 1 / 0),
42
+ greaterThan: (n, l) => (_(n)?.valueOf() ?? 1 / 0) > (_(l)?.valueOf() ?? -1 / 0),
43
+ inRange: (n, l, e) => {
44
+ const t = _(n)?.valueOf();
45
+ return t != null && t >= (_(l)?.valueOf() ?? -1 / 0) && t <= (_(e)?.valueOf() ?? 1 / 0);
46
+ },
47
+ blank: (n) => n == null || n === "",
48
+ notBlank: (n) => n != null && n !== ""
49
+ }, W = {
50
+ equals: (n, l) => l === "true" ? !!n : l === "false" ? !n : !0
51
+ }, $ = {
52
+ in: (n, l) => Array.isArray(l) && l.includes(String(n ?? ""))
53
+ }, H = { text: k, number: K, date: G, boolean: W, set: $ };
54
+ function j(n, l, e) {
55
+ if (!e) return !0;
56
+ const t = e.filterType || l.filter || "text", s = (H[t] || k)[e.type];
57
+ if (!s) return !0;
58
+ const r = b(n, l);
59
+ return s(r, e.value, e.value2);
60
+ }
61
+ function P(n, l, e) {
62
+ const t = Object.entries(l || {}).filter(([, i]) => i != null);
63
+ return t.length === 0 ? n : n.filter((i) => t.every(([s, r]) => {
64
+ const o = e[s];
65
+ return o ? j(i, o, r) : !0;
66
+ }));
67
+ }
68
+ function V(n, l, e) {
69
+ if (!l) return n;
70
+ const t = String(l).toLowerCase();
71
+ return n.filter((i) => {
72
+ for (const s of e) {
73
+ const r = C(i, s);
74
+ if (r && String(r).toLowerCase().includes(t)) return !0;
75
+ }
76
+ return !1;
77
+ });
78
+ }
79
+ function U(n, l, e) {
80
+ if (n == null && l == null) return 0;
81
+ if (n == null) return -1;
82
+ if (l == null) return 1;
83
+ if (e === "number") return Number(n) - Number(l);
84
+ if (e === "date") {
85
+ const t = _(n)?.valueOf() ?? 0, i = _(l)?.valueOf() ?? 0;
86
+ return t - i;
87
+ }
88
+ return e === "boolean" ? n === l ? 0 : n ? 1 : -1 : String(n).localeCompare(String(l), void 0, { numeric: !0, sensitivity: "base" });
89
+ }
90
+ function X(n, l, e) {
91
+ if (!l || l.length === 0) return n;
92
+ const t = n.slice();
93
+ return t.sort((i, s) => {
94
+ for (const { colId: r, sort: o } of l) {
95
+ const a = e[r];
96
+ if (!a) continue;
97
+ const c = b(i, a), u = b(s, a), d = typeof a.comparator == "function" ? a.comparator(c, u, i, s) : U(c, u, a.type);
98
+ if (d !== 0) return o === "desc" ? -d : d;
99
+ }
100
+ return 0;
101
+ }), t;
102
+ }
103
+ function Y(n, l) {
104
+ if (!l || !l.enabled) return { rows: n, total: n.length, pageRows: n };
105
+ const e = n.length, t = Math.max(1, Math.ceil(e / l.pageSize)), i = Math.min(l.page, t - 1), s = i * l.pageSize, r = n.slice(s, s + l.pageSize);
106
+ return { rows: n, total: e, totalPages: t, page: i, pageRows: r };
107
+ }
108
+ function Q(n) {
109
+ if (n.serverSide) {
110
+ const s = n.rowData, r = n.pagination?.pageSize || s.length || 1, o = n.serverRowCount ?? s.length, a = Math.max(1, Math.ceil(o / r)), c = Math.min(n.pagination?.page || 0, a - 1);
111
+ return { filteredSorted: s, rows: s, total: o, totalPages: a, page: c, pageRows: s };
112
+ }
113
+ const l = Object.fromEntries(n.columnDefs.map((s) => [s.field, s])), e = n.columnDefs.filter((s) => !s.hidden && !s._isCheckbox);
114
+ let t = n.rowData;
115
+ t = P(t, n.filterModel, l), t = V(t, n.quickFilter, e), t = X(t, n.sortModel, l);
116
+ const i = Y(t, n.pagination);
117
+ return { filteredSorted: t, ...i };
118
+ }
119
+ function Z(n, l, e, t, i = 6) {
120
+ const s = Math.ceil(l / e), r = Math.max(0, Math.floor(n / e) - i), o = Math.min(t, r + s + i * 2);
121
+ return { first: r, last: o };
122
+ }
123
+ function J(n) {
124
+ return {
125
+ // ---- Data ----
126
+ setRowData(l) {
127
+ n.setRowData(l);
128
+ },
129
+ getRowData() {
130
+ return n.state.rowData.slice();
131
+ },
132
+ applyTransaction(l) {
133
+ return n.applyTransaction(l);
134
+ },
135
+ // Server-side row model
136
+ setRowCount(l) {
137
+ n.setRowCount(l);
138
+ },
139
+ getRowCount() {
140
+ return n.state.serverSide ? n.state.serverRowCount : n.state.rowData.length;
141
+ },
142
+ isServerSide() {
143
+ return !!n.state.serverSide;
144
+ },
145
+ // ---- Columns ----
146
+ setColumnDefs(l) {
147
+ n.setColumnDefs(l);
148
+ },
149
+ getColumnDefs() {
150
+ return n.state.columnDefs.slice();
151
+ },
152
+ setColumnVisible(l, e) {
153
+ n.setColumnVisible(l, e);
154
+ },
155
+ setColumnPinned(l, e) {
156
+ n.setColumnPinned(l, e);
157
+ },
158
+ setColumnWidth(l, e) {
159
+ n.setColumnWidth(l, e);
160
+ },
161
+ moveColumn(l, e) {
162
+ n.moveColumn(l, e);
163
+ },
164
+ autoSizeColumn(l) {
165
+ n.autoSizeColumn(l);
166
+ },
167
+ autoSizeAllColumns() {
168
+ n.state.columnDefs.forEach((l) => n.autoSizeColumn(l.field));
169
+ },
170
+ sizeColumnsToFit() {
171
+ n.sizeColumnsToFit();
172
+ },
173
+ // ---- Sort ----
174
+ setSortModel(l) {
175
+ n.setSortModel(l);
176
+ },
177
+ getSortModel() {
178
+ return n.state.sortModel.slice();
179
+ },
180
+ // ---- Filter ----
181
+ setFilterModel(l) {
182
+ n.setFilterModel(l);
183
+ },
184
+ getFilterModel() {
185
+ return { ...n.state.filterModel };
186
+ },
187
+ setColumnFilter(l, e) {
188
+ n.setColumnFilter(l, e);
189
+ },
190
+ destroyFilter(l) {
191
+ n.setColumnFilter(l, null);
192
+ },
193
+ setQuickFilter(l) {
194
+ n.setQuickFilter(l);
195
+ },
196
+ getQuickFilter() {
197
+ return n.getQuickFilter();
198
+ },
199
+ // ---- Selection ----
200
+ selectAll() {
201
+ n.selectAll();
202
+ },
203
+ deselectAll() {
204
+ n.deselectAll();
205
+ },
206
+ selectRow(l) {
207
+ n.setSelected(l, !0);
208
+ },
209
+ deselectRow(l) {
210
+ n.setSelected(l, !1);
211
+ },
212
+ getSelectedRows() {
213
+ return n.getSelectedRows();
214
+ },
215
+ getSelectedRowIds() {
216
+ return Array.from(n.state.selection);
217
+ },
218
+ // ---- Pagination ----
219
+ paginationGoToPage(l) {
220
+ n.goToPage(l);
221
+ },
222
+ paginationGoToFirstPage() {
223
+ n.goToPage(0);
224
+ },
225
+ paginationGoToNextPage() {
226
+ n.goToPage(n.state.pagination.page + 1);
227
+ },
228
+ paginationGoToPreviousPage() {
229
+ n.goToPage(n.state.pagination.page - 1);
230
+ },
231
+ paginationGoToLastPage() {
232
+ n.goToPage(n.lastPageIndex());
233
+ },
234
+ paginationSetPageSize(l) {
235
+ n.setPageSize(l);
236
+ },
237
+ paginationGetCurrentPage() {
238
+ return n.state.pagination.page;
239
+ },
240
+ paginationGetTotalPages() {
241
+ return n.totalPages();
242
+ },
243
+ paginationGetRowCount() {
244
+ return n.filteredCount();
245
+ },
246
+ paginationGetPageSize() {
247
+ return n.state.pagination.pageSize;
248
+ },
249
+ paginationIsEnabled() {
250
+ return n.state.pagination.enabled;
251
+ },
252
+ // ---- Cell selection ----
253
+ getCellSelection() {
254
+ return n.getCellSelectionDetail();
255
+ },
256
+ getCellRangeValues() {
257
+ return n._cellRangeRows();
258
+ },
259
+ getCellSelectionRowIds() {
260
+ return n.getCellSelectionRowIds();
261
+ },
262
+ // ---- Editing ----
263
+ startEditingCell({ rowId: l, colId: e }) {
264
+ n.startEditingCell(l, e);
265
+ },
266
+ stopEditing(l = !1) {
267
+ n.stopEditing(l);
268
+ },
269
+ // ---- Export ----
270
+ getDataAsCsv(l = {}) {
271
+ return n.getDataAsCsv(l);
272
+ },
273
+ exportDataAsCsv(l = {}) {
274
+ return n.exportDataAsCsv(l);
275
+ },
276
+ // ---- Display ----
277
+ refreshCells(l = {}) {
278
+ n.refresh(l);
279
+ },
280
+ redrawRows(l = {}) {
281
+ n.refresh(l);
282
+ },
283
+ // ---- Events ----
284
+ addEventListener(l, e) {
285
+ n.element.addEventListener(l, e);
286
+ },
287
+ removeEventListener(l, e) {
288
+ n.element.removeEventListener(l, e);
289
+ }
290
+ };
291
+ }
292
+ function f(n, l = {}, e = []) {
293
+ const t = document.createElement(n);
294
+ for (const [i, s] of Object.entries(l))
295
+ s === !1 || s == null || (i === "class" ? t.className = s : i === "style" && typeof s == "object" ? Object.assign(t.style, s) : i.startsWith("on") && typeof s == "function" ? t.addEventListener(i.slice(2).toLowerCase(), s) : s === !0 ? t.setAttribute(i, "") : t.setAttribute(i, String(s)));
296
+ for (const i of [].concat(e))
297
+ i == null || i === !1 || t.appendChild(typeof i == "string" ? document.createTextNode(i) : i);
298
+ return t;
299
+ }
300
+ function A(n, l) {
301
+ for (const [e, t] of Object.entries(l))
302
+ t == null || t === !1 ? n.removeAttribute(e) : t === !0 ? n.setAttribute(e, "") : n.setAttribute(e, String(t));
303
+ }
304
+ function M(n) {
305
+ const l = document.getElementById(n);
306
+ return !l || l.tagName !== "TEMPLATE" ? null : l.content.firstElementChild.cloneNode(!0);
307
+ }
308
+ function p(n, l, e) {
309
+ n.dispatchEvent(new CustomEvent(l, { detail: e, bubbles: !0 }));
310
+ }
311
+ function ee(n, l, e) {
312
+ let t = n.parentElement;
313
+ for (; t; ) {
314
+ if ((t.getAttribute("data-controller") || "").split(/\s+/).includes(l)) {
315
+ const s = e.getControllerForElementAndIdentifier(t, l);
316
+ if (s) return s;
317
+ }
318
+ t = t.parentElement;
319
+ }
320
+ return null;
321
+ }
322
+ const te = 32, L = 100;
323
+ class x extends S {
324
+ constructor() {
325
+ super(...arguments);
326
+ m(this, "_onDocMouseDown", (e) => {
327
+ this._filterPopover && !this._filterPopover.contains(e.target) && !e.target.closest(".sg-filter-icon") && this._closeFilterPopover();
328
+ });
329
+ m(this, "_onScroll", () => {
330
+ this.state.scrollTop = this._viewport.scrollTop, (this.virtualValue || this._displayList.pageRows.length > 200) && this.scheduleRender("scroll");
331
+ });
332
+ m(this, "_onCellMouseDown", (e) => {
333
+ if (e.button !== 0) return;
334
+ if (this.rowDragValue) {
335
+ const s = e.target.closest?.('td[data-gutter="true"]');
336
+ if (s) {
337
+ const r = s.closest("tr");
338
+ this._rowDragPending = { rowId: this._coerceRowId(r.dataset.rowId), x: e.clientX, y: e.clientY }, this._rowDragMoved = !1, document.addEventListener("mousemove", this._onRowDragMove);
339
+ return;
340
+ }
341
+ }
342
+ const t = this._cellAt(e.target);
343
+ if (!t) return;
344
+ const i = e.metaKey || e.ctrlKey;
345
+ e.shiftKey && this._activeCell() ? this._extendActiveRange(t) : i ? (this._addCellRange(t), this._cellDragging = !0) : (this._setSingleCellSel(t), this._cellDragging = !0), this._cellDragMoved = !1, this._focusGrid(), this._applyCellSelHighlight(), p(this.element, "grid:cellSelectionChanged", this.getCellSelectionDetail());
346
+ });
347
+ m(this, "_onCellMouseOver", (e) => {
348
+ if (!this._cellDragging) return;
349
+ const t = this._cellAt(e.target);
350
+ if (!t) return;
351
+ const i = this.state.cellSel.ranges[this.state.cellSel.activeIdx];
352
+ i && i.focus.rowId === t.rowId && i.focus.colId === t.colId || (this._extendActiveRange(t), this._cellDragMoved = !0, this._applyCellSelHighlight(), p(this.element, "grid:cellSelectionChanged", this.getCellSelectionDetail()));
353
+ });
354
+ m(this, "_onCellMouseUp", () => {
355
+ this._cellDragging = !1, this._rowDragPending && (document.removeEventListener("mousemove", this._onRowDragMove), this._rowDrag && this._finishRowDrag(), this._rowDragPending = null);
356
+ });
357
+ // ----- Row drag-and-drop (reorder) with a ghost preview -----
358
+ m(this, "_onRowDragMove", (e) => {
359
+ const t = this._rowDragPending;
360
+ if (t) {
361
+ if (!this._rowDrag) {
362
+ if (Math.abs(e.clientY - t.y) < 5 && Math.abs(e.clientX - t.x) < 5) return;
363
+ this._startRowDrag(t.rowId);
364
+ }
365
+ this._rowDrag && (this._rowDragMoved = !0, this._rowDrag.ghost.style.left = `${e.clientX + 14}px`, this._rowDrag.ghost.style.top = `${e.clientY + 10}px`, this._updateDropIndicator(e.clientY));
366
+ }
367
+ });
368
+ // Copy the active cell range to the clipboard as TSV (rows \n, cols \t).
369
+ m(this, "_onCopy", (e) => {
370
+ if (this.state.editing) return;
371
+ const t = document.activeElement;
372
+ if (t && /^(input|textarea|select)$/i.test(t.tagName) && !this.element.contains(t)) return;
373
+ const i = this._activeRect();
374
+ if (!i) return;
375
+ const s = this._cellRangeRows(i).map((r) => r.map((o) => String(o ?? "")).join(" ")).join(`
376
+ `);
377
+ s && (e.clipboardData?.setData("text/plain", s), e.preventDefault());
378
+ });
379
+ m(this, "_onGridKeydown", (e) => {
380
+ if (!this.cellSelectionValue || this.state.editing) return;
381
+ const t = document.activeElement;
382
+ if (t && /^(input|textarea|select)$/i.test(t.tagName) && this.element.contains(t)) return;
383
+ const i = e.key, s = e.metaKey || e.ctrlKey;
384
+ if (s && i.toLowerCase() === "a") {
385
+ e.preventDefault(), this.rowSelectionValue !== "" ? (this.clearCellSelection(), this.deselectAll(), this.selectAll()) : this._selectAllCells();
386
+ return;
387
+ }
388
+ if (s) return;
389
+ const r = { ArrowUp: [-1, 0], ArrowDown: [1, 0], ArrowLeft: [0, -1], ArrowRight: [0, 1] };
390
+ if (r[i]) {
391
+ e.preventDefault();
392
+ const [o, a] = r[i];
393
+ this._moveActiveCell(o, a, e.shiftKey);
394
+ return;
395
+ }
396
+ if (i === "Tab") {
397
+ e.preventDefault(), this._moveActiveCell(0, e.shiftKey ? -1 : 1, !1);
398
+ return;
399
+ }
400
+ if (i === "Enter") {
401
+ const o = this._activeCell();
402
+ o && (e.preventDefault(), this.startEditingCell(o.rowId, o.colId));
403
+ return;
404
+ }
405
+ if (i === "Escape") {
406
+ this.clearCellSelection();
407
+ return;
408
+ }
409
+ if (i === "Delete" || i === "Backspace") {
410
+ this._clearSelectedCells() && e.preventDefault();
411
+ return;
412
+ }
413
+ if (i.length === 1 && !e.altKey) {
414
+ const o = this._activeCell();
415
+ if (!o) return;
416
+ const a = this._colByField(o.colId);
417
+ if (!a || !a.editable) return;
418
+ e.preventDefault(), this.startEditingCell(o.rowId, o.colId, i);
419
+ }
420
+ });
421
+ m(this, "_onEditorKey", (e) => {
422
+ this.state.editing && (e.key === "Enter" ? (e.preventDefault(), this.stopEditing(!1)) : e.key === "Escape" ? (e.preventDefault(), this.stopEditing(!0)) : e.key === "Tab" && (e.preventDefault(), this._tabToEditableCell(e.shiftKey ? -1 : 1)));
423
+ });
424
+ m(this, "_onEditorBlur", () => {
425
+ this._navigatingEditor || this.state.editing && this.stopEditing(!1);
426
+ });
427
+ }
428
+ initialize() {
429
+ this.state = {
430
+ rowData: [],
431
+ columnDefs: [],
432
+ sortModel: [],
433
+ filterModel: {},
434
+ quickFilter: "",
435
+ selection: /* @__PURE__ */ new Set(),
436
+ focusedCell: null,
437
+ cellSel: { ranges: [], activeIdx: -1 },
438
+ // multi-range: [{anchor,focus}], active range
439
+ editing: null,
440
+ pagination: { enabled: !1, page: 0, pageSize: L },
441
+ scrollTop: 0,
442
+ viewportHeight: 400
443
+ }, this._displayList = { filteredSorted: [], pageRows: [], total: 0, totalPages: 1, page: 0 }, this._renderPending = !1, this._dirty = /* @__PURE__ */ new Set(), this._lastRenderedRowIds = /* @__PURE__ */ new Set(), this._runtimeOverrides = /* @__PURE__ */ Object.create(null);
444
+ }
445
+ connect() {
446
+ this.element.classList.add("sg-grid"), this.heightValue && (this.element.style.height = this.heightValue), this.element.hasAttribute("tabindex") || (this.element.tabIndex = 0), this.element.addEventListener("keydown", this._onGridKeydown), this.state.rowHeight = this.rowHeightValue, this.state.pagination = {
447
+ enabled: this.paginationValue,
448
+ page: 0,
449
+ pageSize: this.pageSizeValue
450
+ }, this.state.serverSide = this.serverSideValue, this.state.serverRowCount = this.rowCountValue, this._captureInitialMarkup(), this._buildChrome(), this.element.gridApi = J(this), queueMicrotask(() => this._initialLoad());
451
+ }
452
+ disconnect() {
453
+ this.element.gridApi = null, this.element.removeEventListener("keydown", this._onGridKeydown), document.removeEventListener("mouseup", this._onCellMouseUp), document.removeEventListener("copy", this._onCopy), document.removeEventListener("mousemove", this._onRowDragMove), this._rowDrag?.ghost?.remove(), this._rowDrag?.indicator?.remove();
454
+ }
455
+ // ----- Initial data + setup -----
456
+ _captureInitialMarkup() {
457
+ const e = this.element.querySelector("tbody");
458
+ e && (this._initialBodyHTML = e.innerHTML, this._initialRows = Array.from(e.querySelectorAll("tr")).map((t, i) => {
459
+ const s = {}, r = t.getAttribute("data-row-id") || t.getAttribute("data-row-row-id-value");
460
+ return s[this.getRowIdValue] = r != null ? this._coerceRowId(r) : i + 1, t.querySelectorAll("td").forEach((o) => {
461
+ const a = o.getAttribute("data-cell-col-id-value") || o.getAttribute("data-col-id");
462
+ a && (s[a] = o.textContent.trim());
463
+ }), s;
464
+ }), e.innerHTML = ""), this._initialHead = this.element.querySelector("thead");
465
+ }
466
+ _buildChrome() {
467
+ let e = this.element.querySelector("table");
468
+ if (!e) {
469
+ e = f("table");
470
+ const i = f("thead");
471
+ e.appendChild(i), this.element.appendChild(e);
472
+ }
473
+ let t = e.querySelector("tbody");
474
+ if (t || (t = f("tbody"), e.appendChild(t)), t.dataset.gridTarget = "body", this._tbody = t, this._table = e, this._thead = e.querySelector("thead"), e.parentElement.classList.contains("sg-body-viewport"))
475
+ this._viewport = e.parentElement;
476
+ else {
477
+ const i = f("div", { class: "sg-body-viewport" });
478
+ e.parentNode.insertBefore(i, e), i.appendChild(e), this._viewport = i;
479
+ }
480
+ this.domLayoutValue === "autoHeight" && (this._viewport.style.overflow = "visible", this.element.style.height = "auto"), this._footer = null;
481
+ }
482
+ async _initialLoad() {
483
+ if (this.rowDataValue && this.rowDataValue.length > 0)
484
+ this.state.rowData = this.rowDataValue;
485
+ else if (this.rowDataUrlValue)
486
+ try {
487
+ const e = await fetch(this.rowDataUrlValue);
488
+ this.state.rowData = await e.json();
489
+ } catch (e) {
490
+ console.error("[stimulus_grid] failed to fetch rowDataUrl", e), this.state.rowData = [];
491
+ }
492
+ else this._initialRows && this._initialRows.length > 0 && (this.state.rowData = this._initialRows);
493
+ this._dirty.add("data"), this._dirty.add("columns"), this._render(), this._attachBodyListeners(), p(this.element, "grid:ready", { api: this.element.gridApi }), p(this.element, "grid:rowDataChanged", { rows: this.state.rowData });
494
+ }
495
+ // Filter UI bridge — implemented by filter_controller, but the grid is the
496
+ // single source of truth so it brokers the popover.
497
+ openFilterFor(e, t) {
498
+ const i = this._colByField(e);
499
+ if (!(!i || !i.filter)) {
500
+ this._closeFilterPopover();
501
+ {
502
+ this._openFallbackFilterPopover(i, t);
503
+ return;
504
+ }
505
+ }
506
+ }
507
+ _closeFilterPopover() {
508
+ this._filterPopover && (this._filterPopover.remove(), this._filterPopover = null, document.removeEventListener("mousedown", this._onDocMouseDown));
509
+ }
510
+ _openFallbackFilterPopover(e, t) {
511
+ const i = this.state.filterModel[e.field] || {}, s = se(e.filter), r = f("div", { class: "sg-filter-popover" }), o = f("select");
512
+ s.forEach((w) => o.append(new Option(w.label, w.value, !1, w.value === i.type)));
513
+ const a = e.filter === "number" ? "number" : e.filter === "date" ? "date" : "text", c = f("input", { type: a, value: i.value ?? "" }), u = f("input", { type: a, value: i.value2 ?? "", style: { display: "none" } }), d = () => {
514
+ const w = o.value, I = w === "inRange", q = !(w === "blank" || w === "notBlank");
515
+ c.style.display = q ? "" : "none", u.style.display = I ? "" : "none";
516
+ };
517
+ o.addEventListener("change", d), d();
518
+ const h = f("div", { class: "sg-filter-actions" }), g = f("button", { type: "button" }, "Clear"), v = f("button", { type: "button", class: "primary" }, "Apply");
519
+ h.append(g, v), g.addEventListener("click", () => {
520
+ this.setColumnFilter(e.field, null), this._closeFilterPopover();
521
+ }), v.addEventListener("click", () => {
522
+ const w = o.value, I = w === "blank" || w === "notBlank" ? { filterType: e.filter, type: w } : { filterType: e.filter, type: w, value: c.value, value2: u.value || void 0 };
523
+ this.setColumnFilter(e.field, I), this._closeFilterPopover();
524
+ }), r.append(
525
+ f("label", {}, "Condition"),
526
+ o,
527
+ c,
528
+ u,
529
+ h
530
+ ), document.body.appendChild(r);
531
+ const y = t.getBoundingClientRect();
532
+ r.style.left = `${y.left + window.scrollX}px`, r.style.top = `${y.bottom + window.scrollY + 2}px`, this._filterPopover = r, document.addEventListener("mousedown", this._onDocMouseDown), c.focus();
533
+ }
534
+ // ----- Column registration (called by header_cell_controller) -----
535
+ registerColumn(e, t) {
536
+ const i = this.state.columnDefs.findIndex((o) => o.field === e.field), s = this._runtimeOverrides[e.field] || {}, r = { ...e, ...s, _headerEl: t };
537
+ if (i >= 0) {
538
+ const o = this.state.columnDefs[i];
539
+ if (o._headerEl === t && ie(o, r)) return;
540
+ this.state.columnDefs[i] = r;
541
+ } else
542
+ this.state.columnDefs.push(r);
543
+ this.scheduleRender("columns");
544
+ }
545
+ unregisterColumn(e) {
546
+ this.state.columnDefs = this.state.columnDefs.filter((t) => t.field !== e), this.scheduleRender("columns");
547
+ }
548
+ // ----- Sort -----
549
+ toggleSort(e, t = !1) {
550
+ const i = this.state.sortModel.findIndex((r) => r.colId === e);
551
+ let s;
552
+ i === -1 ? s = { colId: e, sort: "asc" } : this.state.sortModel[i].sort === "asc" ? s = { colId: e, sort: "desc" } : s = null, t ? (i >= 0 && this.state.sortModel.splice(i, 1), s && this.state.sortModel.push(s)) : this.state.sortModel = s ? [s] : [], this.scheduleRender("sort"), p(this.element, "grid:sortChanged", { sortModel: this.state.sortModel });
553
+ }
554
+ setSortModel(e) {
555
+ this.state.sortModel = Array.isArray(e) ? e.slice() : [], this.scheduleRender("sort"), p(this.element, "grid:sortChanged", { sortModel: this.state.sortModel });
556
+ }
557
+ // ----- Filter -----
558
+ setColumnFilter(e, t) {
559
+ t == null ? delete this.state.filterModel[e] : this.state.filterModel[e] = t, this.state.pagination.page = 0, this.scheduleRender("filter"), p(this.element, "grid:filterChanged", { filterModel: { ...this.state.filterModel } });
560
+ }
561
+ setFilterModel(e) {
562
+ this.state.filterModel = { ...e || {} }, this.state.pagination.page = 0, this.scheduleRender("filter"), p(this.element, "grid:filterChanged", { filterModel: { ...this.state.filterModel } });
563
+ }
564
+ setQuickFilter(e) {
565
+ const t = e == null ? "" : String(e);
566
+ t !== this.state.quickFilter && (this.state.quickFilter = t, this.state.pagination.page = 0, this.scheduleRender("filter"), p(this.element, "grid:filterChanged", {
567
+ filterModel: { ...this.state.filterModel },
568
+ quickFilter: t
569
+ }));
570
+ }
571
+ getQuickFilter() {
572
+ return this.state.quickFilter;
573
+ }
574
+ // ----- Selection -----
575
+ toggleRowSelection(e, t = "single") {
576
+ if (this.rowSelectionValue === "") return;
577
+ const i = this.state.selection;
578
+ this.rowSelectionValue === "single" ? (i.clear(), i.add(e)) : t === "range" && this._lastSelectedId != null ? this._selectRange(this._lastSelectedId, e) : t === "toggle" ? i.has(e) ? i.delete(e) : i.add(e) : (i.clear(), i.add(e)), this._lastSelectedId = e, this.scheduleRender("selection"), p(this.element, "grid:selectionChanged", {
579
+ selectedRows: this.getSelectedRows(),
580
+ selectedIds: Array.from(i)
581
+ });
582
+ }
583
+ setSelected(e, t) {
584
+ t ? this.state.selection.add(e) : this.state.selection.delete(e), this.scheduleRender("selection"), p(this.element, "grid:selectionChanged", {
585
+ selectedRows: this.getSelectedRows(),
586
+ selectedIds: Array.from(this.state.selection)
587
+ });
588
+ }
589
+ selectAll() {
590
+ this._displayList.filteredSorted.forEach((e) => this.state.selection.add(this._rowId(e))), this.scheduleRender("selection"), p(this.element, "grid:selectionChanged", {
591
+ selectedRows: this.getSelectedRows(),
592
+ selectedIds: Array.from(this.state.selection)
593
+ });
594
+ }
595
+ deselectAll() {
596
+ this.state.selection.clear(), this.scheduleRender("selection"), p(this.element, "grid:selectionChanged", { selectedRows: [], selectedIds: [] });
597
+ }
598
+ getSelectedRows() {
599
+ const e = this.state.selection;
600
+ return this.state.rowData.filter((t) => e.has(this._rowId(t)));
601
+ }
602
+ _selectRange(e, t) {
603
+ const i = this._displayList.filteredSorted, s = i.findIndex((c) => this._rowId(c) === e), r = i.findIndex((c) => this._rowId(c) === t);
604
+ if (s < 0 || r < 0) return;
605
+ const [o, a] = s <= r ? [s, r] : [r, s];
606
+ for (let c = o; c <= a; c++) this.state.selection.add(this._rowId(i[c]));
607
+ }
608
+ // ----- Pagination -----
609
+ goToPage(e) {
610
+ const t = this.totalPages() - 1;
611
+ this.state.pagination.page = Math.max(0, Math.min(e, t)), this.scheduleRender("page"), p(this.element, "grid:paginationChanged", {
612
+ page: this.state.pagination.page,
613
+ pageSize: this.state.pagination.pageSize,
614
+ totalPages: this.totalPages()
615
+ });
616
+ }
617
+ setPageSize(e) {
618
+ this.state.pagination.pageSize = Math.max(1, e), this.state.pagination.page = 0, this.scheduleRender("page"), p(this.element, "grid:paginationChanged", {
619
+ page: 0,
620
+ pageSize: this.state.pagination.pageSize,
621
+ totalPages: this.totalPages()
622
+ });
623
+ }
624
+ totalPages() {
625
+ if (!this.state.pagination.enabled) return 1;
626
+ const e = this.filteredCount();
627
+ return Math.max(1, Math.ceil(e / this.state.pagination.pageSize));
628
+ }
629
+ filteredCount() {
630
+ if (this.state.serverSide) return this.state.serverRowCount;
631
+ const e = Object.fromEntries(this.state.columnDefs.map((s) => [s.field, s])), t = this.state.columnDefs.filter((s) => !s.hidden && !s._isCheckbox);
632
+ let i = P(this.state.rowData, this.state.filterModel, e);
633
+ return i = V(i, this.state.quickFilter, t), i.length;
634
+ }
635
+ // Server-side row model: set the total row count so pagination reflects the
636
+ // full table even though only one page is loaded client-side. No event is
637
+ // emitted (callers pair this with setRowData, whose rowDataChanged refreshes
638
+ // the pagination UI) — emitting paginationChanged here would loop grid-sync.
639
+ setRowCount(e) {
640
+ this.state.serverRowCount = Math.max(0, Number(e) || 0), this.scheduleRender("page");
641
+ }
642
+ lastPageIndex() {
643
+ return this.totalPages() - 1;
644
+ }
645
+ // ----- Editing -----
646
+ startEditingCell(e, t, i = void 0) {
647
+ const s = this.state.columnDefs.find((o) => o.field === t);
648
+ if (!s || !s.editable) return;
649
+ const r = this.state.rowData.find((o) => this._rowId(o) === e);
650
+ r && (this.state.editing = { rowId: e, colId: t, originalValue: b(r, s), initialValue: i }, this.scheduleRender("cells"));
651
+ }
652
+ stopEditing(e = !1) {
653
+ if (!this.state.editing) return;
654
+ const { rowId: t, colId: i, originalValue: s, draftValue: r } = this.state.editing, o = this._tbody.querySelector(`tr[data-row-id="${D(t)}"] td[data-col-id="${D(i)}"]`);
655
+ let a = s;
656
+ if (!e && o) {
657
+ const c = o.querySelector("[data-editor-input]") || o.querySelector("input,select,textarea");
658
+ c ? a = ne(c.value, this._colByField(i)?.type) : r !== void 0 && (a = r);
659
+ }
660
+ if (this.state.editing = null, !e && a !== s) {
661
+ const c = this.state.rowData.find((d) => this._rowId(d) === t), u = c[i];
662
+ c[i] = a, p(this.element, "grid:cellValueChanged", { rowId: t, colId: i, oldValue: u, newValue: a });
663
+ }
664
+ this.scheduleRender("cells");
665
+ }
666
+ // ----- Column-level mutations from API or interactions -----
667
+ setColumnVisible(e, t) {
668
+ const i = this._colByField(e);
669
+ i && (i.hidden = !t, this._runtimeOverrides[e] = { ...this._runtimeOverrides[e] || {}, hidden: !t }, this.scheduleRender("columns"), p(this.element, "grid:columnVisible", { colId: e, visible: t }));
670
+ }
671
+ setColumnPinned(e, t) {
672
+ const i = this._colByField(e);
673
+ if (!i) return;
674
+ const s = t || null;
675
+ i.pinned = s, this._runtimeOverrides[e] = { ...this._runtimeOverrides[e] || {}, pinned: s }, this._reorderForPinning(), this.scheduleRender("columns"), p(this.element, "grid:columnPinned", { colId: e, pinned: s });
676
+ }
677
+ setColumnWidth(e, t) {
678
+ const i = this._colByField(e);
679
+ if (!i) return;
680
+ const s = Math.max(i.minWidth || 40, Math.min(i.maxWidth || 4e3, t));
681
+ i.width = s, this._runtimeOverrides[e] = { ...this._runtimeOverrides[e] || {}, width: s }, this.scheduleRender("columns"), p(this.element, "grid:columnResized", { colId: e, width: s });
682
+ }
683
+ moveColumn(e, t) {
684
+ const i = this.state.columnDefs.findIndex((r) => r.field === e);
685
+ if (i < 0 || i === t) return;
686
+ const [s] = this.state.columnDefs.splice(i, 1);
687
+ this.state.columnDefs.splice(t, 0, s), this.scheduleRender("columns"), p(this.element, "grid:columnMoved", { colId: e, fromIndex: i, toIndex: t });
688
+ }
689
+ autoSizeColumn(e) {
690
+ const t = this._colByField(e);
691
+ if (!t) return;
692
+ const i = (t.headerName || t.field || "").length, s = this.state.rowData.slice(0, 200);
693
+ let r = i;
694
+ for (const o of s) {
695
+ const a = String(C(o, t) ?? "").length;
696
+ a > r && (r = a);
697
+ }
698
+ this.setColumnWidth(e, Math.min(400, Math.max(60, r * 8 + 24)));
699
+ }
700
+ sizeColumnsToFit() {
701
+ const e = this._viewport?.clientWidth || this.element.clientWidth || 0;
702
+ if (!e) return;
703
+ const t = this._visibleCols(), i = t.reduce((r, o) => r + (o.width || 150), 0);
704
+ if (i === 0) return;
705
+ const s = e / i;
706
+ t.forEach((r) => {
707
+ r.width = Math.max(r.minWidth || 40, Math.floor((r.width || 150) * s));
708
+ }), this.scheduleRender("columns");
709
+ }
710
+ _reorderForPinning() {
711
+ const e = this.state.columnDefs.filter((s) => s.pinned === "left"), t = this.state.columnDefs.filter((s) => s.pinned === "right"), i = this.state.columnDefs.filter((s) => !s.pinned);
712
+ this.state.columnDefs = [...e, ...i, ...t];
713
+ }
714
+ // ----- Data mutations -----
715
+ setRowData(e) {
716
+ this.state.rowData = Array.isArray(e) ? e : [], this.state.selection.clear(), this.state.serverSide || (this.state.pagination.page = 0), this.scheduleRender("data"), p(this.element, "grid:rowDataChanged", { rows: this.state.rowData });
717
+ }
718
+ applyTransaction(e) {
719
+ const t = [], i = [], s = [], r = new Map(this.state.rowData.map((o) => [this._rowId(o), o]));
720
+ return (e.remove || []).forEach((o) => {
721
+ const a = this._rowId(o);
722
+ r.delete(a) && s.push(o);
723
+ }), (e.update || []).forEach((o) => {
724
+ const a = this._rowId(o);
725
+ r.has(a) && (r.set(a, { ...r.get(a), ...o }), i.push(o));
726
+ }), (e.add || []).forEach((o) => {
727
+ const a = this._rowId(o);
728
+ r.has(a) || (r.set(a, o), t.push(o));
729
+ }), this.state.rowData = Array.from(r.values()), this.scheduleRender("data"), p(this.element, "grid:rowDataChanged", { rows: this.state.rowData }), { added: t, updated: i, removed: s };
730
+ }
731
+ setColumnDefs(e) {
732
+ this.state.columnDefs = e.map((t) => ({ ...t })), this.scheduleRender("columns");
733
+ }
734
+ refresh() {
735
+ this.scheduleRender("cells");
736
+ }
737
+ // ----- Export -----
738
+ getDataAsCsv({ columnSeparator: e = ",", onlySelected: t = !1 } = {}) {
739
+ const i = this._visibleCols().filter((a) => !a._isCheckbox), s = t ? this.getSelectedRows() : this._displayList.filteredSorted, r = (a) => /[",\n\r]/.test(a) ? `"${String(a).replace(/"/g, '""')}"` : String(a), o = [i.map((a) => r(a.headerName || a.field)).join(e)];
740
+ for (const a of s)
741
+ o.push(i.map((c) => r(C(a, c))).join(e));
742
+ return o.join(`
743
+ `);
744
+ }
745
+ exportDataAsCsv({ fileName: e = "export.csv", ...t } = {}) {
746
+ const i = this.getDataAsCsv(t), s = new Blob([i], { type: "text/csv;charset=utf-8" }), r = URL.createObjectURL(s), o = f("a", { href: r, download: e });
747
+ return document.body.appendChild(o), o.click(), o.remove(), URL.revokeObjectURL(r), i;
748
+ }
749
+ // ----- Render pipeline -----
750
+ scheduleRender(e) {
751
+ this._dirty.add(e), !this._renderPending && (this._renderPending = !0, requestAnimationFrame(() => {
752
+ this._renderPending = !1, this._render();
753
+ }));
754
+ }
755
+ _render() {
756
+ const e = this._dirty;
757
+ this._dirty = /* @__PURE__ */ new Set(), (e.has("data") || e.has("filter") || e.has("sort") || e.has("page") || e.size === 0) && (this._displayList = Q({
758
+ rowData: this.state.rowData,
759
+ columnDefs: this.state.columnDefs,
760
+ sortModel: this.state.sortModel,
761
+ filterModel: this.state.filterModel,
762
+ quickFilter: this.state.quickFilter,
763
+ pagination: this.state.pagination,
764
+ serverSide: this.state.serverSide,
765
+ serverRowCount: this.state.serverRowCount
766
+ })), (e.has("columns") || e.has("sort") || e.has("filter") || e.has("selection")) && this._renderHeader(), this._renderBody(), this._renderPagination();
767
+ }
768
+ _renderHeader() {
769
+ if (!this._thead) return;
770
+ const e = this._visibleCols(), t = this._thead.querySelector("tr") || (() => {
771
+ const d = f("tr");
772
+ return this._thead.appendChild(d), d;
773
+ })(), i = /* @__PURE__ */ new Map();
774
+ Array.from(t.querySelectorAll("th")).forEach((d) => {
775
+ const h = d.getAttribute("data-header-cell-field-value") || d.getAttribute("data-field");
776
+ h && i.set(h, d);
777
+ });
778
+ const s = Array.from(t.children).map((d) => d.getAttribute("data-header-cell-field-value") || d.getAttribute("data-field")).filter(Boolean), r = e.map((d) => d.field);
779
+ if (!(s.length === r.length && s.every((d, h) => d === r[h]))) {
780
+ const d = [];
781
+ for (const h of e) {
782
+ let g = i.get(h.field);
783
+ g || (g = f("th", {
784
+ "data-field": h.field,
785
+ "data-synth": "true"
786
+ }, [f("div", { class: "sg-header-content" }, [
787
+ f("span", { class: "sg-header-label" }, h.headerName || h.field || "")
788
+ ])])), d.push(g);
789
+ }
790
+ t.replaceChildren(...d);
791
+ }
792
+ let a = this._table.querySelector("colgroup");
793
+ a || (a = f("colgroup"), this._table.insertBefore(a, this._thead));
794
+ const c = Array.from(a.children);
795
+ for (e.forEach((d, h) => {
796
+ let g = c[h];
797
+ g || (g = f("col"), a.appendChild(g)), g.style.width = d.width ? d.width + "px" : "";
798
+ }); a.children.length > e.length; ) a.lastElementChild.remove();
799
+ const u = this._pinOffsets();
800
+ for (const d of e) {
801
+ const h = t.querySelector(`th[data-header-cell-field-value="${D(d.field)}"]`) || t.querySelector(`th[data-field="${D(d.field)}"]`);
802
+ if (!h) continue;
803
+ const g = this.state.sortModel.find((v) => v.colId === d.field);
804
+ A(h, {
805
+ "data-sortable": d.sortable ? "true" : null,
806
+ "data-filterable": d.filter ? "true" : null,
807
+ "data-filter-active": this.state.filterModel[d.field] ? "true" : null,
808
+ "data-sort": g?.sort || null,
809
+ "data-pinned": d.pinned || null
810
+ }), d.width && (h.style.width = d.width + "px"), h.style.left = d.pinned === "left" ? u.left[d.field] + "px" : "", h.style.right = d.pinned === "right" ? u.right[d.field] + "px" : "", this._ensureHeaderChrome(h, d, g);
811
+ }
812
+ }
813
+ _ensureHeaderChrome(e, t, i) {
814
+ if (t._isRowNumber) {
815
+ e.classList.add("sg-gutter-header"), e.textContent = "";
816
+ return;
817
+ }
818
+ if (t._isCheckbox) {
819
+ e.classList.add("sg-checkbox-header");
820
+ let a = e.querySelector('input[type="checkbox"]');
821
+ a || (a = f("input", { type: "checkbox", "aria-label": "Select all" }), a.addEventListener("change", (d) => {
822
+ d.target.checked ? this.selectAll() : this.deselectAll();
823
+ }), e.textContent = "", e.appendChild(a));
824
+ const c = this._displayList.filteredSorted.length, u = this.state.selection.size;
825
+ a.checked = u > 0 && u >= c, a.indeterminate = u > 0 && u < c;
826
+ return;
827
+ }
828
+ let s = e.querySelector(".sg-header-content");
829
+ if (!s) {
830
+ const a = e.textContent.trim();
831
+ e.textContent = "", s = f("div", { class: "sg-header-content" }, [
832
+ f("span", { class: "sg-header-label" }, a || t.headerName || t.field || "")
833
+ ]), e.appendChild(s);
834
+ }
835
+ let r = s.querySelector(".sg-sort-icon");
836
+ if (t.sortable)
837
+ if (r || (r = f("span", { class: "sg-sort-icon" }), s.appendChild(r)), i && this.state.sortModel.length > 1) {
838
+ let a = s.querySelector(".sg-sort-index");
839
+ a || (a = f("span", { class: "sg-sort-index" }), s.appendChild(a)), a.textContent = String(this.state.sortModel.indexOf(i) + 1);
840
+ } else
841
+ s.querySelector(".sg-sort-index")?.remove();
842
+ else r && r.remove();
843
+ let o = s.querySelector(".sg-filter-icon");
844
+ t.filter ? o || (o = f("span", {
845
+ class: "sg-filter-icon",
846
+ "data-action": "click->header-cell#openFilter",
847
+ title: "Filter"
848
+ }), s.appendChild(o)) : o && o.remove(), t.resizable !== !1 && !e.querySelector(".sg-resize-handle") && !t._isCheckbox && e.appendChild(f("span", {
849
+ class: "sg-resize-handle",
850
+ "data-action": "mousedown->header-cell#startResize"
851
+ }));
852
+ }
853
+ _renderBody() {
854
+ if (!this._tbody) return;
855
+ const e = this._visibleCols(), t = this._displayList.pageRows;
856
+ this._selKeys = this._computeCellSelKeys();
857
+ const i = this.virtualValue || t.length > 200;
858
+ let s = t, r = 0;
859
+ if (i) {
860
+ const d = this._viewport?.clientHeight || 400, h = this.state.rowHeight, g = Z(this.state.scrollTop, d, h, t.length, 8);
861
+ r = g.first, s = t.slice(g.first, g.last);
862
+ }
863
+ const o = /* @__PURE__ */ new Map();
864
+ Array.from(this._tbody.children).forEach((d) => {
865
+ const h = d.dataset.rowId;
866
+ h != null && o.set(h, d);
867
+ });
868
+ const a = document.createDocumentFragment(), c = this.state.pagination.enabled ? this.state.pagination.page * this.state.pagination.pageSize : 0, u = (d) => c + r + d + 1;
869
+ if (i) {
870
+ const d = this.state.rowHeight, h = r * d, g = (t.length - r - s.length) * d;
871
+ a.appendChild(this._spacerRow(h, e.length)), s.forEach((v, y) => a.appendChild(this._buildRow(v, e, o, u(y)))), a.appendChild(this._spacerRow(g, e.length));
872
+ } else
873
+ s.forEach((d, h) => a.appendChild(this._buildRow(d, e, o, u(h))));
874
+ this._tbody.replaceChildren(a);
875
+ }
876
+ _buildRow(e, t, i, s) {
877
+ const r = String(this._rowId(e));
878
+ let o = i.get(r);
879
+ o || (o = f("tr")), o.dataset.rowId = r, o.classList.remove("sg-spacer");
880
+ const a = this.state.selection.has(this._rowId(e));
881
+ return A(o, { "data-selected": a ? "true" : null }), this._renderRow(o, e, t, s), o;
882
+ }
883
+ _spacerRow(e, t) {
884
+ if (e <= 0) {
885
+ const s = f("tr", { class: "sg-spacer", "aria-hidden": "true" });
886
+ return s.style.height = "0px", s.appendChild(f("td", { colspan: String(t), style: { height: "0px", padding: "0", border: "0" } })), s;
887
+ }
888
+ const i = f("tr", { class: "sg-spacer", "aria-hidden": "true" });
889
+ return i.style.height = e + "px", i.appendChild(f("td", { colspan: String(t), style: { height: e + "px", padding: "0", border: "0" } })), i;
890
+ }
891
+ _renderRow(e, t, i, s) {
892
+ e.innerHTML = "";
893
+ const r = this._pinOffsets(), o = this._selKeys || { active: null, range: null }, a = String(this._rowId(t));
894
+ for (const c of i) {
895
+ const u = `${a}:${c.field}`, d = f("td", {
896
+ "data-col-id": c.field,
897
+ "data-pinned": c.pinned || null,
898
+ "data-cell-active": o.active === u ? "true" : null,
899
+ "data-cell-range": o.range && o.range.has(u) ? "true" : null
900
+ });
901
+ if (c.pinned === "left" ? d.style.left = r.left[c.field] + "px" : c.pinned === "right" && (d.style.right = r.right[c.field] + "px"), c._isRowNumber) {
902
+ d.classList.add("sg-gutter-cell"), d.setAttribute("data-gutter", "true"), d.removeAttribute("data-cell-active"), d.removeAttribute("data-cell-range"), d.textContent = s != null ? String(s) : "", e.appendChild(d);
903
+ continue;
904
+ }
905
+ if (c._isCheckbox) {
906
+ d.classList.add("sg-checkbox-cell");
907
+ const g = f("input", { type: "checkbox" });
908
+ g.checked = this.state.selection.has(this._rowId(t)), d.appendChild(g), e.appendChild(d);
909
+ continue;
910
+ }
911
+ if (this.state.editing && this.state.editing.rowId === this._rowId(t) && this.state.editing.colId === c.field) {
912
+ d.setAttribute("data-editing", "true");
913
+ const g = this.state.editing.initialValue !== void 0 ? this.state.editing.initialValue : b(t, c), { node: v, control: y } = this._buildEditor(c, g);
914
+ d.appendChild(v);
915
+ const w = this.state.editing.initialValue !== void 0;
916
+ queueMicrotask(() => {
917
+ y?.focus(), w || y?.select?.();
918
+ });
919
+ } else
920
+ this._renderCellContent(d, t, c);
921
+ e.appendChild(d);
922
+ }
923
+ }
924
+ _renderCellContent(e, t, i) {
925
+ if (i.cellRenderer) {
926
+ const s = M(i.cellRenderer);
927
+ if (s) {
928
+ const r = b(t, i), o = C(t, i);
929
+ (s.dataset.bind || s.dataset.bindText !== void 0) && (s.textContent = s.dataset.bind ? String(t[s.dataset.bind] ?? "") : o), s.dataset.bindAttr && s.setAttribute(s.dataset.bindAttr, r), s.querySelectorAll("[data-bind], [data-bind-attr], [data-bind-text]").forEach((a) => {
930
+ a.dataset.bindText !== void 0 ? a.textContent = o : a.dataset.bind && (a.textContent = String(t[a.dataset.bind] ?? "")), a.dataset.bindAttr && a.setAttribute(a.dataset.bindAttr, r);
931
+ }), e.appendChild(s);
932
+ return;
933
+ }
934
+ }
935
+ e.textContent = C(t, i);
936
+ }
937
+ // Returns { node, control }: the element to mount and the focusable control
938
+ // whose value is read on commit. A column may supply a custom editor via a
939
+ // <template> (col.cellEditor); otherwise a type-appropriate input is built.
940
+ _buildEditor(e, t) {
941
+ if (e.cellEditor) {
942
+ const s = M(e.cellEditor);
943
+ if (s) {
944
+ const r = s.matches?.("input,select,textarea") ? s : s.querySelector?.("[data-editor-input]") || s.querySelector?.("input,select,textarea");
945
+ return r && (this._seedEditorValue(r, e, t), r.addEventListener("keydown", this._onEditorKey), r.addEventListener("blur", this._onEditorBlur)), { node: s, control: r };
946
+ }
947
+ }
948
+ const i = this._buildEditorInput(e, t);
949
+ return { node: i, control: i };
950
+ }
951
+ _seedEditorValue(e, t, i) {
952
+ if (t.type === "date" && i) {
953
+ const s = i instanceof Date ? i : new Date(i);
954
+ e.value = Number.isNaN(s?.getTime?.()) ? i ?? "" : s.toISOString().slice(0, 10);
955
+ } else t.type === "boolean" ? e.value = i === !0 ? "true" : i === !1 ? "false" : "" : e.value = i ?? "";
956
+ }
957
+ _buildEditorInput(e, t) {
958
+ let i;
959
+ if (e.type === "number") i = f("input", { type: "number", value: t ?? "" });
960
+ else if (e.type === "date") {
961
+ const s = t instanceof Date ? t : t ? new Date(t) : null, r = s ? s.toISOString().slice(0, 10) : "";
962
+ i = f("input", { type: "date", value: r });
963
+ } else e.type === "boolean" ? (i = f("select"), i.append(
964
+ new Option("—", ""),
965
+ new Option("true", "true", t === !0, t === !0),
966
+ new Option("false", "false", t === !1, t === !1)
967
+ )) : i = f("input", { type: "text", value: t ?? "" });
968
+ return i.addEventListener("keydown", this._onEditorKey), i.addEventListener("blur", this._onEditorBlur), i;
969
+ }
970
+ _renderPagination() {
971
+ this.state.pagination.enabled;
972
+ }
973
+ // ----- Event delegation (clicks on rendered tbody) -----
974
+ // Stimulus actions on tbody — wired in _buildChrome by adding data-action.
975
+ // For simplicity we add native listeners here.
976
+ _attachBodyListeners() {
977
+ this._listenersAttached || (this._listenersAttached = !0, this._tbody.addEventListener("click", (e) => this._onBodyClick(e)), this._tbody.addEventListener("dblclick", (e) => this._onBodyDblClick(e)), this._tbody.addEventListener("mousedown", this._onCellMouseDown), this._tbody.addEventListener("mouseover", this._onCellMouseOver), document.addEventListener("mouseup", this._onCellMouseUp), document.addEventListener("copy", this._onCopy), this._viewport.addEventListener("scroll", this._onScroll, { passive: !0 }));
978
+ }
979
+ _onBodyClick(e) {
980
+ const t = e.target.closest("tr");
981
+ if (!t || e.target.closest('td[data-editing="true"]')) return;
982
+ const i = this._coerceRowId(t.dataset.rowId), s = e.target.closest("td");
983
+ if (e.target.matches('input[type="checkbox"]')) {
984
+ this.toggleRowSelection(i, "toggle");
985
+ return;
986
+ }
987
+ if (s && s.dataset.gutter === "true") {
988
+ if (this._rowDragMoved) {
989
+ this._rowDragMoved = !1;
990
+ return;
991
+ }
992
+ if (this.rowSelectionValue !== "") {
993
+ const o = e.shiftKey ? "range" : e.metaKey || e.ctrlKey ? "toggle" : "replace";
994
+ this.clearCellSelection(), this.toggleRowSelection(i, o), p(this.element, "grid:rowClicked", { rowId: i, row: this.state.rowData.find((a) => this._rowId(a) === i), event: e });
995
+ }
996
+ this._cellDragMoved = !1;
997
+ return;
998
+ }
999
+ if (s) {
1000
+ const o = this.state.rowData.find((c) => this._rowId(c) === i), a = s.dataset.colId;
1001
+ p(this.element, "grid:cellClicked", { rowId: i, colId: a, value: o?.[a], event: e });
1002
+ }
1003
+ if (this.cellSelectionValue) {
1004
+ if (this._cellDragMoved) {
1005
+ this._cellDragMoved = !1;
1006
+ return;
1007
+ }
1008
+ !e.shiftKey && !(e.metaKey || e.ctrlKey) && this.state.selection.size && this.deselectAll();
1009
+ return;
1010
+ }
1011
+ if (this.suppressRowClickSelectionValue || this.rowSelectionValue === "") {
1012
+ this._cellDragMoved = !1;
1013
+ return;
1014
+ }
1015
+ if (this._cellDragMoved) {
1016
+ this._cellDragMoved = !1;
1017
+ return;
1018
+ }
1019
+ const r = e.shiftKey ? "range" : e.metaKey || e.ctrlKey || this.rowMultiSelectWithClickValue ? "toggle" : "replace";
1020
+ this.toggleRowSelection(i, r), p(this.element, "grid:rowClicked", { rowId: i, row: this.state.rowData.find((o) => this._rowId(o) === i), event: e });
1021
+ }
1022
+ // ----- Cell selection (Numbers/Sheets-style: multi-range + active cell) -----
1023
+ _cellAt(e) {
1024
+ const t = e.closest?.("td"), i = e.closest?.("tr");
1025
+ return !t || !i || t.classList.contains("sg-checkbox-cell") || t.dataset.gutter === "true" || !t.dataset.colId || t.dataset.editing === "true" ? null : { rowId: this._coerceRowId(i.dataset.rowId), colId: t.dataset.colId };
1026
+ }
1027
+ _activeCell() {
1028
+ const e = this.state.cellSel.ranges[this.state.cellSel.activeIdx];
1029
+ return e ? e.anchor : null;
1030
+ }
1031
+ _setSingleCellSel(e) {
1032
+ this.state.cellSel = { ranges: [{ anchor: e, focus: e }], activeIdx: 0 };
1033
+ }
1034
+ _addCellRange(e) {
1035
+ this.state.cellSel.ranges.push({ anchor: e, focus: e }), this.state.cellSel.activeIdx = this.state.cellSel.ranges.length - 1;
1036
+ }
1037
+ _extendActiveRange(e) {
1038
+ const t = this.state.cellSel.ranges[this.state.cellSel.activeIdx];
1039
+ t ? t.focus = e : this._setSingleCellSel(e);
1040
+ }
1041
+ clearCellSelection() {
1042
+ this.state.cellSel = { ranges: [], activeIdx: -1 }, this._applyCellSelHighlight();
1043
+ }
1044
+ _startRowDrag(e) {
1045
+ const t = Array.from(this.state.selection).map(String), i = new Set(t.includes(String(e)) ? t : [String(e)]), s = f("div", { class: "sg-drag-ghost sg-grid" }), r = f("table"), o = f("tbody");
1046
+ let a = 0;
1047
+ this._tbody.querySelectorAll("tr[data-row-id]").forEach((u) => {
1048
+ if (i.has(u.dataset.rowId) && a < 6) {
1049
+ const d = u.cloneNode(!0);
1050
+ d.removeAttribute("data-selected"), d.querySelectorAll("td").forEach((h) => {
1051
+ h.style.left = "", h.style.right = "", h.removeAttribute("data-pinned"), h.removeAttribute("data-cell-active"), h.removeAttribute("data-cell-range");
1052
+ }), o.appendChild(d), a += 1;
1053
+ }
1054
+ }), r.appendChild(o), s.appendChild(r), i.size > a && s.appendChild(f("div", { class: "sg-drag-ghost-more" }, `+${i.size - a} more rows`)), s.style.width = `${Math.min(this._tbody.offsetWidth, 520)}px`, document.body.appendChild(s);
1055
+ const c = f("div", { class: "sg-drop-indicator" });
1056
+ document.body.appendChild(c), this._rowDrag = { ids: i, ghost: s, indicator: c, dropRowId: null, dropBefore: !0 }, document.body.classList.add("sg-row-dragging");
1057
+ }
1058
+ _updateDropIndicator(e) {
1059
+ const t = Array.from(this._tbody.querySelectorAll("tr[data-row-id]"));
1060
+ let i = null, s = !0;
1061
+ for (const c of t) {
1062
+ const u = c.getBoundingClientRect();
1063
+ if (e < u.top + u.height / 2) {
1064
+ i = c, s = !0;
1065
+ break;
1066
+ }
1067
+ i = c, s = !1;
1068
+ }
1069
+ if (!i) return;
1070
+ const r = i.getBoundingClientRect(), o = this._viewport.getBoundingClientRect(), a = this._rowDrag.indicator;
1071
+ a.style.left = `${o.left}px`, a.style.width = `${o.width}px`, a.style.top = `${(s ? r.top : r.bottom) - 1}px`, this._rowDrag.dropRowId = this._coerceRowId(i.dataset.rowId), this._rowDrag.dropBefore = s;
1072
+ }
1073
+ _finishRowDrag() {
1074
+ const { ids: e, ghost: t, indicator: i, dropRowId: s, dropBefore: r } = this._rowDrag;
1075
+ if (t.remove(), i.remove(), document.body.classList.remove("sg-row-dragging"), this._rowDrag = null, s == null || e.has(String(s))) return;
1076
+ const o = this.state.rowData, a = o.filter((d) => e.has(String(this._rowId(d)))), c = o.filter((d) => !e.has(String(this._rowId(d))));
1077
+ let u = c.findIndex((d) => this._rowId(d) === s);
1078
+ u < 0 ? u = c.length : r || (u += 1), c.splice(u, 0, ...a), this.state.rowData = c, this.state.sortModel = [], this.scheduleRender("data"), p(this.element, "grid:rowDragEnd", {
1079
+ ids: a.map((d) => this._rowId(d)),
1080
+ toRowId: s,
1081
+ before: r
1082
+ });
1083
+ }
1084
+ // Toggle the active/range data-attrs on the existing cell DOM without
1085
+ // rebuilding the tbody (so in-flight mouse interactions aren't disrupted).
1086
+ _applyCellSelHighlight() {
1087
+ const e = this._computeCellSelKeys();
1088
+ this._selKeys = e, this._tbody && this._tbody.querySelectorAll("td[data-col-id]").forEach((t) => {
1089
+ const i = t.parentElement, s = `${i && i.dataset.rowId}:${t.dataset.colId}`;
1090
+ e.active === s ? t.setAttribute("data-cell-active", "true") : t.removeAttribute("data-cell-active"), e.range && e.range.has(s) ? t.setAttribute("data-cell-range", "true") : t.removeAttribute("data-cell-range");
1091
+ });
1092
+ }
1093
+ // Rectangle (display indices) for a {anchor,focus} range, or null.
1094
+ _rangeRect(e) {
1095
+ if (!e) return null;
1096
+ const t = this._displayList.pageRows, i = this._visibleCols(), s = (d) => t.findIndex((h) => this._rowId(h) === d), r = (d) => i.findIndex((h) => h.field === d), o = s(e.anchor.rowId), a = r(e.anchor.colId);
1097
+ if (o < 0 || a < 0) return null;
1098
+ const c = s(e.focus.rowId), u = r(e.focus.colId);
1099
+ return {
1100
+ r0: Math.min(o, c < 0 ? o : c),
1101
+ r1: Math.max(o, c < 0 ? o : c),
1102
+ c0: Math.min(a, u < 0 ? a : u),
1103
+ c1: Math.max(a, u < 0 ? a : u),
1104
+ rows: t,
1105
+ cols: i
1106
+ };
1107
+ }
1108
+ _activeRect() {
1109
+ return this._rangeRect(this.state.cellSel.ranges[this.state.cellSel.activeIdx]);
1110
+ }
1111
+ _cellRangeRows(e = this._activeRect()) {
1112
+ if (!e) return [];
1113
+ const t = [];
1114
+ for (let i = e.r0; i <= e.r1; i++) {
1115
+ const s = e.rows[i];
1116
+ if (!s) continue;
1117
+ const r = [];
1118
+ for (let o = e.c0; o <= e.c1; o++) {
1119
+ const a = e.cols[o];
1120
+ a && r.push(C(s, a));
1121
+ }
1122
+ t.push(r);
1123
+ }
1124
+ return t;
1125
+ }
1126
+ // Active cell key + union of all ranges' cells (active stays outlined, unfilled).
1127
+ _computeCellSelKeys() {
1128
+ const e = this._activeCell();
1129
+ if (!e) return { active: null, range: null };
1130
+ const t = `${e.rowId}:${e.colId}`, i = /* @__PURE__ */ new Set();
1131
+ for (const s of this.state.cellSel.ranges) {
1132
+ const r = this._rangeRect(s);
1133
+ if (r)
1134
+ for (let o = r.r0; o <= r.r1; o++) {
1135
+ const a = r.rows[o];
1136
+ if (a)
1137
+ for (let c = r.c0; c <= r.c1; c++) {
1138
+ const u = r.cols[c];
1139
+ if (!u) continue;
1140
+ const d = `${this._rowId(a)}:${u.field}`;
1141
+ d !== t && i.add(d);
1142
+ }
1143
+ }
1144
+ }
1145
+ return { active: t, range: i };
1146
+ }
1147
+ getCellSelectionDetail() {
1148
+ const e = this._activeRect();
1149
+ return {
1150
+ active: this._activeCell(),
1151
+ ranges: this.state.cellSel.ranges.length,
1152
+ rowCount: e ? e.r1 - e.r0 + 1 : 0,
1153
+ colCount: e ? e.c1 - e.c0 + 1 : 0
1154
+ };
1155
+ }
1156
+ // Row ids covered by any cell range.
1157
+ getCellSelectionRowIds() {
1158
+ const e = /* @__PURE__ */ new Set();
1159
+ for (const t of this.state.cellSel.ranges) {
1160
+ const i = this._rangeRect(t);
1161
+ if (i)
1162
+ for (let s = i.r0; s <= i.r1; s++) {
1163
+ const r = i.rows[s];
1164
+ r && e.add(this._rowId(r));
1165
+ }
1166
+ }
1167
+ return Array.from(e);
1168
+ }
1169
+ _focusGrid() {
1170
+ if (document.activeElement !== this.element && !this.element.contains(document.activeElement))
1171
+ try {
1172
+ this.element.focus({ preventScroll: !0 });
1173
+ } catch {
1174
+ }
1175
+ }
1176
+ // ----- Keyboard navigation (Numbers/Sheets-style) -----
1177
+ _navCols() {
1178
+ return this._visibleCols().filter((e) => !e._isCheckbox && !e._isRowNumber);
1179
+ }
1180
+ _moveActiveCell(e, t, i) {
1181
+ const s = this._displayList.pageRows, r = this._navCols();
1182
+ if (!s.length || !r.length) return;
1183
+ const o = (d, h, g) => Math.max(h, Math.min(d, g)), a = this._activeCell();
1184
+ let c = a ? s.findIndex((d) => this._rowId(d) === a.rowId) : 0, u = a ? r.findIndex((d) => d.field === a.colId) : 0;
1185
+ if (c < 0 && (c = 0), u < 0 && (u = 0), i && this.state.cellSel.ranges[this.state.cellSel.activeIdx]) {
1186
+ const d = this.state.cellSel.ranges[this.state.cellSel.activeIdx], h = o(s.findIndex((v) => this._rowId(v) === d.focus.rowId) + e, 0, s.length - 1), g = o(r.findIndex((v) => v.field === d.focus.colId) + t, 0, r.length - 1);
1187
+ this._extendActiveRange({ rowId: this._rowId(s[h]), colId: r[g].field });
1188
+ } else {
1189
+ const d = o(c + e, 0, s.length - 1), h = o(u + t, 0, r.length - 1);
1190
+ this._setSingleCellSel({ rowId: this._rowId(s[d]), colId: r[h].field });
1191
+ }
1192
+ this._applyCellSelHighlight(), this._scrollActiveIntoView(), p(this.element, "grid:cellSelectionChanged", this.getCellSelectionDetail());
1193
+ }
1194
+ _selectAllCells() {
1195
+ const e = this._displayList.pageRows, t = this._navCols();
1196
+ !e.length || !t.length || (this.state.cellSel = {
1197
+ ranges: [{
1198
+ anchor: { rowId: this._rowId(e[0]), colId: t[0].field },
1199
+ focus: { rowId: this._rowId(e[e.length - 1]), colId: t[t.length - 1].field }
1200
+ }],
1201
+ activeIdx: 0
1202
+ }, this._applyCellSelHighlight(), p(this.element, "grid:cellSelectionChanged", this.getCellSelectionDetail()));
1203
+ }
1204
+ // Clear the value of every selected, editable cell (Delete/Backspace).
1205
+ _clearSelectedCells() {
1206
+ let e = !1;
1207
+ for (const t of this.state.cellSel.ranges) {
1208
+ const i = this._rangeRect(t);
1209
+ if (i)
1210
+ for (let s = i.r0; s <= i.r1; s++) {
1211
+ const r = i.rows[s];
1212
+ if (r)
1213
+ for (let o = i.c0; o <= i.c1; o++) {
1214
+ const a = i.cols[o];
1215
+ if (!a || !a.editable || a._isCheckbox || a._isRowNumber) continue;
1216
+ const c = r[a.field];
1217
+ c === "" || c == null || (r[a.field] = "", e = !0, p(this.element, "grid:cellValueChanged", { rowId: this._rowId(r), colId: a.field, oldValue: c, newValue: "" }));
1218
+ }
1219
+ }
1220
+ }
1221
+ return e && this.scheduleRender("cells"), e;
1222
+ }
1223
+ _scrollActiveIntoView() {
1224
+ this._tbody?.querySelector('td[data-cell-active="true"]')?.scrollIntoView({ block: "nearest", inline: "nearest" });
1225
+ }
1226
+ _onBodyDblClick(e) {
1227
+ const t = e.target.closest("tr"), i = e.target.closest("td");
1228
+ if (!t || !i || i.dataset.editing === "true") return;
1229
+ const s = this._coerceRowId(t.dataset.rowId), r = i.dataset.colId;
1230
+ this.startEditingCell(s, r);
1231
+ }
1232
+ // Commit the current editor and open the editor on the next (dir=1) or
1233
+ // previous (dir=-1) editable cell in reading order, wrapping within the
1234
+ // current page. RAILS.md §9 — Tab/Shift+Tab cell navigation.
1235
+ _tabToEditableCell(e) {
1236
+ const t = this.state.editing;
1237
+ if (!t) return;
1238
+ const i = this._visibleCols().filter((h) => h.editable && !h._isCheckbox), s = this._displayList.pageRows, r = s.findIndex((h) => this._rowId(h) === t.rowId), o = i.findIndex((h) => h.field === t.colId);
1239
+ if (!i.length || !s.length || r < 0 || o < 0) {
1240
+ this.stopEditing(!1);
1241
+ return;
1242
+ }
1243
+ const a = s.length * i.length, c = (r * i.length + o + e + a) % a, u = s[Math.floor(c / i.length)], d = i[c % i.length];
1244
+ this._navigatingEditor = !0, this.stopEditing(!1), this.startEditingCell(this._rowId(u), d.field), requestAnimationFrame(() => {
1245
+ this._navigatingEditor = !1;
1246
+ });
1247
+ }
1248
+ // Wire editor listeners whenever a cell mounts an input.
1249
+ // Done lazily via MutationObserver on tbody (cheap because tbody is small per page).
1250
+ // ----- Helpers -----
1251
+ _visibleCols() {
1252
+ return this.state.columnDefs.filter((e) => !e.hidden);
1253
+ }
1254
+ _pinOffsets() {
1255
+ const e = this._visibleCols(), t = {};
1256
+ let i = 0;
1257
+ for (const r of e)
1258
+ r.pinned === "left" && (t[r.field] = i, i += r.width || 150);
1259
+ const s = {};
1260
+ i = 0;
1261
+ for (let r = e.length - 1; r >= 0; r--) {
1262
+ const o = e[r];
1263
+ o.pinned === "right" && (s[o.field] = i, i += o.width || 150);
1264
+ }
1265
+ return { left: t, right: s };
1266
+ }
1267
+ _colByField(e) {
1268
+ return this.state.columnDefs.find((t) => t.field === e);
1269
+ }
1270
+ _rowId(e) {
1271
+ return e?.[this.getRowIdValue] ?? e?.id ?? e;
1272
+ }
1273
+ _coerceRowId(e) {
1274
+ if (e == null) return e;
1275
+ const t = Number(e);
1276
+ return Number.isFinite(t) && String(t) === e ? t : e;
1277
+ }
1278
+ }
1279
+ m(x, "values", {
1280
+ rowData: { type: Array, default: [] },
1281
+ rowDataUrl: { type: String, default: "" },
1282
+ rowSelection: { type: String, default: "" },
1283
+ // '', 'single', 'multiple'
1284
+ rowMultiSelectWithClick: { type: Boolean, default: !1 },
1285
+ suppressRowClickSelection: { type: Boolean, default: !1 },
1286
+ pagination: { type: Boolean, default: !1 },
1287
+ pageSize: { type: Number, default: L },
1288
+ rowHeight: { type: Number, default: te },
1289
+ headerHeight: { type: Number, default: 36 },
1290
+ virtual: { type: Boolean, default: !1 },
1291
+ virtualThreshold: { type: Number, default: 200 },
1292
+ height: { type: String, default: "" },
1293
+ // CSS height, e.g. '480px'
1294
+ getRowId: { type: String, default: "id" },
1295
+ // field name for row identity
1296
+ domLayout: { type: String, default: "" },
1297
+ // '' | 'autoHeight'
1298
+ serverSide: { type: Boolean, default: !1 },
1299
+ // rowData is one server page
1300
+ rowCount: { type: Number, default: 0 },
1301
+ // total rows on the server
1302
+ cellSelection: { type: Boolean, default: !0 },
1303
+ // click=cell; modifier/checkbox=row
1304
+ rowDrag: { type: Boolean, default: !1 }
1305
+ // drag selected rows by the gutter to reorder
1306
+ });
1307
+ function ie(n, l) {
1308
+ const e = ["headerName", "type", "sortable", "filter", "editable", "width", "minWidth", "maxWidth", "pinned", "hidden", "resizable", "cellRenderer", "cellEditor", "_isCheckbox", "_isRowNumber"];
1309
+ for (const t of e) if (n[t] !== l[t]) return !1;
1310
+ return !0;
1311
+ }
1312
+ function se(n) {
1313
+ return n === "number" || n === "date" ? [
1314
+ { value: "equals", label: "Equals" },
1315
+ { value: "notEqual", label: "Not equal" },
1316
+ { value: "lessThan", label: "Less than" },
1317
+ { value: "greaterThan", label: "Greater than" },
1318
+ { value: "inRange", label: "In range" },
1319
+ { value: "blank", label: "Blank" },
1320
+ { value: "notBlank", label: "Not blank" }
1321
+ ] : n === "boolean" ? [
1322
+ { value: "equals", label: "Equals" }
1323
+ ] : [
1324
+ { value: "contains", label: "Contains" },
1325
+ { value: "notContains", label: "Not contains" },
1326
+ { value: "equals", label: "Equals" },
1327
+ { value: "notEqual", label: "Not equal" },
1328
+ { value: "startsWith", label: "Starts with" },
1329
+ { value: "endsWith", label: "Ends with" },
1330
+ { value: "blank", label: "Blank" },
1331
+ { value: "notBlank", label: "Not blank" }
1332
+ ];
1333
+ }
1334
+ function ne(n, l) {
1335
+ if (l === "number") {
1336
+ const e = Number(n);
1337
+ return Number.isFinite(e) ? e : n;
1338
+ }
1339
+ return l === "date" ? n : l === "boolean" ? n === "true" ? !0 : n === "false" ? !1 : null : n;
1340
+ }
1341
+ function D(n) {
1342
+ return typeof CSS < "u" && CSS.escape ? CSS.escape(String(n)) : String(n).replace(/["\\\n\r]/g, (l) => "\\" + l);
1343
+ }
1344
+ class E extends S {
1345
+ constructor() {
1346
+ super(...arguments);
1347
+ /* Single mousedown handler: distinguishes a bare click (→ sort) from a drag
1348
+ * that moves past a small pixel threshold (→ column reorder). Lets us keep
1349
+ * sort + reorder on the same header without a separate drag handle. */
1350
+ m(this, "_onMouseDown", (e) => {
1351
+ if (e.button !== 0 || e.target.closest(".sg-resize-handle, .sg-filter-icon, .sg-reorder-handle")) return;
1352
+ const t = e.clientX, i = e.clientY;
1353
+ let s = !1;
1354
+ const r = (a) => {
1355
+ const c = Math.abs(a.clientX - t), u = Math.abs(a.clientY - i);
1356
+ !s && (c > 5 || u > 5) && (s = !0, document.removeEventListener("mousemove", r), document.removeEventListener("mouseup", o), this._beginReorder(t));
1357
+ }, o = (a) => {
1358
+ document.removeEventListener("mousemove", r), document.removeEventListener("mouseup", o), s || this.sort(a);
1359
+ };
1360
+ document.addEventListener("mousemove", r), document.addEventListener("mouseup", o);
1361
+ });
1362
+ }
1363
+ connect() {
1364
+ if (this.grid = ee(this.element, "grid", this.application), !!this.grid) {
1365
+ if (!this.headerNameValue) {
1366
+ const e = this.element.textContent.trim();
1367
+ e && (this.headerNameValue = e);
1368
+ }
1369
+ this.grid.registerColumn(this.toColumnDef(), this.element), !this.checkboxValue && !this.rowNumberValue && this.element.addEventListener("mousedown", this._onMouseDown);
1370
+ }
1371
+ }
1372
+ disconnect() {
1373
+ this.element.removeEventListener("mousedown", this._onMouseDown), this.grid?.unregisterColumn(this.fieldValue);
1374
+ }
1375
+ toColumnDef() {
1376
+ return {
1377
+ field: this.fieldValue,
1378
+ headerName: this.headerNameValue || this.fieldValue,
1379
+ type: this.typeValue,
1380
+ sortable: this.sortableValue,
1381
+ filter: this.filterValue || null,
1382
+ editable: this.editableValue,
1383
+ width: this.widthValue || void 0,
1384
+ minWidth: this.minWidthValue,
1385
+ maxWidth: this.maxWidthValue,
1386
+ pinned: this.pinnedValue || null,
1387
+ hidden: this.hiddenValue,
1388
+ resizable: this.resizableValue,
1389
+ cellRenderer: this.cellRendererValue || null,
1390
+ cellEditor: this.cellEditorValue || null,
1391
+ _isCheckbox: this.checkboxValue,
1392
+ _isRowNumber: this.rowNumberValue,
1393
+ sortable: this.rowNumberValue ? !1 : this.sortableValue,
1394
+ resizable: this.rowNumberValue ? !1 : this.resizableValue
1395
+ };
1396
+ }
1397
+ _beginReorder(e) {
1398
+ if (!this.grid) return;
1399
+ const t = this.element.parentElement, i = Array.from(t.children), s = i.indexOf(this.element);
1400
+ let r = s;
1401
+ this.element.style.opacity = "0.5", this.element.style.background = "var(--sg-bg-hover, #eef2ff)", document.body.style.cursor = "grabbing";
1402
+ const o = (c) => {
1403
+ const u = c.clientX;
1404
+ let d = i.length;
1405
+ for (let h = 0; h < i.length; h++) {
1406
+ const g = i[h].getBoundingClientRect();
1407
+ if (u < g.left + g.width / 2) {
1408
+ d = h;
1409
+ break;
1410
+ }
1411
+ }
1412
+ r = d > s ? d - 1 : d;
1413
+ }, a = () => {
1414
+ document.removeEventListener("mousemove", o), document.removeEventListener("mouseup", a), this.element.style.opacity = "", this.element.style.background = "", document.body.style.cursor = "", r !== s && this.grid.moveColumn(this.fieldValue, r);
1415
+ };
1416
+ document.addEventListener("mousemove", o), document.addEventListener("mouseup", a);
1417
+ }
1418
+ sort(e) {
1419
+ !this.sortableValue || !this.grid || this.grid.toggleSort(this.fieldValue, e?.shiftKey === !0);
1420
+ }
1421
+ openFilter(e) {
1422
+ e?.stopPropagation(), this.grid && this.grid.openFilterFor(this.fieldValue, this.element);
1423
+ }
1424
+ // Resize: drag handle adjusts column width live.
1425
+ startResize(e) {
1426
+ if (!this.resizableValue || !this.grid) return;
1427
+ e.preventDefault(), e.stopPropagation();
1428
+ const t = e.clientX, i = this.element.offsetWidth, s = (o) => this.grid.setColumnWidth(this.fieldValue, i + (o.clientX - t)), r = () => {
1429
+ document.removeEventListener("mousemove", s), document.removeEventListener("mouseup", r), document.body.style.cursor = "", document.body.style.userSelect = "";
1430
+ };
1431
+ document.addEventListener("mousemove", s), document.addEventListener("mouseup", r), document.body.style.cursor = "col-resize", document.body.style.userSelect = "none";
1432
+ }
1433
+ }
1434
+ m(E, "values", {
1435
+ field: String,
1436
+ headerName: { type: String, default: "" },
1437
+ type: { type: String, default: "text" },
1438
+ // text|number|date|boolean
1439
+ sortable: { type: Boolean, default: !1 },
1440
+ filter: { type: String, default: "" },
1441
+ // ''|text|number|date|boolean|set
1442
+ editable: { type: Boolean, default: !1 },
1443
+ width: { type: Number, default: 0 },
1444
+ minWidth: { type: Number, default: 40 },
1445
+ maxWidth: { type: Number, default: 4e3 },
1446
+ pinned: { type: String, default: "" },
1447
+ // ''|left|right
1448
+ hidden: { type: Boolean, default: !1 },
1449
+ resizable: { type: Boolean, default: !0 },
1450
+ cellRenderer: { type: String, default: "" },
1451
+ cellEditor: { type: String, default: "" },
1452
+ checkbox: { type: Boolean, default: !1 },
1453
+ rowNumber: { type: Boolean, default: !1 }
1454
+ // gutter: shows 1-based row number, click selects row
1455
+ });
1456
+ class T extends S {
1457
+ connect() {
1458
+ }
1459
+ }
1460
+ class N extends S {
1461
+ connect() {
1462
+ }
1463
+ }
1464
+ class F extends S {
1465
+ connect() {
1466
+ }
1467
+ }
1468
+ class R extends S {
1469
+ constructor() {
1470
+ super(...arguments);
1471
+ m(this, "_refresh", () => {
1472
+ const e = this._gridEl?.gridApi;
1473
+ if (!e) return;
1474
+ const t = e.paginationGetCurrentPage(), i = e.paginationGetTotalPages(), s = e.paginationGetRowCount(), r = e.paginationGetPageSize() || 1;
1475
+ if (this.hasPageInfoTarget) {
1476
+ const o = s === 0 ? 0 : t * r + 1, a = Math.min(s, o + r - 1);
1477
+ this.pageInfoTarget.textContent = s === 0 ? "0 rows" : `${o}–${a} of ${s}`;
1478
+ }
1479
+ this.hasFirstTarget && (this.firstTarget.disabled = t === 0), this.hasPrevTarget && (this.prevTarget.disabled = t === 0), this.hasNextTarget && (this.nextTarget.disabled = t >= i - 1), this.hasLastTarget && (this.lastTarget.disabled = t >= i - 1), this.hasPageSizeTarget && document.activeElement !== this.pageSizeTarget && (this.pageSizeTarget.value = String(r));
1480
+ });
1481
+ }
1482
+ connect() {
1483
+ this.element.classList.add("sg-pagination-bar"), this.hasGridOutlet && this._wire(this.gridOutletElement);
1484
+ }
1485
+ disconnect() {
1486
+ this._gridEl && this._unwire(this._gridEl);
1487
+ }
1488
+ gridOutletConnected(e, t) {
1489
+ this._wire(t);
1490
+ }
1491
+ gridOutletDisconnected(e, t) {
1492
+ this._unwire(t);
1493
+ }
1494
+ _wire(e) {
1495
+ this._gridEl = e;
1496
+ for (const t of ["grid:paginationChanged", "grid:rowDataChanged", "grid:filterChanged", "grid:ready"])
1497
+ e.addEventListener(t, this._refresh);
1498
+ e.gridApi && this._refresh();
1499
+ }
1500
+ _unwire(e) {
1501
+ for (const t of ["grid:paginationChanged", "grid:rowDataChanged", "grid:filterChanged", "grid:ready"])
1502
+ e.removeEventListener(t, this._refresh);
1503
+ this._gridEl = null;
1504
+ }
1505
+ first() {
1506
+ this._gridEl?.gridApi?.paginationGoToFirstPage();
1507
+ }
1508
+ prev() {
1509
+ this._gridEl?.gridApi?.paginationGoToPreviousPage();
1510
+ }
1511
+ next() {
1512
+ this._gridEl?.gridApi?.paginationGoToNextPage();
1513
+ }
1514
+ last() {
1515
+ this._gridEl?.gridApi?.paginationGoToLastPage();
1516
+ }
1517
+ changeSize(e) {
1518
+ const t = parseInt(e.target.value, 10);
1519
+ Number.isFinite(t) && t > 0 && this._gridEl?.gridApi?.paginationSetPageSize(t);
1520
+ }
1521
+ }
1522
+ m(R, "outlets", ["grid"]), m(R, "targets", ["first", "prev", "next", "last", "pageInfo", "pageSize"]);
1523
+ function le(n) {
1524
+ const l = n ?? z.start();
1525
+ return l.register("grid", x), l.register("header-cell", E), l.register("row", T), l.register("cell", N), l.register("filter", F), l.register("pagination", R), l;
1526
+ }
1527
+ const re = {
1528
+ start: le,
1529
+ GridController: x,
1530
+ HeaderCellController: E,
1531
+ RowController: T,
1532
+ CellController: N,
1533
+ FilterController: F,
1534
+ PaginationController: R
1535
+ };
1536
+ typeof window < "u" && !window.__stimulusGridStarted && (window.__stimulusGridStarted = !0, window.StimulusGrid = re);
1537
+ export {
1538
+ N as CellController,
1539
+ F as FilterController,
1540
+ x as GridController,
1541
+ E as HeaderCellController,
1542
+ R as PaginationController,
1543
+ T as RowController,
1544
+ re as default,
1545
+ le as start
1546
+ };
1547
+ //# sourceMappingURL=stimulus_grid.esm.js.map