@iyulab/flex-table 0.10.1 → 0.11.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.
@@ -1,2137 +0,0 @@
1
- import { css as W, html as u, LitElement as T, nothing as k } from "lit";
2
- import { property as w, state as C, customElement as M } from "lit/decorators.js";
3
- const K = W`
4
- /* --- Theme Variables (Light defaults) --- */
5
- :host {
6
- --ft-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
7
- --ft-font-size: 14px;
8
- --ft-border-color: #e0e0e0;
9
- --ft-bg: #fff;
10
- --ft-text-color: #202124;
11
- --ft-header-bg: #f8f9fa;
12
- --ft-header-hover-bg: #e8eaed;
13
- --ft-header-text-color: #202124;
14
- --ft-row-even-bg: #fff;
15
- --ft-row-odd-bg: #fafafa;
16
- --ft-row-hover-bg: #f0f4ff;
17
- --ft-active-color: #1a73e8;
18
- --ft-selection-bg: #e8f0fe;
19
- --ft-bool-color: #2196f3;
20
- --ft-sort-indicator-color: #5f6368;
21
- --ft-editor-bg: #fff;
22
- --ft-empty-color: #999;
23
- }
24
-
25
- /* --- Dark Theme (auto via prefers-color-scheme) --- */
26
- @media (prefers-color-scheme: dark) {
27
- :host(:not([theme="light"])) {
28
- --ft-border-color: #3c4043;
29
- --ft-bg: #1e1e1e;
30
- --ft-text-color: #e8eaed;
31
- --ft-header-bg: #292a2d;
32
- --ft-header-hover-bg: #3c4043;
33
- --ft-header-text-color: #e8eaed;
34
- --ft-row-even-bg: #1e1e1e;
35
- --ft-row-odd-bg: #252526;
36
- --ft-row-hover-bg: #2a2d2e;
37
- --ft-active-color: #4da3ff;
38
- --ft-selection-bg: #264f78;
39
- --ft-bool-color: #64b5f6;
40
- --ft-sort-indicator-color: #9aa0a6;
41
- --ft-editor-bg: #2d2d2d;
42
- --ft-empty-color: #9aa0a6;
43
- }
44
- }
45
-
46
- /* --- Force dark via attribute --- */
47
- :host([theme="dark"]) {
48
- --ft-border-color: #3c4043;
49
- --ft-bg: #1e1e1e;
50
- --ft-text-color: #e8eaed;
51
- --ft-header-bg: #292a2d;
52
- --ft-header-hover-bg: #3c4043;
53
- --ft-header-text-color: #e8eaed;
54
- --ft-row-even-bg: #1e1e1e;
55
- --ft-row-odd-bg: #252526;
56
- --ft-row-hover-bg: #2a2d2e;
57
- --ft-active-color: #4da3ff;
58
- --ft-selection-bg: #264f78;
59
- --ft-bool-color: #64b5f6;
60
- --ft-sort-indicator-color: #9aa0a6;
61
- --ft-editor-bg: #2d2d2d;
62
- --ft-empty-color: #9aa0a6;
63
- }
64
-
65
- /* --- Layout --- */
66
- :host {
67
- display: block;
68
- position: relative;
69
- overflow: auto;
70
- font-family: var(--ft-font-family);
71
- font-size: var(--ft-font-size);
72
- color: var(--ft-text-color);
73
- background: var(--ft-bg);
74
- border: 1px solid var(--ft-border-color);
75
- outline: none;
76
- }
77
-
78
- .ft-header {
79
- display: block;
80
- position: sticky;
81
- top: 0;
82
- z-index: 3;
83
- }
84
-
85
- .ft-header-cell {
86
- position: absolute;
87
- top: 0;
88
- padding: 8px 12px;
89
- background: var(--ft-header-bg);
90
- color: var(--ft-header-text-color);
91
- font-weight: 600;
92
- user-select: none;
93
- border-bottom: 2px solid var(--ft-border-color);
94
- overflow: hidden;
95
- text-overflow: ellipsis;
96
- white-space: nowrap;
97
- box-sizing: border-box;
98
- display: flex;
99
- align-items: center;
100
- gap: 4px;
101
- }
102
-
103
- .ft-header-cell.ft-sortable { cursor: pointer; }
104
- .ft-header-cell.ft-sortable:hover { background: var(--ft-header-hover-bg); }
105
-
106
- .ft-resize-handle {
107
- position: absolute;
108
- right: 0; top: 0; bottom: 0;
109
- width: 6px;
110
- cursor: col-resize;
111
- z-index: 3;
112
- }
113
-
114
- .ft-resize-handle:hover,
115
- .ft-resize-handle.ft-resizing {
116
- background: var(--ft-active-color);
117
- opacity: 0.3;
118
- }
119
-
120
- .ft-sort-indicator {
121
- flex-shrink: 0;
122
- font-size: 12px;
123
- color: var(--ft-sort-indicator-color);
124
- }
125
-
126
- .ft-sort-order {
127
- font-size: 10px;
128
- color: var(--ft-sort-indicator-color);
129
- }
130
-
131
- .ft-body { position: relative; }
132
-
133
- .ft-row {
134
- display: block;
135
- position: absolute;
136
- left: 0;
137
- }
138
-
139
- .ft-cell {
140
- position: absolute;
141
- top: 0;
142
- padding: 6px 12px;
143
- border-bottom: 1px solid var(--ft-border-color);
144
- overflow: hidden;
145
- text-overflow: ellipsis;
146
- white-space: nowrap;
147
- box-sizing: border-box;
148
- cursor: default;
149
- }
150
-
151
- .ft-row-even .ft-cell { background: var(--ft-row-even-bg); }
152
- .ft-row-odd .ft-cell { background: var(--ft-row-odd-bg); }
153
- .ft-row:hover .ft-cell { background: var(--ft-row-hover-bg); }
154
-
155
- .ft-cell.ft-active {
156
- outline: 2px solid var(--ft-active-color);
157
- outline-offset: -2px;
158
- z-index: 1;
159
- }
160
-
161
- .ft-cell.ft-selected {
162
- background: var(--ft-selection-bg) !important;
163
- }
164
-
165
- .ft-cell.ft-invalid {
166
- outline: 2px solid #d93025;
167
- outline-offset: -2px;
168
- background: rgba(217, 48, 37, 0.08);
169
- }
170
-
171
- .ft-cell.ft-editing { padding: 0; overflow: visible; }
172
-
173
- .ft-editor {
174
- width: 100%; height: 100%;
175
- border: none; outline: none;
176
- padding: 6px 12px;
177
- font: inherit;
178
- color: var(--ft-text-color);
179
- background: var(--ft-editor-bg);
180
- box-sizing: border-box;
181
- }
182
- .ft-editor:focus {
183
- outline: 2px solid var(--ft-active-color);
184
- outline-offset: -2px;
185
- }
186
-
187
- .ft-editor-number { text-align: right; }
188
-
189
- .ft-cell.ft-type-number {
190
- text-align: right;
191
- font-variant-numeric: tabular-nums;
192
- }
193
-
194
- .ft-cell.ft-type-boolean { text-align: center; }
195
- .ft-bool { color: var(--ft-bool-color); }
196
-
197
- .ft-empty {
198
- padding: 24px;
199
- text-align: center;
200
- color: var(--ft-empty-color);
201
- }
202
-
203
- .ft-row-num-header,
204
- .ft-row-num {
205
- position: absolute;
206
- top: 0;
207
- display: flex;
208
- align-items: center;
209
- justify-content: center;
210
- color: var(--ft-sort-indicator-color);
211
- font-size: 12px;
212
- padding: 6px 4px;
213
- box-sizing: border-box;
214
- user-select: none;
215
- }
216
-
217
- .ft-row-num-header {
218
- background: var(--ft-header-bg);
219
- font-weight: 600;
220
- border-bottom: 2px solid var(--ft-border-color);
221
- padding: 8px 4px;
222
- }
223
-
224
- .ft-row-num {
225
- border-bottom: 1px solid var(--ft-border-color);
226
- cursor: pointer;
227
- background: var(--ft-row-even-bg);
228
- }
229
-
230
- .ft-row-odd .ft-row-num { background: var(--ft-row-odd-bg); }
231
- .ft-row:hover .ft-row-num { background: var(--ft-row-hover-bg); }
232
-
233
- .ft-row-num:hover {
234
- background: var(--ft-header-hover-bg);
235
- }
236
-
237
- /* --- Pinned Columns --- */
238
-
239
- .ft-pinned {
240
- background: var(--ft-bg);
241
- }
242
-
243
- .ft-header-cell.ft-pinned {
244
- background: var(--ft-header-bg);
245
- }
246
-
247
- .ft-row-even .ft-cell.ft-pinned { background: var(--ft-row-even-bg); }
248
- .ft-row-odd .ft-cell.ft-pinned { background: var(--ft-row-odd-bg); }
249
- .ft-row:hover .ft-cell.ft-pinned { background: var(--ft-row-hover-bg); }
250
- .ft-cell.ft-pinned.ft-selected { background: var(--ft-selection-bg) !important; }
251
-
252
- /* --- Filter UI --- */
253
-
254
- .ft-filter-btn {
255
- all: unset;
256
- cursor: pointer;
257
- font-size: 10px;
258
- color: var(--ft-sort-indicator-color);
259
- padding: 0 2px;
260
- margin-left: auto;
261
- opacity: 0.7;
262
- flex-shrink: 0;
263
- }
264
-
265
- .ft-filter-btn:hover { opacity: 1; }
266
-
267
- .ft-filter-btn.ft-filter-active {
268
- color: var(--ft-active-color);
269
- opacity: 1;
270
- }
271
-
272
- .ft-filter-dropdown {
273
- position: absolute;
274
- top: 100%;
275
- left: 0;
276
- min-width: 160px;
277
- background: var(--ft-bg);
278
- border: 1px solid var(--ft-border-color);
279
- border-radius: 4px;
280
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
281
- padding: 8px;
282
- z-index: 10;
283
- box-sizing: border-box;
284
- }
285
-
286
- .ft-filter-input {
287
- width: 100%;
288
- padding: 4px 8px;
289
- font: inherit;
290
- font-size: 13px;
291
- border: 1px solid var(--ft-border-color);
292
- border-radius: 3px;
293
- color: var(--ft-text-color);
294
- background: var(--ft-editor-bg);
295
- box-sizing: border-box;
296
- }
297
-
298
- .ft-filter-input:focus {
299
- outline: 1px solid var(--ft-active-color);
300
- }
301
-
302
- .ft-filter-range {
303
- display: flex;
304
- gap: 4px;
305
- }
306
-
307
- .ft-filter-range .ft-filter-input {
308
- width: 50%;
309
- }
310
-
311
- .ft-filter-actions {
312
- margin-top: 6px;
313
- text-align: right;
314
- }
315
-
316
- .ft-filter-clear {
317
- all: unset;
318
- cursor: pointer;
319
- font-size: 12px;
320
- color: var(--ft-sort-indicator-color);
321
- padding: 2px 8px;
322
- border-radius: 3px;
323
- }
324
-
325
- .ft-filter-clear:hover {
326
- background: var(--ft-header-hover-bg);
327
- color: var(--ft-text-color);
328
- }
329
-
330
- /* --- Row Selection (Checkbox) --- */
331
-
332
- .ft-checkbox-header,
333
- .ft-checkbox-cell {
334
- position: absolute;
335
- top: 0;
336
- display: flex;
337
- align-items: center;
338
- justify-content: center;
339
- padding: 0 4px;
340
- box-sizing: border-box;
341
- user-select: none;
342
- }
343
-
344
- .ft-checkbox-header {
345
- background: var(--ft-header-bg);
346
- border-bottom: 2px solid var(--ft-border-color);
347
- }
348
-
349
- .ft-checkbox-cell {
350
- border-bottom: 1px solid var(--ft-border-color);
351
- background: var(--ft-row-even-bg);
352
- }
353
-
354
- .ft-row-odd .ft-checkbox-cell { background: var(--ft-row-odd-bg); }
355
- .ft-row:hover .ft-checkbox-cell { background: var(--ft-row-hover-bg); }
356
- .ft-row-selected .ft-checkbox-cell { background: var(--ft-selection-bg); }
357
- .ft-row-selected .ft-cell { background: var(--ft-selection-bg) !important; }
358
- .ft-row-selected .ft-row-num { background: var(--ft-selection-bg); }
359
-
360
- .ft-checkbox-header input[type="checkbox"],
361
- .ft-checkbox-cell input[type="checkbox"] {
362
- width: 16px;
363
- height: 16px;
364
- cursor: pointer;
365
- accent-color: var(--ft-active-color);
366
- }
367
-
368
- /* --- Footer Row --- */
369
-
370
- .ft-footer {
371
- display: block;
372
- position: sticky;
373
- bottom: 0;
374
- z-index: 2;
375
- background: var(--ft-header-bg);
376
- border-top: 2px solid var(--ft-border-color);
377
- }
378
-
379
- .ft-footer-cell {
380
- position: absolute;
381
- top: 0;
382
- padding: 6px 12px;
383
- font-weight: 600;
384
- font-size: 13px;
385
- color: var(--ft-header-text-color);
386
- overflow: hidden;
387
- text-overflow: ellipsis;
388
- white-space: nowrap;
389
- box-sizing: border-box;
390
- }
391
- `;
392
- function H(t, e, i) {
393
- return i.renderer ? i.renderer(t, e, i) : q(t, i);
394
- }
395
- function q(t, e) {
396
- if (t == null) return "";
397
- switch (e.type) {
398
- case "number":
399
- return I(t);
400
- case "boolean":
401
- return u`<span class="ft-bool">${t ? "✔" : ""}</span>`;
402
- case "date":
403
- return P(t);
404
- case "datetime":
405
- return V(t);
406
- default:
407
- return String(t);
408
- }
409
- }
410
- function I(t) {
411
- return typeof t == "number" ? Number.isInteger(t) ? t.toLocaleString() : t.toLocaleString(void 0, { minimumFractionDigits: 1 }) : String(t);
412
- }
413
- function P(t) {
414
- if (t instanceof Date) return t.toLocaleDateString();
415
- if (typeof t == "string") {
416
- const e = new Date(t);
417
- return isNaN(e.getTime()) ? t : e.toLocaleDateString();
418
- }
419
- return String(t);
420
- }
421
- function V(t) {
422
- if (t instanceof Date) return t.toLocaleString();
423
- if (typeof t == "string") {
424
- const e = new Date(t);
425
- return isNaN(e.getTime()) ? t : e.toLocaleString();
426
- }
427
- return String(t);
428
- }
429
- function O(t) {
430
- return {
431
- startRow: Math.min(t.startRow, t.endRow),
432
- startCol: Math.min(t.startCol, t.endCol),
433
- endRow: Math.max(t.startRow, t.endRow),
434
- endCol: Math.max(t.startCol, t.endCol)
435
- };
436
- }
437
- class j {
438
- constructor() {
439
- this.activeCell = null, this.rangeAnchor = null, this.range = null, this._rowCount = 0, this._colCount = 0;
440
- }
441
- setDimensions(e, i) {
442
- this._rowCount = e, this._colCount = i;
443
- }
444
- setActive(e, i) {
445
- return e < 0 || e >= this._rowCount || i < 0 || i >= this._colCount ? this.activeCell : (this.activeCell = { row: e, col: i }, this.rangeAnchor = null, this.range = null, this.activeCell);
446
- }
447
- /** Set active cell and extend range from anchor */
448
- setActiveWithRange(e, i) {
449
- return e < 0 || e >= this._rowCount || i < 0 || i >= this._colCount ? this.activeCell : (!this.rangeAnchor && this.activeCell && (this.rangeAnchor = { ...this.activeCell }), this.activeCell = { row: e, col: i }, this.rangeAnchor && (this.range = O({
450
- startRow: this.rangeAnchor.row,
451
- startCol: this.rangeAnchor.col,
452
- endRow: e,
453
- endCol: i
454
- })), this.activeCell);
455
- }
456
- /** Check if a cell is within the current selection range */
457
- isInRange(e, i) {
458
- if (!this.range) return !1;
459
- const s = this.range;
460
- return e >= s.startRow && e <= s.endRow && i >= s.startCol && i <= s.endCol;
461
- }
462
- /** Get the effective range: either the explicit range or just the active cell */
463
- getEffectiveRange() {
464
- return this.range ? this.range : this.activeCell ? {
465
- startRow: this.activeCell.row,
466
- startCol: this.activeCell.col,
467
- endRow: this.activeCell.row,
468
- endCol: this.activeCell.col
469
- } : null;
470
- }
471
- clear() {
472
- this.activeCell = null, this.rangeAnchor = null, this.range = null;
473
- }
474
- moveUp() {
475
- return this.activeCell ? this.setActive(this.activeCell.row - 1, this.activeCell.col) : null;
476
- }
477
- moveDown() {
478
- return this.activeCell ? this.setActive(this.activeCell.row + 1, this.activeCell.col) : null;
479
- }
480
- moveLeft() {
481
- return this.activeCell ? this.setActive(this.activeCell.row, this.activeCell.col - 1) : null;
482
- }
483
- moveRight() {
484
- return this.activeCell ? this.setActive(this.activeCell.row, this.activeCell.col + 1) : null;
485
- }
486
- moveNext() {
487
- if (!this.activeCell) return null;
488
- let { row: e, col: i } = this.activeCell;
489
- return i++, i >= this._colCount && (i = 0, e++), e >= this._rowCount ? this.activeCell : this.setActive(e, i);
490
- }
491
- movePrev() {
492
- if (!this.activeCell) return null;
493
- let { row: e, col: i } = this.activeCell;
494
- return i--, i < 0 && (i = this._colCount - 1, e--), e < 0 ? this.activeCell : this.setActive(e, i);
495
- }
496
- moveToStart() {
497
- return this.setActive(0, 0);
498
- }
499
- moveToEnd() {
500
- return this.setActive(this._rowCount - 1, this._colCount - 1);
501
- }
502
- moveToRowStart() {
503
- return this.activeCell ? this.setActive(this.activeCell.row, 0) : null;
504
- }
505
- moveToRowEnd() {
506
- return this.activeCell ? this.setActive(this.activeCell.row, this._colCount - 1) : null;
507
- }
508
- // Shift variants for range selection
509
- shiftMoveUp() {
510
- return this.activeCell ? this.setActiveWithRange(this.activeCell.row - 1, this.activeCell.col) : null;
511
- }
512
- shiftMoveDown() {
513
- return this.activeCell ? this.setActiveWithRange(this.activeCell.row + 1, this.activeCell.col) : null;
514
- }
515
- shiftMoveLeft() {
516
- return this.activeCell ? this.setActiveWithRange(this.activeCell.row, this.activeCell.col - 1) : null;
517
- }
518
- shiftMoveRight() {
519
- return this.activeCell ? this.setActiveWithRange(this.activeCell.row, this.activeCell.col + 1) : null;
520
- }
521
- }
522
- class B {
523
- constructor() {
524
- this.current = null;
525
- }
526
- start(e, i) {
527
- this.current = { position: e, originalValue: i };
528
- }
529
- isEditing(e, i) {
530
- return this.current !== null && this.current.position.row === e && this.current.position.col === i;
531
- }
532
- cancel() {
533
- const e = this.current;
534
- return this.current = null, e;
535
- }
536
- commit() {
537
- const e = this.current;
538
- return this.current = null, e;
539
- }
540
- }
541
- class X {
542
- constructor() {
543
- this._selected = /* @__PURE__ */ new Set(), this._mode = "multi", this._rowCount = 0;
544
- }
545
- get mode() {
546
- return this._mode;
547
- }
548
- set mode(e) {
549
- if (this._mode = e, e === "single" && this._selected.size > 1) {
550
- const i = this._selected.values().next().value;
551
- this._selected.clear(), i !== void 0 && this._selected.add(i);
552
- }
553
- }
554
- setRowCount(e) {
555
- this._rowCount = e;
556
- for (const i of this._selected)
557
- i >= e && this._selected.delete(i);
558
- }
559
- get selectedIndices() {
560
- return [...this._selected].sort((e, i) => e - i);
561
- }
562
- get selectedCount() {
563
- return this._selected.size;
564
- }
565
- get isAllSelected() {
566
- return this._rowCount > 0 && this._selected.size === this._rowCount;
567
- }
568
- get isSomeSelected() {
569
- return this._selected.size > 0 && this._selected.size < this._rowCount;
570
- }
571
- isSelected(e) {
572
- return this._selected.has(e);
573
- }
574
- toggle(e) {
575
- this._mode === "single" ? this._selected.has(e) ? this._selected.clear() : (this._selected.clear(), this._selected.add(e)) : this._selected.has(e) ? this._selected.delete(e) : this._selected.add(e);
576
- }
577
- select(e) {
578
- this._mode === "single" && this._selected.clear(), this._selected.add(e);
579
- }
580
- deselect(e) {
581
- this._selected.delete(e);
582
- }
583
- selectAll() {
584
- if (this._mode !== "single") {
585
- this._selected.clear();
586
- for (let e = 0; e < this._rowCount; e++)
587
- this._selected.add(e);
588
- }
589
- }
590
- deselectAll() {
591
- this._selected.clear();
592
- }
593
- }
594
- function Y(t, e, i) {
595
- const s = Array.from({ length: t.length }, (r, n) => n);
596
- if (e.length === 0) return s;
597
- const o = /* @__PURE__ */ new Map();
598
- for (const r of i)
599
- o.set(r.key, r.type ?? "text");
600
- return s.sort((r, n) => {
601
- for (const { key: l, direction: a } of e) {
602
- const d = t[r][l], f = t[n][l], h = d == null, g = f == null;
603
- if (h && g) continue;
604
- if (h) return 1;
605
- if (g) return -1;
606
- const p = J(d, f, o.get(l) ?? "text");
607
- if (p !== 0)
608
- return a === "asc" ? p : -p;
609
- }
610
- return r - n;
611
- }), s;
612
- }
613
- function J(t, e, i) {
614
- switch (i) {
615
- case "number": {
616
- const s = Number(t), o = Number(e);
617
- return isNaN(s) && isNaN(o) ? 0 : isNaN(s) ? 1 : isNaN(o) ? -1 : s - o;
618
- }
619
- case "boolean":
620
- return (t ? 1 : 0) - (e ? 1 : 0);
621
- case "date":
622
- case "datetime": {
623
- const s = new Date(t).getTime(), o = new Date(e).getTime();
624
- return (isNaN(s) ? 0 : s) - (isNaN(o) ? 0 : o);
625
- }
626
- default:
627
- return String(t).localeCompare(String(e));
628
- }
629
- }
630
- function G(t, e, i) {
631
- const s = t.find((o) => o.key === e);
632
- return i ? s ? s.direction === "asc" ? t.map((o) => o.key === e ? { ...o, direction: "desc" } : o) : t.filter((o) => o.key !== e) : [...t, { key: e, direction: "asc" }] : s ? s.direction === "asc" ? [{ key: e, direction: "desc" }] : [] : [{ key: e, direction: "asc" }];
633
- }
634
- function Q(t, e, i) {
635
- if (e.length === 0)
636
- return Array.from({ length: t.length }, (o, r) => r);
637
- const s = [];
638
- for (let o = 0; o < t.length; o++) {
639
- const r = t[o];
640
- let n = !0;
641
- for (const l of e)
642
- try {
643
- if (!l.predicate(r[l.key], r)) {
644
- n = !1;
645
- break;
646
- }
647
- } catch (a) {
648
- console.warn('Filter predicate error for key "%s":', l.key, a), i && i(a, r, l);
649
- }
650
- n && s.push(o);
651
- }
652
- return s;
653
- }
654
- const Z = 100;
655
- class tt {
656
- constructor() {
657
- this._undoStack = [], this._redoStack = [], this._maxSize = Z;
658
- }
659
- /** Get/set the maximum number of undo actions stored. */
660
- get maxSize() {
661
- return this._maxSize;
662
- }
663
- set maxSize(e) {
664
- for (this._maxSize = Math.max(1, e); this._undoStack.length > this._maxSize; )
665
- this._undoStack.shift();
666
- }
667
- get canUndo() {
668
- return this._undoStack.length > 0;
669
- }
670
- get canRedo() {
671
- return this._redoStack.length > 0;
672
- }
673
- get undoCount() {
674
- return this._undoStack.length;
675
- }
676
- get redoCount() {
677
- return this._redoStack.length;
678
- }
679
- /**
680
- * Push a new action onto the undo stack.
681
- * Clears the redo stack (new action invalidates redo history).
682
- */
683
- push(e) {
684
- this._undoStack.push(e), this._undoStack.length > this._maxSize && this._undoStack.shift(), this._redoStack.length = 0;
685
- }
686
- /**
687
- * Undo the most recent action.
688
- * Returns the action label, or null if nothing to undo.
689
- */
690
- undo() {
691
- const e = this._undoStack.pop();
692
- return e ? (e.undo(), this._redoStack.push(e), e.label) : null;
693
- }
694
- /**
695
- * Redo the most recently undone action.
696
- * Returns the action label, or null if nothing to redo.
697
- */
698
- redo() {
699
- const e = this._redoStack.pop();
700
- return e ? (e.redo(), this._undoStack.push(e), e.label) : null;
701
- }
702
- /**
703
- * Clear all undo/redo history.
704
- */
705
- clear() {
706
- this._undoStack.length = 0, this._redoStack.length = 0;
707
- }
708
- }
709
- function et(t, e, i) {
710
- const s = [];
711
- for (let o = i.startRow; o <= i.endRow; o++) {
712
- const r = t[o], n = [];
713
- for (let l = i.startCol; l <= i.endCol; l++) {
714
- const a = e[l], d = r[a.key];
715
- n.push(d == null ? "" : String(d));
716
- }
717
- s.push(n.join(" "));
718
- }
719
- return s.join(`
720
- `);
721
- }
722
- function it(t) {
723
- const e = t.replace(/\r\n/g, `
724
- `).replace(/\r/g, `
725
- `), i = [];
726
- let s = [], o = 0;
727
- const r = e.length;
728
- for (; o <= r; ) {
729
- if (o === r) {
730
- (s.length > 0 || i.length > 0) && i.push(s);
731
- break;
732
- }
733
- if (e[o] === '"') {
734
- let l = "";
735
- for (o++; o < r; )
736
- if (e[o] === '"')
737
- if (o + 1 < r && e[o + 1] === '"')
738
- l += '"', o += 2;
739
- else {
740
- o++;
741
- break;
742
- }
743
- else
744
- l += e[o], o++;
745
- s.push(l), o < r && e[o] === " " ? o++ : o < r && e[o] === `
746
- ` && (i.push(s), s = [], o++);
747
- } else {
748
- let l = "";
749
- for (; o < r && e[o] !== " " && e[o] !== `
750
- `; )
751
- l += e[o], o++;
752
- s.push(l), o < r && e[o] === " " ? o++ : o < r && e[o] === `
753
- ` && (i.push(s), s = [], o++);
754
- }
755
- }
756
- for (; i.length > 0; ) {
757
- const n = i[i.length - 1];
758
- if (n.length === 0 || n.length === 1 && n[0] === "")
759
- i.pop();
760
- else
761
- break;
762
- }
763
- return i;
764
- }
765
- function A(t, e) {
766
- if (t === "") return null;
767
- switch (e.type) {
768
- case "number": {
769
- const i = Number(t);
770
- return isNaN(i) ? t : i;
771
- }
772
- case "boolean":
773
- return t.toLowerCase() === "true" || t === "1";
774
- default:
775
- return t;
776
- }
777
- }
778
- function z(t, e, i) {
779
- switch (i) {
780
- case "csv":
781
- return U(t, e, ",");
782
- case "tsv":
783
- return U(t, e, " ");
784
- case "json":
785
- return ot(t, e);
786
- }
787
- }
788
- function st(t, e) {
789
- return t == null ? "" : (e.type === "date" || e.type === "datetime") && t instanceof Date ? t.toISOString() : String(t);
790
- }
791
- function U(t, e, i) {
792
- const s = e.map((r) => L(r.header, i)).join(i), o = t.map(
793
- (r) => e.map((n) => {
794
- const l = st(r[n.key], n);
795
- return L(l, i);
796
- }).join(i)
797
- );
798
- return [s, ...o].join(`
799
- `);
800
- }
801
- function L(t, e) {
802
- return t.includes(e) || t.includes('"') || t.includes(`
803
- `) || t.includes("\r") ? '"' + t.replace(/"/g, '""') + '"' : t;
804
- }
805
- function ot(t, e) {
806
- const i = t.map((s) => {
807
- const o = {};
808
- for (const r of e) {
809
- const n = s[r.key];
810
- (r.type === "date" || r.type === "datetime") && n instanceof Date ? o[r.key] = n.toISOString() : o[r.key] = n ?? null;
811
- }
812
- return o;
813
- });
814
- return JSON.stringify(i, null, 2);
815
- }
816
- function rt(t, e, i) {
817
- const s = new Blob([t], { type: i }), o = URL.createObjectURL(s), r = document.createElement("a");
818
- r.href = o, r.download = e, r.style.display = "none", document.body.appendChild(r), r.click(), document.body.removeChild(r), setTimeout(() => URL.revokeObjectURL(o), 100);
819
- }
820
- const nt = {
821
- csv: "text/csv;charset=utf-8",
822
- tsv: "text/tab-separated-values;charset=utf-8",
823
- json: "application/json;charset=utf-8"
824
- }, lt = {
825
- csv: ".csv",
826
- tsv: ".tsv",
827
- json: ".json"
828
- };
829
- function at(t) {
830
- return nt[t];
831
- }
832
- function ct(t) {
833
- return lt[t];
834
- }
835
- var ht = Object.defineProperty, dt = Object.getOwnPropertyDescriptor, v = (t, e, i, s) => {
836
- for (var o = s > 1 ? void 0 : s ? dt(e, i) : e, r = t.length - 1, n; r >= 0; r--)
837
- (n = t[r]) && (o = (s ? n(e, i, o) : n(o)) || o);
838
- return s && o && ht(e, i, o), o;
839
- };
840
- const S = 120, E = 40, ut = 32, $ = 5;
841
- let _ = class extends T {
842
- constructor() {
843
- super(...arguments), this.columns = [], this.data = [], this.rowHeight = ut, this.showRowNumbers = !1, this.theme = void 0, this.maxRows = 0, this.editable = !0, this.showFilters = !1, this.selectable = !1, this.dataMode = "client", this.footerData = null, this._scrollTop = 0, this._scrollLeft = 0, this._viewportHeight = 0, this._viewportWidth = 0, this._colLeftOffsets = [], this._totalRowWidth = 0, this._activeCell = null, this._editingCell = null, this._sortCriteria = [], this._selection = new j(), this._editing = new B(), this._rowSelection = new X(), this._undo = new tt(), this._filters = [], this._filteredIndices = [], this._sortedIndices = [], this._openFilterKey = null, this._rowSelectionVersion = 0, this._viewDirty = !0, this._resizing = null, this._resizeCleanup = null, this._columnWidths = /* @__PURE__ */ new Map(), this._invalidCells = /* @__PURE__ */ new Map(), this._textFilterState = /* @__PURE__ */ new Map(), this._numberFilterState = /* @__PURE__ */ new Map(), this._dateFilterState = /* @__PURE__ */ new Map();
844
- }
845
- set selectionMode(t) {
846
- this._rowSelection.mode = t, this.requestUpdate();
847
- }
848
- get selectionMode() {
849
- return this._rowSelection.mode;
850
- }
851
- set maxUndoSize(t) {
852
- this._undo.maxSize = t;
853
- }
854
- get maxUndoSize() {
855
- return this._undo.maxSize;
856
- }
857
- get visibleColumns() {
858
- return this.columns.filter((t) => !t.hidden);
859
- }
860
- get _prefixWidth() {
861
- let t = 0;
862
- return this.selectable && (t += 36), this.showRowNumbers && (t += 48), t;
863
- }
864
- /** Whether an undo operation is available. */
865
- get canUndo() {
866
- return this._undo.canUndo;
867
- }
868
- /** Whether a redo operation is available. */
869
- get canRedo() {
870
- return this._undo.canRedo;
871
- }
872
- get activeCell() {
873
- return this._activeCell;
874
- }
875
- get editingCell() {
876
- return this._editingCell;
877
- }
878
- get sortCriteria() {
879
- return [...this._sortCriteria];
880
- }
881
- /** Number of rows after filtering (before pagination). */
882
- get filteredRowCount() {
883
- return this._filteredIndices.length;
884
- }
885
- // --- Public API: Row Selection ---
886
- /** Get data indices of currently selected rows. */
887
- getSelectedRows() {
888
- const t = this._rowSelection.selectedIndices.map((e) => this._toDataIndex(e)).filter((e) => e >= 0 && e < this.data.length);
889
- return {
890
- selectedIndices: t,
891
- selectedRows: t.map((e) => this.data[e])
892
- };
893
- }
894
- /** Select all visible rows (multi mode only). */
895
- selectAll() {
896
- this.selectable && (this._rowSelection.selectAll(), this._rowSelectionVersion++, this._dispatchRowSelectionEvent());
897
- }
898
- /** Deselect all rows. */
899
- deselectAll() {
900
- this.selectable && (this._rowSelection.deselectAll(), this._rowSelectionVersion++, this._dispatchRowSelectionEvent());
901
- }
902
- _dispatchRowSelectionEvent() {
903
- const { selectedIndices: t, selectedRows: e } = this.getSelectedRows();
904
- this.dispatchEvent(new CustomEvent("selection-change", {
905
- detail: { selectedIndices: t, selectedRows: e },
906
- bubbles: !0,
907
- composed: !0
908
- }));
909
- }
910
- // --- Public API: Filtering ---
911
- /**
912
- * Set a filter for a column. Replaces any existing filter on the same key.
913
- */
914
- setFilter(t, e) {
915
- this._filters = this._filters.filter((i) => i.key !== t), this._filters.push({ key: t, predicate: e }), this._recomputeView(), this.requestUpdate(), this._dispatchFilterEvent();
916
- }
917
- /**
918
- * Remove the filter for a column.
919
- */
920
- removeFilter(t) {
921
- const e = this._filters.length;
922
- this._filters = this._filters.filter((i) => i.key !== t), this._filters.length !== e && (this._recomputeView(), this.requestUpdate(), this._dispatchFilterEvent());
923
- }
924
- /**
925
- * Remove all filters.
926
- */
927
- clearFilters() {
928
- this._filters.length !== 0 && (this._filters = [], this._recomputeView(), this.requestUpdate(), this._dispatchFilterEvent());
929
- }
930
- /**
931
- * Get current active filter keys.
932
- */
933
- get filterKeys() {
934
- return this._filters.map((t) => t.key);
935
- }
936
- /**
937
- * Explicitly request a re-render after external data mutations.
938
- * Useful when `data` array contents are mutated in-place without reassignment.
939
- */
940
- refreshData() {
941
- this.requestUpdate("data");
942
- }
943
- _dispatchUndoStateEvent() {
944
- this.dispatchEvent(new CustomEvent("undo-state-change", {
945
- detail: { canUndo: this.canUndo, canRedo: this.canRedo },
946
- bubbles: !0,
947
- composed: !0
948
- }));
949
- }
950
- _dispatchFilterEvent() {
951
- this.dispatchEvent(new CustomEvent("filter-change", {
952
- detail: { keys: this.filterKeys, filteredCount: this.filteredRowCount },
953
- bubbles: !0,
954
- composed: !0
955
- }));
956
- }
957
- // --- Public API: Column Operations ---
958
- /**
959
- * Add a column at the specified index (default: end).
960
- * Returns the added column definition.
961
- */
962
- addColumn(t, e) {
963
- const i = e ?? this.columns.length;
964
- return this.columns = [
965
- ...this.columns.slice(0, i),
966
- t,
967
- ...this.columns.slice(i)
968
- ], this._undo.push({
969
- label: "column-add",
970
- undo: () => {
971
- this.columns = this.columns.filter((s) => s !== t), this.requestUpdate();
972
- },
973
- redo: () => {
974
- this.columns = [
975
- ...this.columns.slice(0, i),
976
- t,
977
- ...this.columns.slice(i)
978
- ], this.requestUpdate();
979
- }
980
- }), this.requestUpdate(), this.dispatchEvent(new CustomEvent("column-add", {
981
- detail: { column: t, index: i },
982
- bubbles: !0,
983
- composed: !0
984
- })), this._dispatchUndoStateEvent(), t;
985
- }
986
- /**
987
- * Delete a column by its key.
988
- * Removes related filters, sort criteria, and column width overrides.
989
- */
990
- deleteColumn(t) {
991
- const e = this.columns.findIndex((n) => n.key === t);
992
- if (e === -1) return;
993
- const i = this.columns[e], s = this._filters.find((n) => n.key === t), o = this._sortCriteria.find((n) => n.key === t), r = this._columnWidths.get(t);
994
- this._filters = this._filters.filter((n) => n.key !== t), this._sortCriteria = this._sortCriteria.filter((n) => n.key !== t), this._columnWidths.delete(t), this.columns = this.columns.filter((n) => n.key !== t), this._undo.push({
995
- label: "column-delete",
996
- undo: () => {
997
- this.columns = [
998
- ...this.columns.slice(0, e),
999
- i,
1000
- ...this.columns.slice(e)
1001
- ], s && this._filters.push(s), o && (this._sortCriteria = [...this._sortCriteria, o]), r !== void 0 && this._columnWidths.set(t, r), this.requestUpdate();
1002
- },
1003
- redo: () => {
1004
- this._filters = this._filters.filter((n) => n.key !== t), this._sortCriteria = this._sortCriteria.filter((n) => n.key !== t), this._columnWidths.delete(t), this.columns = this.columns.filter((n) => n.key !== t), this.requestUpdate();
1005
- }
1006
- }), this._activeCell && this._activeCell.col >= this.visibleColumns.length && (this._selection.clear(), this._activeCell = null), this.requestUpdate(), this.dispatchEvent(new CustomEvent("column-delete", {
1007
- detail: { column: i, key: t, index: e },
1008
- bubbles: !0,
1009
- composed: !0
1010
- })), this._dispatchUndoStateEvent();
1011
- }
1012
- /**
1013
- * Move a column to a new position.
1014
- * @param key Column key to move.
1015
- * @param newIndex Target index in the columns array.
1016
- */
1017
- moveColumn(t, e) {
1018
- const i = this.columns.findIndex((n) => n.key === t);
1019
- if (i === -1) return;
1020
- const s = Math.max(0, Math.min(this.columns.length - 1, e));
1021
- if (i === s) return;
1022
- const o = this.columns[i], r = [...this.columns];
1023
- r.splice(i, 1), r.splice(s, 0, o), this.columns = r, this._undo.push({
1024
- label: "column-reorder",
1025
- undo: () => {
1026
- const n = [...this.columns];
1027
- n.splice(s, 1), n.splice(i, 0, o), this.columns = n, this.requestUpdate();
1028
- },
1029
- redo: () => {
1030
- const n = [...this.columns];
1031
- n.splice(i, 1), n.splice(s, 0, o), this.columns = n, this.requestUpdate();
1032
- }
1033
- }), this.requestUpdate(), this.dispatchEvent(new CustomEvent("column-reorder", {
1034
- detail: { key: t, oldIndex: i, newIndex: s },
1035
- bubbles: !0,
1036
- composed: !0
1037
- })), this._dispatchUndoStateEvent();
1038
- }
1039
- // --- Public API: Row Operations ---
1040
- /**
1041
- * Add a row at the specified index (default: end).
1042
- * Returns the new row.
1043
- */
1044
- addRow(t, e) {
1045
- if (this.maxRows > 0 && this.data.length >= this.maxRows) return null;
1046
- const i = t ?? this._createEmptyRow(), s = e ?? this.data.length;
1047
- return this.data.splice(s, 0, i), this._undo.push({
1048
- label: "row-add",
1049
- undo: () => {
1050
- this.data.splice(s, 1), this.requestUpdate();
1051
- },
1052
- redo: () => {
1053
- this.data.splice(s, 0, i), this.requestUpdate();
1054
- }
1055
- }), this.requestUpdate(), this.dispatchEvent(new CustomEvent("row-add", {
1056
- detail: { row: i, index: s },
1057
- bubbles: !0,
1058
- composed: !0
1059
- })), this._dispatchUndoStateEvent(), i;
1060
- }
1061
- /**
1062
- * Delete rows at the specified data indices.
1063
- * If no indices provided, deletes the currently selected rows.
1064
- */
1065
- deleteRows(t) {
1066
- const e = t ?? this._getSelectedDataRows();
1067
- if (e.length === 0) return;
1068
- const i = [...e].sort((o, r) => r - o), s = [];
1069
- for (const o of i)
1070
- o >= 0 && o < this.data.length && (s.push({ index: o, row: this.data[o] }), this.data.splice(o, 1));
1071
- if (s.length !== 0) {
1072
- if (s.reverse(), this._undo.push({
1073
- label: "row-delete",
1074
- undo: () => {
1075
- for (const { index: o, row: r } of s)
1076
- this.data.splice(o, 0, r);
1077
- this.requestUpdate();
1078
- },
1079
- redo: () => {
1080
- const o = [...s].reverse();
1081
- for (const { index: r } of o)
1082
- this.data.splice(r, 1);
1083
- this.requestUpdate();
1084
- }
1085
- }), this._activeCell) {
1086
- const o = this._toDataIndex(this._activeCell.row);
1087
- e.includes(o) && (this._selection.clear(), this._activeCell = null);
1088
- }
1089
- this.requestUpdate(), this.dispatchEvent(new CustomEvent("row-delete", {
1090
- detail: { indices: s.map((o) => o.index), rows: s.map((o) => o.row) },
1091
- bubbles: !0,
1092
- composed: !0
1093
- })), this._dispatchUndoStateEvent();
1094
- }
1095
- }
1096
- /**
1097
- * Apply multiple cell changes as a single undo-able operation.
1098
- * @param changes Array of { row (data index), key, value } objects.
1099
- */
1100
- updateRows(t) {
1101
- if (t.length === 0) return;
1102
- const e = [];
1103
- for (const i of t) {
1104
- if (i.row < 0 || i.row >= this.data.length) continue;
1105
- const s = this.data[i.row][i.key];
1106
- this.data[i.row][i.key] = i.value, e.push({ row: i.row, key: i.key, oldValue: s, newValue: i.value });
1107
- }
1108
- e.length !== 0 && (this._undo.push({
1109
- label: "batch-update",
1110
- undo: () => {
1111
- for (const i of e)
1112
- this.data[i.row][i.key] = i.oldValue;
1113
- this.requestUpdate();
1114
- },
1115
- redo: () => {
1116
- for (const i of e)
1117
- this.data[i.row][i.key] = i.newValue;
1118
- this.requestUpdate();
1119
- }
1120
- }), this.requestUpdate(), this.dispatchEvent(new CustomEvent("batch-update", {
1121
- detail: { changes: e },
1122
- bubbles: !0,
1123
- composed: !0
1124
- })), this._dispatchUndoStateEvent());
1125
- }
1126
- _createEmptyRow() {
1127
- const t = {};
1128
- for (const e of this.columns)
1129
- switch (e.type) {
1130
- case "number":
1131
- t[e.key] = 0;
1132
- break;
1133
- case "boolean":
1134
- t[e.key] = !1;
1135
- break;
1136
- default:
1137
- t[e.key] = "";
1138
- }
1139
- return t;
1140
- }
1141
- // --- Public API: Export ---
1142
- /**
1143
- * Export table data to string in the specified format.
1144
- * @param options.selectionOnly - Export only the currently selected range
1145
- */
1146
- exportToString(t, e) {
1147
- if (e != null && e.selectionOnly) {
1148
- const s = this._selection.getEffectiveRange();
1149
- if (!s) return "";
1150
- const o = this.visibleColumns.slice(s.startCol, s.endCol + 1), r = [];
1151
- for (let n = s.startRow; n <= s.endRow; n++)
1152
- r.push(this.data[this._toDataIndex(n)]);
1153
- return z(r, o, t);
1154
- }
1155
- const i = this._sortedIndices.map((s) => this.data[s]);
1156
- return z(i, this.visibleColumns, t);
1157
- }
1158
- /**
1159
- * Export table data and trigger file download.
1160
- */
1161
- exportToFile(t, e) {
1162
- const i = this.exportToString(t), s = e ?? `export${ct(t)}`;
1163
- rt(i, s, at(t));
1164
- }
1165
- _getSelectedDataRows() {
1166
- const t = this._selection.getEffectiveRange();
1167
- if (!t) return [];
1168
- const e = [];
1169
- for (let i = t.startRow; i <= t.endRow; i++)
1170
- e.push(this._toDataIndex(i));
1171
- return e;
1172
- }
1173
- /**
1174
- * Get the effective width for a column, checking internal overrides first.
1175
- */
1176
- getColumnWidth(t) {
1177
- return this._columnWidths.get(t);
1178
- }
1179
- _getColWidth(t) {
1180
- const e = this._columnWidths.get(t.key) ?? t.width ?? S;
1181
- return Math.max(e, t.minWidth ?? E);
1182
- }
1183
- get headerHeight() {
1184
- return this.rowHeight + 8;
1185
- }
1186
- /** Number of rows visible after filter + sort. */
1187
- get _visibleRowCount() {
1188
- return this._sortedIndices.length;
1189
- }
1190
- get totalBodyHeight() {
1191
- return this._visibleRowCount * this.rowHeight;
1192
- }
1193
- get visibleRange() {
1194
- const t = Math.max(0, this._scrollTop - this.headerHeight), e = Math.max(0, Math.floor(t / this.rowHeight) - $), i = Math.ceil(this._viewportHeight / this.rowHeight), s = Math.min(this._visibleRowCount, Math.floor(t / this.rowHeight) + i + $);
1195
- return { start: e, end: s };
1196
- }
1197
- connectedCallback() {
1198
- super.connectedCallback(), this._onScroll = this._onScroll.bind(this), this._onKeyDown = this._onKeyDown.bind(this), this._onDocumentClick = this._onDocumentClick.bind(this), this._onContextMenu = this._onContextMenu.bind(this), this.addEventListener("scroll", this._onScroll, { passive: !0 }), this.addEventListener("keydown", this._onKeyDown), this.addEventListener("contextmenu", this._onContextMenu), this.hasAttribute("tabindex") || this.setAttribute("tabindex", "0"), this.setAttribute("role", "grid");
1199
- }
1200
- disconnectedCallback() {
1201
- super.disconnectedCallback(), this.removeEventListener("scroll", this._onScroll), this.removeEventListener("keydown", this._onKeyDown), this.removeEventListener("contextmenu", this._onContextMenu), document.removeEventListener("click", this._onDocumentClick), this._resizeCleanup && (this._resizeCleanup(), this._resizeCleanup = null);
1202
- }
1203
- firstUpdated() {
1204
- this._measureViewport();
1205
- }
1206
- _updateColOffsets() {
1207
- const t = this.visibleColumns, e = [];
1208
- let i = this._prefixWidth;
1209
- for (const s of t)
1210
- e.push(i), i += this._getColWidth(s);
1211
- this._colLeftOffsets = e, this._totalRowWidth = i;
1212
- }
1213
- get visibleColRange() {
1214
- const t = this.visibleColumns;
1215
- if (t.length === 0) return { start: 0, end: 0 };
1216
- const e = this._colLeftOffsets;
1217
- if (e.length === 0) return { start: 0, end: t.length };
1218
- const i = this._scrollLeft, s = i + this._viewportWidth;
1219
- let o = t.length;
1220
- for (let n = 0; n < e.length; n++)
1221
- if (e[n] + this._getColWidth(t[n]) > i) {
1222
- o = n;
1223
- break;
1224
- }
1225
- o = Math.max(0, o - $);
1226
- let r = t.length;
1227
- for (let n = o; n < e.length; n++)
1228
- if (e[n] > s) {
1229
- r = n;
1230
- break;
1231
- }
1232
- return r = Math.min(t.length, r + $), { start: o, end: r };
1233
- }
1234
- willUpdate(t) {
1235
- (!(t.size <= 2 && !t.has("data") && !t.has("columns") && !t.has("_openFilterKey") && !t.has("_editingCell") && !t.has("_activeCell") && (t.has("_scrollTop") || t.has("_scrollLeft"))) || this._viewDirty) && (this._recomputeView(), this._viewDirty = !1), this._updateColOffsets(), this._selection.setDimensions(this._visibleRowCount, this.visibleColumns.length), this._rowSelection.setRowCount(this._visibleRowCount);
1236
- }
1237
- updated() {
1238
- this._measureViewport(), this._focusEditor(), this._adjustFilterDropdown(), this.setAttribute("aria-rowcount", String(this._visibleRowCount)), this.setAttribute("aria-colcount", String(this.visibleColumns.length));
1239
- }
1240
- /** Recompute filter → sort pipeline. */
1241
- _recomputeView() {
1242
- if (this.dataMode === "server") {
1243
- this._filteredIndices = Array.from({ length: this.data.length }, (t, e) => e), this._sortedIndices = this._filteredIndices;
1244
- return;
1245
- }
1246
- if (this._filteredIndices = Q(this.data, this._filters, (t, e, i) => {
1247
- this.dispatchEvent(new CustomEvent("filter-error", {
1248
- detail: { error: t, row: e, filterKey: i.key },
1249
- bubbles: !0,
1250
- composed: !0
1251
- }));
1252
- }), this._filters.length === 0 && this._sortCriteria.length === 0)
1253
- this._sortedIndices = Array.from({ length: this.data.length }, (t, e) => e);
1254
- else if (this._sortCriteria.length === 0)
1255
- this._sortedIndices = [...this._filteredIndices];
1256
- else {
1257
- const t = this._filteredIndices.map((i) => this.data[i]), e = Y(t, this._sortCriteria, this.visibleColumns);
1258
- this._sortedIndices = e.map((i) => this._filteredIndices[i]);
1259
- }
1260
- }
1261
- /** Map visual row index to data row index */
1262
- _toDataIndex(t) {
1263
- return this._sortedIndices[t] ?? t;
1264
- }
1265
- _focusEditor() {
1266
- var e;
1267
- if (!this._editingCell) return;
1268
- const t = (e = this.shadowRoot) == null ? void 0 : e.querySelector(".ft-editor");
1269
- t && document.activeElement !== t && (t.focus(), (t.type === "text" || t.type === "number") && t.select());
1270
- }
1271
- _measureViewport() {
1272
- const t = this.clientHeight, e = this.clientWidth;
1273
- t !== this._viewportHeight && (this._viewportHeight = t), e !== this._viewportWidth && (this._viewportWidth = e);
1274
- }
1275
- _onScroll() {
1276
- this._scrollTop = this.scrollTop, this._scrollLeft = this.scrollLeft;
1277
- }
1278
- _onDocumentClick() {
1279
- this._openFilterKey && (this._openFilterKey = null);
1280
- }
1281
- _onContextMenu(t) {
1282
- var f;
1283
- const i = t.composedPath().find(
1284
- (h) => h instanceof HTMLElement && h.classList.contains("ft-cell")
1285
- );
1286
- if (!i) return;
1287
- const s = i.parentElement;
1288
- if (!s || !s.classList.contains("ft-row")) return;
1289
- const o = parseInt(i.dataset.colIndex ?? "-1", 10), r = s.style.top, n = parseInt(r, 10), l = Math.round(n / this.rowHeight);
1290
- if (o < 0 || l < 0) return;
1291
- const a = this._toDataIndex(l), d = this.visibleColumns[o];
1292
- d && this.dispatchEvent(new CustomEvent("context-menu", {
1293
- detail: {
1294
- x: t.clientX,
1295
- y: t.clientY,
1296
- row: a,
1297
- col: o,
1298
- key: d.key,
1299
- value: (f = this.data[a]) == null ? void 0 : f[d.key],
1300
- rowData: this.data[a]
1301
- },
1302
- bubbles: !0,
1303
- composed: !0
1304
- }));
1305
- }
1306
- _onCellClickEvent(t, e, i) {
1307
- this._editing.current && this._commitEdit(), t.shiftKey ? this._selection.setActiveWithRange(e, i) : this._selection.setActive(e, i), this._activeCell = this._selection.activeCell ? { ...this._selection.activeCell } : null, this._dispatchSelectionEvent(), this.requestUpdate();
1308
- }
1309
- _onRowNumberClick(t) {
1310
- this._editing.current && this._commitEdit();
1311
- const e = this.visibleColumns.length - 1;
1312
- this._selection.setActive(t, 0), this._selection.setActiveWithRange(t, e), this._activeCell = this._selection.activeCell ? { ...this._selection.activeCell } : null, this._dispatchSelectionEvent(), this.requestUpdate();
1313
- }
1314
- _onCellDblClick(t, e) {
1315
- this._editing.current && this._commitEdit(), this._selection.setActive(t, e), this._activeCell = this._selection.activeCell ? { ...this._selection.activeCell } : null, this._dispatchSelectionEvent(), this._startEdit();
1316
- }
1317
- // --- Editing ---
1318
- /** Check if a column is editable based on global + per-column settings */
1319
- _isCellEditable(t) {
1320
- return !(!this.editable || t.editable === !1);
1321
- }
1322
- _startEdit() {
1323
- if (!this._activeCell) return;
1324
- const e = this.visibleColumns[this._activeCell.col];
1325
- if (!e || !this._isCellEditable(e)) return;
1326
- if (e.type === "boolean") {
1327
- const r = this.data[this._toDataIndex(this._activeCell.row)][e.key];
1328
- this._applyEdit(!r);
1329
- return;
1330
- }
1331
- const i = this._toDataIndex(this._activeCell.row), s = this.data[i];
1332
- this._editing.start(this._activeCell, s[e.key]), this._editingCell = { ...this._activeCell }, this.dispatchEvent(new CustomEvent("cell-edit-start", {
1333
- detail: { row: i, col: this._activeCell.col, key: e.key, value: s[e.key] },
1334
- bubbles: !0,
1335
- composed: !0
1336
- }));
1337
- }
1338
- _commitEdit() {
1339
- var e;
1340
- if (!this._editing.current) return;
1341
- const t = (e = this.shadowRoot) == null ? void 0 : e.querySelector(".ft-editor");
1342
- if (t) {
1343
- const i = this.visibleColumns[this._editing.current.position.col], s = A(t.value, i);
1344
- this._applyEdit(s);
1345
- } else
1346
- this._cancelEdit();
1347
- }
1348
- _applyEdit(t) {
1349
- const e = this._editing.commit();
1350
- if (this._editingCell = null, !e) return;
1351
- const { row: i, col: s } = e.position, o = this._toDataIndex(i), r = this.visibleColumns[s], n = e.originalValue;
1352
- if (r.validator) {
1353
- const l = r.validator(t, this.data[o], r);
1354
- if (l) {
1355
- this._markCellInvalid(i, s, l), this.dispatchEvent(new CustomEvent("validation-error", {
1356
- detail: { row: o, col: s, key: r.key, value: t, error: l },
1357
- bubbles: !0,
1358
- composed: !0
1359
- }));
1360
- return;
1361
- }
1362
- }
1363
- this.data[o][r.key] = t, this._undo.push({
1364
- label: "cell-edit",
1365
- undo: () => {
1366
- this.data[o][r.key] = n, this.requestUpdate();
1367
- },
1368
- redo: () => {
1369
- this.data[o][r.key] = t, this.requestUpdate();
1370
- }
1371
- }), this.requestUpdate(), this.dispatchEvent(new CustomEvent("cell-edit-commit", {
1372
- detail: { row: o, col: s, key: r.key, oldValue: n, newValue: t },
1373
- bubbles: !0,
1374
- composed: !0
1375
- })), this._dispatchUndoStateEvent();
1376
- }
1377
- _cancelEdit() {
1378
- const t = this._editing.cancel();
1379
- this._editingCell = null, t && this.dispatchEvent(new CustomEvent("cell-edit-cancel", {
1380
- detail: { row: t.position.row, col: t.position.col },
1381
- bubbles: !0,
1382
- composed: !0
1383
- }));
1384
- }
1385
- _onEditorKeyDown(t) {
1386
- t.key === "Enter" ? (t.preventDefault(), t.stopPropagation(), this._commitEdit(), this._selection.moveDown(), this._syncActiveCell()) : t.key === "Tab" ? (t.preventDefault(), t.stopPropagation(), this._commitEdit(), t.shiftKey ? this._selection.movePrev() : this._selection.moveNext(), this._syncActiveCell()) : t.key === "Escape" && (t.preventDefault(), t.stopPropagation(), this._cancelEdit());
1387
- }
1388
- _syncActiveCell() {
1389
- this._activeCell = this._selection.activeCell ? { ...this._selection.activeCell } : null, this._scrollToActiveCell(), this._dispatchSelectionEvent();
1390
- }
1391
- // --- Navigation ---
1392
- _onKeyDown(t) {
1393
- if (this._editing.current) return;
1394
- const e = this.visibleColumns;
1395
- if (e.length === 0 || this._visibleRowCount === 0) return;
1396
- if ((t.key === "Enter" || t.key === "F2") && this._activeCell) {
1397
- t.preventDefault(), this._startEdit();
1398
- return;
1399
- }
1400
- if (!this._selection.activeCell) {
1401
- if (["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Tab", "Home", "End"].includes(t.key)) {
1402
- this._selection.setActive(0, 0), this._syncActiveCell(), t.preventDefault();
1403
- return;
1404
- }
1405
- return;
1406
- }
1407
- if (this._handleCtrlKey(t) || this._handleAltKey(t, e)) return;
1408
- if (this._activeCell && t.key.length === 1 && !t.ctrlKey && !t.metaKey && !t.altKey) {
1409
- const s = e[this._activeCell.col];
1410
- s && this._isCellEditable(s) && this._startEdit();
1411
- return;
1412
- }
1413
- this._handleNavigation(t) && (t.preventDefault(), this._syncActiveCell());
1414
- }
1415
- _handleCtrlKey(t) {
1416
- if (!(t.ctrlKey || t.metaKey) || t.altKey) return !1;
1417
- switch (t.key.toLowerCase()) {
1418
- case "z":
1419
- return t.preventDefault(), t.shiftKey ? this._undo.redo() : this._undo.undo(), this.requestUpdate(), this._dispatchUndoStateEvent(), !0;
1420
- case "y":
1421
- return t.preventDefault(), this._undo.redo(), this.requestUpdate(), this._dispatchUndoStateEvent(), !0;
1422
- case "c":
1423
- return t.preventDefault(), this._handleCopy(!1), !0;
1424
- case "x":
1425
- return t.preventDefault(), this._handleCopy(this.editable), !0;
1426
- case "v":
1427
- return t.preventDefault(), this.editable && this._handlePaste(), !0;
1428
- default:
1429
- return !1;
1430
- }
1431
- }
1432
- _handleAltKey(t, e) {
1433
- if (!t.altKey || !this._activeCell || t.key !== "ArrowLeft" && t.key !== "ArrowRight") return !1;
1434
- t.preventDefault();
1435
- const i = e[this._activeCell.col];
1436
- if (!i) return !0;
1437
- const s = this._columnWidths.get(i.key) ?? i.width ?? S, o = t.key === "ArrowRight" ? 20 : -20, r = i.minWidth ?? E, n = Math.max(r, s + o);
1438
- return this._columnWidths.set(i.key, n), this.requestUpdate(), this.dispatchEvent(new CustomEvent("column-resize", {
1439
- detail: { key: i.key, width: n, colIndex: this._activeCell.col },
1440
- bubbles: !0,
1441
- composed: !0
1442
- })), !0;
1443
- }
1444
- _handleNavigation(t) {
1445
- switch (t.key) {
1446
- case "ArrowUp":
1447
- return t.shiftKey ? this._selection.shiftMoveUp() : this._selection.moveUp(), !0;
1448
- case "ArrowDown":
1449
- return t.shiftKey ? this._selection.shiftMoveDown() : this._selection.moveDown(), !0;
1450
- case "ArrowLeft":
1451
- return t.shiftKey ? this._selection.shiftMoveLeft() : this._selection.moveLeft(), !0;
1452
- case "ArrowRight":
1453
- return t.shiftKey ? this._selection.shiftMoveRight() : this._selection.moveRight(), !0;
1454
- case "Tab":
1455
- return t.shiftKey ? this._selection.movePrev() : this._selection.moveNext(), !0;
1456
- case "Home":
1457
- return t.ctrlKey ? this._selection.moveToStart() : this._selection.moveToRowStart(), !0;
1458
- case "End":
1459
- return t.ctrlKey ? this._selection.moveToEnd() : this._selection.moveToRowEnd(), !0;
1460
- case "Escape":
1461
- return this._selection.clear(), !0;
1462
- case "Delete":
1463
- case "Backspace":
1464
- return this.editable && this._handleDelete(), !0;
1465
- default:
1466
- return !1;
1467
- }
1468
- }
1469
- // --- Clipboard ---
1470
- async _handleCopy(t) {
1471
- const e = this._selection.getEffectiveRange();
1472
- if (!e) return;
1473
- const i = this.visibleColumns, s = this._sortedIndices.map((r) => this.data[r]), o = et(s, i, e);
1474
- try {
1475
- await navigator.clipboard.writeText(o);
1476
- } catch (r) {
1477
- this.dispatchEvent(new CustomEvent("clipboard-error", {
1478
- detail: { action: "copy", error: r },
1479
- bubbles: !0,
1480
- composed: !0
1481
- }));
1482
- }
1483
- t && this._clearRange(e), this.dispatchEvent(new CustomEvent(t ? "clipboard-cut" : "clipboard-copy", {
1484
- detail: { range: e, text: o },
1485
- bubbles: !0,
1486
- composed: !0
1487
- }));
1488
- }
1489
- async _handlePaste() {
1490
- if (!this._activeCell) return;
1491
- const t = await this._readClipboardText();
1492
- if (t == null) return;
1493
- const e = it(t);
1494
- if (e.length === 0) return;
1495
- const i = this._expandRowsForPaste(this._activeCell.row, e.length), s = this._applyPasteData(this._activeCell, e);
1496
- if (s.length > 0 || i.length > 0) {
1497
- const o = i.length;
1498
- this._undo.push({
1499
- label: "paste",
1500
- undo: () => {
1501
- for (const r of s)
1502
- this.data[r.row][r.key] = r.oldValue;
1503
- o > 0 && this.data.splice(this.data.length - o, o), this.requestUpdate();
1504
- },
1505
- redo: () => {
1506
- if (o > 0)
1507
- for (let r = 0; r < o; r++)
1508
- this.data.push(this._createEmptyRow());
1509
- for (const r of s)
1510
- this.data[r.row][r.key] = r.newValue;
1511
- this.requestUpdate();
1512
- }
1513
- }), this._dispatchUndoStateEvent();
1514
- }
1515
- this.requestUpdate(), this.dispatchEvent(new CustomEvent("clipboard-paste", {
1516
- detail: { changes: s, addedRows: i.length },
1517
- bubbles: !0,
1518
- composed: !0
1519
- }));
1520
- }
1521
- async _readClipboardText() {
1522
- try {
1523
- return await navigator.clipboard.readText();
1524
- } catch (t) {
1525
- return this.dispatchEvent(new CustomEvent("clipboard-error", {
1526
- detail: { action: "paste", error: t },
1527
- bubbles: !0,
1528
- composed: !0
1529
- })), null;
1530
- }
1531
- }
1532
- _expandRowsForPaste(t, e) {
1533
- const i = [], s = t + e, o = this.maxRows > 0 ? this.maxRows : 1 / 0;
1534
- for (; this.data.length < s && this.data.length < o; ) {
1535
- const r = this.data.length;
1536
- this.data.push(this._createEmptyRow()), i.push(r);
1537
- }
1538
- return i.length > 0 && this._recomputeView(), i;
1539
- }
1540
- _applyPasteData(t, e) {
1541
- const i = this.visibleColumns, s = [];
1542
- for (let o = 0; o < e.length; o++) {
1543
- const r = t.row + o;
1544
- if (r >= this._visibleRowCount) break;
1545
- const n = this._toDataIndex(r);
1546
- for (let l = 0; l < e[o].length; l++) {
1547
- const a = t.col + l;
1548
- if (a >= i.length) break;
1549
- const d = i[a], f = this.data[n][d.key], h = A(e[o][l], d);
1550
- this.data[n][d.key] = h, s.push({ row: n, col: a, key: d.key, oldValue: f, newValue: h });
1551
- }
1552
- }
1553
- return s;
1554
- }
1555
- _handleDelete() {
1556
- const t = this._selection.getEffectiveRange();
1557
- t && this._clearRange(t);
1558
- }
1559
- _clearRange(t) {
1560
- const e = this.visibleColumns, i = [];
1561
- for (let s = t.startRow; s <= t.endRow; s++) {
1562
- const o = this._toDataIndex(s);
1563
- for (let r = t.startCol; r <= t.endCol; r++) {
1564
- const n = e[r], l = this.data[o][n.key], a = n.type === "boolean" ? !1 : n.type === "number" ? 0 : "";
1565
- this.data[o][n.key] = a, i.push({ dataRow: o, key: n.key, oldValue: l, clearValue: a });
1566
- }
1567
- }
1568
- i.length > 0 && (this._undo.push({
1569
- label: "clear",
1570
- undo: () => {
1571
- for (const s of i)
1572
- this.data[s.dataRow][s.key] = s.oldValue;
1573
- this.requestUpdate();
1574
- },
1575
- redo: () => {
1576
- for (const s of i)
1577
- this.data[s.dataRow][s.key] = s.clearValue;
1578
- this.requestUpdate();
1579
- }
1580
- }), this._dispatchUndoStateEvent()), this.requestUpdate();
1581
- }
1582
- _scrollToActiveCell() {
1583
- if (!this._activeCell) return;
1584
- const t = this._activeCell.row * this.rowHeight + this.headerHeight, e = t + this.rowHeight;
1585
- t < this.scrollTop + this.headerHeight ? this.scrollTop = t - this.headerHeight : e > this.scrollTop + this.clientHeight && (this.scrollTop = e - this.clientHeight);
1586
- const i = this.visibleColumns, s = this._activeCell.col, o = this._colLeftOffsets[s] ?? 0, r = this._getColWidth(i[s]), n = o + r;
1587
- o < this.scrollLeft ? this.scrollLeft = o : n > this.scrollLeft + this.clientWidth && (this.scrollLeft = n - this.clientWidth);
1588
- }
1589
- _dispatchSelectionEvent() {
1590
- this.dispatchEvent(new CustomEvent("cell-select", {
1591
- detail: this._activeCell ? { ...this._activeCell } : null,
1592
- bubbles: !0,
1593
- composed: !0
1594
- }));
1595
- }
1596
- // --- Sorting ---
1597
- _onHeaderClick(t, e) {
1598
- const i = this.visibleColumns.indexOf(e);
1599
- if (t.ctrlKey || t.metaKey) {
1600
- this._selectColumn(i);
1601
- return;
1602
- }
1603
- e.sortable !== !1 && (this._sortCriteria = G(this._sortCriteria, e.key, t.shiftKey), this.dataMode !== "server" && this._recomputeView(), this.requestUpdate(), this.dispatchEvent(new CustomEvent("sort-change", {
1604
- detail: { criteria: [...this._sortCriteria] },
1605
- bubbles: !0,
1606
- composed: !0
1607
- })));
1608
- }
1609
- _selectColumn(t) {
1610
- var i;
1611
- const e = this._visibleRowCount;
1612
- e === 0 || t < 0 || (this._selection.setActive(0, t), this._selection.setActiveWithRange(e - 1, t), this._activeCell = { row: 0, col: t }, this.requestUpdate(), this.dispatchEvent(new CustomEvent("column-select", {
1613
- detail: {
1614
- colIndex: t,
1615
- key: (i = this.visibleColumns[t]) == null ? void 0 : i.key,
1616
- rowCount: e
1617
- },
1618
- bubbles: !0,
1619
- composed: !0
1620
- })));
1621
- }
1622
- /** Public API: select an entire column by index. */
1623
- selectColumn(t) {
1624
- this._selectColumn(t);
1625
- }
1626
- /** Calculate cumulative left offset for a left-pinned column */
1627
- _getPinnedLeft(t) {
1628
- const e = this.visibleColumns;
1629
- let i = 0;
1630
- this.selectable && (i += 36), this.showRowNumbers && (i += 48);
1631
- for (let s = 0; s < t; s++)
1632
- e[s].pinned === "left" && (i += this._getColWidth(e[s]));
1633
- return i;
1634
- }
1635
- /** Calculate cumulative right offset for a right-pinned column */
1636
- _getPinnedRight(t) {
1637
- const e = this.visibleColumns;
1638
- let i = 0;
1639
- for (let s = e.length - 1; s > t; s--)
1640
- e[s].pinned === "right" && (i += this._getColWidth(e[s]));
1641
- return i;
1642
- }
1643
- _renderHeaderCell(t, e) {
1644
- const i = t.sortable !== !1, s = this._sortCriteria.find((m) => m.key === t.key), o = this._sortCriteria.length > 1 ? this._sortCriteria.findIndex((m) => m.key === t.key) : -1, r = this._filters.some((m) => m.key === t.key), n = t.pinned === "left", l = t.pinned === "right", d = [
1645
- "ft-header-cell",
1646
- i ? "ft-sortable" : "",
1647
- n || l ? "ft-pinned" : ""
1648
- ].filter(Boolean).join(" "), f = i ? s ? s.direction === "asc" ? "ascending" : "descending" : "none" : void 0, h = this._getColWidth(t), g = this.headerHeight, p = this._colLeftOffsets[e] ?? 0;
1649
- let c;
1650
- return n ? c = `position: absolute; top: 0; left: ${this._scrollLeft + this._getPinnedLeft(e)}px; width: ${h}px; height: ${g}px; z-index: 4;` : l ? c = `position: absolute; top: 0; right: ${-this._scrollLeft + this._getPinnedRight(e)}px; width: ${h}px; height: ${g}px; z-index: 4;` : c = `left: ${p}px; width: ${h}px; height: ${g}px;`, u`
1651
- <div class=${d}
1652
- role="columnheader"
1653
- data-col-index=${e}
1654
- style=${c}
1655
- aria-sort=${f ?? k}
1656
- @click=${i ? (m) => this._onHeaderClick(m, t) : void 0}>
1657
- <span>${t.header}</span>
1658
- ${s ? u`<span class="ft-sort-indicator">${s.direction === "asc" ? "▲" : "▼"}</span>` : ""}
1659
- ${o >= 0 ? u`<span class="ft-sort-order">${o + 1}</span>` : ""}
1660
- ${this.showFilters ? u`
1661
- <button class="ft-filter-btn ${r ? "ft-filter-active" : ""}"
1662
- title="Filter"
1663
- aria-label=${`Filter ${t.header}`}
1664
- aria-expanded=${this._openFilterKey === t.key ? "true" : "false"}
1665
- @click=${(m) => this._onFilterBtnClick(m, t)}>
1666
- \u25BD
1667
- </button>
1668
- ` : ""}
1669
- <div class="ft-resize-handle"
1670
- @mousedown=${(m) => this._onResizeStart(m, e)}
1671
- @dblclick=${(m) => this._onResizeAutoFit(m, e)}></div>
1672
- </div>
1673
- ${this._openFilterKey === t.key ? this._renderFilterDropdown(t) : ""}
1674
- `;
1675
- }
1676
- // --- Filter UI ---
1677
- _adjustFilterDropdown() {
1678
- var s;
1679
- if (!this._openFilterKey) return;
1680
- const t = (s = this.shadowRoot) == null ? void 0 : s.querySelector(".ft-filter-dropdown");
1681
- if (!t) return;
1682
- t.style.top = "", t.style.bottom = "";
1683
- const e = t.getBoundingClientRect(), i = window.innerHeight;
1684
- e.bottom > i && (t.style.top = "auto", t.style.bottom = "100%");
1685
- }
1686
- _onFilterBtnClick(t, e) {
1687
- t.preventDefault(), t.stopPropagation();
1688
- const i = this._openFilterKey === e.key ? null : e.key;
1689
- this._openFilterKey = i, i && requestAnimationFrame(() => {
1690
- document.addEventListener("click", this._onDocumentClick, { once: !0 });
1691
- });
1692
- }
1693
- _renderFilterDropdown(t) {
1694
- const e = t.type ?? "text";
1695
- return u`
1696
- <div class="ft-filter-dropdown" @click=${(i) => i.stopPropagation()}>
1697
- ${e === "boolean" ? this._renderBooleanFilter(t) : e === "number" ? this._renderNumberFilter(t) : e === "date" || e === "datetime" ? this._renderDateFilter(t) : this._renderTextFilter(t)}
1698
- <div class="ft-filter-actions">
1699
- <button class="ft-filter-clear" @click=${() => this._clearColumnFilter(t.key)}>Clear</button>
1700
- </div>
1701
- </div>
1702
- `;
1703
- }
1704
- _renderTextFilter(t) {
1705
- return u`
1706
- <input class="ft-filter-input" type="text" placeholder="Search..."
1707
- .value=${this._textFilterState.get(t.key) ?? ""}
1708
- @input=${(e) => {
1709
- const i = e.target.value;
1710
- this._textFilterState.set(t.key, i), i ? this.setFilter(t.key, (s) => String(s ?? "").toLowerCase().includes(i.toLowerCase())) : this.removeFilter(t.key);
1711
- }}
1712
- @keydown=${(e) => {
1713
- e.key === "Escape" && (this._openFilterKey = null), e.stopPropagation();
1714
- }}>
1715
- `;
1716
- }
1717
- _renderNumberFilter(t) {
1718
- const e = this._numberFilterState.get(t.key) ?? {};
1719
- return u`
1720
- <div class="ft-filter-range">
1721
- <input class="ft-filter-input" type="number" placeholder="Min"
1722
- .value=${e.min != null ? String(e.min) : ""}
1723
- @input=${(i) => this._applyNumberFilter(t.key, i, "min")}
1724
- @keydown=${(i) => {
1725
- i.key === "Escape" && (this._openFilterKey = null), i.stopPropagation();
1726
- }}>
1727
- <input class="ft-filter-input" type="number" placeholder="Max"
1728
- .value=${e.max != null ? String(e.max) : ""}
1729
- @input=${(i) => this._applyNumberFilter(t.key, i, "max")}
1730
- @keydown=${(i) => {
1731
- i.key === "Escape" && (this._openFilterKey = null), i.stopPropagation();
1732
- }}>
1733
- </div>
1734
- `;
1735
- }
1736
- _cellKey(t, e) {
1737
- return `${t}:${e}`;
1738
- }
1739
- _markCellInvalid(t, e, i) {
1740
- const s = this._cellKey(t, e), o = this._invalidCells.get(s);
1741
- o && clearTimeout(o.timer);
1742
- const r = setTimeout(() => {
1743
- this._invalidCells.delete(s), this.requestUpdate();
1744
- }, 3e3);
1745
- this._invalidCells.set(s, { error: i, timer: r }), this.requestUpdate();
1746
- }
1747
- _isCellInvalid(t, e) {
1748
- const i = this._invalidCells.get(this._cellKey(t, e));
1749
- return i ? i.error : null;
1750
- }
1751
- _applyNumberFilter(t, e, i) {
1752
- const s = e.target.value, o = this._numberFilterState.get(t) ?? {};
1753
- s === "" ? delete o[i] : o[i] = Number(s), this._numberFilterState.set(t, o), o.min == null && o.max == null ? this.removeFilter(t) : this.setFilter(t, (r) => {
1754
- const n = Number(r);
1755
- return !(isNaN(n) || o.min != null && n < o.min || o.max != null && n > o.max);
1756
- });
1757
- }
1758
- _renderDateFilter(t) {
1759
- const e = t.type === "datetime" ? "datetime-local" : "date", i = this._dateFilterState.get(t.key) ?? {};
1760
- return u`
1761
- <div class="ft-filter-range">
1762
- <input class="ft-filter-input" type=${e} placeholder="From"
1763
- .value=${i.from ?? ""}
1764
- @input=${(s) => this._applyDateFilter(t.key, s, "from")}
1765
- @keydown=${(s) => {
1766
- s.key === "Escape" && (this._openFilterKey = null), s.stopPropagation();
1767
- }}>
1768
- <input class="ft-filter-input" type=${e} placeholder="To"
1769
- .value=${i.to ?? ""}
1770
- @input=${(s) => this._applyDateFilter(t.key, s, "to")}
1771
- @keydown=${(s) => {
1772
- s.key === "Escape" && (this._openFilterKey = null), s.stopPropagation();
1773
- }}>
1774
- </div>
1775
- `;
1776
- }
1777
- _applyDateFilter(t, e, i) {
1778
- const s = e.target.value, o = this._dateFilterState.get(t) ?? {};
1779
- s === "" ? delete o[i] : o[i] = s, this._dateFilterState.set(t, o), o.from == null && o.to == null ? this.removeFilter(t) : this.setFilter(t, (r) => {
1780
- if (r == null || r === "") return !1;
1781
- const n = r instanceof Date ? r : new Date(String(r));
1782
- if (isNaN(n.getTime())) return !1;
1783
- if (o.from) {
1784
- const l = new Date(o.from);
1785
- if (n < l) return !1;
1786
- }
1787
- if (o.to) {
1788
- const l = new Date(o.to);
1789
- if (o.to.length === 10 && l.setHours(23, 59, 59, 999), n > l) return !1;
1790
- }
1791
- return !0;
1792
- });
1793
- }
1794
- _renderBooleanFilter(t) {
1795
- return u`
1796
- <select class="ft-filter-input"
1797
- @change=${(e) => {
1798
- const i = e.target.value;
1799
- i === "all" ? this.removeFilter(t.key) : this.setFilter(t.key, (s) => !!s == (i === "true"));
1800
- }}
1801
- @keydown=${(e) => {
1802
- e.key === "Escape" && (this._openFilterKey = null), e.stopPropagation();
1803
- }}>
1804
- <option value="all">All</option>
1805
- <option value="true">\u2714 True</option>
1806
- <option value="false">\u2718 False</option>
1807
- </select>
1808
- `;
1809
- }
1810
- _clearColumnFilter(t) {
1811
- this.removeFilter(t), this._textFilterState.delete(t), this._numberFilterState.delete(t), this._dateFilterState.delete(t), this._openFilterKey = null;
1812
- }
1813
- // --- Column Resize ---
1814
- _onResizeAutoFit(t, e) {
1815
- var a, d;
1816
- t.preventDefault(), t.stopPropagation();
1817
- const i = this.visibleColumns[e];
1818
- if (!i) return;
1819
- const s = (a = this.shadowRoot) == null ? void 0 : a.querySelectorAll(`.ft-cell[data-col-index="${e}"]`);
1820
- let o = 0;
1821
- const r = (d = this.shadowRoot) == null ? void 0 : d.querySelector(`.ft-header-cell[data-col-index="${e}"]`);
1822
- if (r && (o = Math.max(o, r.scrollWidth)), s)
1823
- for (const f of s)
1824
- o = Math.max(o, f.scrollWidth);
1825
- const n = i.minWidth ?? E, l = Math.max(n, o + 8);
1826
- this._columnWidths.set(i.key, l), this.requestUpdate(), this.dispatchEvent(new CustomEvent("column-resize", {
1827
- detail: { key: i.key, width: l, colIndex: e },
1828
- bubbles: !0,
1829
- composed: !0
1830
- }));
1831
- }
1832
- _onResizeStart(t, e) {
1833
- t.preventDefault(), t.stopPropagation();
1834
- const i = this.visibleColumns[e], s = this._columnWidths.get(i.key) ?? i.width ?? S;
1835
- this._resizing = { colIndex: e, startX: t.clientX, startWidth: s };
1836
- const o = (l) => {
1837
- if (!this._resizing) return;
1838
- const a = l.clientX - this._resizing.startX, d = i.minWidth ?? E, f = Math.max(d, this._resizing.startWidth + a);
1839
- this._columnWidths.set(i.key, f), this.requestUpdate();
1840
- }, r = () => {
1841
- document.removeEventListener("mousemove", o), document.removeEventListener("mouseup", n), this._resizeCleanup = null;
1842
- }, n = () => {
1843
- if (r(), this._resizing) {
1844
- const l = this._columnWidths.get(i.key) ?? S;
1845
- this.dispatchEvent(new CustomEvent("column-resize", {
1846
- detail: { key: i.key, width: l, colIndex: e },
1847
- bubbles: !0,
1848
- composed: !0
1849
- })), this._resizing = null;
1850
- }
1851
- };
1852
- document.addEventListener("mousemove", o), document.addEventListener("mouseup", n), this._resizeCleanup = r;
1853
- }
1854
- // --- Rendering ---
1855
- render() {
1856
- const t = this.visibleColumns;
1857
- if (t.length === 0)
1858
- return u`<div class="ft-empty">No columns defined</div>`;
1859
- const e = this.headerHeight, i = this._totalRowWidth, { start: s, end: o } = this.visibleColRange, r = [];
1860
- for (let c = 0; c < t.length; c++)
1861
- (t[c].pinned === "left" || t[c].pinned === "right") && (c < s || c >= o) && r.push(c);
1862
- let n = 0;
1863
- const l = this._scrollLeft, a = this.selectable ? u`<div class="ft-checkbox-header"
1864
- style="position: absolute; top: 0; left: ${l + n}px; width: 36px; height: ${e}px; z-index: 4;">
1865
- ${this._rowSelection.mode === "multi" ? u`
1866
- <input type="checkbox"
1867
- .checked=${this._rowSelection.isAllSelected}
1868
- .indeterminate=${this._rowSelection.isSomeSelected}
1869
- @change=${this._onSelectAllChange}>
1870
- ` : ""}
1871
- </div>` : "";
1872
- this.selectable && (n += 36);
1873
- const d = this.showRowNumbers ? u`<div class="ft-row-num-header"
1874
- style="position: absolute; top: 0; left: ${l + n}px; width: 48px; height: ${e}px; z-index: 4;">#</div>` : "", f = [];
1875
- for (const c of r)
1876
- f.push(this._renderHeaderCell(t[c], c));
1877
- for (let c = s; c < o; c++)
1878
- f.push(this._renderHeaderCell(t[c], c));
1879
- if (this.data.length === 0 || this._visibleRowCount === 0) {
1880
- const c = this.data.length === 0 ? "No data" : "No matching data";
1881
- return u`
1882
- <div class="ft-header" role="row" style="width: ${i}px; height: ${e}px;">
1883
- ${a}${d}
1884
- ${f}
1885
- </div>
1886
- <div class="ft-empty">${c}</div>
1887
- `;
1888
- }
1889
- const { start: h, end: g } = this.visibleRange, p = [];
1890
- for (let c = h; c < g; c++)
1891
- p.push(this._renderRow(c, s, o, r));
1892
- return u`
1893
- <div class="ft-header" role="row" style="width: ${i}px; height: ${e}px;">
1894
- ${a}${d}
1895
- ${f}
1896
- </div>
1897
- <div class="ft-body" style="height: ${this.totalBodyHeight}px; width: ${i}px;">
1898
- ${p}
1899
- </div>
1900
- ${this.footerData ? this._renderFooter(s, o, r) : ""}
1901
- `;
1902
- }
1903
- _onSelectAllChange(t) {
1904
- t.target.checked ? this._rowSelection.selectAll() : this._rowSelection.deselectAll(), this._rowSelectionVersion++, this._dispatchRowSelectionEvent();
1905
- }
1906
- _onRowCheckboxChange(t, e) {
1907
- t.stopPropagation(), this._rowSelection.toggle(e), this._rowSelectionVersion++, this._dispatchRowSelectionEvent();
1908
- }
1909
- _renderFooter(t, e, i) {
1910
- if (!this.footerData) return "";
1911
- const s = this.visibleColumns, o = this.rowHeight, r = this._totalRowWidth, n = this._scrollLeft;
1912
- let l = 0;
1913
- const a = this.selectable ? u`<div class="ft-footer-cell ft-checkbox-cell"
1914
- style="position: absolute; top: 0; left: ${n + l}px; width: 36px; height: ${o}px; z-index: 2;"></div>` : "";
1915
- this.selectable && (l += 36);
1916
- const d = this.showRowNumbers ? u`<div class="ft-footer-cell ft-row-num"
1917
- style="position: absolute; top: 0; left: ${n + l}px; width: 48px; height: ${o}px; z-index: 2;"></div>` : "", f = [];
1918
- for (const h of i) {
1919
- const g = s[h], p = this._getColWidth(g), c = g.pinned === "right" ? `position: absolute; top: 0; right: ${-n + this._getPinnedRight(h)}px; width: ${p}px; height: ${o}px; z-index: 2;` : `position: absolute; top: 0; left: ${n + this._getPinnedLeft(h)}px; width: ${p}px; height: ${o}px; z-index: 2;`;
1920
- f.push(u`
1921
- <div class="ft-footer-cell ft-pinned" style=${c}>
1922
- ${this.footerData[g.key] ?? ""}</div>
1923
- `);
1924
- }
1925
- for (let h = t; h < e; h++) {
1926
- const g = s[h], p = this._getColWidth(g), c = this._colLeftOffsets[h] ?? 0, m = g.pinned === "left", y = g.pinned === "right", b = m || y;
1927
- let x;
1928
- m ? x = `position: absolute; top: 0; left: ${n + this._getPinnedLeft(h)}px; width: ${p}px; height: ${o}px; z-index: 2;` : y ? x = `position: absolute; top: 0; right: ${-n + this._getPinnedRight(h)}px; width: ${p}px; height: ${o}px; z-index: 2;` : x = `left: ${c}px; width: ${p}px; height: ${o}px;`, f.push(u`
1929
- <div class="ft-footer-cell ${b ? "ft-pinned" : ""}" style=${x}>
1930
- ${this.footerData[g.key] ?? ""}</div>
1931
- `);
1932
- }
1933
- return u`
1934
- <div class="ft-footer" role="row" style="width: ${r}px; height: ${o}px;">
1935
- ${a}${d}
1936
- ${f}
1937
- </div>
1938
- `;
1939
- }
1940
- _renderRow(t, e, i, s) {
1941
- const o = this.visibleColumns, r = this._toDataIndex(t), n = this.data[r], l = t * this.rowHeight, a = this.rowHeight, d = this._totalRowWidth, f = t % 2 === 0 ? "ft-row-even" : "ft-row-odd", h = this.selectable && this._rowSelection.isSelected(t), g = this._scrollLeft;
1942
- let p = 0;
1943
- const c = this.selectable ? u`
1944
- <div class="ft-checkbox-cell"
1945
- style="position: absolute; top: 0; left: ${g + p}px; width: 36px; height: ${a}px; z-index: 2;">
1946
- <input type="checkbox"
1947
- .checked=${h}
1948
- @change=${(b) => this._onRowCheckboxChange(b, t)}>
1949
- </div>
1950
- ` : "";
1951
- this.selectable && (p += 36);
1952
- const m = this.showRowNumbers ? u`
1953
- <div class="ft-row-num"
1954
- style="position: absolute; top: 0; left: ${g + p}px; width: 48px; height: ${a}px; z-index: 2;"
1955
- @click=${() => this._onRowNumberClick(t)}>${r + 1}</div>
1956
- ` : "", y = [];
1957
- for (const b of s)
1958
- y.push(this._renderCell(n, o[b], t, b));
1959
- for (let b = e; b < i; b++)
1960
- y.push(this._renderCell(n, o[b], t, b));
1961
- return u`
1962
- <div class="ft-row ${f} ${h ? "ft-row-selected" : ""}" role="row"
1963
- style="top: ${l}px; height: ${a}px; width: ${d}px;">
1964
- ${c}${m}
1965
- ${y}
1966
- </div>
1967
- `;
1968
- }
1969
- _renderCell(t, e, i, s) {
1970
- var x, R, D, F;
1971
- const o = ((x = this._activeCell) == null ? void 0 : x.row) === i && ((R = this._activeCell) == null ? void 0 : R.col) === s, r = ((D = this._editingCell) == null ? void 0 : D.row) === i && ((F = this._editingCell) == null ? void 0 : F.col) === s, n = this._selection.isInRange(i, s), l = e.pinned === "left", a = e.pinned === "right", d = l || a, f = this._getColWidth(e), h = this.rowHeight, g = this._colLeftOffsets[s] ?? 0;
1972
- let p;
1973
- l ? p = `position: absolute; top: 0; left: ${this._scrollLeft + this._getPinnedLeft(s)}px; width: ${f}px; height: ${h}px; z-index: 2;` : a ? p = `position: absolute; top: 0; right: ${-this._scrollLeft + this._getPinnedRight(s)}px; width: ${f}px; height: ${h}px; z-index: 2;` : p = `left: ${g}px; width: ${f}px; height: ${h}px;`;
1974
- const c = !this._isCellEditable(e);
1975
- if (r)
1976
- return u`
1977
- <div class="ft-cell ft-editing ft-active ${d ? "ft-pinned" : ""}" role="gridcell"
1978
- data-col-index=${s}
1979
- aria-selected="true"
1980
- aria-readonly=${c ? "true" : k}
1981
- style=${p}>
1982
- ${this._renderEditor(t, e)}
1983
- </div>
1984
- `;
1985
- const m = o || n, y = this._isCellInvalid(i, s), b = [
1986
- "ft-cell",
1987
- `ft-type-${e.type ?? "text"}`,
1988
- o ? "ft-active" : "",
1989
- n ? "ft-selected" : "",
1990
- d ? "ft-pinned" : "",
1991
- y ? "ft-invalid" : ""
1992
- ].filter(Boolean).join(" ");
1993
- return u`
1994
- <div class=${b}
1995
- role="gridcell"
1996
- data-col-index=${s}
1997
- aria-selected=${m ? "true" : "false"}
1998
- aria-readonly=${c ? "true" : k}
1999
- aria-invalid=${y ? "true" : k}
2000
- title=${y ?? k}
2001
- style=${p}
2002
- @click=${(N) => this._onCellClickEvent(N, i, s)}
2003
- @dblclick=${() => this._onCellDblClick(i, s)}>
2004
- ${H(t[e.key], t, e)}
2005
- </div>
2006
- `;
2007
- }
2008
- _renderEditor(t, e) {
2009
- if (e.editor)
2010
- return e.editor(t[e.key], t, e);
2011
- const i = t[e.key], s = i == null ? "" : String(i);
2012
- if (e.type === "number")
2013
- return u`
2014
- <input class="ft-editor ft-editor-number" type="number"
2015
- .value=${s}
2016
- @keydown=${this._onEditorKeyDown}
2017
- @blur=${() => this._commitEdit()}>
2018
- `;
2019
- if (e.type === "date") {
2020
- const o = this._toDateInputValue(i);
2021
- return u`
2022
- <input class="ft-editor" type="date"
2023
- .value=${o}
2024
- @keydown=${this._onEditorKeyDown}
2025
- @blur=${() => this._commitEdit()}>
2026
- `;
2027
- }
2028
- if (e.type === "datetime") {
2029
- const o = this._toDateTimeInputValue(i);
2030
- return u`
2031
- <input class="ft-editor" type="datetime-local"
2032
- .value=${o}
2033
- @keydown=${this._onEditorKeyDown}
2034
- @blur=${() => this._commitEdit()}>
2035
- `;
2036
- }
2037
- return u`
2038
- <input class="ft-editor" type="text"
2039
- .value=${s}
2040
- @keydown=${this._onEditorKeyDown}
2041
- @blur=${() => this._commitEdit()}>
2042
- `;
2043
- }
2044
- /** Convert value to YYYY-MM-DD for date input (local timezone) */
2045
- _toDateInputValue(t) {
2046
- if (!t) return "";
2047
- const e = t instanceof Date ? t : new Date(String(t));
2048
- if (isNaN(e.getTime())) return "";
2049
- const i = e.getFullYear(), s = String(e.getMonth() + 1).padStart(2, "0"), o = String(e.getDate()).padStart(2, "0");
2050
- return `${i}-${s}-${o}`;
2051
- }
2052
- /** Convert value to YYYY-MM-DDTHH:mm for datetime-local input (local timezone) */
2053
- _toDateTimeInputValue(t) {
2054
- if (!t) return "";
2055
- const e = t instanceof Date ? t : new Date(String(t));
2056
- if (isNaN(e.getTime())) return "";
2057
- const i = e.getFullYear(), s = String(e.getMonth() + 1).padStart(2, "0"), o = String(e.getDate()).padStart(2, "0"), r = String(e.getHours()).padStart(2, "0"), n = String(e.getMinutes()).padStart(2, "0");
2058
- return `${i}-${s}-${o}T${r}:${n}`;
2059
- }
2060
- };
2061
- _.styles = K;
2062
- v([
2063
- w({ type: Array })
2064
- ], _.prototype, "columns", 2);
2065
- v([
2066
- w({ type: Array, hasChanged: () => !0 })
2067
- ], _.prototype, "data", 2);
2068
- v([
2069
- w({ type: Number, attribute: "row-height" })
2070
- ], _.prototype, "rowHeight", 2);
2071
- v([
2072
- w({ type: Boolean, attribute: "show-row-numbers" })
2073
- ], _.prototype, "showRowNumbers", 2);
2074
- v([
2075
- w({ type: String, reflect: !0 })
2076
- ], _.prototype, "theme", 2);
2077
- v([
2078
- w({ type: Number, attribute: "max-rows" })
2079
- ], _.prototype, "maxRows", 2);
2080
- v([
2081
- w({ type: Boolean })
2082
- ], _.prototype, "editable", 2);
2083
- v([
2084
- w({ type: Boolean, attribute: "show-filters" })
2085
- ], _.prototype, "showFilters", 2);
2086
- v([
2087
- w({ type: Boolean })
2088
- ], _.prototype, "selectable", 2);
2089
- v([
2090
- w({ type: String, attribute: "selection-mode" })
2091
- ], _.prototype, "selectionMode", 1);
2092
- v([
2093
- w({ type: String, attribute: "data-mode" })
2094
- ], _.prototype, "dataMode", 2);
2095
- v([
2096
- w({ type: Object, attribute: "footer-data" })
2097
- ], _.prototype, "footerData", 2);
2098
- v([
2099
- w({ type: Number, attribute: "max-undo-size" })
2100
- ], _.prototype, "maxUndoSize", 1);
2101
- v([
2102
- C()
2103
- ], _.prototype, "_scrollTop", 2);
2104
- v([
2105
- C()
2106
- ], _.prototype, "_scrollLeft", 2);
2107
- v([
2108
- C()
2109
- ], _.prototype, "_viewportHeight", 2);
2110
- v([
2111
- C()
2112
- ], _.prototype, "_viewportWidth", 2);
2113
- v([
2114
- C()
2115
- ], _.prototype, "_activeCell", 2);
2116
- v([
2117
- C()
2118
- ], _.prototype, "_editingCell", 2);
2119
- v([
2120
- C()
2121
- ], _.prototype, "_sortCriteria", 2);
2122
- v([
2123
- C()
2124
- ], _.prototype, "_openFilterKey", 2);
2125
- v([
2126
- C()
2127
- ], _.prototype, "_rowSelectionVersion", 2);
2128
- _ = v([
2129
- M("flex-table")
2130
- ], _);
2131
- export {
2132
- _ as F,
2133
- X as R,
2134
- tt as U,
2135
- z as e,
2136
- H as r
2137
- };