@svgrid/grid 1.1.1 → 1.1.2
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/dist/FlexRender.svelte +96 -96
- package/dist/SvGrid.controller.svelte.js +17 -6
- package/dist/SvGrid.css +2012 -2012
- package/dist/SvGrid.svelte +2569 -2346
- package/dist/build-api.js +2 -2
- package/dist/cell-values.d.ts +1 -1
- package/dist/cell-values.js +7 -7
- package/dist/column-types.js +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +2 -2
- package/dist/summaries.js +4 -4
- package/package.json +1 -1
- package/src/FlexRender.svelte +96 -96
- package/src/SvGrid.controller.svelte.ts +2363 -2352
- package/src/SvGrid.css +2012 -2012
- package/src/SvGrid.svelte +2569 -2346
- package/src/SvGrid.types.ts +537 -537
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +59 -59
- package/src/build-api.ts +683 -683
- package/src/cell-formatting.ts +169 -169
- package/src/cell-values.ts +4 -4
- package/src/column-types.ts +1 -1
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1077 -1077
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/editing.ts +669 -669
- package/src/flex-render.ts +3 -3
- package/src/index.ts +208 -208
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/merge-objects.ts +48 -48
- package/src/render-component.ts +28 -28
- package/src/spreadsheet.test.ts +489 -489
- package/src/spreadsheet.ts +304 -304
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/summaries.ts +4 -4
- package/src/svgrid-wrapper.types.ts +412 -412
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.wrapper.test.ts +40 -40
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
package/src/SvGrid.svelte
CHANGED
|
@@ -1,2346 +1,2569 @@
|
|
|
1
|
-
<script
|
|
2
|
-
lang="ts"
|
|
3
|
-
generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData"
|
|
4
|
-
>
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import "./sv-grid-scrollbar";
|
|
19
|
-
import "./SvGrid.css";
|
|
20
|
-
import type { Snippet } from "svelte";
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
ctrl.
|
|
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
|
-
const
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
const
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
const
|
|
274
|
-
const
|
|
275
|
-
const
|
|
276
|
-
const
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
const
|
|
281
|
-
const
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
const
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
const
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
<path d="
|
|
333
|
-
|
|
334
|
-
<path d="
|
|
335
|
-
{:else if name === "
|
|
336
|
-
<path d="
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
<
|
|
342
|
-
<path d="
|
|
343
|
-
{:else if name === "
|
|
344
|
-
<path d="
|
|
345
|
-
<path d="
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
<path d="
|
|
349
|
-
<path d="
|
|
350
|
-
{:else if name === "
|
|
351
|
-
<path d="
|
|
352
|
-
{:else if name === "op-
|
|
353
|
-
<
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
<path d="
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
<path d="
|
|
360
|
-
<path d="
|
|
361
|
-
<path d="
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
<
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
<path d="
|
|
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
|
-
{:else if ctrl.editingCell?.editorType === "
|
|
621
|
-
{@const opts = getColumnEditorOptions(column, row)}
|
|
622
|
-
{@const multi = column.columnDef.editorMultiple === true}
|
|
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
|
-
class="sv-grid-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
{
|
|
1093
|
-
{
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
>
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
{
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
{/if}
|
|
1236
|
-
{#
|
|
1237
|
-
<th
|
|
1238
|
-
class="sv-grid-column sv-grid-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
{
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
1
|
+
<script
|
|
2
|
+
lang="ts"
|
|
3
|
+
generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData"
|
|
4
|
+
>
|
|
5
|
+
import {
|
|
6
|
+
getGridCellA11yProps,
|
|
7
|
+
getGridCellDomId,
|
|
8
|
+
getGridHeaderA11yProps,
|
|
9
|
+
getGridRootA11yProps,
|
|
10
|
+
getGridRowA11yProps,
|
|
11
|
+
type EditorContext,
|
|
12
|
+
type CellEditorOption,
|
|
13
|
+
type Column,
|
|
14
|
+
type Row,
|
|
15
|
+
type RowData,
|
|
16
|
+
type TableFeatures,
|
|
17
|
+
} from "./index";
|
|
18
|
+
import "./sv-grid-scrollbar";
|
|
19
|
+
import "./SvGrid.css";
|
|
20
|
+
import type { Snippet } from "svelte";
|
|
21
|
+
import {
|
|
22
|
+
RenderSnippetConfig,
|
|
23
|
+
RenderComponentConfig,
|
|
24
|
+
} from "./render-component";
|
|
25
|
+
import { buildSparkline, toSparklineValues } from "./sparkline";
|
|
26
|
+
import SvGridDropdown from "./SvGridDropdown.svelte";
|
|
27
|
+
import type {
|
|
28
|
+
Props,
|
|
29
|
+
SelectionPoint,
|
|
30
|
+
SelectionRange,
|
|
31
|
+
CellEditState,
|
|
32
|
+
FilterOperator,
|
|
33
|
+
FilterOption,
|
|
34
|
+
MenuPosition,
|
|
35
|
+
} from "./SvGrid.types";
|
|
36
|
+
import {
|
|
37
|
+
cfTextStyle,
|
|
38
|
+
fmtStat,
|
|
39
|
+
getEditableInputValue,
|
|
40
|
+
getEditorInputType,
|
|
41
|
+
toValueArray,
|
|
42
|
+
getOptionLabel,
|
|
43
|
+
getOptionColor,
|
|
44
|
+
colorfulChipStyle,
|
|
45
|
+
getEditorClass,
|
|
46
|
+
} from "./SvGrid.helpers";
|
|
47
|
+
import { createSvGridController } from "./SvGrid.controller.svelte";
|
|
48
|
+
import GridMenus from "./GridMenus.svelte";
|
|
49
|
+
import GridFooter from "./GridFooter.svelte";
|
|
50
|
+
let props: Props<TFeatures, TData> = $props();
|
|
51
|
+
const ctrl = createSvGridController(props);
|
|
52
|
+
|
|
53
|
+
// ---- View facade: re-bind the controller's reactive members as locals so the
|
|
54
|
+
// markup stays identical. Assignable state + bindings + DOM refs use c.* directly.
|
|
55
|
+
const paginationEnabled = $derived(ctrl.paginationEnabled);
|
|
56
|
+
const filterRowValues = $derived(ctrl.filterRowValues);
|
|
57
|
+
const filterMenuValues = $derived(ctrl.filterMenuValues);
|
|
58
|
+
const tooltip = $derived(ctrl.tooltip);
|
|
59
|
+
const showTooltipFor = $derived(ctrl.showTooltipFor);
|
|
60
|
+
const hideTooltip = $derived(ctrl.hideTooltip);
|
|
61
|
+
const findHits = $derived(ctrl.findHits);
|
|
62
|
+
const headerHeight = $derived(ctrl.headerHeight);
|
|
63
|
+
const resizingColumnId = $derived(ctrl.resizingColumnId);
|
|
64
|
+
const selectionColumnWidth = $derived(ctrl.selectionColumnWidth);
|
|
65
|
+
const rowNumberColumnWidth = $derived(ctrl.rowNumberColumnWidth);
|
|
66
|
+
const showRowNumbersEffective = $derived(ctrl.showRowNumbersEffective);
|
|
67
|
+
const columnMenuFor = $derived(ctrl.columnMenuFor);
|
|
68
|
+
const filterMenuFor = $derived(ctrl.filterMenuFor);
|
|
69
|
+
const operatorMenuFor = $derived(ctrl.operatorMenuFor);
|
|
70
|
+
const viewportWidth = $derived(ctrl.viewportWidth);
|
|
71
|
+
const viewportHeight = $derived(ctrl.viewportHeight);
|
|
72
|
+
const scrollMetrics = $derived(ctrl.scrollMetrics);
|
|
73
|
+
const hasVerticalOverflow = $derived(ctrl.hasVerticalOverflow);
|
|
74
|
+
const showGlobalFilterEffective = $derived(ctrl.showGlobalFilterEffective);
|
|
75
|
+
const showFilterRowEffective = $derived(ctrl.showFilterRowEffective);
|
|
76
|
+
const showInlineColumnFilterEffective = $derived(
|
|
77
|
+
ctrl.showInlineColumnFilterEffective,
|
|
78
|
+
);
|
|
79
|
+
const showRowSelectionEffective = $derived(ctrl.showRowSelectionEffective);
|
|
80
|
+
const grid = $derived(ctrl.grid);
|
|
81
|
+
const allColumns = $derived(ctrl.allColumns);
|
|
82
|
+
const headerGroups = $derived(ctrl.headerGroups);
|
|
83
|
+
const groupHeaderRows = $derived(ctrl.groupHeaderRows);
|
|
84
|
+
const cellPinStyle = $derived(ctrl.cellPinStyle);
|
|
85
|
+
const isColumnPinned = $derived(ctrl.isColumnPinned);
|
|
86
|
+
const colDragId = $derived(ctrl.colDragId);
|
|
87
|
+
const colDropOnId = $derived(ctrl.colDropOnId);
|
|
88
|
+
const colDropSide = $derived(ctrl.colDropSide);
|
|
89
|
+
const onColumnHeaderDragStart = $derived(ctrl.onColumnHeaderDragStart);
|
|
90
|
+
const onColumnHeaderDragOver = $derived(ctrl.onColumnHeaderDragOver);
|
|
91
|
+
const onColumnHeaderDragLeave = $derived(ctrl.onColumnHeaderDragLeave);
|
|
92
|
+
const onColumnHeaderDrop = $derived(ctrl.onColumnHeaderDrop);
|
|
93
|
+
const onColumnHeaderDragEnd = $derived(ctrl.onColumnHeaderDragEnd);
|
|
94
|
+
// Managed row dragging
|
|
95
|
+
const rowDragManagedEffective = $derived(props.rowDragManaged === true);
|
|
96
|
+
const rowDropIndex = $derived(ctrl.rowDropIndex);
|
|
97
|
+
const rowDropSide = $derived(ctrl.rowDropSide);
|
|
98
|
+
const onRowDragStart = $derived(ctrl.onRowDragStart);
|
|
99
|
+
const onRowDragOver = $derived(ctrl.onRowDragOver);
|
|
100
|
+
const onRowDragLeave = $derived(ctrl.onRowDragLeave);
|
|
101
|
+
const onRowDrop = $derived(ctrl.onRowDrop);
|
|
102
|
+
const onRowsContainerDragOver = $derived(ctrl.onRowsContainerDragOver);
|
|
103
|
+
const onRowsContainerDrop = $derived(ctrl.onRowsContainerDrop);
|
|
104
|
+
const onRowDragEndHandler = $derived(ctrl.onRowDragEnd);
|
|
105
|
+
// Drag attributes spread onto each body <tr>; empty object when disabled so
|
|
106
|
+
// rows stay non-draggable and no handlers fire.
|
|
107
|
+
function rowDragAttrs(rowIndex: number) {
|
|
108
|
+
if (!rowDragManagedEffective) return {};
|
|
109
|
+
return {
|
|
110
|
+
draggable: true,
|
|
111
|
+
ondragstart: (e: DragEvent) => onRowDragStart(e, rowIndex),
|
|
112
|
+
ondragover: (e: DragEvent) => onRowDragOver(e, rowIndex),
|
|
113
|
+
ondragleave: () => onRowDragLeave(rowIndex),
|
|
114
|
+
ondrop: (e: DragEvent) => onRowDrop(e, rowIndex),
|
|
115
|
+
ondragend: () => onRowDragEndHandler(),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function rowDropClass(rowIndex: number) {
|
|
119
|
+
if (!rowDragManagedEffective || rowDropIndex !== rowIndex) return "";
|
|
120
|
+
return rowDropSide === "after"
|
|
121
|
+
? "sv-grid-row-drop-after"
|
|
122
|
+
: "sv-grid-row-drop-before";
|
|
123
|
+
}
|
|
124
|
+
const getColumnBaseValue = $derived(ctrl.getColumnBaseValue);
|
|
125
|
+
const hasConditionalFormats = $derived(ctrl.hasConditionalFormats);
|
|
126
|
+
const cellConditionalFormat = $derived(ctrl.cellConditionalFormat);
|
|
127
|
+
const isGroupRow = $derived(ctrl.isGroupRow);
|
|
128
|
+
const sortDirectionByColumn = $derived(ctrl.sortDirectionByColumn);
|
|
129
|
+
const groupingColumns = $derived(ctrl.groupingColumns);
|
|
130
|
+
const paginationState = $derived(ctrl.paginationState);
|
|
131
|
+
const allRowsBeforePagination = $derived(ctrl.allRowsBeforePagination);
|
|
132
|
+
const allRows = $derived(ctrl.allRows);
|
|
133
|
+
const statusBarEnabled = $derived(ctrl.statusBarEnabled);
|
|
134
|
+
const statusBarAggregates = $derived(ctrl.statusBarAggregates);
|
|
135
|
+
const statusBarStats = $derived(ctrl.statusBarStats);
|
|
136
|
+
const toolPanelEnabled = $derived(ctrl.toolPanelEnabled);
|
|
137
|
+
const toolPanelColumns = $derived(ctrl.toolPanelColumns);
|
|
138
|
+
const toolPanelHeaderLabel = $derived(ctrl.toolPanelHeaderLabel);
|
|
139
|
+
const toggleColumnVisibleInPanel = $derived(ctrl.toggleColumnVisibleInPanel);
|
|
140
|
+
const moveColumnInPanel = $derived(ctrl.moveColumnInPanel);
|
|
141
|
+
const toggleGroupInPanel = $derived(ctrl.toggleGroupInPanel);
|
|
142
|
+
const virtualizer = $derived(ctrl.virtualizer);
|
|
143
|
+
const rowVirtualizationEnabled = $derived(ctrl.rowVirtualizationEnabled);
|
|
144
|
+
const columnVirtualizationEnabled = $derived(
|
|
145
|
+
ctrl.columnVirtualizationEnabled,
|
|
146
|
+
);
|
|
147
|
+
const virtualRows = $derived(ctrl.virtualRows);
|
|
148
|
+
// DOM-space spacer heights + capped total: identical to the logical
|
|
149
|
+
// virtualizer values for normal grids, scaled down past
|
|
150
|
+
// MAX_DOM_SCROLL_HEIGHT so huge grids stay scrollable to the last row on
|
|
151
|
+
// mobile (where the max element height is lowest).
|
|
152
|
+
const rowTopSpacer = $derived(ctrl.rowTopSpacer);
|
|
153
|
+
const rowBottomSpacer = $derived(ctrl.rowBottomSpacer);
|
|
154
|
+
const rowDomTotalSize = $derived(ctrl.rowDomTotalSize);
|
|
155
|
+
const renderedColumns = $derived(ctrl.renderedColumns);
|
|
156
|
+
const totalColumnWidth = $derived(ctrl.totalColumnWidth);
|
|
157
|
+
|
|
158
|
+
// ---- Built-in cell flash (ColumnDef `cellFlash`) ----------------------
|
|
159
|
+
// Flashes a cell when its value changes (edits, streaming feeds, server
|
|
160
|
+
// pushes). Keyed by rowId so virtualization recycling a <td> into a new row
|
|
161
|
+
// on scroll does NOT trigger a spurious flash - only a same-row value change
|
|
162
|
+
// does. Inert (active:false) for columns without `cellFlash`.
|
|
163
|
+
function cellFlashAction(
|
|
164
|
+
node: HTMLElement,
|
|
165
|
+
params: {
|
|
166
|
+
rowId: string;
|
|
167
|
+
value: unknown;
|
|
168
|
+
active: boolean;
|
|
169
|
+
className: string;
|
|
170
|
+
},
|
|
171
|
+
) {
|
|
172
|
+
let prevRow = params.rowId;
|
|
173
|
+
let prev = params.value;
|
|
174
|
+
return {
|
|
175
|
+
update(next: {
|
|
176
|
+
rowId: string;
|
|
177
|
+
value: unknown;
|
|
178
|
+
active: boolean;
|
|
179
|
+
className: string;
|
|
180
|
+
}) {
|
|
181
|
+
if (next.rowId !== prevRow) {
|
|
182
|
+
// A different row scrolled into this recycled slot - reset, no flash.
|
|
183
|
+
prevRow = next.rowId;
|
|
184
|
+
prev = next.value;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (next.active && next.className && !Object.is(next.value, prev)) {
|
|
188
|
+
node.classList.remove(next.className);
|
|
189
|
+
void node.offsetWidth; // reflow so the animation restarts
|
|
190
|
+
node.classList.add(next.className);
|
|
191
|
+
}
|
|
192
|
+
prev = next.value;
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function flashClassFor(
|
|
197
|
+
cfg: boolean | { className?: string } | undefined,
|
|
198
|
+
): string {
|
|
199
|
+
console.log("flash cell");
|
|
200
|
+
if (cfg && typeof cfg === "object" && cfg.className) return cfg.className;
|
|
201
|
+
return "sv-grid-cell-flash";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---- Full-row editing -------------------------------------------------
|
|
205
|
+
const fullRowEdit = $derived(ctrl.fullRowEdit);
|
|
206
|
+
// Commit the whole row when the user clicks away from its editors (Excel /
|
|
207
|
+
// AG-Grid feel). Clicking within any full-row editor keeps editing.
|
|
208
|
+
$effect(() => {
|
|
209
|
+
if (!fullRowEdit) return;
|
|
210
|
+
const onDown = (e: PointerEvent) => {
|
|
211
|
+
const t = e.target as HTMLElement | null;
|
|
212
|
+
if (t && t.closest(".sv-grid-fr-editor")) return;
|
|
213
|
+
ctrl.commitFullRowEdit();
|
|
214
|
+
};
|
|
215
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
216
|
+
return () => document.removeEventListener("pointerdown", onDown, true);
|
|
217
|
+
});
|
|
218
|
+
function fullRowKeydown(e: KeyboardEvent) {
|
|
219
|
+
if (e.key === "Enter") {
|
|
220
|
+
e.preventDefault();
|
|
221
|
+
ctrl.commitFullRowEdit();
|
|
222
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
223
|
+
} else if (e.key === "Escape") {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
ctrl.cancelFullRowEdit();
|
|
226
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function optionValueOf(o: unknown): string {
|
|
230
|
+
return typeof o === "object" && o !== null && "value" in o
|
|
231
|
+
? String((o as { value: unknown }).value)
|
|
232
|
+
: String(o);
|
|
233
|
+
}
|
|
234
|
+
function optionLabelOf(o: unknown): string {
|
|
235
|
+
return typeof o === "object" && o !== null && "label" in o
|
|
236
|
+
? String((o as { label: unknown }).label)
|
|
237
|
+
: String(o);
|
|
238
|
+
}
|
|
239
|
+
const hasHorizontalOverflow = $derived(ctrl.hasHorizontalOverflow);
|
|
240
|
+
const columnWindowStart = $derived(ctrl.columnWindowStart);
|
|
241
|
+
const columnWindowRightSpacer = $derived(ctrl.columnWindowRightSpacer);
|
|
242
|
+
const activeCell = $derived(ctrl.activeCell);
|
|
243
|
+
const activeDescendantId = $derived(ctrl.activeDescendantId);
|
|
244
|
+
const summaryByColumn = $derived(ctrl.summaryByColumn);
|
|
245
|
+
const hasMeasured = $derived(ctrl.hasMeasured);
|
|
246
|
+
const onBodyScroll = $derived(ctrl.onBodyScroll);
|
|
247
|
+
const computeRowClass = $derived(ctrl.computeRowClass);
|
|
248
|
+
const computeCellClass = $derived(ctrl.computeCellClass);
|
|
249
|
+
const computeCellTooltip = $derived(ctrl.computeCellTooltip);
|
|
250
|
+
const computeCellNote = $derived(ctrl.computeCellNote);
|
|
251
|
+
const getCellDisplayValue = $derived(ctrl.getCellDisplayValue);
|
|
252
|
+
const getColumnAlign = $derived(ctrl.getColumnAlign);
|
|
253
|
+
const getColumnEditorOptions = $derived(ctrl.getColumnEditorOptions);
|
|
254
|
+
const formatListCellValue = $derived(ctrl.formatListCellValue);
|
|
255
|
+
const formatCellValue = $derived(ctrl.formatCellValue);
|
|
256
|
+
const getPinnedCellValue = $derived(ctrl.getPinnedCellValue);
|
|
257
|
+
const formatPinnedValue = $derived(ctrl.formatPinnedValue);
|
|
258
|
+
const computePinnedCellClass = $derived(ctrl.computePinnedCellClass);
|
|
259
|
+
const isRowSelected = $derived(ctrl.isRowSelected);
|
|
260
|
+
const toggleRowSelectionById = $derived(ctrl.toggleRowSelectionById);
|
|
261
|
+
const headerSelectionState = $derived(ctrl.headerSelectionState);
|
|
262
|
+
const toggleSelectAllRows = $derived(ctrl.toggleSelectAllRows);
|
|
263
|
+
const setActiveCell = $derived(ctrl.setActiveCell);
|
|
264
|
+
const scrollActiveCellIntoView = $derived(ctrl.scrollActiveCellIntoView);
|
|
265
|
+
const getColumnWidth = $derived(ctrl.getColumnWidth);
|
|
266
|
+
const startColumnResize = $derived(ctrl.startColumnResize);
|
|
267
|
+
const getCellRangeEdges = $derived(ctrl.getCellRangeEdges);
|
|
268
|
+
const fillHandleCell = $derived(ctrl.fillHandleCell);
|
|
269
|
+
const isInFillPreview = $derived(ctrl.isInFillPreview);
|
|
270
|
+
const startFillDrag = $derived(ctrl.startFillDrag);
|
|
271
|
+
const onCellPointerDown = $derived(ctrl.onCellPointerDown);
|
|
272
|
+
const onCellPointerEnter = $derived(ctrl.onCellPointerEnter);
|
|
273
|
+
const endDragSelection = $derived(ctrl.endDragSelection);
|
|
274
|
+
const onWindowPointerMove = $derived(ctrl.onWindowPointerMove);
|
|
275
|
+
const onCellClick = $derived(ctrl.onCellClick);
|
|
276
|
+
const emitCellDoubleClick = $derived(ctrl.emitCellDoubleClick);
|
|
277
|
+
const openContextMenu = $derived(ctrl.openContextMenu);
|
|
278
|
+
const saveEditingCell = $derived(ctrl.saveEditingCell);
|
|
279
|
+
const updateEditingCellValue = $derived(ctrl.updateEditingCellValue);
|
|
280
|
+
const onEditorKeyDown = $derived(ctrl.onEditorKeyDown);
|
|
281
|
+
const focusOnMount = $derived(ctrl.focusOnMount);
|
|
282
|
+
const onHeaderSortClick = $derived(ctrl.onHeaderSortClick);
|
|
283
|
+
const onGridKeyDown = $derived(ctrl.onGridKeyDown);
|
|
284
|
+
const onGridPaste = $derived(ctrl.onGridPaste);
|
|
285
|
+
const changePage = $derived(ctrl.changePage);
|
|
286
|
+
const goToPage = $derived(ctrl.goToPage);
|
|
287
|
+
const setPageSize = $derived(ctrl.setPageSize);
|
|
288
|
+
const updateFilterRow = $derived(ctrl.updateFilterRow);
|
|
289
|
+
const updateFilterMenuValue = $derived(ctrl.updateFilterMenuValue);
|
|
290
|
+
const updateFilterMenuValueTo = $derived(ctrl.updateFilterMenuValueTo);
|
|
291
|
+
const updateFilterOperator = $derived(ctrl.updateFilterOperator);
|
|
292
|
+
const operatorsForColumn = $derived(ctrl.operatorsForColumn);
|
|
293
|
+
const clearColumnFilter = $derived(ctrl.clearColumnFilter);
|
|
294
|
+
const toggleCheckboxWithKeyboard = $derived(ctrl.toggleCheckboxWithKeyboard);
|
|
295
|
+
const operatorOption = $derived(ctrl.operatorOption);
|
|
296
|
+
const defaultOperatorFor = $derived(ctrl.defaultOperatorFor);
|
|
297
|
+
const isColumnFiltered = $derived(ctrl.isColumnFiltered);
|
|
298
|
+
const openColumnMenu = $derived(ctrl.openColumnMenu);
|
|
299
|
+
const openFilterMenu = $derived(ctrl.openFilterMenu);
|
|
300
|
+
const openOperatorMenu = $derived(ctrl.openOperatorMenu);
|
|
301
|
+
const onWindowKeydown = $derived(ctrl.onWindowKeydown);
|
|
302
|
+
</script>
|
|
303
|
+
|
|
304
|
+
<svelte:window
|
|
305
|
+
onkeydown={onWindowKeydown}
|
|
306
|
+
onpointerup={endDragSelection}
|
|
307
|
+
onpointermove={onWindowPointerMove}
|
|
308
|
+
/>
|
|
309
|
+
|
|
310
|
+
{#if props.loading && !props.loadingOverlay}
|
|
311
|
+
<div class="sv-grid-state sv-grid-state-loading" role="status">
|
|
312
|
+
Loading grid data...
|
|
313
|
+
</div>
|
|
314
|
+
{:else if props.error}
|
|
315
|
+
<div class="sv-grid-state sv-grid-state-error" role="alert">
|
|
316
|
+
{props.error}
|
|
317
|
+
</div>
|
|
318
|
+
{:else}
|
|
319
|
+
{#snippet icon(name: string)}
|
|
320
|
+
<svg
|
|
321
|
+
class="sv-grid-icon"
|
|
322
|
+
viewBox="0 0 24 24"
|
|
323
|
+
fill="none"
|
|
324
|
+
stroke="currentColor"
|
|
325
|
+
stroke-width="2.2"
|
|
326
|
+
stroke-linecap="round"
|
|
327
|
+
stroke-linejoin="round"
|
|
328
|
+
aria-hidden="true"
|
|
329
|
+
>
|
|
330
|
+
{#if name === "sort"}
|
|
331
|
+
<path d="M8 10l4-4 4 4" />
|
|
332
|
+
<path d="M8 14l4 4 4-4" />
|
|
333
|
+
{:else if name === "sort-asc"}
|
|
334
|
+
<path d="M6 14l6-6 6 6" />
|
|
335
|
+
{:else if name === "sort-desc"}
|
|
336
|
+
<path d="M6 10l6 6 6-6" />
|
|
337
|
+
{:else if name === "filter"}
|
|
338
|
+
<path d="M3 5h18l-7 8v6l-4 2v-8z" />
|
|
339
|
+
{:else if name === "menu"}
|
|
340
|
+
<path d="M4 7h16" />
|
|
341
|
+
<path d="M4 12h16" />
|
|
342
|
+
<path d="M4 17h16" />
|
|
343
|
+
{:else if name === "group"}
|
|
344
|
+
<path d="M12 3l8 4.5-8 4.5-8-4.5z" />
|
|
345
|
+
<path d="M4 12l8 4.5 8-4.5" />
|
|
346
|
+
<path d="M4 16.5l8 4.5 8-4.5" />
|
|
347
|
+
{:else if name === "x"}
|
|
348
|
+
<path d="M18 6L6 18" />
|
|
349
|
+
<path d="M6 6l12 12" />
|
|
350
|
+
{:else if name === "chevron-down"}
|
|
351
|
+
<path d="M6 9l6 6 6-6" />
|
|
352
|
+
{:else if name === "op-contains"}
|
|
353
|
+
<circle cx="11" cy="11" r="6" />
|
|
354
|
+
<path d="M20 20l-4.5-4.5" />
|
|
355
|
+
{:else if name === "op-equals"}
|
|
356
|
+
<path d="M5 9.5h14" />
|
|
357
|
+
<path d="M5 14.5h14" />
|
|
358
|
+
{:else if name === "op-startsWith"}
|
|
359
|
+
<path d="M5 5v14" />
|
|
360
|
+
<path d="M9 9h10" />
|
|
361
|
+
<path d="M9 15h7" />
|
|
362
|
+
{:else if name === "op-greaterThan"}
|
|
363
|
+
<path d="M8 5l9 7-9 7" />
|
|
364
|
+
{:else if name === "op-lessThan"}
|
|
365
|
+
<path d="M16 5l-9 7 9 7" />
|
|
366
|
+
{:else if name === "op-isBlank"}
|
|
367
|
+
<circle cx="12" cy="12" r="8" />
|
|
368
|
+
<path d="M6.5 6.5l11 11" />
|
|
369
|
+
{:else if name === "autosize"}
|
|
370
|
+
<path d="M3 12h18" />
|
|
371
|
+
<path d="M3 12l4-4" />
|
|
372
|
+
<path d="M3 12l4 4" />
|
|
373
|
+
<path d="M21 12l-4-4" />
|
|
374
|
+
<path d="M21 12l-4 4" />
|
|
375
|
+
{:else if name === "columns"}
|
|
376
|
+
<rect x="3" y="4" width="5" height="16" rx="1" />
|
|
377
|
+
<rect x="10" y="4" width="5" height="16" rx="1" />
|
|
378
|
+
<rect x="17" y="4" width="4" height="16" rx="1" />
|
|
379
|
+
{:else if name === "reset"}
|
|
380
|
+
<path d="M3 4v6h6" />
|
|
381
|
+
<path d="M3.5 10A9 9 0 1 0 6 5.3" />
|
|
382
|
+
{/if}
|
|
383
|
+
</svg>
|
|
384
|
+
{/snippet}
|
|
385
|
+
|
|
386
|
+
{#snippet cellBody(
|
|
387
|
+
row: Row<TData>,
|
|
388
|
+
column: Column<TData>,
|
|
389
|
+
cellValue: unknown,
|
|
390
|
+
)}
|
|
391
|
+
{#if column.columnDef.editorType === "checkbox" || typeof cellValue === "boolean"}
|
|
392
|
+
<div
|
|
393
|
+
class="sv-grid-checkbox sv-grid-checkbox-readonly"
|
|
394
|
+
role="checkbox"
|
|
395
|
+
aria-checked={Boolean(cellValue)}
|
|
396
|
+
aria-readonly="true"
|
|
397
|
+
aria-label={toolPanelHeaderLabel(column)}
|
|
398
|
+
></div>
|
|
399
|
+
{:else if (column.columnDef.editorType === "list" || column.columnDef.editorType === "chips") && column.columnDef.cell == null}
|
|
400
|
+
{@const arr = Array.isArray(cellValue)
|
|
401
|
+
? cellValue
|
|
402
|
+
: cellValue == null || cellValue === ""
|
|
403
|
+
? []
|
|
404
|
+
: [cellValue]}
|
|
405
|
+
{@const opts = getColumnEditorOptions(column, row)}
|
|
406
|
+
{@const isChipsType = column.columnDef.editorType === "chips"}
|
|
407
|
+
{@const anyColored = arr.some((v) => getOptionColor(opts, v))}
|
|
408
|
+
{#if arr.length > 0 && (isChipsType || anyColored)}
|
|
409
|
+
<!-- Render as chips when:
|
|
410
|
+
- the column is `chips` (always pill-style), OR
|
|
411
|
+
- the column is `list` and at least one selected option has
|
|
412
|
+
a `color` (so single-list cells like Priority "high" get
|
|
413
|
+
a pill too, not just plain text). -->
|
|
414
|
+
<div class="sv-grid-chips-display">
|
|
415
|
+
{#each arr as v (String(v))}
|
|
416
|
+
<span
|
|
417
|
+
class="sv-grid-chip"
|
|
418
|
+
style={colorfulChipStyle(getOptionColor(opts, v))}
|
|
419
|
+
>
|
|
420
|
+
{getOptionLabel(opts, v)}
|
|
421
|
+
</span>
|
|
422
|
+
{/each}
|
|
423
|
+
</div>
|
|
424
|
+
{:else}
|
|
425
|
+
{formatListCellValue(column, cellValue, row)}
|
|
426
|
+
{/if}
|
|
427
|
+
{:else if column.columnDef.sparkline && column.columnDef.cell == null}
|
|
428
|
+
{@const geo = buildSparkline(
|
|
429
|
+
toSparklineValues(cellValue),
|
|
430
|
+
column.columnDef.sparkline,
|
|
431
|
+
)}
|
|
432
|
+
{#if geo}
|
|
433
|
+
{@const vals = toSparklineValues(cellValue)}
|
|
434
|
+
<svg
|
|
435
|
+
class="sv-grid-sparkline"
|
|
436
|
+
width={geo.width}
|
|
437
|
+
height={geo.height}
|
|
438
|
+
viewBox={`0 0 ${geo.width} ${geo.height}`}
|
|
439
|
+
preserveAspectRatio="none"
|
|
440
|
+
role="img"
|
|
441
|
+
aria-label={`Sparkline, ${vals.length} points, last ${vals[vals.length - 1]}`}
|
|
442
|
+
>
|
|
443
|
+
{#if geo.areaPath}
|
|
444
|
+
<path
|
|
445
|
+
d={geo.areaPath}
|
|
446
|
+
fill={geo.color}
|
|
447
|
+
fill-opacity="0.18"
|
|
448
|
+
stroke="none"
|
|
449
|
+
/>
|
|
450
|
+
{/if}
|
|
451
|
+
{#if geo.linePath}
|
|
452
|
+
<path
|
|
453
|
+
d={geo.linePath}
|
|
454
|
+
fill="none"
|
|
455
|
+
stroke={geo.color}
|
|
456
|
+
stroke-width={geo.lineWidth}
|
|
457
|
+
stroke-linejoin="round"
|
|
458
|
+
stroke-linecap="round"
|
|
459
|
+
/>
|
|
460
|
+
{/if}
|
|
461
|
+
{#each geo.bars as bar, i (i)}
|
|
462
|
+
<rect
|
|
463
|
+
x={bar.x}
|
|
464
|
+
y={bar.y}
|
|
465
|
+
width={bar.w}
|
|
466
|
+
height={bar.h}
|
|
467
|
+
rx="0.5"
|
|
468
|
+
fill={bar.negative ? geo.negativeColor : geo.color}
|
|
469
|
+
/>
|
|
470
|
+
{/each}
|
|
471
|
+
{#if geo.lastPoint}
|
|
472
|
+
<circle
|
|
473
|
+
cx={geo.lastPoint.x}
|
|
474
|
+
cy={geo.lastPoint.y}
|
|
475
|
+
r={geo.lineWidth + 0.5}
|
|
476
|
+
fill={geo.color}
|
|
477
|
+
/>
|
|
478
|
+
{/if}
|
|
479
|
+
</svg>
|
|
480
|
+
{/if}
|
|
481
|
+
{:else}
|
|
482
|
+
{#if row.depth > 0 && column.id === allColumns[0]?.id}
|
|
483
|
+
<span
|
|
484
|
+
class="sv-grid-group-child-indent"
|
|
485
|
+
style={`width: ${row.depth * 20}px;`}
|
|
486
|
+
aria-hidden="true"
|
|
487
|
+
></span>
|
|
488
|
+
{/if}
|
|
489
|
+
{@const cellTemplate = column.columnDef.cell}
|
|
490
|
+
{#if typeof cellTemplate === "function"}
|
|
491
|
+
{@const rendered = cellTemplate({
|
|
492
|
+
cell: {
|
|
493
|
+
id: `${row.id}_${column.id}`,
|
|
494
|
+
row,
|
|
495
|
+
column,
|
|
496
|
+
getValue: () => cellValue,
|
|
497
|
+
getContext: () => ({}) as any,
|
|
498
|
+
},
|
|
499
|
+
row,
|
|
500
|
+
column,
|
|
501
|
+
table: grid,
|
|
502
|
+
getValue: () => cellValue,
|
|
503
|
+
})}
|
|
504
|
+
{#if rendered instanceof RenderSnippetConfig}
|
|
505
|
+
{@render rendered.snippet(rendered.params)}
|
|
506
|
+
{:else if rendered instanceof RenderComponentConfig}
|
|
507
|
+
<rendered.component {...rendered.props ?? {}} />
|
|
508
|
+
{:else if typeof rendered === "string" || typeof rendered === "number"}
|
|
509
|
+
{rendered}
|
|
510
|
+
{:else}
|
|
511
|
+
{formatCellValue(column, cellValue, row)}
|
|
512
|
+
{/if}
|
|
513
|
+
{:else if typeof cellTemplate === "string"}
|
|
514
|
+
{cellTemplate}
|
|
515
|
+
{:else}
|
|
516
|
+
{formatCellValue(column, cellValue, row)}
|
|
517
|
+
{/if}
|
|
518
|
+
{/if}
|
|
519
|
+
{/snippet}
|
|
520
|
+
|
|
521
|
+
<!-- Cell content wrapped with conditional-formatting overlays. The color-
|
|
522
|
+
scale fill and data bar are absolutely-positioned layers BEHIND the
|
|
523
|
+
text (the app stylesheet forces `.sv-grid-cell` background with
|
|
524
|
+
!important, so an overlay is the only way to tint reliably). Text
|
|
525
|
+
color / weight and the icon-set glyph ride on the content wrapper.
|
|
526
|
+
Falls straight through to `cellBody` when no format applies, so the
|
|
527
|
+
common (unformatted) path pays nothing. -->
|
|
528
|
+
{#snippet cellBodyWithFormat(
|
|
529
|
+
row: Row<TData>,
|
|
530
|
+
column: Column<TData>,
|
|
531
|
+
cellValue: unknown,
|
|
532
|
+
)}
|
|
533
|
+
{@const cf = cellConditionalFormat(row, column, cellValue)}
|
|
534
|
+
{#if cf && (cf.background || cf.dataBar || cf.icon || cf.color || cf.fontWeight != null)}
|
|
535
|
+
{#if cf.background}
|
|
536
|
+
<div class="sv-grid-cf-bg" style={`background:${cf.background}`}></div>
|
|
537
|
+
{/if}
|
|
538
|
+
{#if cf.dataBar}
|
|
539
|
+
<div
|
|
540
|
+
class="sv-grid-cf-bar"
|
|
541
|
+
style={`width:${cf.dataBar.percent}%;background:${cf.dataBar.color}`}
|
|
542
|
+
></div>
|
|
543
|
+
{/if}
|
|
544
|
+
<span class="sv-grid-cf-content" style={cfTextStyle(cf)}>
|
|
545
|
+
{#if cf.icon}<span class="sv-grid-cf-icon">{cf.icon}</span>{/if}
|
|
546
|
+
{#if !cf.iconOnly}{@render cellBody(row, column, cellValue)}{/if}
|
|
547
|
+
</span>
|
|
548
|
+
{:else}
|
|
549
|
+
{@render cellBody(row, column, cellValue)}
|
|
550
|
+
{/if}
|
|
551
|
+
{/snippet}
|
|
552
|
+
|
|
553
|
+
<!-- Active cell editor. Branches on `ctrl.editingCell.editorType`. Rendered
|
|
554
|
+
only when a cell is in edit mode; the call site supplies the column
|
|
555
|
+
AND row so we can resolve row-dependent `editorOptions` (cascade). -->
|
|
556
|
+
{#snippet editorBody(column: Column<TData>, row: Row<TData>)}
|
|
557
|
+
{#if column.columnDef.cellEditor}
|
|
558
|
+
<!-- Custom editor slot. The columnDef provides a snippet that
|
|
559
|
+
receives the editor context (value + commit + cancel) so the
|
|
560
|
+
consumer fully owns the in-cell UI. -->
|
|
561
|
+
{@const customEditor = column.columnDef
|
|
562
|
+
.cellEditor as unknown as import("svelte").Snippet<
|
|
563
|
+
[EditorContext<TData>]
|
|
564
|
+
>}
|
|
565
|
+
{@render customEditor({
|
|
566
|
+
cell: row.getAllCells().find((c) => c.column.id === column.id)!,
|
|
567
|
+
row,
|
|
568
|
+
column,
|
|
569
|
+
table: grid,
|
|
570
|
+
getValue: () => ctrl.editingCell?.value,
|
|
571
|
+
value: ctrl.editingCell?.value,
|
|
572
|
+
update: (next: unknown) => {
|
|
573
|
+
// Stage the draft without closing. Live-preview controls
|
|
574
|
+
// (sliders, color pickers) call this on every input tick.
|
|
575
|
+
ctrl.editingCell = ctrl.editingCell
|
|
576
|
+
? { ...ctrl.editingCell, value: next }
|
|
577
|
+
: ctrl.editingCell;
|
|
578
|
+
},
|
|
579
|
+
commit: (next?: unknown) => {
|
|
580
|
+
// Write + close. If the caller passed a value, stage it
|
|
581
|
+
// first; otherwise save whatever update() last wrote.
|
|
582
|
+
if (next !== undefined) {
|
|
583
|
+
ctrl.editingCell = ctrl.editingCell
|
|
584
|
+
? { ...ctrl.editingCell, value: next }
|
|
585
|
+
: ctrl.editingCell;
|
|
586
|
+
}
|
|
587
|
+
saveEditingCell();
|
|
588
|
+
},
|
|
589
|
+
cancel: () => {
|
|
590
|
+
ctrl.editingCell = null;
|
|
591
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
592
|
+
},
|
|
593
|
+
})}
|
|
594
|
+
{:else if ctrl.editingCell?.editorType === "checkbox"}
|
|
595
|
+
<button
|
|
596
|
+
type="button"
|
|
597
|
+
class="sv-grid-checkbox"
|
|
598
|
+
role="checkbox"
|
|
599
|
+
aria-checked={Boolean(ctrl.editingCell.value)}
|
|
600
|
+
aria-label="Edit checkbox value"
|
|
601
|
+
onclick={(event) => {
|
|
602
|
+
event.stopPropagation();
|
|
603
|
+
const nextValue = !Boolean(ctrl.editingCell?.value);
|
|
604
|
+
ctrl.editingCell = ctrl.editingCell
|
|
605
|
+
? { ...ctrl.editingCell, value: nextValue }
|
|
606
|
+
: ctrl.editingCell;
|
|
607
|
+
saveEditingCell();
|
|
608
|
+
}}
|
|
609
|
+
onkeydown={(event) =>
|
|
610
|
+
toggleCheckboxWithKeyboard(event, () => {
|
|
611
|
+
event.stopPropagation();
|
|
612
|
+
const nextValue = !Boolean(ctrl.editingCell?.value);
|
|
613
|
+
ctrl.editingCell = ctrl.editingCell
|
|
614
|
+
? { ...ctrl.editingCell, value: nextValue }
|
|
615
|
+
: ctrl.editingCell;
|
|
616
|
+
saveEditingCell();
|
|
617
|
+
})}
|
|
618
|
+
onblur={() => saveEditingCell()}
|
|
619
|
+
></button>
|
|
620
|
+
{:else if ctrl.editingCell?.editorType === "list"}
|
|
621
|
+
{@const opts = getColumnEditorOptions(column, row)}
|
|
622
|
+
{@const multi = column.columnDef.editorMultiple === true}
|
|
623
|
+
<SvGridDropdown
|
|
624
|
+
options={opts}
|
|
625
|
+
value={ctrl.editingCell?.value}
|
|
626
|
+
multiple={multi}
|
|
627
|
+
placeholder="Select…"
|
|
628
|
+
onChange={(next) => {
|
|
629
|
+
ctrl.editingCell = ctrl.editingCell
|
|
630
|
+
? { ...ctrl.editingCell, value: next }
|
|
631
|
+
: ctrl.editingCell;
|
|
632
|
+
}}
|
|
633
|
+
onCommit={() => saveEditingCell()}
|
|
634
|
+
onCancel={() => {
|
|
635
|
+
ctrl.editingCell = null;
|
|
636
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
637
|
+
}}
|
|
638
|
+
/>
|
|
639
|
+
{:else if ctrl.editingCell?.editorType === "chips"}
|
|
640
|
+
{@const opts = getColumnEditorOptions(column, row)}
|
|
641
|
+
{@const multi = column.columnDef.editorMultiple === true}
|
|
642
|
+
{@const selectedArr = toValueArray(ctrl.editingCell?.value)}
|
|
643
|
+
{@render chipsEditor(opts, multi, selectedArr)}
|
|
644
|
+
{:else if ctrl.editingCell?.editorType === "rating"}
|
|
645
|
+
{@const ratingVal = Math.max(
|
|
646
|
+
0,
|
|
647
|
+
Math.min(5, Math.round(Number(ctrl.editingCell?.value) || 0)),
|
|
648
|
+
)}
|
|
649
|
+
<span class="sv-grid-rating-editor" role="radiogroup" aria-label="Rating">
|
|
650
|
+
{#each [1, 2, 3, 4, 5] as n (n)}
|
|
651
|
+
<button
|
|
652
|
+
type="button"
|
|
653
|
+
role="radio"
|
|
654
|
+
aria-checked={ratingVal >= n}
|
|
655
|
+
aria-label={`${n} ${n === 1 ? "star" : "stars"}`}
|
|
656
|
+
class={`sv-grid-rating-star ${ratingVal >= n ? "sv-grid-rating-star-on" : ""}`}
|
|
657
|
+
onmousedown={(event) => event.preventDefault()}
|
|
658
|
+
onclick={(event) => {
|
|
659
|
+
event.stopPropagation();
|
|
660
|
+
ctrl.editingCell = ctrl.editingCell
|
|
661
|
+
? { ...ctrl.editingCell, value: n }
|
|
662
|
+
: ctrl.editingCell;
|
|
663
|
+
saveEditingCell();
|
|
664
|
+
}}
|
|
665
|
+
onkeydown={onEditorKeyDown}>★</button
|
|
666
|
+
>
|
|
667
|
+
{/each}
|
|
668
|
+
<button
|
|
669
|
+
type="button"
|
|
670
|
+
aria-label="Clear rating"
|
|
671
|
+
class="sv-grid-rating-clear"
|
|
672
|
+
onmousedown={(event) => event.preventDefault()}
|
|
673
|
+
onclick={(event) => {
|
|
674
|
+
event.stopPropagation();
|
|
675
|
+
ctrl.editingCell = ctrl.editingCell
|
|
676
|
+
? { ...ctrl.editingCell, value: 0 }
|
|
677
|
+
: ctrl.editingCell;
|
|
678
|
+
saveEditingCell();
|
|
679
|
+
}}>×</button
|
|
680
|
+
>
|
|
681
|
+
</span>
|
|
682
|
+
{:else if ctrl.editingCell?.editorType === "select"}
|
|
683
|
+
<!-- Custom dropdown: opens a themed popover identical in feel to
|
|
684
|
+
the existing 'list' editor (single-select, no typeahead). -->
|
|
685
|
+
{@const selectOpts = getColumnEditorOptions(column, row)}
|
|
686
|
+
<SvGridDropdown
|
|
687
|
+
options={selectOpts}
|
|
688
|
+
value={ctrl.editingCell?.value}
|
|
689
|
+
multiple={false}
|
|
690
|
+
placeholder="Select…"
|
|
691
|
+
onChange={(next) => {
|
|
692
|
+
ctrl.editingCell = ctrl.editingCell
|
|
693
|
+
? { ...ctrl.editingCell, value: next }
|
|
694
|
+
: ctrl.editingCell;
|
|
695
|
+
}}
|
|
696
|
+
onCommit={() => saveEditingCell()}
|
|
697
|
+
onCancel={() => {
|
|
698
|
+
ctrl.editingCell = null;
|
|
699
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
700
|
+
}}
|
|
701
|
+
/>
|
|
702
|
+
{:else if ctrl.editingCell?.editorType === "rich-select"}
|
|
703
|
+
<!-- Searchable combobox: same popover as 'select' with a
|
|
704
|
+
typeahead filter input baked in at the top. -->
|
|
705
|
+
{@const richOpts = getColumnEditorOptions(column, row)}
|
|
706
|
+
<SvGridDropdown
|
|
707
|
+
options={richOpts}
|
|
708
|
+
value={ctrl.editingCell?.value}
|
|
709
|
+
multiple={false}
|
|
710
|
+
searchable={true}
|
|
711
|
+
placeholder="Search…"
|
|
712
|
+
onChange={(next) => {
|
|
713
|
+
ctrl.editingCell = ctrl.editingCell
|
|
714
|
+
? { ...ctrl.editingCell, value: next }
|
|
715
|
+
: ctrl.editingCell;
|
|
716
|
+
}}
|
|
717
|
+
onCommit={() => saveEditingCell()}
|
|
718
|
+
onCancel={() => {
|
|
719
|
+
ctrl.editingCell = null;
|
|
720
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
721
|
+
}}
|
|
722
|
+
/>
|
|
723
|
+
{:else if ctrl.editingCell?.editorType === "textarea"}
|
|
724
|
+
<!-- Multi-line editor. Commits on Tab, Ctrl/Cmd+Enter, or blur.
|
|
725
|
+
Plain Enter inserts a newline (the whole point of textarea).
|
|
726
|
+
Esc cancels. -->
|
|
727
|
+
<textarea
|
|
728
|
+
use:focusOnMount
|
|
729
|
+
class="sv-grid-cell-editor sv-grid-cell-editor-textarea"
|
|
730
|
+
rows="4"
|
|
731
|
+
value={String(ctrl.editingCell?.value ?? "")}
|
|
732
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
733
|
+
oninput={(event) =>
|
|
734
|
+
updateEditingCellValue(
|
|
735
|
+
(event.currentTarget as HTMLTextAreaElement).value,
|
|
736
|
+
)}
|
|
737
|
+
onkeydown={(event) => {
|
|
738
|
+
event.stopPropagation();
|
|
739
|
+
if (event.key === "Escape") {
|
|
740
|
+
event.preventDefault();
|
|
741
|
+
ctrl.editingCell = null;
|
|
742
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
// Tab and Ctrl/Cmd+Enter both commit. Plain Enter inserts a newline.
|
|
746
|
+
if (
|
|
747
|
+
event.key === "Tab" ||
|
|
748
|
+
(event.key === "Enter" && (event.ctrlKey || event.metaKey))
|
|
749
|
+
) {
|
|
750
|
+
event.preventDefault();
|
|
751
|
+
saveEditingCell();
|
|
752
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
753
|
+
}
|
|
754
|
+
}}
|
|
755
|
+
onblur={() => saveEditingCell()}
|
|
756
|
+
></textarea>
|
|
757
|
+
{:else if ctrl.editingCell?.editorType === "autocomplete"}
|
|
758
|
+
<!-- Free-text autocomplete: a text input with a live-filtered
|
|
759
|
+
suggestion list. Typing edits the value freely; clicking a
|
|
760
|
+
suggestion (or blur) commits. Accepts values not in the list. -->
|
|
761
|
+
{@const acOpts = getColumnEditorOptions(column, row)}
|
|
762
|
+
{@const acText = String(ctrl.editingCell?.value ?? "")}
|
|
763
|
+
{@const acFiltered = acText.trim()
|
|
764
|
+
? acOpts.filter((o) =>
|
|
765
|
+
String(o.label).toLowerCase().includes(acText.toLowerCase()),
|
|
766
|
+
)
|
|
767
|
+
: acOpts}
|
|
768
|
+
<div class="sv-grid-autocomplete">
|
|
769
|
+
<input
|
|
770
|
+
use:focusOnMount
|
|
771
|
+
class="sv-grid-cell-editor sv-grid-cell-editor-autocomplete"
|
|
772
|
+
type="text"
|
|
773
|
+
value={acText}
|
|
774
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
775
|
+
oninput={(event) =>
|
|
776
|
+
updateEditingCellValue(
|
|
777
|
+
(event.currentTarget as HTMLInputElement).value,
|
|
778
|
+
)}
|
|
779
|
+
onblur={() => saveEditingCell()}
|
|
780
|
+
onkeydown={onEditorKeyDown}
|
|
781
|
+
/>
|
|
782
|
+
{#if acFiltered.length > 0}
|
|
783
|
+
<div class="sv-grid-autocomplete-list" role="listbox">
|
|
784
|
+
{#each acFiltered.slice(0, 50) as opt (opt.value)}
|
|
785
|
+
<button
|
|
786
|
+
type="button"
|
|
787
|
+
class="sv-grid-autocomplete-option"
|
|
788
|
+
role="option"
|
|
789
|
+
aria-selected={String(opt.value) === acText}
|
|
790
|
+
onmousedown={(event) => {
|
|
791
|
+
event.preventDefault();
|
|
792
|
+
ctrl.editingCell = ctrl.editingCell
|
|
793
|
+
? { ...ctrl.editingCell, value: opt.value }
|
|
794
|
+
: ctrl.editingCell;
|
|
795
|
+
saveEditingCell();
|
|
796
|
+
}}>{opt.label}</button
|
|
797
|
+
>
|
|
798
|
+
{/each}
|
|
799
|
+
</div>
|
|
800
|
+
{/if}
|
|
801
|
+
</div>
|
|
802
|
+
{:else if ctrl.editingCell?.editorType === "color"}
|
|
803
|
+
<!-- Native <input type="color"> opens its picker in a separate OS
|
|
804
|
+
overlay; once the picker closes, focus stays on the input so
|
|
805
|
+
`blur` never fires on its own. Commit on `change` (which fires
|
|
806
|
+
exactly once when the picker is dismissed) so the chosen color
|
|
807
|
+
is saved without needing the user to click elsewhere. -->
|
|
808
|
+
<input
|
|
809
|
+
use:focusOnMount
|
|
810
|
+
class={getEditorClass("color")}
|
|
811
|
+
type="color"
|
|
812
|
+
value={getEditableInputValue("color", ctrl.editingCell?.value)}
|
|
813
|
+
oninput={(event) =>
|
|
814
|
+
updateEditingCellValue(
|
|
815
|
+
(event.currentTarget as HTMLInputElement).value,
|
|
816
|
+
)}
|
|
817
|
+
onchange={(event) => {
|
|
818
|
+
updateEditingCellValue(
|
|
819
|
+
(event.currentTarget as HTMLInputElement).value,
|
|
820
|
+
);
|
|
821
|
+
saveEditingCell();
|
|
822
|
+
}}
|
|
823
|
+
onblur={() => saveEditingCell()}
|
|
824
|
+
onkeydown={onEditorKeyDown}
|
|
825
|
+
/>
|
|
826
|
+
{:else}
|
|
827
|
+
<input
|
|
828
|
+
use:focusOnMount
|
|
829
|
+
class={getEditorClass(ctrl.editingCell?.editorType ?? "text")}
|
|
830
|
+
type={getEditorInputType(ctrl.editingCell?.editorType ?? "text")}
|
|
831
|
+
value={getEditableInputValue(
|
|
832
|
+
ctrl.editingCell?.editorType ?? "text",
|
|
833
|
+
ctrl.editingCell?.value,
|
|
834
|
+
)}
|
|
835
|
+
oninput={(event) =>
|
|
836
|
+
updateEditingCellValue(
|
|
837
|
+
(event.currentTarget as HTMLInputElement).value,
|
|
838
|
+
)}
|
|
839
|
+
onblur={() => saveEditingCell()}
|
|
840
|
+
onkeydown={onEditorKeyDown}
|
|
841
|
+
/>
|
|
842
|
+
{/if}
|
|
843
|
+
{/snippet}
|
|
844
|
+
|
|
845
|
+
<!-- Full-row editor: a lightweight inline editor per editable cell, shown
|
|
846
|
+
for every editable column of the row in full-row edit. Kept separate
|
|
847
|
+
from `editorBody` (which owns the 14 rich single-cell editors) so
|
|
848
|
+
cell editing is untouched. Covers the common editor types. -->
|
|
849
|
+
{#snippet fullRowEditor(column: Column<TData>, row: Row<TData>)}
|
|
850
|
+
{@const et = column.columnDef.editorType ?? "text"}
|
|
851
|
+
{@const val = fullRowEdit?.draft[column.id]}
|
|
852
|
+
{#if et === "checkbox"}
|
|
853
|
+
<input
|
|
854
|
+
type="checkbox"
|
|
855
|
+
class="sv-grid-fr-editor sv-grid-fr-checkbox"
|
|
856
|
+
checked={Boolean(val)}
|
|
857
|
+
onchange={(e) =>
|
|
858
|
+
ctrl.setFullRowDraft(column.id, e.currentTarget.checked)}
|
|
859
|
+
onkeydown={fullRowKeydown}
|
|
860
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
861
|
+
onclick={(e) => e.stopPropagation()}
|
|
862
|
+
/>
|
|
863
|
+
{:else if et === "list" || et === "select" || et === "rich-select"}
|
|
864
|
+
{@const opts = getColumnEditorOptions(column, row)}
|
|
865
|
+
<select
|
|
866
|
+
class="sv-grid-cell-editor sv-grid-fr-editor"
|
|
867
|
+
value={String(val ?? "")}
|
|
868
|
+
onchange={(e) => ctrl.setFullRowDraft(column.id, e.currentTarget.value)}
|
|
869
|
+
onkeydown={fullRowKeydown}
|
|
870
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
871
|
+
onclick={(e) => e.stopPropagation()}
|
|
872
|
+
>
|
|
873
|
+
{#each opts as o (optionValueOf(o))}
|
|
874
|
+
<option value={optionValueOf(o)}>{optionLabelOf(o)}</option>
|
|
875
|
+
{/each}
|
|
876
|
+
</select>
|
|
877
|
+
{:else}
|
|
878
|
+
{@const inputType =
|
|
879
|
+
et === "number"
|
|
880
|
+
? "number"
|
|
881
|
+
: et === "date"
|
|
882
|
+
? "date"
|
|
883
|
+
: et === "datetime"
|
|
884
|
+
? "datetime-local"
|
|
885
|
+
: et === "time"
|
|
886
|
+
? "time"
|
|
887
|
+
: et === "password"
|
|
888
|
+
? "password"
|
|
889
|
+
: "text"}
|
|
890
|
+
<input
|
|
891
|
+
type={inputType}
|
|
892
|
+
class="sv-grid-cell-editor sv-grid-fr-editor"
|
|
893
|
+
value={String(val ?? "")}
|
|
894
|
+
oninput={(e) => ctrl.setFullRowDraft(column.id, e.currentTarget.value)}
|
|
895
|
+
onkeydown={fullRowKeydown}
|
|
896
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
897
|
+
onclick={(e) => e.stopPropagation()}
|
|
898
|
+
/>
|
|
899
|
+
{/if}
|
|
900
|
+
{/snippet}
|
|
901
|
+
|
|
902
|
+
{#snippet chipsEditor(
|
|
903
|
+
opts: CellEditorOption[],
|
|
904
|
+
multi: boolean,
|
|
905
|
+
selectedArr: Array<string | number>,
|
|
906
|
+
)}
|
|
907
|
+
{#if opts.length > 0}
|
|
908
|
+
<!-- Options-driven chips editor: defer to the custom dropdown,
|
|
909
|
+
which renders the selected values as chips in its trigger and
|
|
910
|
+
pops out a styled listbox. Identical UX to the list editor
|
|
911
|
+
with renderChipsInTrigger flipped on. -->
|
|
912
|
+
<SvGridDropdown
|
|
913
|
+
options={opts}
|
|
914
|
+
value={ctrl.editingCell?.value}
|
|
915
|
+
multiple={multi}
|
|
916
|
+
placeholder="Pick…"
|
|
917
|
+
renderChipsInTrigger={true}
|
|
918
|
+
onChange={(next) => {
|
|
919
|
+
ctrl.editingCell = ctrl.editingCell
|
|
920
|
+
? { ...ctrl.editingCell, value: next }
|
|
921
|
+
: ctrl.editingCell;
|
|
922
|
+
}}
|
|
923
|
+
onCommit={() => saveEditingCell()}
|
|
924
|
+
onCancel={() => {
|
|
925
|
+
ctrl.editingCell = null;
|
|
926
|
+
ctrl.gridRootEl?.focus({ preventScroll: true });
|
|
927
|
+
}}
|
|
928
|
+
/>
|
|
929
|
+
{:else}
|
|
930
|
+
<!-- Free-form chips: typed tags. Enter / comma commits a chip,
|
|
931
|
+
Backspace on empty input removes the last chip, blur saves. -->
|
|
932
|
+
<div
|
|
933
|
+
class="sv-grid-cell-editor sv-grid-cell-editor-chips"
|
|
934
|
+
role="group"
|
|
935
|
+
tabindex={-1}
|
|
936
|
+
>
|
|
937
|
+
<div class="sv-grid-chips-row">
|
|
938
|
+
{#each selectedArr as v, idx (String(v) + "_" + idx)}
|
|
939
|
+
<span class="sv-grid-chip sv-grid-chip-removable">
|
|
940
|
+
{String(v)}
|
|
941
|
+
<button
|
|
942
|
+
type="button"
|
|
943
|
+
class="sv-grid-chip-remove"
|
|
944
|
+
aria-label="Remove {String(v)}"
|
|
945
|
+
onmousedown={(event) => event.preventDefault()}
|
|
946
|
+
onclick={() => {
|
|
947
|
+
const next = selectedArr.filter((_, i) => i !== idx);
|
|
948
|
+
ctrl.editingCell = ctrl.editingCell
|
|
949
|
+
? {
|
|
950
|
+
...ctrl.editingCell,
|
|
951
|
+
value: multi ? next : (next[0] ?? null),
|
|
952
|
+
}
|
|
953
|
+
: ctrl.editingCell;
|
|
954
|
+
}}>×</button
|
|
955
|
+
>
|
|
956
|
+
</span>
|
|
957
|
+
{/each}
|
|
958
|
+
<input
|
|
959
|
+
use:focusOnMount
|
|
960
|
+
class="sv-grid-chip-input"
|
|
961
|
+
type="text"
|
|
962
|
+
placeholder={multi ? "Type, Enter to add" : "Type a value"}
|
|
963
|
+
onkeydown={(event) => {
|
|
964
|
+
if (event.key === "Enter" || (multi && event.key === ",")) {
|
|
965
|
+
event.preventDefault();
|
|
966
|
+
event.stopPropagation();
|
|
967
|
+
const input = event.currentTarget as HTMLInputElement;
|
|
968
|
+
const raw = input.value.trim();
|
|
969
|
+
if (raw) {
|
|
970
|
+
const next = multi ? [...selectedArr, raw] : [raw];
|
|
971
|
+
ctrl.editingCell = ctrl.editingCell
|
|
972
|
+
? { ...ctrl.editingCell, value: multi ? next : raw }
|
|
973
|
+
: ctrl.editingCell;
|
|
974
|
+
input.value = "";
|
|
975
|
+
if (!multi) saveEditingCell();
|
|
976
|
+
}
|
|
977
|
+
} else if (event.key === "Escape") {
|
|
978
|
+
onEditorKeyDown(event);
|
|
979
|
+
} else if (event.key === "Backspace") {
|
|
980
|
+
const input = event.currentTarget as HTMLInputElement;
|
|
981
|
+
if (input.value === "" && selectedArr.length > 0) {
|
|
982
|
+
event.preventDefault();
|
|
983
|
+
const next = selectedArr.slice(0, -1);
|
|
984
|
+
ctrl.editingCell = ctrl.editingCell
|
|
985
|
+
? {
|
|
986
|
+
...ctrl.editingCell,
|
|
987
|
+
value: multi ? next : (next[0] ?? null),
|
|
988
|
+
}
|
|
989
|
+
: ctrl.editingCell;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}}
|
|
993
|
+
onblur={() => saveEditingCell()}
|
|
994
|
+
/>
|
|
995
|
+
{#if multi}
|
|
996
|
+
<button
|
|
997
|
+
type="button"
|
|
998
|
+
class="sv-grid-chip-commit"
|
|
999
|
+
onmousedown={(event) => event.preventDefault()}
|
|
1000
|
+
onclick={() => saveEditingCell()}
|
|
1001
|
+
aria-label="Commit chip selection">Done</button
|
|
1002
|
+
>
|
|
1003
|
+
{/if}
|
|
1004
|
+
</div>
|
|
1005
|
+
</div>
|
|
1006
|
+
{/if}
|
|
1007
|
+
{/snippet}
|
|
1008
|
+
|
|
1009
|
+
{#snippet groupRowContent(row: Row<TData>)}
|
|
1010
|
+
{@const groupingColumnId = groupingColumns[row.depth] ?? ""}
|
|
1011
|
+
{@const groupingColumn = allColumns.find((c) => c.id === groupingColumnId)}
|
|
1012
|
+
{@const headerLabel =
|
|
1013
|
+
typeof groupingColumn?.columnDef.header === "string"
|
|
1014
|
+
? groupingColumn.columnDef.header
|
|
1015
|
+
: groupingColumnId}
|
|
1016
|
+
{@const groupValueRaw = row.getCellValueByColumnId(groupingColumnId)}
|
|
1017
|
+
{@const groupValue = groupingColumn
|
|
1018
|
+
? formatCellValue(groupingColumn, groupValueRaw, row)
|
|
1019
|
+
: String(groupValueRaw ?? "")}
|
|
1020
|
+
{@const count = row.leafCount ?? row.subRows?.length ?? 0}
|
|
1021
|
+
<div
|
|
1022
|
+
class="sv-grid-group-content"
|
|
1023
|
+
style={`padding-left: ${row.depth * 20}px;`}
|
|
1024
|
+
>
|
|
1025
|
+
<button
|
|
1026
|
+
type="button"
|
|
1027
|
+
class="sv-grid-group-toggle"
|
|
1028
|
+
aria-expanded={row.getIsExpanded?.() ? "true" : "false"}
|
|
1029
|
+
aria-label={row.getIsExpanded?.() ? "Collapse group" : "Expand group"}
|
|
1030
|
+
onclick={(event) => {
|
|
1031
|
+
event.stopPropagation();
|
|
1032
|
+
row.toggleExpanded?.();
|
|
1033
|
+
}}>{row.getIsExpanded?.() ? "▾" : "▸"}</button
|
|
1034
|
+
>
|
|
1035
|
+
<span class="sv-grid-group-label">{headerLabel}: {groupValue}</span>
|
|
1036
|
+
<span class="sv-grid-group-count"
|
|
1037
|
+
>{count} {count === 1 ? "row" : "rows"}</span
|
|
1038
|
+
>
|
|
1039
|
+
{#each allColumns as col (col.id)}
|
|
1040
|
+
{#if col.columnDef.aggregate && col.id !== groupingColumnId}
|
|
1041
|
+
{@const aggVal = row.getCellValueByColumnId(col.id)}
|
|
1042
|
+
{#if aggVal != null && aggVal !== ""}
|
|
1043
|
+
<span class="sv-grid-group-agg">
|
|
1044
|
+
<span class="sv-grid-group-agg-label"
|
|
1045
|
+
>{typeof col.columnDef.header === "string"
|
|
1046
|
+
? col.columnDef.header
|
|
1047
|
+
: col.id}</span
|
|
1048
|
+
>
|
|
1049
|
+
{formatCellValue(col, aggVal, row)}
|
|
1050
|
+
</span>
|
|
1051
|
+
{/if}
|
|
1052
|
+
{/if}
|
|
1053
|
+
{/each}
|
|
1054
|
+
</div>
|
|
1055
|
+
{/snippet}
|
|
1056
|
+
|
|
1057
|
+
<!-- A full-width detail row: one colspan cell spanning every column,
|
|
1058
|
+
hosting the consumer's `renderDetailRow` snippet. Auto height (no
|
|
1059
|
+
fixed row height) so the panel grows to fit its content. -->
|
|
1060
|
+
{#snippet detailRowMarkup(detailRow: Row<TData>, detailRowIndex: number)}
|
|
1061
|
+
<tr
|
|
1062
|
+
class="sv-grid-row sv-grid-detail-row"
|
|
1063
|
+
{...getGridRowA11yProps(detailRowIndex + 1)}
|
|
1064
|
+
>
|
|
1065
|
+
<td
|
|
1066
|
+
class="sv-grid-cell sv-grid-detail-cell"
|
|
1067
|
+
colspan={allColumns.length +
|
|
1068
|
+
(showRowNumbersEffective ? 1 : 0) +
|
|
1069
|
+
(showRowSelectionEffective ? 1 : 0)}
|
|
1070
|
+
>
|
|
1071
|
+
{#if props.renderDetailRow}
|
|
1072
|
+
{@render props.renderDetailRow({
|
|
1073
|
+
row: detailRow.original as TData,
|
|
1074
|
+
rowIndex: detailRowIndex,
|
|
1075
|
+
})}
|
|
1076
|
+
{/if}
|
|
1077
|
+
</td>
|
|
1078
|
+
</tr>
|
|
1079
|
+
{/snippet}
|
|
1080
|
+
|
|
1081
|
+
<!-- A single pinned row (top or bottom). Read-only by design: no
|
|
1082
|
+
inline editing, no row-selection checkbox, no fill handle.
|
|
1083
|
+
Position-sticky CSS keeps it anchored to the top of the body or
|
|
1084
|
+
the bottom of the viewport while the rest scrolls. -->
|
|
1085
|
+
{#snippet pinnedRowBody(
|
|
1086
|
+
rowData: TData,
|
|
1087
|
+
where: "top" | "bottom",
|
|
1088
|
+
index: number,
|
|
1089
|
+
)}
|
|
1090
|
+
<tr
|
|
1091
|
+
class={`sv-grid-row sv-grid-pinned-row sv-grid-pinned-row-${where}`}
|
|
1092
|
+
data-pinned-row={where}
|
|
1093
|
+
data-pinned-index={index}
|
|
1094
|
+
>
|
|
1095
|
+
{#if showRowNumbersEffective}
|
|
1096
|
+
<td
|
|
1097
|
+
class="sv-grid-cell sv-grid-row-number-cell"
|
|
1098
|
+
style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
|
|
1099
|
+
>{where === "top" ? "↑" : "↓"}</td
|
|
1100
|
+
>
|
|
1101
|
+
{/if}
|
|
1102
|
+
{#if showRowSelectionEffective}
|
|
1103
|
+
<td
|
|
1104
|
+
class="sv-grid-cell sv-grid-selection-cell"
|
|
1105
|
+
style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
|
|
1106
|
+
></td>
|
|
1107
|
+
{/if}
|
|
1108
|
+
{#if columnVirtualizationEnabled && columnWindowStart > 0}
|
|
1109
|
+
<td
|
|
1110
|
+
class="sv-grid-cell sv-grid-cell-spacer"
|
|
1111
|
+
aria-hidden="true"
|
|
1112
|
+
style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
|
|
1113
|
+
></td>
|
|
1114
|
+
{/if}
|
|
1115
|
+
{#each renderedColumns as rendered (rendered.column.id)}
|
|
1116
|
+
{@const value = getPinnedCellValue(rowData, rendered.column)}
|
|
1117
|
+
{@const userCellClass = computePinnedCellClass(
|
|
1118
|
+
rowData,
|
|
1119
|
+
rendered.column,
|
|
1120
|
+
)}
|
|
1121
|
+
<td
|
|
1122
|
+
class={`sv-grid-cell ${userCellClass}`}
|
|
1123
|
+
data-col-id={rendered.column.id}
|
|
1124
|
+
data-pinned={isColumnPinned(rendered.column.id) ?? undefined}
|
|
1125
|
+
style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
|
|
1126
|
+
>{formatPinnedValue(rendered.column, value)}</td
|
|
1127
|
+
>
|
|
1128
|
+
{/each}
|
|
1129
|
+
{#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
|
|
1130
|
+
<td
|
|
1131
|
+
class="sv-grid-cell sv-grid-cell-spacer"
|
|
1132
|
+
aria-hidden="true"
|
|
1133
|
+
style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
|
|
1134
|
+
></td>
|
|
1135
|
+
{/if}
|
|
1136
|
+
</tr>
|
|
1137
|
+
{/snippet}
|
|
1138
|
+
|
|
1139
|
+
<div
|
|
1140
|
+
class="sv-grid-root"
|
|
1141
|
+
class:sv-grid-root-fill={props.containerHeight === "100%"}
|
|
1142
|
+
>
|
|
1143
|
+
{#if showGlobalFilterEffective}
|
|
1144
|
+
<label class="sv-grid-global-filter">
|
|
1145
|
+
Filter all rows
|
|
1146
|
+
<input bind:value={ctrl.globalFilter} placeholder="Type to filter..." />
|
|
1147
|
+
</label>
|
|
1148
|
+
{/if}
|
|
1149
|
+
|
|
1150
|
+
{#if toolPanelEnabled}
|
|
1151
|
+
<div class="sv-grid-toolbar">
|
|
1152
|
+
<button
|
|
1153
|
+
type="button"
|
|
1154
|
+
class="sv-grid-toolbar-btn"
|
|
1155
|
+
class:is-active={ctrl.toolPanelOpen}
|
|
1156
|
+
aria-label={ctrl.toolPanelOpen
|
|
1157
|
+
? "Close tool panel"
|
|
1158
|
+
: "Open tool panel (columns & filters)"}
|
|
1159
|
+
aria-expanded={ctrl.toolPanelOpen}
|
|
1160
|
+
onclick={() => (ctrl.toolPanelOpen = !ctrl.toolPanelOpen)}
|
|
1161
|
+
>
|
|
1162
|
+
<svg
|
|
1163
|
+
viewBox="0 0 24 24"
|
|
1164
|
+
width="15"
|
|
1165
|
+
height="15"
|
|
1166
|
+
fill="none"
|
|
1167
|
+
stroke="currentColor"
|
|
1168
|
+
stroke-width="2"
|
|
1169
|
+
stroke-linecap="round"
|
|
1170
|
+
stroke-linejoin="round"
|
|
1171
|
+
aria-hidden="true"
|
|
1172
|
+
>
|
|
1173
|
+
<rect x="3" y="4" width="6" height="16" rx="1" />
|
|
1174
|
+
<rect x="11" y="4" width="4" height="16" rx="1" />
|
|
1175
|
+
<rect x="17" y="4" width="4" height="16" rx="1" />
|
|
1176
|
+
</svg>
|
|
1177
|
+
Columns & Filters
|
|
1178
|
+
</button>
|
|
1179
|
+
</div>
|
|
1180
|
+
{/if}
|
|
1181
|
+
|
|
1182
|
+
<div
|
|
1183
|
+
class="sv-grid-shell"
|
|
1184
|
+
style={`height: ${
|
|
1185
|
+
typeof props.containerHeight === "string"
|
|
1186
|
+
? props.containerHeight
|
|
1187
|
+
: `${props.containerHeight ?? 520}px`
|
|
1188
|
+
}; --sg-thead-h: ${headerHeight}px; --sg-pinned-row-h: ${typeof props.rowHeight === "number" ? props.rowHeight : 30}px;`}
|
|
1189
|
+
>
|
|
1190
|
+
<div
|
|
1191
|
+
class="sv-grid-container sv-grid-container-custom-scrollbars"
|
|
1192
|
+
bind:this={ctrl.scrollContainer}
|
|
1193
|
+
onscroll={onBodyScroll}
|
|
1194
|
+
style={`overflow: auto; position: relative; height: calc(100% - ${hasMeasured && hasHorizontalOverflow ? 16 : 0}px);`}
|
|
1195
|
+
>
|
|
1196
|
+
<table
|
|
1197
|
+
bind:this={ctrl.gridRootEl}
|
|
1198
|
+
class="sv-grid-table"
|
|
1199
|
+
{...getGridRootA11yProps({
|
|
1200
|
+
activeDescendantId,
|
|
1201
|
+
rowCount: allRows.length,
|
|
1202
|
+
colCount: allColumns.length,
|
|
1203
|
+
})}
|
|
1204
|
+
onkeydown={onGridKeyDown}
|
|
1205
|
+
onpaste={onGridPaste}
|
|
1206
|
+
style={`min-width: ${totalColumnWidth}px;`}
|
|
1207
|
+
>
|
|
1208
|
+
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
1209
|
+
<thead class="sv-grid-head" bind:this={ctrl.theadEl} role="rowgroup">
|
|
1210
|
+
<!-- Multi-level group header rows. Only present when the
|
|
1211
|
+
consumer's column tree has `columns: [...]` nesting.
|
|
1212
|
+
Each TH spans the leaf widths underneath via the
|
|
1213
|
+
precomputed `widthPx` + `colSpan` from groupHeaderRows. -->
|
|
1214
|
+
{#each groupHeaderRows as row (row.id)}
|
|
1215
|
+
<tr
|
|
1216
|
+
class="sv-grid-row sv-grid-header-row sv-grid-group-header-row"
|
|
1217
|
+
{...getGridRowA11yProps()}
|
|
1218
|
+
style={props.headerHeight
|
|
1219
|
+
? `height: ${props.headerHeight}px;`
|
|
1220
|
+
: undefined}
|
|
1221
|
+
>
|
|
1222
|
+
{#if showRowNumbersEffective}
|
|
1223
|
+
<th
|
|
1224
|
+
class="sv-grid-column sv-grid-row-number-column"
|
|
1225
|
+
style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
|
|
1226
|
+
aria-hidden="true"
|
|
1227
|
+
></th>
|
|
1228
|
+
{/if}
|
|
1229
|
+
{#if showRowSelectionEffective}
|
|
1230
|
+
<th
|
|
1231
|
+
class="sv-grid-column sv-grid-selection-column"
|
|
1232
|
+
style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
|
|
1233
|
+
aria-hidden="true"
|
|
1234
|
+
></th>
|
|
1235
|
+
{/if}
|
|
1236
|
+
{#each row.cells as cell (cell.key)}
|
|
1237
|
+
<th
|
|
1238
|
+
class="sv-grid-column sv-grid-group-header-cell"
|
|
1239
|
+
class:sv-grid-group-header-placeholder={cell.isPlaceholder}
|
|
1240
|
+
colspan={cell.colSpan}
|
|
1241
|
+
style={`width: ${cell.widthPx}px; min-width: ${cell.widthPx}px; max-width: ${cell.widthPx}px;`}
|
|
1242
|
+
>
|
|
1243
|
+
{#if !cell.isPlaceholder}
|
|
1244
|
+
{#if cell.collapsible}
|
|
1245
|
+
<button
|
|
1246
|
+
type="button"
|
|
1247
|
+
class="sv-grid-group-toggle"
|
|
1248
|
+
class:is-collapsed={cell.collapsed}
|
|
1249
|
+
aria-expanded={!cell.collapsed}
|
|
1250
|
+
aria-label={cell.collapsed
|
|
1251
|
+
? `Expand ${cell.label}`
|
|
1252
|
+
: `Collapse ${cell.label}`}
|
|
1253
|
+
title={cell.collapsed
|
|
1254
|
+
? "Expand group"
|
|
1255
|
+
: "Collapse group"}
|
|
1256
|
+
onclick={() => ctrl.toggleColumnGroup(cell.groupId!)}
|
|
1257
|
+
>
|
|
1258
|
+
<span class="sv-grid-group-header-label"
|
|
1259
|
+
>{cell.label}</span
|
|
1260
|
+
>
|
|
1261
|
+
<svg
|
|
1262
|
+
class="sv-grid-group-caret"
|
|
1263
|
+
viewBox="0 0 16 16"
|
|
1264
|
+
width="10"
|
|
1265
|
+
height="10"
|
|
1266
|
+
fill="none"
|
|
1267
|
+
stroke="currentColor"
|
|
1268
|
+
stroke-width="2.5"
|
|
1269
|
+
stroke-linecap="round"
|
|
1270
|
+
stroke-linejoin="round"
|
|
1271
|
+
aria-hidden="true"
|
|
1272
|
+
>
|
|
1273
|
+
<polyline points="4 6 8 10 12 6"></polyline>
|
|
1274
|
+
</svg>
|
|
1275
|
+
</button>
|
|
1276
|
+
{:else}
|
|
1277
|
+
<span class="sv-grid-group-header-label"
|
|
1278
|
+
>{cell.label}</span
|
|
1279
|
+
>
|
|
1280
|
+
{/if}
|
|
1281
|
+
{/if}
|
|
1282
|
+
</th>
|
|
1283
|
+
{/each}
|
|
1284
|
+
</tr>
|
|
1285
|
+
{/each}
|
|
1286
|
+
{#each headerGroups as headerGroup (headerGroup.id)}
|
|
1287
|
+
<tr
|
|
1288
|
+
class="sv-grid-row sv-grid-header-row"
|
|
1289
|
+
{...getGridRowA11yProps()}
|
|
1290
|
+
style={props.headerHeight
|
|
1291
|
+
? `height: ${props.headerHeight}px;`
|
|
1292
|
+
: undefined}
|
|
1293
|
+
>
|
|
1294
|
+
{#if showRowNumbersEffective}
|
|
1295
|
+
<th
|
|
1296
|
+
class="sv-grid-column sv-grid-row-number-column"
|
|
1297
|
+
style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
|
|
1298
|
+
aria-label="Row number"
|
|
1299
|
+
>
|
|
1300
|
+
<span class="sv-grid-row-number-head">#</span>
|
|
1301
|
+
</th>
|
|
1302
|
+
{/if}
|
|
1303
|
+
{#if showRowSelectionEffective}
|
|
1304
|
+
<th
|
|
1305
|
+
class="sv-grid-column sv-grid-selection-column"
|
|
1306
|
+
style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
|
|
1307
|
+
>
|
|
1308
|
+
<button
|
|
1309
|
+
type="button"
|
|
1310
|
+
class="sv-grid-checkbox"
|
|
1311
|
+
role="checkbox"
|
|
1312
|
+
aria-checked={headerSelectionState === "all"
|
|
1313
|
+
? "true"
|
|
1314
|
+
: headerSelectionState === "some"
|
|
1315
|
+
? "mixed"
|
|
1316
|
+
: "false"}
|
|
1317
|
+
aria-label="Select all rows"
|
|
1318
|
+
onclick={toggleSelectAllRows}
|
|
1319
|
+
onkeydown={(event) =>
|
|
1320
|
+
toggleCheckboxWithKeyboard(event, toggleSelectAllRows)}
|
|
1321
|
+
></button>
|
|
1322
|
+
</th>
|
|
1323
|
+
{/if}
|
|
1324
|
+
{#if columnVirtualizationEnabled && columnWindowStart > 0}
|
|
1325
|
+
<th
|
|
1326
|
+
class="sv-grid-column sv-grid-column-spacer"
|
|
1327
|
+
aria-hidden="true"
|
|
1328
|
+
style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
|
|
1329
|
+
></th>
|
|
1330
|
+
{/if}
|
|
1331
|
+
{#each renderedColumns as rendered (rendered.column.id)}
|
|
1332
|
+
{@const header = headerGroup.headers[rendered.item.index]}
|
|
1333
|
+
{#if header}
|
|
1334
|
+
{@const sortDirection =
|
|
1335
|
+
sortDirectionByColumn[header.column.id]}
|
|
1336
|
+
{@const isGrouped = groupingColumns.includes(
|
|
1337
|
+
header.column.id,
|
|
1338
|
+
)}
|
|
1339
|
+
<th
|
|
1340
|
+
class="sv-grid-column"
|
|
1341
|
+
class:is-drag-target-before={colDropOnId ===
|
|
1342
|
+
header.column.id && colDropSide === "before"}
|
|
1343
|
+
class:is-drag-target-after={colDropOnId ===
|
|
1344
|
+
header.column.id && colDropSide === "after"}
|
|
1345
|
+
class:is-dragging={colDragId === header.column.id}
|
|
1346
|
+
data-svgrid-header-col={header.column.id}
|
|
1347
|
+
data-align={getColumnAlign(rendered.column)}
|
|
1348
|
+
data-pinned={isColumnPinned(rendered.column.id) ??
|
|
1349
|
+
undefined}
|
|
1350
|
+
draggable={(props.enableColumnReorder ?? false)
|
|
1351
|
+
? true
|
|
1352
|
+
: undefined}
|
|
1353
|
+
ondragstart={(e) =>
|
|
1354
|
+
(props.enableColumnReorder ?? false) &&
|
|
1355
|
+
onColumnHeaderDragStart(e, header.column.id)}
|
|
1356
|
+
ondragover={(e) =>
|
|
1357
|
+
(props.enableColumnReorder ?? false) &&
|
|
1358
|
+
onColumnHeaderDragOver(e, header.column.id)}
|
|
1359
|
+
ondragleave={() =>
|
|
1360
|
+
(props.enableColumnReorder ?? false) &&
|
|
1361
|
+
onColumnHeaderDragLeave(header.column.id)}
|
|
1362
|
+
ondrop={(e) =>
|
|
1363
|
+
(props.enableColumnReorder ?? false) &&
|
|
1364
|
+
onColumnHeaderDrop(e, header.column.id)}
|
|
1365
|
+
ondragend={() =>
|
|
1366
|
+
(props.enableColumnReorder ?? false) &&
|
|
1367
|
+
onColumnHeaderDragEnd()}
|
|
1368
|
+
style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
|
|
1369
|
+
{...getGridHeaderA11yProps({
|
|
1370
|
+
sortable: header.column.getCanSort(),
|
|
1371
|
+
sortDirection:
|
|
1372
|
+
sortDirection === "asc"
|
|
1373
|
+
? "ascending"
|
|
1374
|
+
: sortDirection === "desc"
|
|
1375
|
+
? "descending"
|
|
1376
|
+
: "none",
|
|
1377
|
+
})}
|
|
1378
|
+
>
|
|
1379
|
+
{#if !header.isPlaceholder}
|
|
1380
|
+
<div class="sv-grid-header-cell">
|
|
1381
|
+
{#if typeof header.column.columnDef.header === "function"}
|
|
1382
|
+
{@const rendered = header.column.columnDef.header(
|
|
1383
|
+
header.getContext(),
|
|
1384
|
+
)}
|
|
1385
|
+
<!-- Custom header (snippet/component): rendered
|
|
1386
|
+
OUTSIDE the sort button so the consumer's
|
|
1387
|
+
own interactive elements (menu buttons,
|
|
1388
|
+
dropdowns, etc) are valid DOM and receive
|
|
1389
|
+
their own clicks. Clicking blank header
|
|
1390
|
+
background still triggers sort via the
|
|
1391
|
+
wrapper's role=button + click handler. -->
|
|
1392
|
+
<div
|
|
1393
|
+
class="sv-grid-header-custom"
|
|
1394
|
+
role="button"
|
|
1395
|
+
tabindex="-1"
|
|
1396
|
+
onclick={(event) => {
|
|
1397
|
+
if (!header.column.getCanSort()) return;
|
|
1398
|
+
// If the click landed on an interactive
|
|
1399
|
+
// element inside the custom header, let
|
|
1400
|
+
// that element handle it.
|
|
1401
|
+
const t = event.target as HTMLElement | null;
|
|
1402
|
+
if (
|
|
1403
|
+
t &&
|
|
1404
|
+
t.closest(
|
|
1405
|
+
'button, a, input, select, textarea, [role="button"], [role="menuitem"]',
|
|
1406
|
+
) &&
|
|
1407
|
+
!t.classList.contains("sv-grid-header-custom")
|
|
1408
|
+
)
|
|
1409
|
+
return;
|
|
1410
|
+
onHeaderSortClick(event, header.column.id);
|
|
1411
|
+
}}
|
|
1412
|
+
onkeydown={(event) => {
|
|
1413
|
+
if (event.key !== "Enter" && event.key !== " ")
|
|
1414
|
+
return;
|
|
1415
|
+
if (!header.column.getCanSort()) return;
|
|
1416
|
+
event.preventDefault();
|
|
1417
|
+
onHeaderSortClick(
|
|
1418
|
+
event as unknown as MouseEvent,
|
|
1419
|
+
header.column.id,
|
|
1420
|
+
);
|
|
1421
|
+
}}
|
|
1422
|
+
>
|
|
1423
|
+
{#if rendered instanceof RenderSnippetConfig}
|
|
1424
|
+
{@render rendered.snippet(rendered.params)}
|
|
1425
|
+
{:else if rendered instanceof RenderComponentConfig}
|
|
1426
|
+
<rendered.component {...rendered.props ?? {}} />
|
|
1427
|
+
{:else if typeof rendered === "string" || typeof rendered === "number"}
|
|
1428
|
+
{rendered}
|
|
1429
|
+
{:else}
|
|
1430
|
+
{header.id}
|
|
1431
|
+
{/if}
|
|
1432
|
+
{#if header.column.getCanSort()}
|
|
1433
|
+
{#if sortDirection === "asc"}
|
|
1434
|
+
<span class="sv-grid-header-icon"
|
|
1435
|
+
>{@render icon("sort-asc")}</span
|
|
1436
|
+
>
|
|
1437
|
+
{:else if sortDirection === "desc"}
|
|
1438
|
+
<span class="sv-grid-header-icon"
|
|
1439
|
+
>{@render icon("sort-desc")}</span
|
|
1440
|
+
>
|
|
1441
|
+
{/if}
|
|
1442
|
+
{/if}
|
|
1443
|
+
</div>
|
|
1444
|
+
{:else}
|
|
1445
|
+
<button
|
|
1446
|
+
type="button"
|
|
1447
|
+
class="sv-grid-header-sort"
|
|
1448
|
+
onclick={(event) =>
|
|
1449
|
+
onHeaderSortClick(event, header.column.id)}
|
|
1450
|
+
>
|
|
1451
|
+
<span class="sv-grid-header-label">
|
|
1452
|
+
{typeof header.column.columnDef.header ===
|
|
1453
|
+
"string"
|
|
1454
|
+
? header.column.columnDef.header
|
|
1455
|
+
: header.id}
|
|
1456
|
+
</span>
|
|
1457
|
+
{#if header.column.getCanSort()}
|
|
1458
|
+
{#if sortDirection === "asc"}
|
|
1459
|
+
<span class="sv-grid-header-icon"
|
|
1460
|
+
>{@render icon("sort-asc")}</span
|
|
1461
|
+
>
|
|
1462
|
+
{:else if sortDirection === "desc"}
|
|
1463
|
+
<span class="sv-grid-header-icon"
|
|
1464
|
+
>{@render icon("sort-desc")}</span
|
|
1465
|
+
>
|
|
1466
|
+
{:else}
|
|
1467
|
+
<span
|
|
1468
|
+
class="sv-grid-header-icon sv-grid-header-icon-hint"
|
|
1469
|
+
>{@render icon("sort")}</span
|
|
1470
|
+
>
|
|
1471
|
+
{/if}
|
|
1472
|
+
{/if}
|
|
1473
|
+
</button>
|
|
1474
|
+
{/if}
|
|
1475
|
+
{#if isGrouped}
|
|
1476
|
+
<span
|
|
1477
|
+
class="sv-grid-header-icon sv-grid-header-icon-flag"
|
|
1478
|
+
title="Grouped">{@render icon("group")}</span
|
|
1479
|
+
>
|
|
1480
|
+
{/if}
|
|
1481
|
+
{#if header.column.getCanFilter()}
|
|
1482
|
+
<button
|
|
1483
|
+
type="button"
|
|
1484
|
+
class="sv-grid-col-menu-btn sv-grid-col-filter-btn"
|
|
1485
|
+
class:is-open={filterMenuFor === header.column.id}
|
|
1486
|
+
class:is-active={isColumnFiltered(
|
|
1487
|
+
header.column.id,
|
|
1488
|
+
)}
|
|
1489
|
+
aria-label="Filter"
|
|
1490
|
+
aria-haspopup="menu"
|
|
1491
|
+
onclick={(event) =>
|
|
1492
|
+
openFilterMenu(event, header.column.id)}
|
|
1493
|
+
>
|
|
1494
|
+
{@render icon("filter")}
|
|
1495
|
+
</button>
|
|
1496
|
+
{/if}
|
|
1497
|
+
<button
|
|
1498
|
+
type="button"
|
|
1499
|
+
class="sv-grid-col-menu-btn"
|
|
1500
|
+
class:is-open={columnMenuFor === header.column.id}
|
|
1501
|
+
aria-label="Column menu"
|
|
1502
|
+
aria-haspopup="menu"
|
|
1503
|
+
onclick={(event) =>
|
|
1504
|
+
openColumnMenu(event, header.column.id)}
|
|
1505
|
+
>
|
|
1506
|
+
{@render icon("menu")}
|
|
1507
|
+
</button>
|
|
1508
|
+
</div>
|
|
1509
|
+
{#if showInlineColumnFilterEffective && header.column.getCanFilter()}
|
|
1510
|
+
<input
|
|
1511
|
+
class="sv-grid-column-filter"
|
|
1512
|
+
placeholder="Filter"
|
|
1513
|
+
oninput={(event) => {
|
|
1514
|
+
const value = (
|
|
1515
|
+
event.currentTarget as HTMLInputElement
|
|
1516
|
+
).value;
|
|
1517
|
+
grid.setColumnFilters((prev) => [
|
|
1518
|
+
...prev.filter(
|
|
1519
|
+
(entry) => entry.id !== header.column.id,
|
|
1520
|
+
),
|
|
1521
|
+
...(value
|
|
1522
|
+
? [
|
|
1523
|
+
{
|
|
1524
|
+
id: header.column.id,
|
|
1525
|
+
value,
|
|
1526
|
+
fn: "includesString" as const,
|
|
1527
|
+
},
|
|
1528
|
+
]
|
|
1529
|
+
: []),
|
|
1530
|
+
]);
|
|
1531
|
+
}}
|
|
1532
|
+
/>
|
|
1533
|
+
{/if}
|
|
1534
|
+
<div
|
|
1535
|
+
class="sv-grid-resize-handle"
|
|
1536
|
+
class:is-resizing={resizingColumnId ===
|
|
1537
|
+
header.column.id}
|
|
1538
|
+
role="separator"
|
|
1539
|
+
aria-orientation="vertical"
|
|
1540
|
+
aria-label="Resize column"
|
|
1541
|
+
onpointerdown={(event) =>
|
|
1542
|
+
startColumnResize(event, header.column.id)}
|
|
1543
|
+
ondblclick={(event) => event.stopPropagation()}
|
|
1544
|
+
></div>
|
|
1545
|
+
{/if}
|
|
1546
|
+
</th>
|
|
1547
|
+
{/if}
|
|
1548
|
+
{/each}
|
|
1549
|
+
{#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
|
|
1550
|
+
<th
|
|
1551
|
+
class="sv-grid-column sv-grid-column-spacer"
|
|
1552
|
+
aria-hidden="true"
|
|
1553
|
+
style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
|
|
1554
|
+
></th>
|
|
1555
|
+
{/if}
|
|
1556
|
+
</tr>
|
|
1557
|
+
{#if showFilterRowEffective}
|
|
1558
|
+
<tr {...getGridRowA11yProps()}>
|
|
1559
|
+
{#if showRowNumbersEffective}
|
|
1560
|
+
<th
|
|
1561
|
+
class="sv-grid-column sv-grid-row-number-column"
|
|
1562
|
+
style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
|
|
1563
|
+
></th>
|
|
1564
|
+
{/if}
|
|
1565
|
+
{#if showRowSelectionEffective}
|
|
1566
|
+
<th
|
|
1567
|
+
class="sv-grid-column sv-grid-selection-column"
|
|
1568
|
+
style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
|
|
1569
|
+
></th>
|
|
1570
|
+
{/if}
|
|
1571
|
+
{#if columnVirtualizationEnabled && columnWindowStart > 0}
|
|
1572
|
+
<th
|
|
1573
|
+
class="sv-grid-column sv-grid-column-spacer"
|
|
1574
|
+
aria-hidden="true"
|
|
1575
|
+
style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
|
|
1576
|
+
></th>
|
|
1577
|
+
{/if}
|
|
1578
|
+
{#each renderedColumns as rendered (rendered.column.id)}
|
|
1579
|
+
{@const activeOperator =
|
|
1580
|
+
filterMenuValues[rendered.column.id]?.operator ??
|
|
1581
|
+
defaultOperatorFor(rendered.column)}
|
|
1582
|
+
<th
|
|
1583
|
+
class="sv-grid-column"
|
|
1584
|
+
data-pinned={isColumnPinned(rendered.column.id) ??
|
|
1585
|
+
undefined}
|
|
1586
|
+
style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
|
|
1587
|
+
>
|
|
1588
|
+
<div class="sv-grid-filter-row-control">
|
|
1589
|
+
<button
|
|
1590
|
+
type="button"
|
|
1591
|
+
class="sv-grid-filter-operator-btn"
|
|
1592
|
+
class:is-open={operatorMenuFor === rendered.column.id}
|
|
1593
|
+
title={`Condition: ${operatorOption(activeOperator).label}`}
|
|
1594
|
+
aria-label={`Filter condition: ${operatorOption(activeOperator).label}`}
|
|
1595
|
+
onclick={(event) =>
|
|
1596
|
+
openOperatorMenu(event, rendered.column.id)}
|
|
1597
|
+
>
|
|
1598
|
+
<span class="sv-grid-header-icon"
|
|
1599
|
+
>{@render icon(
|
|
1600
|
+
operatorOption(activeOperator).iconName,
|
|
1601
|
+
)}</span
|
|
1602
|
+
>
|
|
1603
|
+
<span class="sv-grid-caret"
|
|
1604
|
+
>{@render icon("chevron-down")}</span
|
|
1605
|
+
>
|
|
1606
|
+
</button>
|
|
1607
|
+
{#if activeOperator !== "isBlank"}
|
|
1608
|
+
{@const frType = getEditorInputType(
|
|
1609
|
+
rendered.column.columnDef.editorType ?? "text",
|
|
1610
|
+
)}
|
|
1611
|
+
<input
|
|
1612
|
+
class="sv-grid-filter-value"
|
|
1613
|
+
type={frType}
|
|
1614
|
+
placeholder={activeOperator === "between"
|
|
1615
|
+
? "From"
|
|
1616
|
+
: "Filter…"}
|
|
1617
|
+
data-svgrid-filter-col={rendered.column.id}
|
|
1618
|
+
value={filterRowValues[rendered.column.id] ?? ""}
|
|
1619
|
+
oninput={(event) =>
|
|
1620
|
+
updateFilterRow(
|
|
1621
|
+
rendered.column.id,
|
|
1622
|
+
(event.currentTarget as HTMLInputElement).value,
|
|
1623
|
+
)}
|
|
1624
|
+
/>
|
|
1625
|
+
{#if activeOperator === "between"}
|
|
1626
|
+
<input
|
|
1627
|
+
class="sv-grid-filter-value sv-grid-filter-value-to"
|
|
1628
|
+
type={frType}
|
|
1629
|
+
placeholder="To"
|
|
1630
|
+
value={filterMenuValues[rendered.column.id]
|
|
1631
|
+
?.valueTo ?? ""}
|
|
1632
|
+
oninput={(event) =>
|
|
1633
|
+
updateFilterMenuValueTo(
|
|
1634
|
+
rendered.column.id,
|
|
1635
|
+
(event.currentTarget as HTMLInputElement)
|
|
1636
|
+
.value,
|
|
1637
|
+
)}
|
|
1638
|
+
/>
|
|
1639
|
+
{/if}
|
|
1640
|
+
{/if}
|
|
1641
|
+
</div>
|
|
1642
|
+
</th>
|
|
1643
|
+
{/each}
|
|
1644
|
+
{#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
|
|
1645
|
+
<th
|
|
1646
|
+
class="sv-grid-column sv-grid-column-spacer"
|
|
1647
|
+
aria-hidden="true"
|
|
1648
|
+
style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
|
|
1649
|
+
></th>
|
|
1650
|
+
{/if}
|
|
1651
|
+
</tr>
|
|
1652
|
+
{/if}
|
|
1653
|
+
{/each}
|
|
1654
|
+
</thead>
|
|
1655
|
+
{#if props.pinnedTopRows && props.pinnedTopRows.length > 0}
|
|
1656
|
+
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
1657
|
+
<tbody
|
|
1658
|
+
class="sv-grid-pinned sv-grid-pinned-top-body"
|
|
1659
|
+
role="rowgroup"
|
|
1660
|
+
>
|
|
1661
|
+
{#each props.pinnedTopRows as r, i (i)}
|
|
1662
|
+
{@render pinnedRowBody(r, "top", i)}
|
|
1663
|
+
{/each}
|
|
1664
|
+
</tbody>
|
|
1665
|
+
{/if}
|
|
1666
|
+
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
1667
|
+
<tbody
|
|
1668
|
+
class="sv-grid-body"
|
|
1669
|
+
role="rowgroup"
|
|
1670
|
+
ondragover={rowDragManagedEffective
|
|
1671
|
+
? onRowsContainerDragOver
|
|
1672
|
+
: undefined}
|
|
1673
|
+
ondrop={rowDragManagedEffective ? onRowsContainerDrop : undefined}
|
|
1674
|
+
>
|
|
1675
|
+
{#if !allRows.length && !(props.loading && props.loadingOverlay)}
|
|
1676
|
+
<tr class="sv-grid-row sv-grid-empty-row">
|
|
1677
|
+
<td
|
|
1678
|
+
class="sv-grid-cell sv-grid-empty-cell"
|
|
1679
|
+
colSpan={allColumns.length +
|
|
1680
|
+
(showRowNumbersEffective ? 1 : 0) +
|
|
1681
|
+
(showRowSelectionEffective ? 1 : 0)}
|
|
1682
|
+
>
|
|
1683
|
+
{props.emptyMessage ?? "No rows to display."}
|
|
1684
|
+
</td>
|
|
1685
|
+
</tr>
|
|
1686
|
+
{:else if rowVirtualizationEnabled}
|
|
1687
|
+
{#if rowTopSpacer > 0}
|
|
1688
|
+
<tr class="sv-grid-row sv-grid-row-spacer" aria-hidden="true">
|
|
1689
|
+
<td
|
|
1690
|
+
class="sv-grid-cell sv-grid-cell-spacer"
|
|
1691
|
+
style={`height: ${rowTopSpacer}px; padding: 0; border: 0;`}
|
|
1692
|
+
colSpan={allColumns.length +
|
|
1693
|
+
(showRowNumbersEffective ? 1 : 0) +
|
|
1694
|
+
(showRowSelectionEffective ? 1 : 0)}
|
|
1695
|
+
></td>
|
|
1696
|
+
</tr>
|
|
1697
|
+
{/if}
|
|
1698
|
+
{#each virtualRows as rowItem (rowItem.key)}
|
|
1699
|
+
{@const rowIndex = rowItem.index}
|
|
1700
|
+
{@const row = allRows[rowIndex]}
|
|
1701
|
+
{#if row}
|
|
1702
|
+
{#if props.isDetailRow?.(row.original as TData, rowIndex)}
|
|
1703
|
+
{@render detailRowMarkup(row, rowIndex)}
|
|
1704
|
+
{:else if isGroupRow(row)}
|
|
1705
|
+
<tr
|
|
1706
|
+
class="sv-grid-row sv-grid-group-row"
|
|
1707
|
+
class:sv-grid-row-selected={isRowSelected(row.id)}
|
|
1708
|
+
aria-level={row.depth + 1}
|
|
1709
|
+
aria-expanded={row.getIsExpanded?.() ? "true" : "false"}
|
|
1710
|
+
{...getGridRowA11yProps(rowIndex + 1)}
|
|
1711
|
+
style={`height: ${rowItem.size}px;`}
|
|
1712
|
+
>
|
|
1713
|
+
<td
|
|
1714
|
+
class="sv-grid-cell sv-grid-group-cell"
|
|
1715
|
+
class:sv-grid-cell-active={activeCell.rowIndex ===
|
|
1716
|
+
rowIndex}
|
|
1717
|
+
colspan={allColumns.length +
|
|
1718
|
+
(showRowNumbersEffective ? 1 : 0) +
|
|
1719
|
+
(showRowSelectionEffective ? 1 : 0)}
|
|
1720
|
+
onclick={() => row.toggleExpanded?.()}
|
|
1721
|
+
>
|
|
1722
|
+
{@render groupRowContent(row)}
|
|
1723
|
+
</td>
|
|
1724
|
+
</tr>
|
|
1725
|
+
{:else}
|
|
1726
|
+
{@const userRowClass = computeRowClass(row, rowIndex)}
|
|
1727
|
+
<tr
|
|
1728
|
+
class={`sv-grid-row ${userRowClass} ${rowDropClass(rowIndex)}`}
|
|
1729
|
+
class:sv-grid-row-selected={isRowSelected(row.id)}
|
|
1730
|
+
class:sv-grid-row-alt={props.zebraRows &&
|
|
1731
|
+
rowIndex % 2 === 1}
|
|
1732
|
+
class:sv-grid-row-draggable={rowDragManagedEffective}
|
|
1733
|
+
{...getGridRowA11yProps(rowIndex + 1)}
|
|
1734
|
+
{...rowDragAttrs(rowIndex)}
|
|
1735
|
+
style={`height: ${rowItem.size}px;`}
|
|
1736
|
+
>
|
|
1737
|
+
{#if showRowNumbersEffective}
|
|
1738
|
+
<td
|
|
1739
|
+
class="sv-grid-cell sv-grid-row-number-cell"
|
|
1740
|
+
style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
|
|
1741
|
+
>{rowIndex + 1}</td
|
|
1742
|
+
>
|
|
1743
|
+
{/if}
|
|
1744
|
+
{#if showRowSelectionEffective}
|
|
1745
|
+
<td
|
|
1746
|
+
class="sv-grid-cell sv-grid-selection-cell"
|
|
1747
|
+
style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
|
|
1748
|
+
onclick={() => toggleRowSelectionById(row.id)}
|
|
1749
|
+
>
|
|
1750
|
+
<button
|
|
1751
|
+
type="button"
|
|
1752
|
+
class="sv-grid-checkbox"
|
|
1753
|
+
role="checkbox"
|
|
1754
|
+
aria-checked={isRowSelected(row.id)}
|
|
1755
|
+
aria-label="Select row"
|
|
1756
|
+
onclick={(event) => {
|
|
1757
|
+
event.stopPropagation();
|
|
1758
|
+
toggleRowSelectionById(row.id);
|
|
1759
|
+
}}
|
|
1760
|
+
onkeydown={(event) =>
|
|
1761
|
+
toggleCheckboxWithKeyboard(event, () => {
|
|
1762
|
+
event.stopPropagation();
|
|
1763
|
+
toggleRowSelectionById(row.id);
|
|
1764
|
+
})}
|
|
1765
|
+
></button>
|
|
1766
|
+
</td>
|
|
1767
|
+
{/if}
|
|
1768
|
+
{#if columnVirtualizationEnabled && columnWindowStart > 0}
|
|
1769
|
+
<td
|
|
1770
|
+
class="sv-grid-cell sv-grid-cell-spacer"
|
|
1771
|
+
aria-hidden="true"
|
|
1772
|
+
style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
|
|
1773
|
+
></td>
|
|
1774
|
+
{/if}
|
|
1775
|
+
{#each renderedColumns as rendered (rendered.column.id)}
|
|
1776
|
+
{@const colIndex = rendered.item.index}
|
|
1777
|
+
{@const baseValue = getColumnBaseValue(
|
|
1778
|
+
row,
|
|
1779
|
+
rendered.column,
|
|
1780
|
+
)}
|
|
1781
|
+
{@const cellValue = getCellDisplayValue(
|
|
1782
|
+
row.id,
|
|
1783
|
+
rendered.column.id,
|
|
1784
|
+
baseValue,
|
|
1785
|
+
)}
|
|
1786
|
+
{@const isEditing =
|
|
1787
|
+
ctrl.editingCell?.rowId === row.id &&
|
|
1788
|
+
ctrl.editingCell?.columnId === rendered.column.id}
|
|
1789
|
+
{@const inRowEdit =
|
|
1790
|
+
!!fullRowEdit &&
|
|
1791
|
+
fullRowEdit.rowId === row.id &&
|
|
1792
|
+
rendered.column.id in fullRowEdit.draft}
|
|
1793
|
+
{@const rangeEdges = getCellRangeEdges(
|
|
1794
|
+
rowIndex,
|
|
1795
|
+
colIndex,
|
|
1796
|
+
)}
|
|
1797
|
+
{@const hasFillHandle =
|
|
1798
|
+
fillHandleCell &&
|
|
1799
|
+
fillHandleCell.rowIndex === rowIndex &&
|
|
1800
|
+
fillHandleCell.colIndex === colIndex}
|
|
1801
|
+
{@const userCellClass = computeCellClass(
|
|
1802
|
+
row,
|
|
1803
|
+
rendered.column,
|
|
1804
|
+
)}
|
|
1805
|
+
{@const cellTooltip = computeCellTooltip(
|
|
1806
|
+
row,
|
|
1807
|
+
rendered.column,
|
|
1808
|
+
)}
|
|
1809
|
+
{@const cellNote = computeCellNote(
|
|
1810
|
+
row,
|
|
1811
|
+
rendered.column,
|
|
1812
|
+
)}
|
|
1813
|
+
<td
|
|
1814
|
+
class={`sv-grid-cell ${userCellClass}`}
|
|
1815
|
+
class:sv-grid-cell-editing={isEditing || inRowEdit}
|
|
1816
|
+
class:sv-grid-cell-active={activeCell.rowIndex ===
|
|
1817
|
+
rowIndex && activeCell.colIndex === colIndex}
|
|
1818
|
+
class:sv-grid-cell-has-fill-handle={hasFillHandle}
|
|
1819
|
+
class:sv-grid-cell-cf={hasConditionalFormats}
|
|
1820
|
+
class:sv-grid-cell-has-note={cellNote != null}
|
|
1821
|
+
data-svgrid-row={rowIndex}
|
|
1822
|
+
data-svgrid-col={colIndex}
|
|
1823
|
+
data-col-id={rendered.column.id}
|
|
1824
|
+
data-align={getColumnAlign(rendered.column)}
|
|
1825
|
+
data-pinned={isColumnPinned(rendered.column.id) ??
|
|
1826
|
+
undefined}
|
|
1827
|
+
data-selected-range={rangeEdges ? "true" : undefined}
|
|
1828
|
+
data-range-top={rangeEdges?.top ? "true" : undefined}
|
|
1829
|
+
data-range-bottom={rangeEdges?.bottom
|
|
1830
|
+
? "true"
|
|
1831
|
+
: undefined}
|
|
1832
|
+
data-range-left={rangeEdges?.left
|
|
1833
|
+
? "true"
|
|
1834
|
+
: undefined}
|
|
1835
|
+
data-range-right={rangeEdges?.right
|
|
1836
|
+
? "true"
|
|
1837
|
+
: undefined}
|
|
1838
|
+
data-fill-preview={isInFillPreview(rowIndex, colIndex)
|
|
1839
|
+
? "true"
|
|
1840
|
+
: undefined}
|
|
1841
|
+
style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
|
|
1842
|
+
onpointerdown={(event) =>
|
|
1843
|
+
onCellPointerDown(rowIndex, colIndex, event)}
|
|
1844
|
+
onpointerenter={() =>
|
|
1845
|
+
onCellPointerEnter(rowIndex, colIndex)}
|
|
1846
|
+
ondblclick={() =>
|
|
1847
|
+
emitCellDoubleClick(rowIndex, colIndex)}
|
|
1848
|
+
onclick={() => onCellClick(rowIndex, colIndex)}
|
|
1849
|
+
oncontextmenu={(event) =>
|
|
1850
|
+
openContextMenu(
|
|
1851
|
+
event,
|
|
1852
|
+
rowIndex,
|
|
1853
|
+
colIndex,
|
|
1854
|
+
rendered.column.id,
|
|
1855
|
+
)}
|
|
1856
|
+
use:cellFlashAction={{
|
|
1857
|
+
rowId: row.id,
|
|
1858
|
+
value: cellValue,
|
|
1859
|
+
active: !!rendered.column.columnDef.cellFlash,
|
|
1860
|
+
className: flashClassFor(
|
|
1861
|
+
rendered.column.columnDef.cellFlash,
|
|
1862
|
+
),
|
|
1863
|
+
}}
|
|
1864
|
+
{...getGridCellA11yProps({
|
|
1865
|
+
id: getGridCellDomId("svgrid", rowIndex, colIndex),
|
|
1866
|
+
rowIndex: rowIndex + 1,
|
|
1867
|
+
colIndex: colIndex + 1,
|
|
1868
|
+
selected: isRowSelected(row.id),
|
|
1869
|
+
})}
|
|
1870
|
+
>
|
|
1871
|
+
{#if inRowEdit}
|
|
1872
|
+
{@render fullRowEditor(rendered.column, row)}
|
|
1873
|
+
{:else if isEditing}
|
|
1874
|
+
{@render editorBody(rendered.column, row)}
|
|
1875
|
+
{:else}
|
|
1876
|
+
{@render cellBodyWithFormat(
|
|
1877
|
+
row,
|
|
1878
|
+
rendered.column,
|
|
1879
|
+
cellValue,
|
|
1880
|
+
)}
|
|
1881
|
+
{/if}
|
|
1882
|
+
{#if !isEditing && fillHandleCell && fillHandleCell.rowIndex === rowIndex && fillHandleCell.colIndex === colIndex}
|
|
1883
|
+
<!-- Excel-style fill handle: drag down/right to
|
|
1884
|
+
extend the selection and pattern-fill the new
|
|
1885
|
+
cells on release. Rendered inside the bottom-
|
|
1886
|
+
right cell of the selection range (or active
|
|
1887
|
+
cell if there's no range). -->
|
|
1888
|
+
<div
|
|
1889
|
+
class="sv-grid-fill-handle"
|
|
1890
|
+
role="button"
|
|
1891
|
+
aria-label="Fill handle"
|
|
1892
|
+
onpointerdown={(event) =>
|
|
1893
|
+
startFillDrag(event, rowIndex, colIndex)}
|
|
1894
|
+
></div>
|
|
1895
|
+
{/if}
|
|
1896
|
+
{#if cellNote != null && !isEditing}
|
|
1897
|
+
<span
|
|
1898
|
+
class="sv-grid-cell-note-corner"
|
|
1899
|
+
aria-label="Note"
|
|
1900
|
+
onpointerenter={(event) => {
|
|
1901
|
+
event.stopPropagation();
|
|
1902
|
+
showTooltipFor(
|
|
1903
|
+
event.currentTarget as HTMLElement,
|
|
1904
|
+
cellNote,
|
|
1905
|
+
);
|
|
1906
|
+
}}
|
|
1907
|
+
onpointerleave={(event) => {
|
|
1908
|
+
event.stopPropagation();
|
|
1909
|
+
hideTooltip();
|
|
1910
|
+
}}
|
|
1911
|
+
></span>
|
|
1912
|
+
{/if}
|
|
1913
|
+
</td>
|
|
1914
|
+
{/each}
|
|
1915
|
+
{#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
|
|
1916
|
+
<td
|
|
1917
|
+
class="sv-grid-cell sv-grid-cell-spacer"
|
|
1918
|
+
aria-hidden="true"
|
|
1919
|
+
style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
|
|
1920
|
+
></td>
|
|
1921
|
+
{/if}
|
|
1922
|
+
</tr>
|
|
1923
|
+
{/if}
|
|
1924
|
+
{/if}
|
|
1925
|
+
{/each}
|
|
1926
|
+
{#if rowBottomSpacer > 0}
|
|
1927
|
+
<tr class="sv-grid-row sv-grid-row-spacer" aria-hidden="true">
|
|
1928
|
+
<td
|
|
1929
|
+
class="sv-grid-cell sv-grid-cell-spacer"
|
|
1930
|
+
style={`height: ${rowBottomSpacer}px; padding: 0; border: 0;`}
|
|
1931
|
+
colSpan={allColumns.length +
|
|
1932
|
+
(showRowNumbersEffective ? 1 : 0) +
|
|
1933
|
+
(showRowSelectionEffective ? 1 : 0)}
|
|
1934
|
+
></td>
|
|
1935
|
+
</tr>
|
|
1936
|
+
{/if}
|
|
1937
|
+
{:else}
|
|
1938
|
+
{#each allRows as row, rowIndex (row.id)}
|
|
1939
|
+
{#if props.isDetailRow?.(row.original as TData, rowIndex)}
|
|
1940
|
+
{@render detailRowMarkup(row, rowIndex)}
|
|
1941
|
+
{:else if isGroupRow(row)}
|
|
1942
|
+
<tr
|
|
1943
|
+
class="sv-grid-row sv-grid-group-row"
|
|
1944
|
+
class:sv-grid-row-selected={isRowSelected(row.id)}
|
|
1945
|
+
aria-level={row.depth + 1}
|
|
1946
|
+
aria-expanded={row.getIsExpanded?.() ? "true" : "false"}
|
|
1947
|
+
{...getGridRowA11yProps(rowIndex + 1)}
|
|
1948
|
+
>
|
|
1949
|
+
<td
|
|
1950
|
+
class="sv-grid-cell sv-grid-group-cell"
|
|
1951
|
+
class:sv-grid-cell-active={activeCell.rowIndex ===
|
|
1952
|
+
rowIndex}
|
|
1953
|
+
colspan={allColumns.length +
|
|
1954
|
+
(showRowNumbersEffective ? 1 : 0) +
|
|
1955
|
+
(showRowSelectionEffective ? 1 : 0)}
|
|
1956
|
+
onclick={() => row.toggleExpanded?.()}
|
|
1957
|
+
>
|
|
1958
|
+
{@render groupRowContent(row)}
|
|
1959
|
+
</td>
|
|
1960
|
+
</tr>
|
|
1961
|
+
{:else}
|
|
1962
|
+
{@const userRowClass = computeRowClass(row, rowIndex)}
|
|
1963
|
+
<tr
|
|
1964
|
+
class={`sv-grid-row ${userRowClass} ${rowDropClass(rowIndex)}`}
|
|
1965
|
+
class:sv-grid-row-selected={isRowSelected(row.id)}
|
|
1966
|
+
class:sv-grid-row-alt={props.zebraRows &&
|
|
1967
|
+
rowIndex % 2 === 1}
|
|
1968
|
+
class:sv-grid-row-draggable={rowDragManagedEffective}
|
|
1969
|
+
{...getGridRowA11yProps(rowIndex + 1)}
|
|
1970
|
+
{...rowDragAttrs(rowIndex)}
|
|
1971
|
+
>
|
|
1972
|
+
{#if showRowNumbersEffective}
|
|
1973
|
+
<td
|
|
1974
|
+
class="sv-grid-cell sv-grid-row-number-cell"
|
|
1975
|
+
style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
|
|
1976
|
+
>{rowIndex + 1}</td
|
|
1977
|
+
>
|
|
1978
|
+
{/if}
|
|
1979
|
+
{#if showRowSelectionEffective}
|
|
1980
|
+
<td
|
|
1981
|
+
class="sv-grid-cell sv-grid-selection-cell"
|
|
1982
|
+
style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
|
|
1983
|
+
onclick={() => toggleRowSelectionById(row.id)}
|
|
1984
|
+
>
|
|
1985
|
+
<button
|
|
1986
|
+
type="button"
|
|
1987
|
+
class="sv-grid-checkbox"
|
|
1988
|
+
role="checkbox"
|
|
1989
|
+
aria-checked={isRowSelected(row.id)}
|
|
1990
|
+
aria-label="Select row"
|
|
1991
|
+
onclick={(event) => {
|
|
1992
|
+
event.stopPropagation();
|
|
1993
|
+
toggleRowSelectionById(row.id);
|
|
1994
|
+
}}
|
|
1995
|
+
onkeydown={(event) =>
|
|
1996
|
+
toggleCheckboxWithKeyboard(event, () => {
|
|
1997
|
+
event.stopPropagation();
|
|
1998
|
+
toggleRowSelectionById(row.id);
|
|
1999
|
+
})}
|
|
2000
|
+
></button>
|
|
2001
|
+
</td>
|
|
2002
|
+
{/if}
|
|
2003
|
+
{#if columnVirtualizationEnabled && columnWindowStart > 0}
|
|
2004
|
+
<td
|
|
2005
|
+
class="sv-grid-cell sv-grid-cell-spacer"
|
|
2006
|
+
aria-hidden="true"
|
|
2007
|
+
style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
|
|
2008
|
+
></td>
|
|
2009
|
+
{/if}
|
|
2010
|
+
{#each renderedColumns as rendered (rendered.column.id)}
|
|
2011
|
+
{@const colIndex = rendered.item.index}
|
|
2012
|
+
{@const baseValue = getColumnBaseValue(
|
|
2013
|
+
row,
|
|
2014
|
+
rendered.column,
|
|
2015
|
+
)}
|
|
2016
|
+
{@const cellValue = getCellDisplayValue(
|
|
2017
|
+
row.id,
|
|
2018
|
+
rendered.column.id,
|
|
2019
|
+
baseValue,
|
|
2020
|
+
)}
|
|
2021
|
+
{@const isEditing =
|
|
2022
|
+
ctrl.editingCell?.rowId === row.id &&
|
|
2023
|
+
ctrl.editingCell?.columnId === rendered.column.id}
|
|
2024
|
+
{@const inRowEdit =
|
|
2025
|
+
!!fullRowEdit &&
|
|
2026
|
+
fullRowEdit.rowId === row.id &&
|
|
2027
|
+
rendered.column.id in fullRowEdit.draft}
|
|
2028
|
+
{@const rangeEdges = getCellRangeEdges(
|
|
2029
|
+
rowIndex,
|
|
2030
|
+
colIndex,
|
|
2031
|
+
)}
|
|
2032
|
+
{@const userCellClass = computeCellClass(
|
|
2033
|
+
row,
|
|
2034
|
+
rendered.column,
|
|
2035
|
+
)}
|
|
2036
|
+
{@const cellTooltip = computeCellTooltip(
|
|
2037
|
+
row,
|
|
2038
|
+
rendered.column,
|
|
2039
|
+
)}
|
|
2040
|
+
{@const cellNote = computeCellNote(row, rendered.column)}
|
|
2041
|
+
<td
|
|
2042
|
+
class={`sv-grid-cell ${userCellClass}`}
|
|
2043
|
+
class:sv-grid-cell-editing={isEditing || inRowEdit}
|
|
2044
|
+
class:sv-grid-cell-active={activeCell.rowIndex ===
|
|
2045
|
+
rowIndex && activeCell.colIndex === colIndex}
|
|
2046
|
+
class:sv-grid-cell-cf={hasConditionalFormats}
|
|
2047
|
+
class:sv-grid-cell-has-note={cellNote != null}
|
|
2048
|
+
data-svgrid-row={rowIndex}
|
|
2049
|
+
data-svgrid-col={colIndex}
|
|
2050
|
+
data-col-id={rendered.column.id}
|
|
2051
|
+
data-pinned={isColumnPinned(rendered.column.id) ??
|
|
2052
|
+
undefined}
|
|
2053
|
+
data-selected-range={rangeEdges ? "true" : undefined}
|
|
2054
|
+
data-range-top={rangeEdges?.top ? "true" : undefined}
|
|
2055
|
+
data-range-bottom={rangeEdges?.bottom
|
|
2056
|
+
? "true"
|
|
2057
|
+
: undefined}
|
|
2058
|
+
data-range-left={rangeEdges?.left ? "true" : undefined}
|
|
2059
|
+
data-range-right={rangeEdges?.right
|
|
2060
|
+
? "true"
|
|
2061
|
+
: undefined}
|
|
2062
|
+
style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
|
|
2063
|
+
onpointerdown={(event) =>
|
|
2064
|
+
onCellPointerDown(rowIndex, colIndex, event)}
|
|
2065
|
+
onpointerenter={(event) => {
|
|
2066
|
+
onCellPointerEnter(rowIndex, colIndex);
|
|
2067
|
+
// Column tooltip fires on whole-cell hover.
|
|
2068
|
+
// Per-cell notes are gated on the corner hot-
|
|
2069
|
+
// zone below (Excel-style: hover the small
|
|
2070
|
+
// triangle to read the note).
|
|
2071
|
+
if (cellTooltip)
|
|
2072
|
+
showTooltipFor(
|
|
2073
|
+
event.currentTarget as HTMLElement,
|
|
2074
|
+
cellTooltip,
|
|
2075
|
+
);
|
|
2076
|
+
}}
|
|
2077
|
+
onpointerleave={hideTooltip}
|
|
2078
|
+
ondblclick={() =>
|
|
2079
|
+
emitCellDoubleClick(rowIndex, colIndex)}
|
|
2080
|
+
onclick={() => onCellClick(rowIndex, colIndex)}
|
|
2081
|
+
oncontextmenu={(event) =>
|
|
2082
|
+
openContextMenu(
|
|
2083
|
+
event,
|
|
2084
|
+
rowIndex,
|
|
2085
|
+
colIndex,
|
|
2086
|
+
rendered.column.id,
|
|
2087
|
+
)}
|
|
2088
|
+
use:cellFlashAction={{
|
|
2089
|
+
rowId: row.id,
|
|
2090
|
+
value: cellValue,
|
|
2091
|
+
active: !!rendered.column.columnDef.cellFlash,
|
|
2092
|
+
className: flashClassFor(
|
|
2093
|
+
rendered.column.columnDef.cellFlash,
|
|
2094
|
+
),
|
|
2095
|
+
}}
|
|
2096
|
+
{...getGridCellA11yProps({
|
|
2097
|
+
id: getGridCellDomId("svgrid", rowIndex, colIndex),
|
|
2098
|
+
rowIndex: rowIndex + 1,
|
|
2099
|
+
colIndex: colIndex + 1,
|
|
2100
|
+
selected: isRowSelected(row.id),
|
|
2101
|
+
})}
|
|
2102
|
+
>
|
|
2103
|
+
{#if inRowEdit}
|
|
2104
|
+
{@render fullRowEditor(rendered.column, row)}
|
|
2105
|
+
{:else if isEditing}
|
|
2106
|
+
{@render editorBody(rendered.column, row)}
|
|
2107
|
+
{:else}
|
|
2108
|
+
{@render cellBodyWithFormat(
|
|
2109
|
+
row,
|
|
2110
|
+
rendered.column,
|
|
2111
|
+
cellValue,
|
|
2112
|
+
)}
|
|
2113
|
+
{/if}
|
|
2114
|
+
{#if cellNote != null && !isEditing}
|
|
2115
|
+
<!-- Excel-style per-cell note indicator. The
|
|
2116
|
+
triangle itself is the hot-zone; hover
|
|
2117
|
+
just the corner to see the note (Excel
|
|
2118
|
+
red-dot behaviour). The cell-level
|
|
2119
|
+
tooltip handler only shows the column
|
|
2120
|
+
tooltip, so the two surfaces stay
|
|
2121
|
+
separate. -->
|
|
2122
|
+
<span
|
|
2123
|
+
class="sv-grid-cell-note-corner"
|
|
2124
|
+
aria-label="Note"
|
|
2125
|
+
onpointerenter={(event) => {
|
|
2126
|
+
event.stopPropagation();
|
|
2127
|
+
showTooltipFor(
|
|
2128
|
+
event.currentTarget as HTMLElement,
|
|
2129
|
+
cellNote,
|
|
2130
|
+
);
|
|
2131
|
+
}}
|
|
2132
|
+
onpointerleave={(event) => {
|
|
2133
|
+
event.stopPropagation();
|
|
2134
|
+
hideTooltip();
|
|
2135
|
+
}}
|
|
2136
|
+
></span>
|
|
2137
|
+
{/if}
|
|
2138
|
+
</td>
|
|
2139
|
+
{/each}
|
|
2140
|
+
{#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
|
|
2141
|
+
<td
|
|
2142
|
+
class="sv-grid-cell sv-grid-cell-spacer"
|
|
2143
|
+
aria-hidden="true"
|
|
2144
|
+
style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
|
|
2145
|
+
></td>
|
|
2146
|
+
{/if}
|
|
2147
|
+
</tr>
|
|
2148
|
+
{/if}
|
|
2149
|
+
{/each}
|
|
2150
|
+
{/if}
|
|
2151
|
+
</tbody>
|
|
2152
|
+
{#if props.pinnedBottomRows && props.pinnedBottomRows.length > 0}
|
|
2153
|
+
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
2154
|
+
<tbody
|
|
2155
|
+
class="sv-grid-pinned sv-grid-pinned-bottom-body"
|
|
2156
|
+
role="rowgroup"
|
|
2157
|
+
>
|
|
2158
|
+
{#each props.pinnedBottomRows as r, i (i)}
|
|
2159
|
+
{@render pinnedRowBody(r, "bottom", i)}
|
|
2160
|
+
{/each}
|
|
2161
|
+
</tbody>
|
|
2162
|
+
{/if}
|
|
2163
|
+
{#if props.enableRowSummaries ?? true}
|
|
2164
|
+
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
2165
|
+
<tfoot class="sv-grid-foot" role="rowgroup">
|
|
2166
|
+
<tr
|
|
2167
|
+
class="sv-grid-row sv-grid-summary-row"
|
|
2168
|
+
{...getGridRowA11yProps()}
|
|
2169
|
+
>
|
|
2170
|
+
{#if showRowNumbersEffective}
|
|
2171
|
+
<!-- Row-number column has no aggregate; the digit it normally
|
|
2172
|
+
shows is the row index, which doesn't make sense to sum. -->
|
|
2173
|
+
<th
|
|
2174
|
+
class="sv-grid-column sv-grid-summary-column sv-grid-row-number-column"
|
|
2175
|
+
style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
|
|
2176
|
+
></th>
|
|
2177
|
+
{/if}
|
|
2178
|
+
{#if showRowSelectionEffective}
|
|
2179
|
+
<!-- Selection column is checkbox-only; no aggregate. -->
|
|
2180
|
+
<th
|
|
2181
|
+
class="sv-grid-column sv-grid-summary-column sv-grid-selection-column"
|
|
2182
|
+
style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
|
|
2183
|
+
></th>
|
|
2184
|
+
{/if}
|
|
2185
|
+
{#if columnVirtualizationEnabled && columnWindowStart > 0}
|
|
2186
|
+
<th
|
|
2187
|
+
class="sv-grid-column sv-grid-column-spacer"
|
|
2188
|
+
aria-hidden="true"
|
|
2189
|
+
style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
|
|
2190
|
+
></th>
|
|
2191
|
+
{/if}
|
|
2192
|
+
{#each renderedColumns as rendered (rendered.column.id)}
|
|
2193
|
+
<th
|
|
2194
|
+
class="sv-grid-column sv-grid-summary-column"
|
|
2195
|
+
data-pinned={isColumnPinned(rendered.column.id) ??
|
|
2196
|
+
undefined}
|
|
2197
|
+
style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
|
|
2198
|
+
>
|
|
2199
|
+
{summaryByColumn[rendered.column.id] ?? ""}
|
|
2200
|
+
</th>
|
|
2201
|
+
{/each}
|
|
2202
|
+
{#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
|
|
2203
|
+
<th
|
|
2204
|
+
class="sv-grid-column sv-grid-column-spacer"
|
|
2205
|
+
aria-hidden="true"
|
|
2206
|
+
style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
|
|
2207
|
+
></th>
|
|
2208
|
+
{/if}
|
|
2209
|
+
</tr>
|
|
2210
|
+
</tfoot>
|
|
2211
|
+
{/if}
|
|
2212
|
+
</table>
|
|
2213
|
+
</div>
|
|
2214
|
+
<!-- 16-px placeholder above the vertical scrollbar that matches the
|
|
2215
|
+
header height - fills the gap so the scrollbar starts exactly
|
|
2216
|
+
under the header row. Only needed when the vertical scrollbar
|
|
2217
|
+
is actually rendered; without this guard the block stays as a
|
|
2218
|
+
visible colored rectangle in the top-right corner even on
|
|
2219
|
+
demos with no vertical overflow. -->
|
|
2220
|
+
{#if hasMeasured && hasVerticalOverflow}
|
|
2221
|
+
<div
|
|
2222
|
+
class="sv-grid-scrollbar-corner"
|
|
2223
|
+
aria-hidden="true"
|
|
2224
|
+
style={`height: ${headerHeight}px;`}
|
|
2225
|
+
></div>
|
|
2226
|
+
{/if}
|
|
2227
|
+
<!-- Scrollbar attributes come from AUTHORITATIVE sources, not the
|
|
2228
|
+
DOM. `scrollMetrics.scrollHeight/scrollWidth` are read inside a
|
|
2229
|
+
Svelte derived during render - the browser hasn't painted yet,
|
|
2230
|
+
so those values lag one frame behind the real layout. The
|
|
2231
|
+
scrollbar's own `hidden`-check then trips on stale `content-size
|
|
2232
|
+
<= viewport-size`, disables itself, and the user can't drag it.
|
|
2233
|
+
Using `virtualRowTotalSize` / `totalColumnWidth` (which come
|
|
2234
|
+
straight from the virtualizers' authoritative state) avoids the
|
|
2235
|
+
lag - the scrollbar always sees the same overflow numbers our
|
|
2236
|
+
gating did. -->
|
|
2237
|
+
{#if hasMeasured && hasVerticalOverflow}
|
|
2238
|
+
<!-- content-size = the container's actual `scrollHeight`.
|
|
2239
|
+
The scroll container's real `scrollHeight` includes
|
|
2240
|
+
EVERYTHING in the scroll content: the sticky thead, the
|
|
2241
|
+
tbody (top spacer + rendered rows + bottom spacer), and a
|
|
2242
|
+
sticky tfoot if rendered. Computing it from
|
|
2243
|
+
`virtualRowTotalSize + headerHeight` works in the common
|
|
2244
|
+
case but undercounts when the footer is present or the
|
|
2245
|
+
header is more than one row, leaving the last few rows
|
|
2246
|
+
beyond the scrollbar's reach. We read it from the DOM
|
|
2247
|
+
once `hasMeasured` is true (i.e. after the first
|
|
2248
|
+
ResizeObserver tick), and fall back to the virtualizer
|
|
2249
|
+
math if for some reason the DOM read returns 0 (early
|
|
2250
|
+
render races). -->
|
|
2251
|
+
<sv-grid-scrollbar
|
|
2252
|
+
class="sv-grid-scrollbar sv-grid-scrollbar-vertical"
|
|
2253
|
+
bind:this={ctrl.verticalScrollbarEl}
|
|
2254
|
+
orientation="vertical"
|
|
2255
|
+
viewport-size={viewportHeight}
|
|
2256
|
+
content-size={scrollMetrics.scrollHeight ||
|
|
2257
|
+
rowDomTotalSize + headerHeight}
|
|
2258
|
+
value={scrollMetrics.scrollTop}
|
|
2259
|
+
step={typeof props.rowHeight === "number" ? props.rowHeight : 30}
|
|
2260
|
+
style={`top: ${headerHeight}px; height: calc(100% - ${headerHeight + (hasHorizontalOverflow ? 16 : 0)}px);`}
|
|
2261
|
+
></sv-grid-scrollbar>
|
|
2262
|
+
{/if}
|
|
2263
|
+
{#if hasMeasured && hasHorizontalOverflow}
|
|
2264
|
+
{@const horizontalContentSize =
|
|
2265
|
+
totalColumnWidth +
|
|
2266
|
+
(showRowNumbersEffective ? rowNumberColumnWidth : 0) +
|
|
2267
|
+
(showRowSelectionEffective ? selectionColumnWidth : 0)}
|
|
2268
|
+
<sv-grid-scrollbar
|
|
2269
|
+
class="sv-grid-scrollbar sv-grid-scrollbar-horizontal"
|
|
2270
|
+
bind:this={ctrl.horizontalScrollbarEl}
|
|
2271
|
+
orientation="horizontal"
|
|
2272
|
+
viewport-size={viewportWidth}
|
|
2273
|
+
content-size={scrollMetrics.scrollWidth || horizontalContentSize}
|
|
2274
|
+
value={scrollMetrics.scrollLeft}
|
|
2275
|
+
step={props.columnWidth ?? 140}
|
|
2276
|
+
style={`width: calc(100% - ${hasVerticalOverflow ? 16 : 0}px);`}
|
|
2277
|
+
></sv-grid-scrollbar>
|
|
2278
|
+
{/if}
|
|
2279
|
+
{#if hasMeasured && hasVerticalOverflow && hasHorizontalOverflow}
|
|
2280
|
+
<div class="sv-grid-scrollbar-corner-br" aria-hidden="true"></div>
|
|
2281
|
+
{/if}
|
|
2282
|
+
</div>
|
|
2283
|
+
|
|
2284
|
+
<GridFooter {ctrl} />
|
|
2285
|
+
|
|
2286
|
+
{#if ctrl.findOpen}
|
|
2287
|
+
<!-- Find-in-grid overlay. Anchored to the TOP of the grid root so
|
|
2288
|
+
it tracks the grid even when the page scrolls. Ctrl+F opens;
|
|
2289
|
+
Enter cycles to the next hit; Esc closes. -->
|
|
2290
|
+
<div class="sv-grid-find" role="search" aria-label="Find in grid">
|
|
2291
|
+
<svg class="sv-grid-find-icon" viewBox="0 0 16 16" aria-hidden="true">
|
|
2292
|
+
<circle
|
|
2293
|
+
cx="7"
|
|
2294
|
+
cy="7"
|
|
2295
|
+
r="4.5"
|
|
2296
|
+
fill="none"
|
|
2297
|
+
stroke="currentColor"
|
|
2298
|
+
stroke-width="1.5"
|
|
2299
|
+
/>
|
|
2300
|
+
<line
|
|
2301
|
+
x1="10.2"
|
|
2302
|
+
y1="10.2"
|
|
2303
|
+
x2="14"
|
|
2304
|
+
y2="14"
|
|
2305
|
+
stroke="currentColor"
|
|
2306
|
+
stroke-width="1.5"
|
|
2307
|
+
stroke-linecap="round"
|
|
2308
|
+
/>
|
|
2309
|
+
</svg>
|
|
2310
|
+
<input
|
|
2311
|
+
class="sv-grid-find-input"
|
|
2312
|
+
type="search"
|
|
2313
|
+
placeholder="Find in grid…"
|
|
2314
|
+
autofocus
|
|
2315
|
+
bind:value={ctrl.findQuery}
|
|
2316
|
+
oninput={() => (ctrl.findHitIndex = 0)}
|
|
2317
|
+
onkeydown={(event) => {
|
|
2318
|
+
event.stopPropagation();
|
|
2319
|
+
if (event.key === "Enter") {
|
|
2320
|
+
event.preventDefault();
|
|
2321
|
+
if (findHits.length === 0) return;
|
|
2322
|
+
ctrl.findHitIndex =
|
|
2323
|
+
(ctrl.findHitIndex +
|
|
2324
|
+
(event.shiftKey ? -1 : 1) +
|
|
2325
|
+
findHits.length) %
|
|
2326
|
+
findHits.length;
|
|
2327
|
+
const hit = findHits[ctrl.findHitIndex];
|
|
2328
|
+
if (hit) {
|
|
2329
|
+
setActiveCell(hit.rowIndex, hit.colIndex);
|
|
2330
|
+
scrollActiveCellIntoView(hit.rowIndex, hit.colIndex);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
if (event.key === "Escape") {
|
|
2334
|
+
event.preventDefault();
|
|
2335
|
+
ctrl.findOpen = false;
|
|
2336
|
+
ctrl.findQuery = "";
|
|
2337
|
+
}
|
|
2338
|
+
}}
|
|
2339
|
+
/>
|
|
2340
|
+
<span class="sv-grid-find-count">
|
|
2341
|
+
{findHits.length === 0 && ctrl.findQuery.trim()
|
|
2342
|
+
? "No matches"
|
|
2343
|
+
: findHits.length === 0
|
|
2344
|
+
? ""
|
|
2345
|
+
: `${ctrl.findHitIndex + 1} of ${findHits.length}`}
|
|
2346
|
+
</span>
|
|
2347
|
+
<button
|
|
2348
|
+
type="button"
|
|
2349
|
+
class="sv-grid-find-step"
|
|
2350
|
+
aria-label="Previous match"
|
|
2351
|
+
disabled={findHits.length === 0}
|
|
2352
|
+
onclick={() => {
|
|
2353
|
+
ctrl.findHitIndex =
|
|
2354
|
+
(ctrl.findHitIndex - 1 + findHits.length) % findHits.length;
|
|
2355
|
+
const hit = findHits[ctrl.findHitIndex];
|
|
2356
|
+
if (hit) {
|
|
2357
|
+
setActiveCell(hit.rowIndex, hit.colIndex);
|
|
2358
|
+
scrollActiveCellIntoView(hit.rowIndex, hit.colIndex);
|
|
2359
|
+
}
|
|
2360
|
+
}}>↑</button
|
|
2361
|
+
>
|
|
2362
|
+
<button
|
|
2363
|
+
type="button"
|
|
2364
|
+
class="sv-grid-find-step"
|
|
2365
|
+
aria-label="Next match"
|
|
2366
|
+
disabled={findHits.length === 0}
|
|
2367
|
+
onclick={() => {
|
|
2368
|
+
ctrl.findHitIndex = (ctrl.findHitIndex + 1) % findHits.length;
|
|
2369
|
+
const hit = findHits[ctrl.findHitIndex];
|
|
2370
|
+
if (hit) {
|
|
2371
|
+
setActiveCell(hit.rowIndex, hit.colIndex);
|
|
2372
|
+
scrollActiveCellIntoView(hit.rowIndex, hit.colIndex);
|
|
2373
|
+
}
|
|
2374
|
+
}}>↓</button
|
|
2375
|
+
>
|
|
2376
|
+
<button
|
|
2377
|
+
type="button"
|
|
2378
|
+
class="sv-grid-find-close"
|
|
2379
|
+
aria-label="Close find"
|
|
2380
|
+
onclick={() => {
|
|
2381
|
+
ctrl.findOpen = false;
|
|
2382
|
+
ctrl.findQuery = "";
|
|
2383
|
+
}}>✕</button
|
|
2384
|
+
>
|
|
2385
|
+
</div>
|
|
2386
|
+
{/if}
|
|
2387
|
+
|
|
2388
|
+
{#if props.loading && props.loadingOverlay}
|
|
2389
|
+
<div class="sv-grid-loading-overlay" role="status" aria-live="polite">
|
|
2390
|
+
<div class="sv-grid-loading-bar"></div>
|
|
2391
|
+
{#if allRows.length === 0}
|
|
2392
|
+
<div class="sv-grid-skeleton" aria-hidden="true">
|
|
2393
|
+
{#each Array(props.loadingSkeletonRows ?? 8) as _, r (r)}
|
|
2394
|
+
<div class="sv-grid-skeleton-row">
|
|
2395
|
+
{#each allColumns as col (col.id)}
|
|
2396
|
+
<div
|
|
2397
|
+
class="sv-grid-skeleton-cell"
|
|
2398
|
+
style={`width:${getColumnWidth(col.id)}px`}
|
|
2399
|
+
>
|
|
2400
|
+
<span class="sv-grid-skeleton-bar"></span>
|
|
2401
|
+
</div>
|
|
2402
|
+
{/each}
|
|
2403
|
+
</div>
|
|
2404
|
+
{/each}
|
|
2405
|
+
</div>
|
|
2406
|
+
{/if}
|
|
2407
|
+
<span class="sv-grid-sr-only">Loading…</span>
|
|
2408
|
+
</div>
|
|
2409
|
+
{/if}
|
|
2410
|
+
|
|
2411
|
+
{#if toolPanelEnabled}
|
|
2412
|
+
{#if ctrl.toolPanelOpen}
|
|
2413
|
+
{@const panelTab = ctrl.toolPanelTab}
|
|
2414
|
+
<aside class="sv-grid-tool-panel" aria-label="Tool panel">
|
|
2415
|
+
<div class="sv-grid-tool-panel-head">
|
|
2416
|
+
<span>{panelTab === "filters" ? "Filters" : "Columns"}</span>
|
|
2417
|
+
<button
|
|
2418
|
+
type="button"
|
|
2419
|
+
class="sv-grid-tool-panel-close"
|
|
2420
|
+
aria-label="Close"
|
|
2421
|
+
onclick={() => (ctrl.toolPanelOpen = false)}>✕</button
|
|
2422
|
+
>
|
|
2423
|
+
</div>
|
|
2424
|
+
<div class="sv-grid-tool-panel-tabs" role="tablist">
|
|
2425
|
+
<button
|
|
2426
|
+
type="button"
|
|
2427
|
+
class="sv-grid-tool-panel-tab"
|
|
2428
|
+
class:is-active={panelTab === "columns"}
|
|
2429
|
+
role="tab"
|
|
2430
|
+
aria-selected={panelTab === "columns"}
|
|
2431
|
+
onclick={() => (ctrl.toolPanelTab = "columns")}>Columns</button
|
|
2432
|
+
>
|
|
2433
|
+
<button
|
|
2434
|
+
type="button"
|
|
2435
|
+
class="sv-grid-tool-panel-tab"
|
|
2436
|
+
class:is-active={panelTab === "filters"}
|
|
2437
|
+
role="tab"
|
|
2438
|
+
aria-selected={panelTab === "filters"}
|
|
2439
|
+
onclick={() => (ctrl.toolPanelTab = "filters")}>Filters</button
|
|
2440
|
+
>
|
|
2441
|
+
</div>
|
|
2442
|
+
{#if panelTab === "columns"}
|
|
2443
|
+
<ul class="sv-grid-tool-panel-list">
|
|
2444
|
+
{#each toolPanelColumns as column, i (column.id)}
|
|
2445
|
+
{@const visible = !ctrl.hiddenColumns[column.id]}
|
|
2446
|
+
{@const grouped = groupingColumns.includes(column.id)}
|
|
2447
|
+
<li class="sv-grid-tool-panel-item">
|
|
2448
|
+
<label class="sv-grid-tool-panel-vis">
|
|
2449
|
+
<input
|
|
2450
|
+
type="checkbox"
|
|
2451
|
+
checked={visible}
|
|
2452
|
+
onchange={() => toggleColumnVisibleInPanel(column.id)}
|
|
2453
|
+
/>
|
|
2454
|
+
<span class="sv-grid-tool-panel-name"
|
|
2455
|
+
>{toolPanelHeaderLabel(column)}</span
|
|
2456
|
+
>
|
|
2457
|
+
</label>
|
|
2458
|
+
<span class="sv-grid-tool-panel-actions">
|
|
2459
|
+
<button
|
|
2460
|
+
type="button"
|
|
2461
|
+
class="sv-grid-tool-panel-btn"
|
|
2462
|
+
class:is-active={grouped}
|
|
2463
|
+
aria-label={grouped ? "Ungroup" : "Group by"}
|
|
2464
|
+
title={grouped ? "Ungroup" : "Group by this column"}
|
|
2465
|
+
onclick={() => toggleGroupInPanel(column.id)}>⊞</button
|
|
2466
|
+
>
|
|
2467
|
+
<button
|
|
2468
|
+
type="button"
|
|
2469
|
+
class="sv-grid-tool-panel-btn"
|
|
2470
|
+
aria-label="Move up"
|
|
2471
|
+
disabled={i === 0}
|
|
2472
|
+
onclick={() => moveColumnInPanel(column.id, -1)}>↑</button
|
|
2473
|
+
>
|
|
2474
|
+
<button
|
|
2475
|
+
type="button"
|
|
2476
|
+
class="sv-grid-tool-panel-btn"
|
|
2477
|
+
aria-label="Move down"
|
|
2478
|
+
disabled={i === toolPanelColumns.length - 1}
|
|
2479
|
+
onclick={() => moveColumnInPanel(column.id, 1)}>↓</button
|
|
2480
|
+
>
|
|
2481
|
+
</span>
|
|
2482
|
+
</li>
|
|
2483
|
+
{/each}
|
|
2484
|
+
</ul>
|
|
2485
|
+
{:else}
|
|
2486
|
+
<!-- Filters tab: one filter control per filterable column, sharing
|
|
2487
|
+
the same filterMenuValues state as the column menu / filter row. -->
|
|
2488
|
+
<div class="sv-grid-tool-panel-filters">
|
|
2489
|
+
{#each toolPanelColumns as column (column.id)}
|
|
2490
|
+
{#if column.columnDef.field && column.columnDef.filterable !== false}
|
|
2491
|
+
{@const active =
|
|
2492
|
+
filterMenuValues[column.id]?.operator ??
|
|
2493
|
+
defaultOperatorFor(column)}
|
|
2494
|
+
{@const fType = getEditorInputType(
|
|
2495
|
+
column.columnDef.editorType ?? "text",
|
|
2496
|
+
)}
|
|
2497
|
+
<div
|
|
2498
|
+
class="sv-grid-tp-filter"
|
|
2499
|
+
class:is-filtered={isColumnFiltered(column.id)}
|
|
2500
|
+
>
|
|
2501
|
+
<div class="sv-grid-tp-filter-head">
|
|
2502
|
+
<span class="sv-grid-tp-filter-name"
|
|
2503
|
+
>{toolPanelHeaderLabel(column)}</span
|
|
2504
|
+
>
|
|
2505
|
+
{#if isColumnFiltered(column.id)}
|
|
2506
|
+
<button
|
|
2507
|
+
type="button"
|
|
2508
|
+
class="sv-grid-tp-filter-clear"
|
|
2509
|
+
aria-label={`Clear ${toolPanelHeaderLabel(column)} filter`}
|
|
2510
|
+
title="Clear filter"
|
|
2511
|
+
onclick={() => clearColumnFilter(column.id)}>✕</button
|
|
2512
|
+
>
|
|
2513
|
+
{/if}
|
|
2514
|
+
</div>
|
|
2515
|
+
<select
|
|
2516
|
+
class="sv-grid-tp-filter-op"
|
|
2517
|
+
aria-label={`${toolPanelHeaderLabel(column)} filter condition`}
|
|
2518
|
+
value={active}
|
|
2519
|
+
onchange={(e) =>
|
|
2520
|
+
updateFilterOperator(
|
|
2521
|
+
column.id,
|
|
2522
|
+
e.currentTarget.value as FilterOperator,
|
|
2523
|
+
)}
|
|
2524
|
+
>
|
|
2525
|
+
{#each operatorsForColumn(column) as option (option.value)}
|
|
2526
|
+
<option value={option.value}
|
|
2527
|
+
>{operatorOption(option.value).label}</option
|
|
2528
|
+
>
|
|
2529
|
+
{/each}
|
|
2530
|
+
</select>
|
|
2531
|
+
{#if active !== "isBlank"}
|
|
2532
|
+
<input
|
|
2533
|
+
class="sv-grid-tp-filter-input"
|
|
2534
|
+
type={fType}
|
|
2535
|
+
placeholder={active === "between" ? "From" : "Filter…"}
|
|
2536
|
+
value={filterMenuValues[column.id]?.value ?? ""}
|
|
2537
|
+
oninput={(e) =>
|
|
2538
|
+
updateFilterMenuValue(
|
|
2539
|
+
column.id,
|
|
2540
|
+
e.currentTarget.value,
|
|
2541
|
+
)}
|
|
2542
|
+
/>
|
|
2543
|
+
{#if active === "between"}
|
|
2544
|
+
<input
|
|
2545
|
+
class="sv-grid-tp-filter-input"
|
|
2546
|
+
type={fType}
|
|
2547
|
+
placeholder="To"
|
|
2548
|
+
value={filterMenuValues[column.id]?.valueTo ?? ""}
|
|
2549
|
+
oninput={(e) =>
|
|
2550
|
+
updateFilterMenuValueTo(
|
|
2551
|
+
column.id,
|
|
2552
|
+
e.currentTarget.value,
|
|
2553
|
+
)}
|
|
2554
|
+
/>
|
|
2555
|
+
{/if}
|
|
2556
|
+
{/if}
|
|
2557
|
+
</div>
|
|
2558
|
+
{/if}
|
|
2559
|
+
{/each}
|
|
2560
|
+
</div>
|
|
2561
|
+
{/if}
|
|
2562
|
+
</aside>
|
|
2563
|
+
{/if}
|
|
2564
|
+
{/if}
|
|
2565
|
+
</div>
|
|
2566
|
+
<!-- /.sv-grid-root -->
|
|
2567
|
+
|
|
2568
|
+
<GridMenus {ctrl} {icon} />
|
|
2569
|
+
{/if}
|