@rimelight/ui 0.0.50 → 0.0.52
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.
- package/package.json +10 -8
- package/src/components/dropdown-menu/RLADropdownMenu.astro +258 -250
- package/src/components/dropdown-menu/RLSDropdownMenu.tsx +161 -140
- package/src/components/head/RLSHead.tsx +2 -7
- package/src/components/popover/RLAPopover.astro +1 -2
- package/src/components/select/RLASelect.astro +1 -1
- package/src/components/table/RLATable.astro +1692 -1044
- package/src/components/table/RLSTable.tsx +1239 -162
- package/src/components/table/table.theme.ts +20 -14
- package/src/components/table/table.ts +211 -43
- package/LICENSE +0 -21
|
@@ -1,1044 +1,1692 @@
|
|
|
1
|
-
---
|
|
2
|
-
import type {
|
|
3
|
-
TableProps,
|
|
4
|
-
TableColumn,
|
|
5
|
-
TableRow,
|
|
6
|
-
TableGroup,
|
|
7
|
-
SortingState
|
|
8
|
-
} from "./table"
|
|
9
|
-
import { tableTheme } from "./table.theme"
|
|
10
|
-
import RLAIcon from "../icon/RLAIcon.astro"
|
|
11
|
-
import RLAInput from "../input/RLAInput.astro"
|
|
12
|
-
import RLADropdownMenu from "../dropdown-menu/RLADropdownMenu.astro"
|
|
13
|
-
|
|
14
|
-
type AnyData = Record<string, any>
|
|
15
|
-
|
|
16
|
-
const {
|
|
17
|
-
data = [],
|
|
18
|
-
columns = [],
|
|
19
|
-
caption,
|
|
20
|
-
meta,
|
|
21
|
-
sticky = false,
|
|
22
|
-
loading = false,
|
|
23
|
-
loadingColor = "primary",
|
|
24
|
-
loadingAnimation = "carousel",
|
|
25
|
-
empty = "No data available",
|
|
26
|
-
striped = false,
|
|
27
|
-
bordered = false,
|
|
28
|
-
hoverable = false,
|
|
29
|
-
globalFilter = "",
|
|
30
|
-
columnFilters = [],
|
|
31
|
-
columnVisibility = {},
|
|
32
|
-
columnPinning = {},
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
)
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
)
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1
|
+
---
|
|
2
|
+
import type {
|
|
3
|
+
TableProps,
|
|
4
|
+
TableColumn,
|
|
5
|
+
TableRow,
|
|
6
|
+
TableGroup,
|
|
7
|
+
SortingState
|
|
8
|
+
} from "./table"
|
|
9
|
+
import { tableTheme } from "./table.theme"
|
|
10
|
+
import RLAIcon from "../icon/RLAIcon.astro"
|
|
11
|
+
import RLAInput from "../input/RLAInput.astro"
|
|
12
|
+
import RLADropdownMenu from "../dropdown-menu/RLADropdownMenu.astro"
|
|
13
|
+
|
|
14
|
+
type AnyData = Record<string, any>
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
data = [],
|
|
18
|
+
columns = [],
|
|
19
|
+
caption,
|
|
20
|
+
meta,
|
|
21
|
+
sticky = false,
|
|
22
|
+
loading = false,
|
|
23
|
+
loadingColor = "primary",
|
|
24
|
+
loadingAnimation = "carousel",
|
|
25
|
+
empty = "No data available",
|
|
26
|
+
striped = false,
|
|
27
|
+
bordered = false,
|
|
28
|
+
hoverable = false,
|
|
29
|
+
globalFilter = "",
|
|
30
|
+
columnFilters = [],
|
|
31
|
+
columnVisibility = {},
|
|
32
|
+
columnPinning = {},
|
|
33
|
+
columnSizing = {},
|
|
34
|
+
columnOrder = [],
|
|
35
|
+
enableColumnResizing = false,
|
|
36
|
+
enableColumnReordering = false,
|
|
37
|
+
enableRowReordering = false,
|
|
38
|
+
sorting = [],
|
|
39
|
+
grouping = [],
|
|
40
|
+
rowPinning = {},
|
|
41
|
+
rowSelection = {},
|
|
42
|
+
expanded = {},
|
|
43
|
+
pagination,
|
|
44
|
+
getSubRows,
|
|
45
|
+
getRowCanExpand,
|
|
46
|
+
getRowId,
|
|
47
|
+
searchable = false,
|
|
48
|
+
searchPlaceholder = "Filter table...",
|
|
49
|
+
showColumnsToggle = false,
|
|
50
|
+
exportable = false,
|
|
51
|
+
exportFilename = "export",
|
|
52
|
+
actions: tableActions,
|
|
53
|
+
rowActions,
|
|
54
|
+
class: className,
|
|
55
|
+
ui,
|
|
56
|
+
...rest
|
|
57
|
+
} = Astro.props as TableProps
|
|
58
|
+
|
|
59
|
+
const classes = tableTheme({
|
|
60
|
+
sticky,
|
|
61
|
+
loading,
|
|
62
|
+
loadingColor,
|
|
63
|
+
loadingAnimation,
|
|
64
|
+
striped,
|
|
65
|
+
bordered,
|
|
66
|
+
hoverable,
|
|
67
|
+
ui,
|
|
68
|
+
class: className,
|
|
69
|
+
externalScroll: false
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// ─── Column resolution ────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
let rawColumns: TableColumn[] = []
|
|
75
|
+
|
|
76
|
+
if (columns && columns.length > 0) {
|
|
77
|
+
rawColumns = [...columns]
|
|
78
|
+
} else if (data && data.length > 0 && typeof data[0] === "object" && data[0] !== null) {
|
|
79
|
+
const firstRow = data[0] as AnyData
|
|
80
|
+
rawColumns = Object.keys(firstRow).map((key) => ({
|
|
81
|
+
id: key,
|
|
82
|
+
accessorKey: key,
|
|
83
|
+
header: key.charAt(0).toUpperCase() + key.slice(1),
|
|
84
|
+
enableSorting: true
|
|
85
|
+
}))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Auto-inject rowActions / actions column if defined on table props and not in columns
|
|
89
|
+
const tableActionsDef = tableActions || rowActions
|
|
90
|
+
if (tableActionsDef && !rawColumns.some((c) => c.id === "actions")) {
|
|
91
|
+
rawColumns.push({
|
|
92
|
+
id: "actions",
|
|
93
|
+
header: "",
|
|
94
|
+
size: 80,
|
|
95
|
+
minSize: 60,
|
|
96
|
+
enableSorting: false,
|
|
97
|
+
enableHiding: false,
|
|
98
|
+
meta: {
|
|
99
|
+
class: {
|
|
100
|
+
th: "text-right",
|
|
101
|
+
td: "text-right"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
actions: tableActionsDef
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Normalize actions column defaults
|
|
109
|
+
for (const c of rawColumns) {
|
|
110
|
+
if (c.id === "actions" || c.actions || c.items) {
|
|
111
|
+
if (c.enableSorting === undefined) c.enableSorting = false
|
|
112
|
+
if (c.header === undefined && c.id === "actions") c.header = ""
|
|
113
|
+
if (c.size === undefined && c.id === "actions") c.size = 60
|
|
114
|
+
if (c.minSize === undefined && c.id === "actions") c.minSize = 60
|
|
115
|
+
if (!c.meta?.class?.td && c.id === "actions") {
|
|
116
|
+
c.meta = {
|
|
117
|
+
...c.meta,
|
|
118
|
+
class: {
|
|
119
|
+
...c.meta?.class,
|
|
120
|
+
th: c.meta?.class?.th || "text-right",
|
|
121
|
+
td: c.meta?.class?.td || "text-right"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getCellActions(row: TableRow, col: TableColumn) {
|
|
129
|
+
const actionsFn = col.actions ?? col.items ?? (col.id === "actions" ? tableActionsDef : undefined)
|
|
130
|
+
if (actionsFn) {
|
|
131
|
+
return typeof actionsFn === "function" ? actionsFn(row) : actionsFn
|
|
132
|
+
}
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (enableRowReordering && !rawColumns.some((c) => c.id === "reorder" || c.id === "dragHandle")) {
|
|
137
|
+
const reorderCol: TableColumn = {
|
|
138
|
+
id: "reorder",
|
|
139
|
+
size: 36,
|
|
140
|
+
minSize: 36,
|
|
141
|
+
maxSize: 36,
|
|
142
|
+
enableSorting: false,
|
|
143
|
+
enableHiding: false,
|
|
144
|
+
header: "",
|
|
145
|
+
cell: () => `<div class="flex items-center justify-center"><span class="${classes.dragHandle}" data-slot="drag-handle" draggable="true"><span class="i-lucide-grip-vertical size-4"></span></span></div>`
|
|
146
|
+
}
|
|
147
|
+
rawColumns = [reorderCol, ...rawColumns]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let resolvedColumns: TableColumn[] = rawColumns
|
|
151
|
+
if (columnOrder && columnOrder.length > 0) {
|
|
152
|
+
const ordered: TableColumn[] = []
|
|
153
|
+
const map = new Map<string, TableColumn>()
|
|
154
|
+
for (const c of rawColumns) {
|
|
155
|
+
map.set(String(c.id || c.accessorKey || ""), c)
|
|
156
|
+
}
|
|
157
|
+
for (const id of columnOrder) {
|
|
158
|
+
if (map.has(id)) {
|
|
159
|
+
ordered.push(map.get(id)!)
|
|
160
|
+
map.delete(id)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const remaining of map.values()) {
|
|
164
|
+
ordered.push(remaining)
|
|
165
|
+
}
|
|
166
|
+
resolvedColumns = ordered
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Filter by column visibility
|
|
170
|
+
const visibleColumns = resolvedColumns.filter((col) => {
|
|
171
|
+
const id = (col.id || col.accessorKey || "") as string
|
|
172
|
+
if (!id) return true
|
|
173
|
+
if (col.enableHiding === false) return true
|
|
174
|
+
return columnVisibility[id] !== false
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Dropdown items for column visibility toggle
|
|
178
|
+
const columnDropdownItems = resolvedColumns
|
|
179
|
+
.filter((col) => col.enableHiding !== false && col.id !== "reorder")
|
|
180
|
+
.map((col) => {
|
|
181
|
+
const colId = (col.id || col.accessorKey || "") as string
|
|
182
|
+
const rawLabel = typeof col.header === "string" ? col.header : colId
|
|
183
|
+
return {
|
|
184
|
+
id: colId,
|
|
185
|
+
label: rawLabel.charAt(0).toUpperCase() + rawLabel.slice(1),
|
|
186
|
+
type: "checkbox" as const,
|
|
187
|
+
checked: columnVisibility[colId] !== false
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const exportDropdownItems = [
|
|
192
|
+
{
|
|
193
|
+
id: "csv",
|
|
194
|
+
label: "Export as CSV",
|
|
195
|
+
icon: "i-lucide-file-spreadsheet"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: "json",
|
|
199
|
+
label: "Export as JSON",
|
|
200
|
+
icon: "i-lucide-file-json"
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
// ─── Row building ─────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
function getNestedValue(obj: AnyData, key: string): any {
|
|
207
|
+
if (key.includes(".")) {
|
|
208
|
+
return key.split(".").reduce((acc: any, k) => acc?.[k], obj)
|
|
209
|
+
}
|
|
210
|
+
return obj[key]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function buildRowTree(rawItems: any[], parentDepth = 0): TableRow[] {
|
|
214
|
+
return rawItems.map((item, idx) => {
|
|
215
|
+
const rId = getRowId
|
|
216
|
+
? getRowId(item, idx)
|
|
217
|
+
: item?.id !== undefined
|
|
218
|
+
? String(item.id)
|
|
219
|
+
: String(idx)
|
|
220
|
+
|
|
221
|
+
const isExpanded = !!expanded[rId]
|
|
222
|
+
|
|
223
|
+
let subRowsList: TableRow[] | undefined = undefined
|
|
224
|
+
if (getSubRows) {
|
|
225
|
+
const children = getSubRows(item, idx)
|
|
226
|
+
if (children && children.length > 0) {
|
|
227
|
+
subRowsList = buildRowTree(children, parentDepth + 1)
|
|
228
|
+
}
|
|
229
|
+
} else if (Array.isArray(item?.children) && item.children.length > 0) {
|
|
230
|
+
subRowsList = buildRowTree(item.children, parentDepth + 1)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const rowObj: TableRow = {
|
|
234
|
+
id: rId,
|
|
235
|
+
original: item,
|
|
236
|
+
index: idx,
|
|
237
|
+
depth: parentDepth,
|
|
238
|
+
getValue: (key: string) => {
|
|
239
|
+
if (!item || typeof item !== "object") return undefined
|
|
240
|
+
const colDef = resolvedColumns.find((c) => (c.id || c.accessorKey) === key)
|
|
241
|
+
if (colDef && typeof colDef.accessorFn === "function") {
|
|
242
|
+
return colDef.accessorFn(item)
|
|
243
|
+
}
|
|
244
|
+
return getNestedValue(item as AnyData, key)
|
|
245
|
+
},
|
|
246
|
+
getIsSelected: () => {
|
|
247
|
+
if (rowSelection[rId]) return true
|
|
248
|
+
if (subRowsList && subRowsList.length > 0) {
|
|
249
|
+
return subRowsList.every((s) => s.getIsSelected())
|
|
250
|
+
}
|
|
251
|
+
return false
|
|
252
|
+
},
|
|
253
|
+
getIsSomeSelected: () => {
|
|
254
|
+
if (!subRowsList || subRowsList.length === 0) return false
|
|
255
|
+
const count = subRowsList.filter((s) => s.getIsSelected() || s.getIsSomeSelected()).length
|
|
256
|
+
return count > 0 && !subRowsList.every((s) => s.getIsSelected())
|
|
257
|
+
},
|
|
258
|
+
toggleSelected: () => {},
|
|
259
|
+
getToggleSelectedHandler: () => () => {},
|
|
260
|
+
getIsExpanded: () => isExpanded,
|
|
261
|
+
toggleExpanded: () => {},
|
|
262
|
+
getToggleExpandedHandler: () => () => {},
|
|
263
|
+
getIsPinned: () => {
|
|
264
|
+
if (rowPinning.top?.includes(rId)) return "top"
|
|
265
|
+
if (rowPinning.bottom?.includes(rId)) return "bottom"
|
|
266
|
+
return false
|
|
267
|
+
},
|
|
268
|
+
pin: () => {},
|
|
269
|
+
getIsGrouped: () => false,
|
|
270
|
+
getCanExpand: () => {
|
|
271
|
+
if (typeof getRowCanExpand === "function") {
|
|
272
|
+
return getRowCanExpand(rowObj)
|
|
273
|
+
}
|
|
274
|
+
return !!(subRowsList && subRowsList.length > 0)
|
|
275
|
+
},
|
|
276
|
+
...(subRowsList ? { subRows: subRowsList } : {})
|
|
277
|
+
}
|
|
278
|
+
return rowObj
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let processedRows: TableRow[] = buildRowTree(data)
|
|
283
|
+
|
|
284
|
+
// ─── Filtering ────────────────────────────────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
if (globalFilter) {
|
|
287
|
+
const query = globalFilter.toLowerCase()
|
|
288
|
+
processedRows = processedRows.filter((r) => {
|
|
289
|
+
if (typeof r.original !== "object" || !r.original)
|
|
290
|
+
return String(r.original).toLowerCase().includes(query)
|
|
291
|
+
return (
|
|
292
|
+
Object.values(r.original as AnyData).some((val) =>
|
|
293
|
+
String(val ?? "").toLowerCase().includes(query)
|
|
294
|
+
) ||
|
|
295
|
+
resolvedColumns.some((col) => {
|
|
296
|
+
const v = col.accessorFn
|
|
297
|
+
? col.accessorFn(r.original)
|
|
298
|
+
: r.getValue((col.id || col.accessorKey || "") as string)
|
|
299
|
+
return String(v ?? "").toLowerCase().includes(query)
|
|
300
|
+
})
|
|
301
|
+
)
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
for (const filter of columnFilters) {
|
|
306
|
+
if (!filter.id || filter.value === undefined || filter.value === null || filter.value === "")
|
|
307
|
+
continue
|
|
308
|
+
const fVal = String(filter.value).toLowerCase()
|
|
309
|
+
processedRows = processedRows.filter((r) =>
|
|
310
|
+
String(r.getValue(filter.id) ?? "").toLowerCase().includes(fVal)
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ─── Sorting ──────────────────────────────────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
if (sorting && sorting.length > 0) {
|
|
317
|
+
processedRows = [...processedRows].sort((a, b) => {
|
|
318
|
+
for (const { id, desc } of sorting as SortingState) {
|
|
319
|
+
const valA = a.getValue(id)
|
|
320
|
+
const valB = b.getValue(id)
|
|
321
|
+
if (valA === valB) continue
|
|
322
|
+
if (valA === undefined || valA === null) return 1
|
|
323
|
+
if (valB === undefined || valB === null) return -1
|
|
324
|
+
let cmp: number
|
|
325
|
+
if (typeof valA === "number" && typeof valB === "number") {
|
|
326
|
+
cmp = valA - valB
|
|
327
|
+
} else if (valA instanceof Date && valB instanceof Date) {
|
|
328
|
+
cmp = valA.getTime() - valB.getTime()
|
|
329
|
+
} else {
|
|
330
|
+
const numA = Number(valA)
|
|
331
|
+
const numB = Number(valB)
|
|
332
|
+
if (!isNaN(numA) && !isNaN(numB)) {
|
|
333
|
+
cmp = numA - numB
|
|
334
|
+
} else {
|
|
335
|
+
cmp = String(valA).localeCompare(String(valB))
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return desc ? -cmp : cmp
|
|
339
|
+
}
|
|
340
|
+
return 0
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ─── Multi-level Grouping ─────────────────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
type GroupedItem =
|
|
347
|
+
| { type: "group"; group: TableGroup; depth: number }
|
|
348
|
+
| { type: "row"; row: TableRow }
|
|
349
|
+
|
|
350
|
+
function groupRowsRecursively(
|
|
351
|
+
rows: TableRow[],
|
|
352
|
+
groupCols: string[],
|
|
353
|
+
depth = 0,
|
|
354
|
+
parentPath = ""
|
|
355
|
+
): GroupedItem[] {
|
|
356
|
+
if (depth >= groupCols.length || rows.length === 0) {
|
|
357
|
+
return rows.map((r) => ({ type: "row", row: r }))
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const colId = groupCols[depth]!
|
|
361
|
+
const groupMap = new Map<string, TableRow[]>()
|
|
362
|
+
const groupOrder: string[] = []
|
|
363
|
+
|
|
364
|
+
for (const row of rows) {
|
|
365
|
+
const val = String(row.getValue(colId) ?? "")
|
|
366
|
+
if (!groupMap.has(val)) {
|
|
367
|
+
groupMap.set(val, [])
|
|
368
|
+
groupOrder.push(val)
|
|
369
|
+
}
|
|
370
|
+
row.groupingColumnId = colId
|
|
371
|
+
row.groupingValue = val
|
|
372
|
+
groupMap.get(val)!.push(row)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const result: GroupedItem[] = []
|
|
376
|
+
for (const val of groupOrder) {
|
|
377
|
+
const subRows = groupMap.get(val)!
|
|
378
|
+
const groupId = `group-${parentPath}${colId}-${val}`
|
|
379
|
+
const group: TableGroup = {
|
|
380
|
+
id: groupId,
|
|
381
|
+
columnId: colId,
|
|
382
|
+
value: val,
|
|
383
|
+
depth,
|
|
384
|
+
rows: subRows
|
|
385
|
+
}
|
|
386
|
+
result.push({ type: "group", group, depth })
|
|
387
|
+
result.push(...groupRowsRecursively(subRows, groupCols, depth + 1, `${groupId}/`))
|
|
388
|
+
}
|
|
389
|
+
return result
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let groupedItems: GroupedItem[] = []
|
|
393
|
+
if (grouping && grouping.length > 0) {
|
|
394
|
+
groupedItems = groupRowsRecursively(processedRows, grouping, 0, "")
|
|
395
|
+
} else {
|
|
396
|
+
groupedItems = processedRows.map((row) => ({ type: "row", row }))
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ─── Row pinning ──────────────────────────────────────────────────────────────
|
|
400
|
+
|
|
401
|
+
if ((rowPinning.top?.length ?? 0) > 0 || (rowPinning.bottom?.length ?? 0) > 0) {
|
|
402
|
+
const topItems: GroupedItem[] = []
|
|
403
|
+
const midItems: GroupedItem[] = []
|
|
404
|
+
const botItems: GroupedItem[] = []
|
|
405
|
+
|
|
406
|
+
for (const item of groupedItems) {
|
|
407
|
+
if (item.type === "row") {
|
|
408
|
+
if (rowPinning.top?.includes(item.row.id)) topItems.push(item)
|
|
409
|
+
else if (rowPinning.bottom?.includes(item.row.id)) botItems.push(item)
|
|
410
|
+
else midItems.push(item)
|
|
411
|
+
} else {
|
|
412
|
+
midItems.push(item)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
groupedItems = [...topItems, ...midItems, ...botItems]
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ─── Pagination ───────────────────────────────────────────────────────────────
|
|
419
|
+
|
|
420
|
+
let paginatedItems = groupedItems
|
|
421
|
+
if (pagination) {
|
|
422
|
+
const { pageIndex = 0, pageSize = 10 } = pagination
|
|
423
|
+
const rowItems = groupedItems.filter((i) => i.type === "row")
|
|
424
|
+
const pageRows = rowItems.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize)
|
|
425
|
+
const pageRowIds = new Set(pageRows.map((i) => (i as { type: "row"; row: TableRow }).row.id))
|
|
426
|
+
|
|
427
|
+
if (grouping && grouping.length > 0) {
|
|
428
|
+
const result: GroupedItem[] = []
|
|
429
|
+
for (const item of groupedItems) {
|
|
430
|
+
if (item.type === "group") {
|
|
431
|
+
if (item.group.rows.some((r) => pageRowIds.has(r.id))) result.push(item)
|
|
432
|
+
} else if (pageRowIds.has(item.row.id)) {
|
|
433
|
+
result.push(item)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
paginatedItems = result
|
|
437
|
+
} else {
|
|
438
|
+
paginatedItems = pageRows
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ─── Column pinning offsets ───────────────────────────────────────────────────
|
|
443
|
+
|
|
444
|
+
const colOffsetLeft: Record<string, number> = {}
|
|
445
|
+
const colOffsetRight: Record<string, number> = {}
|
|
446
|
+
|
|
447
|
+
if (columnPinning.left && columnPinning.left.length > 0) {
|
|
448
|
+
let offset = 0
|
|
449
|
+
for (const colId of columnPinning.left) {
|
|
450
|
+
colOffsetLeft[colId] = offset
|
|
451
|
+
const col = resolvedColumns.find((c) => (c.id || c.accessorKey) === colId)
|
|
452
|
+
const sized = columnSizing[colId]
|
|
453
|
+
offset += typeof sized === "number" ? sized : typeof col?.size === "number" ? col.size : 150
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (columnPinning.right && columnPinning.right.length > 0) {
|
|
458
|
+
let offset = 0
|
|
459
|
+
for (const colId of [...columnPinning.right].reverse()) {
|
|
460
|
+
colOffsetRight[colId] = offset
|
|
461
|
+
const col = resolvedColumns.find((c) => (c.id || c.accessorKey) === colId)
|
|
462
|
+
const sized = columnSizing[colId]
|
|
463
|
+
offset += typeof sized === "number" ? sized : typeof col?.size === "number" ? col.size : 150
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
468
|
+
|
|
469
|
+
const hasFooters = visibleColumns.some(
|
|
470
|
+
(col) =>
|
|
471
|
+
col.footer ||
|
|
472
|
+
Astro.slots.has(`${String(col.id || col.accessorKey)}-footer`)
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
const showToolbar = searchable || showColumnsToggle || exportable || Astro.slots.has("toolbar")
|
|
476
|
+
|
|
477
|
+
function getPinStyle(colId: string): string {
|
|
478
|
+
if (colOffsetLeft[colId] !== undefined)
|
|
479
|
+
return `left:${colOffsetLeft[colId]}px;`
|
|
480
|
+
if (colOffsetRight[colId] !== undefined)
|
|
481
|
+
return `right:${colOffsetRight[colId]}px;`
|
|
482
|
+
return ""
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function isPinned(colId: string): boolean {
|
|
486
|
+
return colId in colOffsetLeft || colId in colOffsetRight
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function getColumnSizeStyle(col: TableColumn): string {
|
|
490
|
+
const colId = (col.id || col.accessorKey || "") as string
|
|
491
|
+
const sizeVal = columnSizing[colId] ?? col.size
|
|
492
|
+
const styles: string[] = []
|
|
493
|
+
if (sizeVal) {
|
|
494
|
+
styles.push(`width:${typeof sizeVal === "number" ? `${sizeVal}px` : sizeVal};`)
|
|
495
|
+
}
|
|
496
|
+
if (col.minSize) {
|
|
497
|
+
styles.push(`min-width:${typeof col.minSize === "number" ? `${col.minSize}px` : col.minSize};`)
|
|
498
|
+
}
|
|
499
|
+
if (col.maxSize) {
|
|
500
|
+
styles.push(`max-width:${typeof col.maxSize === "number" ? `${col.maxSize}px` : col.maxSize};`)
|
|
501
|
+
}
|
|
502
|
+
return styles.join(" ")
|
|
503
|
+
}
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
<rla-table
|
|
507
|
+
class:list={[classes.root, className]}
|
|
508
|
+
data-slot="root"
|
|
509
|
+
{...rest}
|
|
510
|
+
>
|
|
511
|
+
{showToolbar && (
|
|
512
|
+
<div
|
|
513
|
+
class={classes.toolbar}
|
|
514
|
+
data-slot="toolbar"
|
|
515
|
+
>
|
|
516
|
+
{searchable && (
|
|
517
|
+
<RLAInput
|
|
518
|
+
type="search"
|
|
519
|
+
size="sm"
|
|
520
|
+
placeholder={searchPlaceholder}
|
|
521
|
+
leadingIcon="i-lucide-search"
|
|
522
|
+
class="max-w-xs min-w-[10ch]"
|
|
523
|
+
data-rla-table-search
|
|
524
|
+
/>
|
|
525
|
+
)}
|
|
526
|
+
|
|
527
|
+
<slot name="toolbar" />
|
|
528
|
+
|
|
529
|
+
<div class="ml-auto flex items-center gap-2 shrink-0">
|
|
530
|
+
{exportable && (
|
|
531
|
+
<RLADropdownMenu
|
|
532
|
+
label="Export"
|
|
533
|
+
icon="i-lucide-download"
|
|
534
|
+
items={exportDropdownItems as any}
|
|
535
|
+
content={{ align: "end" }}
|
|
536
|
+
size="sm"
|
|
537
|
+
color="neutral"
|
|
538
|
+
data-rla-export-menu
|
|
539
|
+
/>
|
|
540
|
+
)}
|
|
541
|
+
|
|
542
|
+
{showColumnsToggle && (
|
|
543
|
+
<RLADropdownMenu
|
|
544
|
+
label="Columns"
|
|
545
|
+
icon="i-lucide-columns"
|
|
546
|
+
items={columnDropdownItems as any}
|
|
547
|
+
content={{ align: "end" }}
|
|
548
|
+
size="sm"
|
|
549
|
+
color="neutral"
|
|
550
|
+
/>
|
|
551
|
+
)}
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
)}
|
|
555
|
+
|
|
556
|
+
<div class="overflow-x-auto w-full">
|
|
557
|
+
<table class={classes.base} data-slot="base">
|
|
558
|
+
<colgroup>
|
|
559
|
+
{visibleColumns.map((col) => {
|
|
560
|
+
const colId = (col.id || col.accessorKey || "") as string
|
|
561
|
+
const sizeStyle = getColumnSizeStyle(col)
|
|
562
|
+
return <col data-col-id={colId} style={sizeStyle || undefined} />
|
|
563
|
+
})}
|
|
564
|
+
</colgroup>
|
|
565
|
+
|
|
566
|
+
{(caption || Astro.slots.has("caption")) && (
|
|
567
|
+
<caption class={classes.caption} data-slot="caption">
|
|
568
|
+
<slot name="caption">{caption}</slot>
|
|
569
|
+
</caption>
|
|
570
|
+
)}
|
|
571
|
+
|
|
572
|
+
{/* ── thead ── */}
|
|
573
|
+
{visibleColumns.length > 0 && (
|
|
574
|
+
<thead class={classes.thead} data-slot="thead">
|
|
575
|
+
<tr class={classes.tr} data-slot="tr">
|
|
576
|
+
{visibleColumns.map((col) => {
|
|
577
|
+
const colId = (col.id || col.accessorKey || "") as string
|
|
578
|
+
const pinned = isPinned(colId)
|
|
579
|
+
const metaThClass =
|
|
580
|
+
typeof col.meta?.class?.th === "function"
|
|
581
|
+
? col.meta.class.th(col)
|
|
582
|
+
: col.meta?.class?.th || ""
|
|
583
|
+
const metaThStyle =
|
|
584
|
+
typeof col.meta?.style?.th === "function"
|
|
585
|
+
? col.meta.style.th(col)
|
|
586
|
+
: col.meta?.style?.th || ""
|
|
587
|
+
const canSort =
|
|
588
|
+
col.enableSorting !== false &&
|
|
589
|
+
!!(col.accessorKey || col.id || col.accessorFn) &&
|
|
590
|
+
colId !== "actions" &&
|
|
591
|
+
colId !== "select" &&
|
|
592
|
+
colId !== "reorder" &&
|
|
593
|
+
colId !== "dragHandle"
|
|
594
|
+
const activeSort = (sorting as SortingState).find((s) => s.id === colId)
|
|
595
|
+
const sortDir = activeSort ? (activeSort.desc ? "desc" : "asc") : "none"
|
|
596
|
+
const headerSlotName = `${colId}-header`
|
|
597
|
+
|
|
598
|
+
const sizeStyle = getColumnSizeStyle(col)
|
|
599
|
+
const pinStyle = getPinStyle(colId)
|
|
600
|
+
const combinedStyle = [sizeStyle, pinStyle, metaThStyle]
|
|
601
|
+
.filter(Boolean)
|
|
602
|
+
.join("")
|
|
603
|
+
|
|
604
|
+
const headerLabel =
|
|
605
|
+
typeof col.header === "string"
|
|
606
|
+
? col.header
|
|
607
|
+
: col.header !== undefined
|
|
608
|
+
? ""
|
|
609
|
+
: colId === "reorder" || colId === "select" || colId === "actions" || colId === "dragHandle"
|
|
610
|
+
? ""
|
|
611
|
+
: colId.charAt(0).toUpperCase() + colId.slice(1)
|
|
612
|
+
|
|
613
|
+
return (
|
|
614
|
+
<th
|
|
615
|
+
scope="col"
|
|
616
|
+
class:list={[
|
|
617
|
+
classes.th,
|
|
618
|
+
pinned ? "sticky bg-default z-10" : "",
|
|
619
|
+
metaThClass
|
|
620
|
+
]}
|
|
621
|
+
style={combinedStyle || undefined}
|
|
622
|
+
data-col-id={colId}
|
|
623
|
+
data-slot="th"
|
|
624
|
+
>
|
|
625
|
+
<div class="flex items-center gap-1.5">
|
|
626
|
+
{enableColumnReordering && colId !== "reorder" && colId !== "select" && colId !== "actions" && colId !== "dragHandle" && (
|
|
627
|
+
<span
|
|
628
|
+
class={classes.dragHandle}
|
|
629
|
+
data-slot="drag-handle"
|
|
630
|
+
draggable="true"
|
|
631
|
+
>
|
|
632
|
+
<span class="i-lucide-grip-vertical size-3.5 opacity-40 hover:opacity-100" />
|
|
633
|
+
</span>
|
|
634
|
+
)}
|
|
635
|
+
|
|
636
|
+
<div class="flex-1 min-w-0">
|
|
637
|
+
{Astro.slots.has(headerSlotName) ? (
|
|
638
|
+
<slot name={headerSlotName} {...({ column: col } as any)} />
|
|
639
|
+
) : canSort ? (
|
|
640
|
+
<button
|
|
641
|
+
type="button"
|
|
642
|
+
class="inline-flex items-center gap-1.5 hover:text-highlighted focus:outline-none transition-colors cursor-pointer group -mx-1 px-1 py-0.5 rounded"
|
|
643
|
+
data-rla-sort-btn
|
|
644
|
+
data-rla-col-id={colId}
|
|
645
|
+
data-sort-dir={sortDir}
|
|
646
|
+
aria-label={`Sort by ${colId}`}
|
|
647
|
+
>
|
|
648
|
+
<span>
|
|
649
|
+
{typeof col.header === "function"
|
|
650
|
+
? col.header({ column: col, table: {} })
|
|
651
|
+
: headerLabel}
|
|
652
|
+
</span>
|
|
653
|
+
<span
|
|
654
|
+
data-sort-icon
|
|
655
|
+
class:list={[
|
|
656
|
+
sortDir === "asc"
|
|
657
|
+
? "i-lucide-arrow-up-narrow-wide text-primary"
|
|
658
|
+
: sortDir === "desc"
|
|
659
|
+
? "i-lucide-arrow-down-wide-narrow text-primary"
|
|
660
|
+
: "i-lucide-arrow-up-down opacity-40 group-hover:opacity-100",
|
|
661
|
+
"size-3.5 transition-all shrink-0"
|
|
662
|
+
]}
|
|
663
|
+
/>
|
|
664
|
+
</button>
|
|
665
|
+
) : typeof col.header === "function" ? (
|
|
666
|
+
<Fragment set:html={col.header({ column: col, table: {} })} />
|
|
667
|
+
) : (
|
|
668
|
+
headerLabel
|
|
669
|
+
)}
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
|
|
673
|
+
{enableColumnResizing && (
|
|
674
|
+
<div
|
|
675
|
+
class={classes.resizer}
|
|
676
|
+
data-slot="resizer"
|
|
677
|
+
data-col-id={colId}
|
|
678
|
+
/>
|
|
679
|
+
)}
|
|
680
|
+
</th>
|
|
681
|
+
)
|
|
682
|
+
})}
|
|
683
|
+
</tr>
|
|
684
|
+
</thead>
|
|
685
|
+
)}
|
|
686
|
+
|
|
687
|
+
{/* ── tbody ── */}
|
|
688
|
+
<tbody class={classes.tbody} data-slot="tbody">
|
|
689
|
+
<slot name="body-top" />
|
|
690
|
+
|
|
691
|
+
{loading ? (
|
|
692
|
+
<tr>
|
|
693
|
+
<td
|
|
694
|
+
colspan={Math.max(1, visibleColumns.length)}
|
|
695
|
+
class={classes.loading}
|
|
696
|
+
data-slot="loading"
|
|
697
|
+
>
|
|
698
|
+
{Astro.slots.has("loading") ? (
|
|
699
|
+
<slot name="loading" />
|
|
700
|
+
) : (
|
|
701
|
+
<div class="flex items-center justify-center gap-2 text-muted py-4">
|
|
702
|
+
<RLAIcon name="i-lucide-loader-2" class="size-5 animate-spin text-primary" />
|
|
703
|
+
<span>Loading...</span>
|
|
704
|
+
</div>
|
|
705
|
+
)}
|
|
706
|
+
</td>
|
|
707
|
+
</tr>
|
|
708
|
+
) : paginatedItems.length === 0 ? (
|
|
709
|
+
<tr>
|
|
710
|
+
<td
|
|
711
|
+
colspan={Math.max(1, visibleColumns.length)}
|
|
712
|
+
class={classes.empty}
|
|
713
|
+
data-slot="empty"
|
|
714
|
+
>
|
|
715
|
+
{Astro.slots.has("empty") ? (
|
|
716
|
+
<slot name="empty" />
|
|
717
|
+
) : (
|
|
718
|
+
<div class="flex flex-col items-center justify-center py-6 gap-2 text-muted">
|
|
719
|
+
<RLAIcon name="i-lucide-inbox" class="size-8 opacity-40" />
|
|
720
|
+
<p class="text-sm font-medium">{empty}</p>
|
|
721
|
+
</div>
|
|
722
|
+
)}
|
|
723
|
+
</td>
|
|
724
|
+
</tr>
|
|
725
|
+
) : (
|
|
726
|
+
paginatedItems.map((item) => {
|
|
727
|
+
// ── Group header row ──────────────────────────────────────────
|
|
728
|
+
if (item.type === "group") {
|
|
729
|
+
const { group, depth } = item
|
|
730
|
+
return (
|
|
731
|
+
<tr class={classes.trGroup} data-slot="tr-group" data-group-id={group.id} data-depth={depth}>
|
|
732
|
+
<td
|
|
733
|
+
colspan={Math.max(1, visibleColumns.length)}
|
|
734
|
+
class={classes.tdGroup}
|
|
735
|
+
style={depth ? `padding-left: ${depth * 1.5 + 1}rem;` : undefined}
|
|
736
|
+
data-slot="td-group"
|
|
737
|
+
>
|
|
738
|
+
{Astro.slots.has("group-header") ? (
|
|
739
|
+
<slot
|
|
740
|
+
name="group-header"
|
|
741
|
+
{...({ group, colSpan: visibleColumns.length } as any)}
|
|
742
|
+
/>
|
|
743
|
+
) : (
|
|
744
|
+
<span class="flex items-center gap-2">
|
|
745
|
+
<span class="i-lucide-layers size-3.5 text-primary opacity-70" />
|
|
746
|
+
<span class="text-xs uppercase text-muted font-medium">{group.columnId}:</span>
|
|
747
|
+
<span class="text-highlighted">{String(group.value)}</span>
|
|
748
|
+
<span class="ml-auto text-muted font-normal normal-case">
|
|
749
|
+
{group.rows.length} {group.rows.length === 1 ? "row" : "rows"}
|
|
750
|
+
</span>
|
|
751
|
+
</span>
|
|
752
|
+
)}
|
|
753
|
+
</td>
|
|
754
|
+
</tr>
|
|
755
|
+
)
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// ── Data row ──────────────────────────────────────────────────
|
|
759
|
+
const { row } = item
|
|
760
|
+
const currentGroupId = item.type === "row" && (item as any).groupId ? (item as any).groupId : undefined
|
|
761
|
+
const trMetaClass =
|
|
762
|
+
typeof meta?.class?.tr === "function"
|
|
763
|
+
? meta.class.tr(row)
|
|
764
|
+
: meta?.class?.tr || ""
|
|
765
|
+
const trMetaStyle =
|
|
766
|
+
typeof meta?.style?.tr === "function"
|
|
767
|
+
? meta.style.tr(row)
|
|
768
|
+
: meta?.style?.tr || ""
|
|
769
|
+
const isSelected = row.getIsSelected()
|
|
770
|
+
const isExpanded = row.getIsExpanded()
|
|
771
|
+
const isPinned_ = row.getIsPinned()
|
|
772
|
+
|
|
773
|
+
return (
|
|
774
|
+
<>
|
|
775
|
+
<tr
|
|
776
|
+
class:list={[classes.tr, trMetaClass]}
|
|
777
|
+
style={trMetaStyle || undefined}
|
|
778
|
+
data-slot="tr"
|
|
779
|
+
data-row-id={row.id}
|
|
780
|
+
data-row-index={row.index}
|
|
781
|
+
data-group-id={currentGroupId}
|
|
782
|
+
data-selected={isSelected ? "true" : undefined}
|
|
783
|
+
data-expanded={isExpanded ? "true" : undefined}
|
|
784
|
+
data-pinned={isPinned_ || undefined}
|
|
785
|
+
>
|
|
786
|
+
{visibleColumns.map((col) => {
|
|
787
|
+
const colId = (col.id || col.accessorKey || "") as string
|
|
788
|
+
const cellValue = typeof col.accessorFn === "function"
|
|
789
|
+
? col.accessorFn(row.original)
|
|
790
|
+
: (col.accessorKey || col.id)
|
|
791
|
+
? row.getValue((col.accessorKey || col.id) as string)
|
|
792
|
+
: undefined
|
|
793
|
+
const cellSlotName = `${colId}-cell`
|
|
794
|
+
const pinned = isPinned(colId)
|
|
795
|
+
|
|
796
|
+
const metaTdClass =
|
|
797
|
+
typeof col.meta?.class?.td === "function"
|
|
798
|
+
? col.meta.class.td(row)
|
|
799
|
+
: col.meta?.class?.td || ""
|
|
800
|
+
const metaTdStyle =
|
|
801
|
+
typeof col.meta?.style?.td === "function"
|
|
802
|
+
? col.meta.style.td(row)
|
|
803
|
+
: col.meta?.style?.td || ""
|
|
804
|
+
const colSpanVal =
|
|
805
|
+
typeof col.meta?.colspan?.td === "function"
|
|
806
|
+
? col.meta.colspan.td(row)
|
|
807
|
+
: col.meta?.colspan?.td
|
|
808
|
+
const rowSpanVal =
|
|
809
|
+
typeof col.meta?.rowspan?.td === "function"
|
|
810
|
+
? col.meta.rowspan.td(row)
|
|
811
|
+
: col.meta?.rowspan?.td
|
|
812
|
+
|
|
813
|
+
const pinStyle_ = getPinStyle(colId)
|
|
814
|
+
const combinedTdStyle = [pinStyle_, metaTdStyle].filter(Boolean).join("")
|
|
815
|
+
|
|
816
|
+
return (
|
|
817
|
+
<td
|
|
818
|
+
class:list={[
|
|
819
|
+
classes.td,
|
|
820
|
+
pinned ? "sticky bg-default z-10" : "",
|
|
821
|
+
metaTdClass
|
|
822
|
+
]}
|
|
823
|
+
style={combinedTdStyle || undefined}
|
|
824
|
+
colspan={colSpanVal}
|
|
825
|
+
rowspan={rowSpanVal}
|
|
826
|
+
data-slot="td"
|
|
827
|
+
data-col-id={colId}
|
|
828
|
+
>
|
|
829
|
+
{Astro.slots.has(cellSlotName) ? (
|
|
830
|
+
<slot
|
|
831
|
+
name={cellSlotName}
|
|
832
|
+
{...({
|
|
833
|
+
row,
|
|
834
|
+
column: col,
|
|
835
|
+
cell: { value: cellValue },
|
|
836
|
+
getValue: () => cellValue,
|
|
837
|
+
renderValue: () => cellValue
|
|
838
|
+
} as any)}
|
|
839
|
+
/>
|
|
840
|
+
) : (() => {
|
|
841
|
+
const actionItems = getCellActions(row, col)
|
|
842
|
+
if (Array.isArray(actionItems) && actionItems.length > 0) {
|
|
843
|
+
return (
|
|
844
|
+
<div class="flex items-center justify-end">
|
|
845
|
+
<RLADropdownMenu
|
|
846
|
+
items={actionItems as any}
|
|
847
|
+
content={{ align: "end", side: "bottom" }}
|
|
848
|
+
size="xs"
|
|
849
|
+
color="neutral"
|
|
850
|
+
/>
|
|
851
|
+
</div>
|
|
852
|
+
)
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (typeof col.cell === "function") {
|
|
856
|
+
const rendered = col.cell({
|
|
857
|
+
row,
|
|
858
|
+
getValue: () => cellValue,
|
|
859
|
+
renderValue: () => cellValue,
|
|
860
|
+
column: col,
|
|
861
|
+
table: {}
|
|
862
|
+
})
|
|
863
|
+
if (Array.isArray(rendered) && rendered.length > 0) {
|
|
864
|
+
return (
|
|
865
|
+
<div class="flex items-center justify-end">
|
|
866
|
+
<RLADropdownMenu
|
|
867
|
+
items={rendered as any}
|
|
868
|
+
content={{ align: "end", side: "bottom" }}
|
|
869
|
+
size="xs"
|
|
870
|
+
color="neutral"
|
|
871
|
+
/>
|
|
872
|
+
</div>
|
|
873
|
+
)
|
|
874
|
+
}
|
|
875
|
+
return <Fragment set:html={rendered} />
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return cellValue !== undefined && cellValue !== null ? (
|
|
879
|
+
String(cellValue)
|
|
880
|
+
) : (
|
|
881
|
+
"—"
|
|
882
|
+
)
|
|
883
|
+
})()}
|
|
884
|
+
</td>
|
|
885
|
+
)
|
|
886
|
+
})}
|
|
887
|
+
</tr>
|
|
888
|
+
|
|
889
|
+
{/* Expanded sub-row */}
|
|
890
|
+
{row.getCanExpand() && isExpanded && Astro.slots.has("expanded") && (
|
|
891
|
+
<tr class={classes.trExpanded} data-slot="tr-expanded" data-row-id={row.id}>
|
|
892
|
+
<td
|
|
893
|
+
colspan={Math.max(1, visibleColumns.length)}
|
|
894
|
+
class={classes.tdExpanded}
|
|
895
|
+
data-slot="td-expanded"
|
|
896
|
+
>
|
|
897
|
+
<slot name="expanded" {...({ row } as any)} />
|
|
898
|
+
</td>
|
|
899
|
+
</tr>
|
|
900
|
+
)}
|
|
901
|
+
|
|
902
|
+
{/* Sub-rows (tree data) */}
|
|
903
|
+
{row.subRows && row.subRows.length > 0 && isExpanded &&
|
|
904
|
+
row.subRows.map((subRow) => {
|
|
905
|
+
const subTrMetaClass =
|
|
906
|
+
typeof meta?.class?.tr === "function"
|
|
907
|
+
? meta.class.tr(subRow)
|
|
908
|
+
: meta?.class?.tr || ""
|
|
909
|
+
return (
|
|
910
|
+
<tr
|
|
911
|
+
class:list={[classes.tr, subTrMetaClass]}
|
|
912
|
+
data-slot="tr"
|
|
913
|
+
data-row-id={subRow.id}
|
|
914
|
+
data-row-index={subRow.index}
|
|
915
|
+
data-depth={subRow.depth}
|
|
916
|
+
>
|
|
917
|
+
{visibleColumns.map((col) => {
|
|
918
|
+
const colId = (col.id || col.accessorKey || "") as string
|
|
919
|
+
const cellValue = typeof col.accessorFn === "function"
|
|
920
|
+
? col.accessorFn(subRow.original)
|
|
921
|
+
: (col.accessorKey || col.id)
|
|
922
|
+
? subRow.getValue((col.accessorKey || col.id) as string)
|
|
923
|
+
: undefined
|
|
924
|
+
const cellSlotName = `${colId}-cell`
|
|
925
|
+
const pinned = isPinned(colId)
|
|
926
|
+
const metaTdClass =
|
|
927
|
+
typeof col.meta?.class?.td === "function"
|
|
928
|
+
? col.meta.class.td(subRow)
|
|
929
|
+
: col.meta?.class?.td || ""
|
|
930
|
+
const metaTdStyle =
|
|
931
|
+
typeof col.meta?.style?.td === "function"
|
|
932
|
+
? col.meta.style.td(subRow)
|
|
933
|
+
: col.meta?.style?.td || ""
|
|
934
|
+
const colSpanVal =
|
|
935
|
+
typeof col.meta?.colspan?.td === "function"
|
|
936
|
+
? col.meta.colspan.td(subRow)
|
|
937
|
+
: col.meta?.colspan?.td
|
|
938
|
+
const rowSpanVal =
|
|
939
|
+
typeof col.meta?.rowspan?.td === "function"
|
|
940
|
+
? col.meta.rowspan.td(subRow)
|
|
941
|
+
: col.meta?.rowspan?.td
|
|
942
|
+
|
|
943
|
+
const sizeStyle = getColumnSizeStyle(col)
|
|
944
|
+
const pinStyle_ = getPinStyle(colId)
|
|
945
|
+
const combinedTdStyle = [sizeStyle, pinStyle_, metaTdStyle].filter(Boolean).join("")
|
|
946
|
+
|
|
947
|
+
return (
|
|
948
|
+
<td
|
|
949
|
+
class:list={[
|
|
950
|
+
classes.td,
|
|
951
|
+
pinned ? "sticky bg-default z-10" : "",
|
|
952
|
+
metaTdClass
|
|
953
|
+
]}
|
|
954
|
+
style={combinedTdStyle || undefined}
|
|
955
|
+
colspan={colSpanVal}
|
|
956
|
+
rowspan={rowSpanVal}
|
|
957
|
+
data-slot="td"
|
|
958
|
+
data-col-id={colId}
|
|
959
|
+
>
|
|
960
|
+
{Astro.slots.has(cellSlotName) ? (
|
|
961
|
+
<slot
|
|
962
|
+
name={cellSlotName}
|
|
963
|
+
{...({
|
|
964
|
+
row: subRow,
|
|
965
|
+
column: col,
|
|
966
|
+
cell: { value: cellValue },
|
|
967
|
+
getValue: () => cellValue,
|
|
968
|
+
renderValue: () => cellValue
|
|
969
|
+
} as any)}
|
|
970
|
+
/>
|
|
971
|
+
) : (() => {
|
|
972
|
+
const actionItems = getCellActions(subRow, col)
|
|
973
|
+
if (Array.isArray(actionItems) && actionItems.length > 0) {
|
|
974
|
+
return (
|
|
975
|
+
<div class="flex items-center justify-end">
|
|
976
|
+
<RLADropdownMenu
|
|
977
|
+
items={actionItems as any}
|
|
978
|
+
content={{ align: "end", side: "bottom" }}
|
|
979
|
+
size="xs"
|
|
980
|
+
color="neutral"
|
|
981
|
+
/>
|
|
982
|
+
</div>
|
|
983
|
+
)
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (typeof col.cell === "function") {
|
|
987
|
+
const rendered = col.cell({
|
|
988
|
+
row: subRow,
|
|
989
|
+
getValue: () => cellValue,
|
|
990
|
+
renderValue: () => cellValue,
|
|
991
|
+
column: col,
|
|
992
|
+
table: {}
|
|
993
|
+
})
|
|
994
|
+
if (Array.isArray(rendered) && rendered.length > 0) {
|
|
995
|
+
return (
|
|
996
|
+
<div class="flex items-center justify-end">
|
|
997
|
+
<RLADropdownMenu
|
|
998
|
+
items={rendered as any}
|
|
999
|
+
content={{ align: "end", side: "bottom" }}
|
|
1000
|
+
size="xs"
|
|
1001
|
+
color="neutral"
|
|
1002
|
+
/>
|
|
1003
|
+
</div>
|
|
1004
|
+
)
|
|
1005
|
+
}
|
|
1006
|
+
return <Fragment set:html={rendered} />
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
return cellValue !== undefined && cellValue !== null ? (
|
|
1010
|
+
String(cellValue)
|
|
1011
|
+
) : (
|
|
1012
|
+
"—"
|
|
1013
|
+
)
|
|
1014
|
+
})()}
|
|
1015
|
+
</td>
|
|
1016
|
+
)
|
|
1017
|
+
})}
|
|
1018
|
+
</tr>
|
|
1019
|
+
)
|
|
1020
|
+
})}
|
|
1021
|
+
</>
|
|
1022
|
+
)
|
|
1023
|
+
})
|
|
1024
|
+
)}
|
|
1025
|
+
|
|
1026
|
+
<slot name="body-bottom" />
|
|
1027
|
+
</tbody>
|
|
1028
|
+
|
|
1029
|
+
{/* ── tfoot ── */}
|
|
1030
|
+
{hasFooters && (
|
|
1031
|
+
<tfoot class={classes.tfoot} data-slot="tfoot">
|
|
1032
|
+
<tr class={classes.tr} data-slot="tr">
|
|
1033
|
+
{visibleColumns.map((col) => {
|
|
1034
|
+
const colId = (col.id || col.accessorKey || "") as string
|
|
1035
|
+
const footerSlotName = `${colId}-footer`
|
|
1036
|
+
return (
|
|
1037
|
+
<td class={classes.td} data-slot="td">
|
|
1038
|
+
{Astro.slots.has(footerSlotName) ? (
|
|
1039
|
+
<slot name={footerSlotName} {...({ column: col } as any)} />
|
|
1040
|
+
) : typeof col.footer === "function" ? (
|
|
1041
|
+
<Fragment set:html={col.footer({ column: col, table: {} })} />
|
|
1042
|
+
) : (
|
|
1043
|
+
col.footer || ""
|
|
1044
|
+
)}
|
|
1045
|
+
</td>
|
|
1046
|
+
)
|
|
1047
|
+
})}
|
|
1048
|
+
</tr>
|
|
1049
|
+
</tfoot>
|
|
1050
|
+
)}
|
|
1051
|
+
</table>
|
|
1052
|
+
</div>
|
|
1053
|
+
</rla-table>
|
|
1054
|
+
|
|
1055
|
+
<script>
|
|
1056
|
+
if (typeof window !== "undefined") {
|
|
1057
|
+
class RLATableElement extends HTMLElement {
|
|
1058
|
+
connectedCallback() {
|
|
1059
|
+
this._initSearch()
|
|
1060
|
+
this._initSorting()
|
|
1061
|
+
this._initRowEvents()
|
|
1062
|
+
this._initSelection()
|
|
1063
|
+
this._initExpandToggles()
|
|
1064
|
+
this._initColumnResizing()
|
|
1065
|
+
this._initColumnReordering()
|
|
1066
|
+
this._initRowReordering()
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
private _initColumnResizing() {
|
|
1070
|
+
const resizers = this.querySelectorAll<HTMLElement>("[data-slot=resizer]")
|
|
1071
|
+
resizers.forEach((resizer) => {
|
|
1072
|
+
resizer.addEventListener("pointerdown", (e) => {
|
|
1073
|
+
e.preventDefault()
|
|
1074
|
+
e.stopPropagation()
|
|
1075
|
+
const startX = e.clientX
|
|
1076
|
+
const th = resizer.closest<HTMLTableCellElement>("th")
|
|
1077
|
+
const colId = resizer.getAttribute("data-col-id") || th?.getAttribute("data-col-id")
|
|
1078
|
+
if (!th || !colId) return
|
|
1079
|
+
const initialWidth = th.offsetWidth
|
|
1080
|
+
const colEl = this.querySelector<HTMLTableColElement>(`col[data-col-id="${colId}"]`)
|
|
1081
|
+
|
|
1082
|
+
const onPointerMove = (moveEv: PointerEvent) => {
|
|
1083
|
+
const deltaX = moveEv.clientX - startX
|
|
1084
|
+
const newWidth = Math.max(40, initialWidth + deltaX)
|
|
1085
|
+
th.style.width = `${newWidth}px`
|
|
1086
|
+
if (colEl) colEl.style.width = `${newWidth}px`
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const onPointerUp = () => {
|
|
1090
|
+
window.removeEventListener("pointermove", onPointerMove)
|
|
1091
|
+
window.removeEventListener("pointerup", onPointerUp)
|
|
1092
|
+
this.dispatchEvent(
|
|
1093
|
+
new CustomEvent("rla-column-resize", {
|
|
1094
|
+
detail: { colId, width: th.offsetWidth },
|
|
1095
|
+
bubbles: true
|
|
1096
|
+
})
|
|
1097
|
+
)
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
window.addEventListener("pointermove", onPointerMove)
|
|
1101
|
+
window.addEventListener("pointerup", onPointerUp)
|
|
1102
|
+
})
|
|
1103
|
+
})
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
private _initColumnReordering() {
|
|
1107
|
+
const handles = this.querySelectorAll<HTMLElement>("thead th [data-slot=drag-handle]")
|
|
1108
|
+
let draggedColId: string | null = null
|
|
1109
|
+
|
|
1110
|
+
handles.forEach((handle) => {
|
|
1111
|
+
handle.addEventListener("dragstart", (e) => {
|
|
1112
|
+
const th = handle.closest<HTMLTableCellElement>("th")
|
|
1113
|
+
draggedColId = th?.getAttribute("data-col-id") || null
|
|
1114
|
+
e.dataTransfer?.setData("text/plain", draggedColId || "")
|
|
1115
|
+
if (e.dataTransfer) {
|
|
1116
|
+
e.dataTransfer.effectAllowed = "move"
|
|
1117
|
+
}
|
|
1118
|
+
})
|
|
1119
|
+
handle.addEventListener("dragend", () => {
|
|
1120
|
+
draggedColId = null
|
|
1121
|
+
this.querySelectorAll("thead th").forEach((th) => th.removeAttribute("data-drop-position"))
|
|
1122
|
+
})
|
|
1123
|
+
})
|
|
1124
|
+
|
|
1125
|
+
const ths = this.querySelectorAll<HTMLTableCellElement>("thead th")
|
|
1126
|
+
ths.forEach((th) => {
|
|
1127
|
+
th.addEventListener("dragover", (e) => {
|
|
1128
|
+
if (!draggedColId) return
|
|
1129
|
+
e.preventDefault()
|
|
1130
|
+
const rect = th.getBoundingClientRect()
|
|
1131
|
+
const pos = e.clientX < rect.left + rect.width / 2 ? "left" : "right"
|
|
1132
|
+
ths.forEach((other) => {
|
|
1133
|
+
if (other !== th) other.removeAttribute("data-drop-position")
|
|
1134
|
+
})
|
|
1135
|
+
th.setAttribute("data-drop-position", pos)
|
|
1136
|
+
})
|
|
1137
|
+
th.addEventListener("dragleave", () => {
|
|
1138
|
+
th.removeAttribute("data-drop-position")
|
|
1139
|
+
})
|
|
1140
|
+
th.addEventListener("drop", (e) => {
|
|
1141
|
+
if (!draggedColId) return
|
|
1142
|
+
e.preventDefault()
|
|
1143
|
+
const targetColId = th.getAttribute("data-col-id")
|
|
1144
|
+
const pos = th.getAttribute("data-drop-position") || "left"
|
|
1145
|
+
th.removeAttribute("data-drop-position")
|
|
1146
|
+
if (!targetColId || draggedColId === targetColId) return
|
|
1147
|
+
this.dispatchEvent(
|
|
1148
|
+
new CustomEvent("rla-column-reorder", {
|
|
1149
|
+
detail: { from: draggedColId, to: targetColId, position: pos },
|
|
1150
|
+
bubbles: true
|
|
1151
|
+
})
|
|
1152
|
+
)
|
|
1153
|
+
})
|
|
1154
|
+
})
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
private _initRowReordering() {
|
|
1158
|
+
const handles = this.querySelectorAll<HTMLElement>("tbody tr [data-slot=drag-handle]")
|
|
1159
|
+
let draggedRowId: string | null = null
|
|
1160
|
+
|
|
1161
|
+
handles.forEach((handle) => {
|
|
1162
|
+
handle.addEventListener("dragstart", (e) => {
|
|
1163
|
+
const tr = handle.closest<HTMLTableRowElement>("tr")
|
|
1164
|
+
draggedRowId = tr?.getAttribute("data-row-id") || null
|
|
1165
|
+
e.dataTransfer?.setData("text/plain", draggedRowId || "")
|
|
1166
|
+
if (e.dataTransfer) {
|
|
1167
|
+
e.dataTransfer.effectAllowed = "move"
|
|
1168
|
+
}
|
|
1169
|
+
})
|
|
1170
|
+
handle.addEventListener("dragend", () => {
|
|
1171
|
+
draggedRowId = null
|
|
1172
|
+
this.querySelectorAll("tbody tr").forEach((tr) => tr.removeAttribute("data-drop-position"))
|
|
1173
|
+
})
|
|
1174
|
+
})
|
|
1175
|
+
|
|
1176
|
+
const trs = this.querySelectorAll<HTMLTableRowElement>("tbody tr[data-slot=tr]")
|
|
1177
|
+
trs.forEach((tr) => {
|
|
1178
|
+
tr.addEventListener("dragover", (e) => {
|
|
1179
|
+
if (!draggedRowId) return
|
|
1180
|
+
e.preventDefault()
|
|
1181
|
+
const rect = tr.getBoundingClientRect()
|
|
1182
|
+
const pos = e.clientY < rect.top + rect.height / 2 ? "top" : "bottom"
|
|
1183
|
+
trs.forEach((other) => {
|
|
1184
|
+
if (other !== tr) other.removeAttribute("data-drop-position")
|
|
1185
|
+
})
|
|
1186
|
+
tr.setAttribute("data-drop-position", pos)
|
|
1187
|
+
})
|
|
1188
|
+
tr.addEventListener("dragleave", () => {
|
|
1189
|
+
tr.removeAttribute("data-drop-position")
|
|
1190
|
+
})
|
|
1191
|
+
tr.addEventListener("drop", (e) => {
|
|
1192
|
+
if (!draggedRowId) return
|
|
1193
|
+
e.preventDefault()
|
|
1194
|
+
const targetRowId = tr.getAttribute("data-row-id")
|
|
1195
|
+
const pos = tr.getAttribute("data-drop-position") || "top"
|
|
1196
|
+
tr.removeAttribute("data-drop-position")
|
|
1197
|
+
if (!targetRowId || draggedRowId === targetRowId) return
|
|
1198
|
+
|
|
1199
|
+
// Move in DOM
|
|
1200
|
+
const sourceTr = this.querySelector<HTMLTableRowElement>(`tbody tr[data-row-id="${draggedRowId}"]`)
|
|
1201
|
+
if (sourceTr && tr) {
|
|
1202
|
+
if (pos === "top") {
|
|
1203
|
+
tr.insertAdjacentElement("beforebegin", sourceTr)
|
|
1204
|
+
} else {
|
|
1205
|
+
tr.insertAdjacentElement("afterend", sourceTr)
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
this.dispatchEvent(
|
|
1210
|
+
new CustomEvent("rla-row-reorder", {
|
|
1211
|
+
detail: { from: draggedRowId, to: targetRowId, position: pos },
|
|
1212
|
+
bubbles: true
|
|
1213
|
+
})
|
|
1214
|
+
)
|
|
1215
|
+
})
|
|
1216
|
+
})
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// ── Search / Filtering ────────────────────────────────────────────────
|
|
1220
|
+
private _initSearch() {
|
|
1221
|
+
const searchInput = this.querySelector<HTMLInputElement>("[data-rla-table-search]")
|
|
1222
|
+
if (!searchInput) return
|
|
1223
|
+
|
|
1224
|
+
searchInput.addEventListener("input", () => {
|
|
1225
|
+
const query = searchInput.value.toLowerCase().trim()
|
|
1226
|
+
const tbody = this.querySelector("tbody")
|
|
1227
|
+
if (!tbody) return
|
|
1228
|
+
|
|
1229
|
+
const rows = tbody.querySelectorAll<HTMLTableRowElement>("tr[data-slot=tr]")
|
|
1230
|
+
rows.forEach((row) => {
|
|
1231
|
+
const text = row.textContent?.toLowerCase() ?? ""
|
|
1232
|
+
const match = !query || text.includes(query)
|
|
1233
|
+
row.style.display = match ? "" : "none"
|
|
1234
|
+
const rowId = row.getAttribute("data-row-id")
|
|
1235
|
+
if (rowId) {
|
|
1236
|
+
const expandedRow = tbody.querySelector<HTMLTableRowElement>(
|
|
1237
|
+
`tr[data-slot=tr-expanded][data-row-id="${rowId}"]`
|
|
1238
|
+
)
|
|
1239
|
+
if (expandedRow) {
|
|
1240
|
+
expandedRow.style.display = match && row.getAttribute("data-expanded") === "true" ? "" : "none"
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
})
|
|
1244
|
+
})
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// ── Sorting ───────────────────────────────────────────────────────────
|
|
1248
|
+
private _initSorting() {
|
|
1249
|
+
const sortButtons = this.querySelectorAll<HTMLButtonElement>("[data-rla-sort-btn]")
|
|
1250
|
+
const activeSorts: Array<{ id: string; dir: "asc" | "desc" }> = []
|
|
1251
|
+
|
|
1252
|
+
sortButtons.forEach((btn) => {
|
|
1253
|
+
btn.addEventListener("click", (e) => {
|
|
1254
|
+
const colId = btn.getAttribute("data-rla-col-id")
|
|
1255
|
+
if (!colId) return
|
|
1256
|
+
|
|
1257
|
+
const currentDir = btn.getAttribute("data-sort-dir") ?? "none"
|
|
1258
|
+
const isMulti = (e as MouseEvent).shiftKey
|
|
1259
|
+
|
|
1260
|
+
let nextDir: "asc" | "desc" | "none" = "asc"
|
|
1261
|
+
if (currentDir === "asc") nextDir = "desc"
|
|
1262
|
+
else if (currentDir === "desc") nextDir = "none"
|
|
1263
|
+
|
|
1264
|
+
if (!isMulti) {
|
|
1265
|
+
activeSorts.length = 0
|
|
1266
|
+
sortButtons.forEach((otherBtn) => {
|
|
1267
|
+
if (otherBtn !== btn) {
|
|
1268
|
+
otherBtn.setAttribute("data-sort-dir", "none")
|
|
1269
|
+
const icon = otherBtn.querySelector("[data-sort-icon]")
|
|
1270
|
+
if (icon) {
|
|
1271
|
+
icon.className =
|
|
1272
|
+
"i-lucide-arrow-up-down opacity-40 group-hover:opacity-100 size-3.5 transition-all shrink-0"
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
})
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
btn.setAttribute("data-sort-dir", nextDir)
|
|
1279
|
+
const icon = btn.querySelector("[data-sort-icon]")
|
|
1280
|
+
if (icon) {
|
|
1281
|
+
if (nextDir === "asc") {
|
|
1282
|
+
icon.className =
|
|
1283
|
+
"i-lucide-arrow-up-narrow-wide text-primary size-3.5 transition-all shrink-0"
|
|
1284
|
+
} else if (nextDir === "desc") {
|
|
1285
|
+
icon.className =
|
|
1286
|
+
"i-lucide-arrow-down-wide-narrow text-primary size-3.5 transition-all shrink-0"
|
|
1287
|
+
} else {
|
|
1288
|
+
icon.className =
|
|
1289
|
+
"i-lucide-arrow-up-down opacity-40 group-hover:opacity-100 size-3.5 transition-all shrink-0"
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
const existingIdx = activeSorts.findIndex((s) => s.id === colId)
|
|
1294
|
+
if (nextDir === "none") {
|
|
1295
|
+
if (existingIdx >= 0) activeSorts.splice(existingIdx, 1)
|
|
1296
|
+
} else if (existingIdx >= 0) {
|
|
1297
|
+
activeSorts[existingIdx] = { id: colId, dir: nextDir }
|
|
1298
|
+
} else {
|
|
1299
|
+
activeSorts.push({ id: colId, dir: nextDir })
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
const tbody = this.querySelector("tbody")
|
|
1303
|
+
const table = this.querySelector("table")
|
|
1304
|
+
if (tbody && table) {
|
|
1305
|
+
const groupRows = Array.from(
|
|
1306
|
+
tbody.querySelectorAll<HTMLTableRowElement>("tr[data-slot=tr-group]")
|
|
1307
|
+
)
|
|
1308
|
+
|
|
1309
|
+
if (groupRows.length > 0) {
|
|
1310
|
+
groupRows.forEach((groupHeader) => {
|
|
1311
|
+
const groupId = groupHeader.getAttribute("data-group-id")
|
|
1312
|
+
const rowsInGroup = Array.from(
|
|
1313
|
+
tbody.querySelectorAll<HTMLTableRowElement>(`tr[data-slot=tr][data-group-id="${groupId}"]`)
|
|
1314
|
+
)
|
|
1315
|
+
rowsInGroup.sort((a, b) => {
|
|
1316
|
+
for (const { id, dir } of activeSorts) {
|
|
1317
|
+
const idx = Array.from(table.querySelectorAll("thead th")).findIndex(
|
|
1318
|
+
(th) => (th as HTMLElement).getAttribute("data-col-id") === id
|
|
1319
|
+
)
|
|
1320
|
+
if (idx < 0) continue
|
|
1321
|
+
const cellA = a.children[idx]?.textContent?.trim() ?? ""
|
|
1322
|
+
const cellB = b.children[idx]?.textContent?.trim() ?? ""
|
|
1323
|
+
const cleanA = cellA.replace(/[$€£,\s]/g, "")
|
|
1324
|
+
const cleanB = cellB.replace(/[$€£,\s]/g, "")
|
|
1325
|
+
const isNumA = cleanA !== "" && !isNaN(Number(cleanA))
|
|
1326
|
+
const isNumB = cleanB !== "" && !isNaN(Number(cleanB))
|
|
1327
|
+
|
|
1328
|
+
let cmp: number
|
|
1329
|
+
if (isNumA && isNumB) {
|
|
1330
|
+
cmp = Number(cleanA) - Number(cleanB)
|
|
1331
|
+
} else {
|
|
1332
|
+
cmp = cellA.localeCompare(cellB)
|
|
1333
|
+
}
|
|
1334
|
+
if (dir === "desc") cmp = -cmp
|
|
1335
|
+
if (cmp !== 0) return cmp
|
|
1336
|
+
}
|
|
1337
|
+
return 0
|
|
1338
|
+
})
|
|
1339
|
+
let lastElement: Element = groupHeader
|
|
1340
|
+
rowsInGroup.forEach((r) => {
|
|
1341
|
+
lastElement.insertAdjacentElement("afterend", r)
|
|
1342
|
+
lastElement = r
|
|
1343
|
+
})
|
|
1344
|
+
})
|
|
1345
|
+
} else {
|
|
1346
|
+
const rows = Array.from(
|
|
1347
|
+
tbody.querySelectorAll<HTMLTableRowElement>("tr[data-slot=tr]")
|
|
1348
|
+
)
|
|
1349
|
+
rows.sort((a, b) => {
|
|
1350
|
+
for (const { id, dir } of activeSorts) {
|
|
1351
|
+
const idx = Array.from(table.querySelectorAll("thead th")).findIndex(
|
|
1352
|
+
(th) => (th as HTMLElement).getAttribute("data-col-id") === id
|
|
1353
|
+
)
|
|
1354
|
+
if (idx < 0) continue
|
|
1355
|
+
const cellA = a.children[idx]?.textContent?.trim() ?? ""
|
|
1356
|
+
const cellB = b.children[idx]?.textContent?.trim() ?? ""
|
|
1357
|
+
const cleanA = cellA.replace(/[$€£,\s]/g, "")
|
|
1358
|
+
const cleanB = cellB.replace(/[$€£,\s]/g, "")
|
|
1359
|
+
const isNumA = cleanA !== "" && !isNaN(Number(cleanA))
|
|
1360
|
+
const isNumB = cleanB !== "" && !isNaN(Number(cleanB))
|
|
1361
|
+
|
|
1362
|
+
let cmp: number
|
|
1363
|
+
if (isNumA && isNumB) {
|
|
1364
|
+
cmp = Number(cleanA) - Number(cleanB)
|
|
1365
|
+
} else {
|
|
1366
|
+
cmp = cellA.localeCompare(cellB)
|
|
1367
|
+
}
|
|
1368
|
+
if (dir === "desc") cmp = -cmp
|
|
1369
|
+
if (cmp !== 0) return cmp
|
|
1370
|
+
}
|
|
1371
|
+
return 0
|
|
1372
|
+
})
|
|
1373
|
+
rows.forEach((row) => tbody.appendChild(row))
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
this.dispatchEvent(
|
|
1378
|
+
new CustomEvent("rla-sorting-change", {
|
|
1379
|
+
detail: { id: colId, dir: nextDir },
|
|
1380
|
+
bubbles: true
|
|
1381
|
+
})
|
|
1382
|
+
)
|
|
1383
|
+
})
|
|
1384
|
+
})
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// ── Row events ────────────────────────────────────────────────────────
|
|
1388
|
+
private _initRowEvents() {
|
|
1389
|
+
const tbody = this.querySelector("tbody")
|
|
1390
|
+
if (!tbody) return
|
|
1391
|
+
|
|
1392
|
+
tbody.addEventListener("click", (e) => {
|
|
1393
|
+
const target = e.target as HTMLElement
|
|
1394
|
+
if (target.closest("a, button, input, select, textarea, [role=button]")) return
|
|
1395
|
+
const tr = target.closest<HTMLElement>("tr[data-row-id]")
|
|
1396
|
+
if (!tr) return
|
|
1397
|
+
this.dispatchEvent(
|
|
1398
|
+
new CustomEvent("rla-row-select", {
|
|
1399
|
+
detail: { rowId: tr.getAttribute("data-row-id"), event: e },
|
|
1400
|
+
bubbles: true
|
|
1401
|
+
})
|
|
1402
|
+
)
|
|
1403
|
+
})
|
|
1404
|
+
|
|
1405
|
+
tbody.addEventListener("mouseover", (e) => {
|
|
1406
|
+
const tr = (e.target as HTMLElement).closest<HTMLElement>("tr[data-row-id]")
|
|
1407
|
+
if (!tr) return
|
|
1408
|
+
this.dispatchEvent(
|
|
1409
|
+
new CustomEvent("rla-row-hover", {
|
|
1410
|
+
detail: { rowId: tr.getAttribute("data-row-id"), event: e },
|
|
1411
|
+
bubbles: true
|
|
1412
|
+
})
|
|
1413
|
+
)
|
|
1414
|
+
})
|
|
1415
|
+
|
|
1416
|
+
tbody.addEventListener("mouseleave", (e) => {
|
|
1417
|
+
this.dispatchEvent(
|
|
1418
|
+
new CustomEvent("rla-row-hover", {
|
|
1419
|
+
detail: { rowId: null, event: e },
|
|
1420
|
+
bubbles: true
|
|
1421
|
+
})
|
|
1422
|
+
)
|
|
1423
|
+
})
|
|
1424
|
+
|
|
1425
|
+
tbody.addEventListener("contextmenu", (e) => {
|
|
1426
|
+
const tr = (e.target as HTMLElement).closest<HTMLElement>("tr[data-row-id]")
|
|
1427
|
+
if (!tr) return
|
|
1428
|
+
this.dispatchEvent(
|
|
1429
|
+
new CustomEvent("rla-row-contextmenu", {
|
|
1430
|
+
detail: { rowId: tr.getAttribute("data-row-id"), event: e },
|
|
1431
|
+
bubbles: true
|
|
1432
|
+
})
|
|
1433
|
+
)
|
|
1434
|
+
})
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// ── Selection handling ────────────────────────────────────────────────
|
|
1438
|
+
private _initSelection() {
|
|
1439
|
+
const tbody = this.querySelector("tbody")
|
|
1440
|
+
if (!tbody) return
|
|
1441
|
+
const selectAll = this.querySelector<HTMLInputElement>("[data-rla-select-all]")
|
|
1442
|
+
const rowCheckboxes = tbody.querySelectorAll<HTMLInputElement>("[data-row-checkbox]")
|
|
1443
|
+
|
|
1444
|
+
const updateSelectAllState = () => {
|
|
1445
|
+
if (!selectAll) return
|
|
1446
|
+
const checkedCount = Array.from(rowCheckboxes).filter((cb) => cb.checked).length
|
|
1447
|
+
if (checkedCount === 0) {
|
|
1448
|
+
selectAll.checked = false
|
|
1449
|
+
selectAll.indeterminate = false
|
|
1450
|
+
} else if (checkedCount === rowCheckboxes.length) {
|
|
1451
|
+
selectAll.checked = true
|
|
1452
|
+
selectAll.indeterminate = false
|
|
1453
|
+
} else {
|
|
1454
|
+
selectAll.checked = false
|
|
1455
|
+
selectAll.indeterminate = true
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
if (selectAll) {
|
|
1460
|
+
selectAll.addEventListener("change", () => {
|
|
1461
|
+
const isChecked = selectAll.checked
|
|
1462
|
+
rowCheckboxes.forEach((cb) => {
|
|
1463
|
+
cb.checked = isChecked
|
|
1464
|
+
const tr = cb.closest<HTMLTableRowElement>("tr[data-row-id]")
|
|
1465
|
+
if (tr) {
|
|
1466
|
+
if (isChecked) tr.setAttribute("data-selected", "true")
|
|
1467
|
+
else tr.removeAttribute("data-selected")
|
|
1468
|
+
}
|
|
1469
|
+
})
|
|
1470
|
+
})
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
rowCheckboxes.forEach((cb) => {
|
|
1474
|
+
cb.addEventListener("change", () => {
|
|
1475
|
+
const tr = cb.closest<HTMLTableRowElement>("tr[data-row-id]")
|
|
1476
|
+
if (tr) {
|
|
1477
|
+
if (cb.checked) tr.setAttribute("data-selected", "true")
|
|
1478
|
+
else tr.removeAttribute("data-selected")
|
|
1479
|
+
}
|
|
1480
|
+
updateSelectAllState()
|
|
1481
|
+
})
|
|
1482
|
+
})
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// ── Row Expand Toggles ────────────────────────────────────────────────
|
|
1486
|
+
private _initExpandToggles() {
|
|
1487
|
+
const tbody = this.querySelector("tbody")
|
|
1488
|
+
if (!tbody) return
|
|
1489
|
+
|
|
1490
|
+
tbody.addEventListener("click", (e) => {
|
|
1491
|
+
const target = e.target as HTMLElement
|
|
1492
|
+
const btn = target.closest<HTMLButtonElement>("[data-row-expand]")
|
|
1493
|
+
if (!btn) return
|
|
1494
|
+
e.stopPropagation()
|
|
1495
|
+
|
|
1496
|
+
const rowId = btn.getAttribute("data-row-expand")
|
|
1497
|
+
if (!rowId) return
|
|
1498
|
+
|
|
1499
|
+
const tr = tbody.querySelector<HTMLTableRowElement>(`tr[data-slot=tr][data-row-id="${rowId}"]`)
|
|
1500
|
+
const expandedRow = tbody.querySelector<HTMLTableRowElement>(`tr[data-slot=tr-expanded][data-row-id="${rowId}"]`)
|
|
1501
|
+
|
|
1502
|
+
const isExpanded = tr?.getAttribute("data-expanded") === "true"
|
|
1503
|
+
const next = !isExpanded
|
|
1504
|
+
|
|
1505
|
+
if (tr) {
|
|
1506
|
+
if (next) tr.setAttribute("data-expanded", "true")
|
|
1507
|
+
else tr.removeAttribute("data-expanded")
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
if (expandedRow) {
|
|
1511
|
+
expandedRow.style.display = next ? "" : "none"
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
const chevron = btn.querySelector(".i-lucide-chevron-right, .i-lucide-chevron-down")
|
|
1515
|
+
if (chevron) {
|
|
1516
|
+
if (next) {
|
|
1517
|
+
chevron.classList.remove("i-lucide-chevron-right")
|
|
1518
|
+
chevron.classList.add("i-lucide-chevron-down")
|
|
1519
|
+
} else {
|
|
1520
|
+
chevron.classList.remove("i-lucide-chevron-down")
|
|
1521
|
+
chevron.classList.add("i-lucide-chevron-right")
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
this.dispatchEvent(
|
|
1526
|
+
new CustomEvent("rla-row-expand-change", {
|
|
1527
|
+
detail: { rowId, expanded: next },
|
|
1528
|
+
bubbles: true
|
|
1529
|
+
})
|
|
1530
|
+
)
|
|
1531
|
+
})
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// ── Public API (imperative) ───────────────────────────────────────────
|
|
1535
|
+
|
|
1536
|
+
/** Filter visible rows by a text query (case-insensitive). */
|
|
1537
|
+
setGlobalFilter(query: string) {
|
|
1538
|
+
const searchInput = this.querySelector<HTMLInputElement>("[data-rla-table-search]")
|
|
1539
|
+
if (searchInput) {
|
|
1540
|
+
searchInput.value = query
|
|
1541
|
+
searchInput.dispatchEvent(new Event("input"))
|
|
1542
|
+
} else {
|
|
1543
|
+
const tbody = this.querySelector("tbody")
|
|
1544
|
+
if (!tbody) return
|
|
1545
|
+
const q = query.toLowerCase().trim()
|
|
1546
|
+
tbody
|
|
1547
|
+
.querySelectorAll<HTMLTableRowElement>("tr[data-slot=tr]")
|
|
1548
|
+
.forEach((row) => {
|
|
1549
|
+
row.style.display =
|
|
1550
|
+
!q || (row.textContent?.toLowerCase() ?? "").includes(q) ? "" : "none"
|
|
1551
|
+
})
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
/** Show or hide a column by its column ID. */
|
|
1556
|
+
setColumnVisibility(colId: string, visible: boolean) {
|
|
1557
|
+
this.dispatchEvent(
|
|
1558
|
+
new CustomEvent("rla-dropdown-checkbox-change", {
|
|
1559
|
+
detail: { id: colId, checked: visible },
|
|
1560
|
+
bubbles: false
|
|
1561
|
+
})
|
|
1562
|
+
)
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
/** Return all currently-visible data rows. */
|
|
1566
|
+
getVisibleRows(): HTMLTableRowElement[] {
|
|
1567
|
+
const tbody = this.querySelector("tbody")
|
|
1568
|
+
if (!tbody) return []
|
|
1569
|
+
return Array.from(tbody.querySelectorAll<HTMLTableRowElement>("tr[data-slot=tr]")).filter(
|
|
1570
|
+
(r) => r.style.display !== "none"
|
|
1571
|
+
)
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/** Return all selected rows (those with data-selected=true). */
|
|
1575
|
+
getSelectedRows(): HTMLTableRowElement[] {
|
|
1576
|
+
const tbody = this.querySelector("tbody")
|
|
1577
|
+
if (!tbody) return []
|
|
1578
|
+
return Array.from(
|
|
1579
|
+
tbody.querySelectorAll<HTMLTableRowElement>("tr[data-selected=true]")
|
|
1580
|
+
)
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/** Export table data to CSV. */
|
|
1584
|
+
exportToCsv(options?: { filename?: string; selectedOnly?: boolean; columns?: string[] }): string {
|
|
1585
|
+
const table = this.querySelector("table")
|
|
1586
|
+
if (!table) return ""
|
|
1587
|
+
const ths = Array.from(table.querySelectorAll("thead th"))
|
|
1588
|
+
const colIds = ths
|
|
1589
|
+
.map((th) => th.getAttribute("data-col-id") || "")
|
|
1590
|
+
.filter((id) => id && id !== "select" && id !== "actions" && id !== "reorder" && id !== "dragHandle")
|
|
1591
|
+
const targetCols = options?.columns && options.columns.length > 0 ? options.columns : colIds
|
|
1592
|
+
|
|
1593
|
+
const escapeCsv = (val: string) => {
|
|
1594
|
+
if (val.includes(",") || val.includes('"') || val.includes("\n")) {
|
|
1595
|
+
return `"${val.replace(/"/g, '""')}"`
|
|
1596
|
+
}
|
|
1597
|
+
return val
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
const headers = targetCols.map((id) => escapeCsv(id.charAt(0).toUpperCase() + id.slice(1)))
|
|
1601
|
+
const rows = options?.selectedOnly ? this.getSelectedRows() : this.getVisibleRows()
|
|
1602
|
+
|
|
1603
|
+
const lines: string[] = [headers.join(",")]
|
|
1604
|
+
rows.forEach((tr) => {
|
|
1605
|
+
const cells = targetCols.map((colId) => {
|
|
1606
|
+
const td = tr.querySelector(`td[data-col-id="${colId}"]`)
|
|
1607
|
+
return escapeCsv(td?.textContent?.trim() ?? "")
|
|
1608
|
+
})
|
|
1609
|
+
lines.push(cells.join(","))
|
|
1610
|
+
})
|
|
1611
|
+
|
|
1612
|
+
const csvContent = lines.join("\r\n")
|
|
1613
|
+
if (options?.filename) {
|
|
1614
|
+
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" })
|
|
1615
|
+
const url = URL.createObjectURL(blob)
|
|
1616
|
+
const link = document.createElement("a")
|
|
1617
|
+
link.href = url
|
|
1618
|
+
link.download = options.filename.endsWith(".csv") ? options.filename : `${options.filename}.csv`
|
|
1619
|
+
link.click()
|
|
1620
|
+
URL.revokeObjectURL(url)
|
|
1621
|
+
}
|
|
1622
|
+
return csvContent
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
/** Export table data to JSON. */
|
|
1626
|
+
exportToJson(options?: { filename?: string; selectedOnly?: boolean; columns?: string[] }): string {
|
|
1627
|
+
const table = this.querySelector("table")
|
|
1628
|
+
if (!table) return "[]"
|
|
1629
|
+
const ths = Array.from(table.querySelectorAll("thead th"))
|
|
1630
|
+
const colIds = ths
|
|
1631
|
+
.map((th) => th.getAttribute("data-col-id") || "")
|
|
1632
|
+
.filter((id) => id && id !== "select" && id !== "actions" && id !== "reorder" && id !== "dragHandle")
|
|
1633
|
+
const targetCols = options?.columns && options.columns.length > 0 ? options.columns : colIds
|
|
1634
|
+
const rows = options?.selectedOnly ? this.getSelectedRows() : this.getVisibleRows()
|
|
1635
|
+
|
|
1636
|
+
const dataArr = rows.map((tr) => {
|
|
1637
|
+
const rowObj: Record<string, string> = {}
|
|
1638
|
+
targetCols.forEach((colId) => {
|
|
1639
|
+
const td = tr.querySelector(`td[data-col-id="${colId}"]`)
|
|
1640
|
+
rowObj[colId] = td?.textContent?.trim() ?? ""
|
|
1641
|
+
})
|
|
1642
|
+
return rowObj
|
|
1643
|
+
})
|
|
1644
|
+
|
|
1645
|
+
const jsonContent = JSON.stringify(dataArr, null, 2)
|
|
1646
|
+
if (options?.filename) {
|
|
1647
|
+
const blob = new Blob([jsonContent], { type: "application/json;charset=utf-8;" })
|
|
1648
|
+
const url = URL.createObjectURL(blob)
|
|
1649
|
+
const link = document.createElement("a")
|
|
1650
|
+
link.href = url
|
|
1651
|
+
link.download = options.filename.endsWith(".json") ? options.filename : `${options.filename}.json`
|
|
1652
|
+
link.click()
|
|
1653
|
+
URL.revokeObjectURL(url)
|
|
1654
|
+
}
|
|
1655
|
+
return jsonContent
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
/** Return map of unique values and counts for a given column. */
|
|
1659
|
+
getFacetedUniqueValues(columnId: string): Map<string, number> {
|
|
1660
|
+
const map = new Map<string, number>()
|
|
1661
|
+
const rows = this.getVisibleRows()
|
|
1662
|
+
rows.forEach((tr) => {
|
|
1663
|
+
const td = tr.querySelector(`td[data-col-id="${columnId}"]`)
|
|
1664
|
+
const val = td?.textContent?.trim() ?? ""
|
|
1665
|
+
map.set(val, (map.get(val) || 0) + 1)
|
|
1666
|
+
})
|
|
1667
|
+
return map
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
/** Return min/max numeric range for a given column. */
|
|
1671
|
+
getFacetedMinMaxValues(columnId: string): [number, number] | undefined {
|
|
1672
|
+
const rows = this.getVisibleRows()
|
|
1673
|
+
let min: number | undefined
|
|
1674
|
+
let max: number | undefined
|
|
1675
|
+
rows.forEach((tr) => {
|
|
1676
|
+
const td = tr.querySelector(`td[data-col-id="${columnId}"]`)
|
|
1677
|
+
const text = (td?.textContent?.trim() ?? "").replace(/[$€£,\s]/g, "")
|
|
1678
|
+
const val = Number(text)
|
|
1679
|
+
if (!isNaN(val)) {
|
|
1680
|
+
if (min === undefined || val < min) min = val
|
|
1681
|
+
if (max === undefined || val > max) max = val
|
|
1682
|
+
}
|
|
1683
|
+
})
|
|
1684
|
+
return min !== undefined && max !== undefined ? [min, max] : undefined
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
if (!customElements.get("rla-table")) {
|
|
1689
|
+
customElements.define("rla-table", RLATableElement)
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
</script>
|