@smartnet360/svelte-components 0.0.135 → 0.0.137

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,597 +0,0 @@
1
- <script lang="ts">
2
- import { onMount, onDestroy } from 'svelte';
3
- import { TabulatorFull as Tabulator } from 'tabulator-tables';
4
- // Import Bootstrap 5 theme for proper styling
5
- import 'tabulator-tables/dist/css/tabulator_bootstrap5.min.css';
6
- import type { Options, RowComponent } from 'tabulator-tables';
7
- import type {
8
- CellTableProps,
9
- CellData,
10
- RowSelectionEvent,
11
- RowClickEvent,
12
- RowDblClickEvent,
13
- RowContextMenuEvent,
14
- DataChangeEvent
15
- } from './types';
16
- import {
17
- getColumnsForPreset,
18
- getGroupHeaderFormatter,
19
- DEFAULT_TECH_COLORS,
20
- DEFAULT_STATUS_COLORS,
21
- cellDataSorter
22
- } from './column-config';
23
-
24
- // Type for Tabulator group component (not exported from tabulator-tables)
25
- interface TabulatorGroup {
26
- getKey(): string | number | boolean;
27
- getRows(): RowComponent[];
28
- scrollTo(): Promise<void>;
29
- }
30
-
31
- interface Props extends CellTableProps {
32
- /** Row selection change event */
33
- onselectionchange?: (event: RowSelectionEvent) => void;
34
- /** Row click event */
35
- onrowclick?: (event: RowClickEvent) => void;
36
- /** Row double-click event */
37
- onrowdblclick?: (event: RowDblClickEvent) => void;
38
- /** Row context menu (right-click) event */
39
- onrowcontextmenu?: (event: RowContextMenuEvent) => void;
40
- /** Data change event (filter, sort, etc.) */
41
- ondatachange?: (event: DataChangeEvent) => void;
42
- }
43
-
44
- let {
45
- cells = [],
46
- groupBy = 'none',
47
- columnPreset = 'default',
48
- columnVisibility,
49
- selectable = false,
50
- multiSelect = true,
51
- height = '100%',
52
- virtualDom = true,
53
- tabulatorOptions,
54
- techColors = DEFAULT_TECH_COLORS,
55
- statusColors = DEFAULT_STATUS_COLORS,
56
- headerFilters = true,
57
- resizableColumns = true,
58
- movableColumns = true,
59
- persistLayout = true,
60
- storageKey = 'cell-table-layout',
61
- onselectionchange,
62
- onrowclick,
63
- onrowdblclick,
64
- onrowcontextmenu,
65
- ondatachange
66
- }: Props = $props();
67
-
68
- let tableContainer: HTMLDivElement;
69
- let table: Tabulator | null = null;
70
- let isInitialized = $state(false);
71
-
72
- // Reactive column configuration - only changes when preset changes
73
- let columns = $derived.by(() => {
74
- // Get base columns from preset
75
- const baseColumns = getColumnsForPreset(columnPreset, techColors, statusColors, headerFilters);
76
-
77
- // Add row selection checkbox column if selectable
78
- if (selectable) {
79
- const selectColumn = {
80
- title: '',
81
- formatter: 'rowSelection',
82
- titleFormatter: multiSelect ? 'rowSelection' : undefined,
83
- hozAlign: 'center' as const,
84
- headerSort: false,
85
- width: 40,
86
- frozen: true,
87
- cssClass: 'cell-table-select-column',
88
- };
89
- return [selectColumn, ...baseColumns];
90
- }
91
-
92
- return baseColumns;
93
- });
94
-
95
- // Pre-sort data using our custom multi-level sorter
96
- let sortedCells = $derived.by(() => {
97
- return [...cells].sort(cellDataSorter);
98
- });
99
-
100
- // Build Tabulator options
101
- function buildOptions(): Options {
102
- const baseOptions: Options = {
103
- data: sortedCells,
104
- columns: columns,
105
- layout: 'fitDataFill',
106
- height: height,
107
- placeholder: 'No cells to display',
108
-
109
- // Virtual DOM for performance with large datasets
110
- renderVertical: virtualDom ? 'virtual' : 'basic',
111
-
112
- // Interactivity
113
- resizableColumns: resizableColumns,
114
- movableColumns: movableColumns,
115
-
116
- // No initialSort - data is pre-sorted by cellDataSorter (tech → fband → sector)
117
-
118
- // Row selection
119
- selectable: selectable ? (multiSelect ? true : 1) : false,
120
- selectableRangeMode: 'click',
121
-
122
- // Persistence - save column widths, order, visibility and group settings
123
- persistence: persistLayout ? {
124
- columns: ['width', 'visible', 'order'],
125
- group: true,
126
- } : false,
127
- persistenceMode: persistLayout ? 'local' : undefined,
128
- persistenceID: persistLayout ? storageKey : undefined,
129
-
130
- // Grouping
131
- ...(groupBy !== 'none' ? {
132
- groupBy: groupBy,
133
- groupStartOpen: true,
134
- groupHeader: getGroupHeaderFormatter(groupBy),
135
- groupToggleElement: 'header',
136
- } : {}),
137
-
138
- // Pagination (optional, disabled by default for virtual scrolling)
139
- // pagination: true,
140
- // paginationSize: 50,
141
-
142
- // Clipboard
143
- clipboard: true,
144
- clipboardCopyRowRange: 'selected',
145
- };
146
-
147
- // Merge with custom options
148
- return { ...baseOptions, ...tabulatorOptions };
149
- }
150
-
151
- // Initialize table
152
- function initTable() {
153
- if (!tableContainer) return;
154
-
155
- const options = buildOptions();
156
- table = new Tabulator(tableContainer, options);
157
-
158
- // Bind events
159
- table.on('rowClick', (e, row) => {
160
- if (onrowclick) {
161
- onrowclick({
162
- row: (row as RowComponent).getData() as CellData,
163
- event: e as MouseEvent
164
- });
165
- }
166
- });
167
-
168
- table.on('rowDblClick', (e, row) => {
169
- if (onrowdblclick) {
170
- onrowdblclick({
171
- row: (row as RowComponent).getData() as CellData,
172
- event: e as MouseEvent
173
- });
174
- }
175
- });
176
-
177
- table.on('rowContext', (e, row) => {
178
- if (onrowcontextmenu) {
179
- (e as MouseEvent).preventDefault();
180
- onrowcontextmenu({
181
- row: (row as RowComponent).getData() as CellData,
182
- event: e as MouseEvent
183
- });
184
- }
185
- });
186
-
187
- table.on('rowSelectionChanged', (data, rows) => {
188
- if (onselectionchange) {
189
- const cellData = data as CellData[];
190
- onselectionchange({
191
- rows: cellData,
192
- ids: cellData.map(d => d.id)
193
- });
194
- }
195
- });
196
-
197
- table.on('dataFiltered', (filters, rows) => {
198
- if (ondatachange) {
199
- ondatachange({
200
- type: 'filter',
201
- rowCount: cells.length,
202
- filteredCount: (rows as RowComponent[]).length
203
- });
204
- }
205
- });
206
-
207
- table.on('dataSorted', () => {
208
- if (ondatachange) {
209
- ondatachange({
210
- type: 'sort',
211
- rowCount: cells.length,
212
- filteredCount: table?.getDataCount('active') ?? 0
213
- });
214
- }
215
- });
216
-
217
- // Mark as initialized after table is ready
218
- table.on('tableBuilt', () => {
219
- isInitialized = true;
220
- // Fire initial data change event
221
- if (ondatachange) {
222
- ondatachange({
223
- type: 'load',
224
- rowCount: cells.length,
225
- filteredCount: cells.length
226
- });
227
- }
228
- });
229
- }
230
-
231
- // Track previous values to avoid unnecessary updates
232
- let prevCellsRef: unknown[] | null = null;
233
- let prevGroupBy: string | null = null;
234
- // Initialize with current preset to prevent setColumns on first load (which would override persisted layout)
235
- let prevColumnPreset: string | null = columnPreset;
236
-
237
- // Update table data when cells change (reference comparison)
238
- $effect(() => {
239
- // Track the cells array reference to detect any change
240
- const currentRef = sortedCells;
241
-
242
- // Update if reference changed (new array assignment)
243
- if (isInitialized && table && currentRef !== prevCellsRef) {
244
- prevCellsRef = currentRef;
245
- table.replaceData(sortedCells);
246
- ondatachange?.({
247
- type: 'load',
248
- rowCount: sortedCells.length,
249
- filteredCount: sortedCells.length
250
- });
251
- }
252
- });
253
-
254
- // Update grouping when groupBy changes
255
- $effect(() => {
256
- if (isInitialized && table && groupBy !== prevGroupBy) {
257
- prevGroupBy = groupBy;
258
- if (groupBy === 'none') {
259
- table.setGroupBy(false);
260
- } else {
261
- table.setGroupBy(groupBy);
262
- table.setGroupHeader(getGroupHeaderFormatter(groupBy));
263
- }
264
- }
265
- });
266
-
267
- // Update columns when preset changes
268
- $effect(() => {
269
- if (isInitialized && table && columnPreset !== prevColumnPreset) {
270
- prevColumnPreset = columnPreset;
271
- table.setColumns(columns);
272
- }
273
- });
274
-
275
- onMount(() => {
276
- initTable();
277
- });
278
-
279
- onDestroy(() => {
280
- if (table) {
281
- table.destroy();
282
- table = null;
283
- }
284
- });
285
-
286
- // Public API methods
287
- export function getTable(): Tabulator | null {
288
- return table;
289
- }
290
-
291
- export function getSelectedRows(): CellData[] {
292
- return table?.getSelectedData() as CellData[] ?? [];
293
- }
294
-
295
- export function clearSelection(): void {
296
- table?.deselectRow();
297
- }
298
-
299
- export function selectRow(id: string): void {
300
- const row = table?.getRow(id);
301
- if (row) row.select();
302
- }
303
-
304
- export function scrollToRow(id: string): void {
305
- table?.scrollToRow(id, 'top', true);
306
- }
307
-
308
- export function downloadCSV(filename: string = 'cells.csv'): void {
309
- table?.download('csv', filename);
310
- }
311
-
312
- export function downloadJSON(filename: string = 'cells.json'): void {
313
- table?.download('json', filename);
314
- }
315
-
316
- export function setFilter(field: string, type: string, value: unknown): void {
317
- table?.setFilter(field, type as any, value);
318
- }
319
-
320
- export function clearFilters(): void {
321
- if (!table) return;
322
- // Clear programmatic filters
323
- table.clearFilter();
324
- // Clear header filter inputs
325
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
326
- (table as any).clearHeaderFilter();
327
- }
328
-
329
- export function redraw(): void {
330
- table?.redraw(true);
331
- }
332
-
333
- export function collapseAll(): void {
334
- if (!table) return;
335
- // Use setGroupStartOpen to collapse all groups, then refresh data to apply
336
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
337
- (table as any).setGroupStartOpen(false);
338
- table.setData(table.getData());
339
- }
340
-
341
- export function expandAll(): void {
342
- if (!table) return;
343
- // Use setGroupStartOpen to expand all groups, then refresh data to apply
344
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
345
- (table as any).setGroupStartOpen(true);
346
- table.setData(table.getData());
347
- }
348
-
349
- export function toggleHeaderFilters(visible: boolean): void {
350
- if (!table) return;
351
- const headerFiltersElement = tableContainer.querySelector('.tabulator-header-filter');
352
- if (headerFiltersElement) {
353
- // Toggle all header filter rows
354
- const filterRows = tableContainer.querySelectorAll('.tabulator-col .tabulator-header-filter');
355
- filterRows.forEach(el => {
356
- (el as HTMLElement).style.display = visible ? '' : 'none';
357
- });
358
- table.redraw();
359
- }
360
- }
361
-
362
- export function showColumn(field: string): void {
363
- table?.showColumn(field);
364
- }
365
-
366
- export function hideColumn(field: string): void {
367
- table?.hideColumn(field);
368
- }
369
-
370
- export function getVisibleColumns(): string[] {
371
- if (!table) return [];
372
- const columns = table.getColumns();
373
- return columns
374
- .filter(col => col.isVisible())
375
- .map(col => col.getField())
376
- .filter((field): field is string => !!field);
377
- }
378
-
379
- /** Get all group keys (when grouping is active) */
380
- export function getGroups(): { key: string; count: number }[] {
381
- if (!table || groupBy === 'none') return [];
382
- try {
383
- const groups = table.getGroups() as TabulatorGroup[];
384
- return groups.map(g => ({
385
- key: String(g.getKey()),
386
- count: g.getRows().length
387
- }));
388
- } catch {
389
- return [];
390
- }
391
- }
392
-
393
- /** Scroll to a specific group by key */
394
- export function scrollToGroup(key: string): void {
395
- if (!table || groupBy === 'none') return;
396
- try {
397
- const groups = table.getGroups() as TabulatorGroup[];
398
- const group = groups.find(g => String(g.getKey()) === key);
399
- if (group) {
400
- group.scrollTo();
401
- }
402
- } catch (e) {
403
- console.warn('Failed to scroll to group:', e);
404
- }
405
- }
406
- </script>
407
-
408
- <div class="cell-table-container">
409
- <div bind:this={tableContainer} class="cell-table"></div>
410
- </div>
411
-
412
- <style>
413
- .cell-table-container {
414
- width: 100%;
415
- height: 100%;
416
- display: flex;
417
- flex-direction: column;
418
- }
419
-
420
- .cell-table {
421
- flex: 1;
422
- min-height: 0;
423
- }
424
-
425
- /* Bootstrap-aligned Tabulator theme overrides */
426
- :global(.cell-table-container .tabulator) {
427
- font-family: var(--bs-body-font-family, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
428
- font-size: 0.875rem;
429
- border: 1px solid var(--bs-border-color, #dee2e6);
430
- border-radius: var(--bs-border-radius, 0.375rem);
431
- background-color: var(--bs-body-bg, #fff);
432
- }
433
-
434
- /* Header styling */
435
- :global(.cell-table-container .tabulator-header) {
436
- background-color: var(--bs-tertiary-bg, #f8f9fa);
437
- border-bottom: 2px solid var(--bs-border-color, #dee2e6);
438
- }
439
-
440
- :global(.cell-table-container .tabulator-col) {
441
- background-color: var(--bs-tertiary-bg, #f8f9fa);
442
- border-right: 1px solid var(--bs-border-color, #dee2e6);
443
- }
444
-
445
- :global(.cell-table-container .tabulator-col-title) {
446
- font-weight: 600;
447
- color: var(--bs-body-color, #212529);
448
- padding: 0.5rem 0.75rem;
449
- }
450
-
451
- :global(.cell-table-container .tabulator-col-sorter) {
452
- color: var(--bs-secondary-color, #6c757d);
453
- }
454
-
455
- /* Header filter inputs */
456
- :global(.cell-table-container .tabulator-header-filter input) {
457
- font-size: 0.75rem;
458
- padding: 0.25rem 0.5rem;
459
- border: 1px solid var(--bs-border-color, #dee2e6);
460
- border-radius: var(--bs-border-radius-sm, 0.25rem);
461
- background-color: var(--bs-body-bg, #fff);
462
- color: var(--bs-body-color, #212529);
463
- }
464
-
465
- :global(.cell-table-container .tabulator-header-filter input:focus) {
466
- border-color: var(--bs-primary, #0d6efd);
467
- outline: 0;
468
- box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
469
- }
470
-
471
- /* Row styling */
472
- :global(.cell-table-container .tabulator-row) {
473
- border-bottom: 1px solid var(--bs-border-color-translucent, rgba(0, 0, 0, 0.1));
474
- }
475
-
476
- :global(.cell-table-container .tabulator-row:hover) {
477
- background-color: var(--bs-tertiary-bg, #f8f9fa);
478
- }
479
-
480
- :global(.cell-table-container .tabulator-row.tabulator-row-even) {
481
- background-color: var(--bs-body-bg, #fff);
482
- }
483
-
484
- :global(.cell-table-container .tabulator-row.tabulator-row-odd) {
485
- background-color: rgba(var(--bs-tertiary-bg-rgb, 248, 249, 250), 0.5);
486
- }
487
-
488
- :global(.cell-table-container .tabulator-row.tabulator-selected) {
489
- background-color: rgba(var(--bs-primary-rgb, 13, 110, 253), 0.1);
490
- }
491
-
492
- :global(.cell-table-container .tabulator-row.tabulator-selected:hover) {
493
- background-color: rgba(var(--bs-primary-rgb, 13, 110, 253), 0.15);
494
- }
495
-
496
- /* Cell styling */
497
- :global(.cell-table-container .tabulator-cell) {
498
- padding: 0.5rem 0.75rem;
499
- border-right: 1px solid var(--bs-border-color-translucent, rgba(0, 0, 0, 0.05));
500
- color: var(--bs-body-color, #212529);
501
- }
502
-
503
- :global(.cell-table-container .tabulator-cell.tabulator-frozen) {
504
- background-color: var(--bs-tertiary-bg, #f8f9fa);
505
- border-right: 2px solid var(--bs-border-color, #dee2e6);
506
- }
507
-
508
- /* Group header styling */
509
- :global(.cell-table-container .tabulator-row.tabulator-group) {
510
- background-color: var(--bs-secondary-bg, #e9ecef);
511
- border-bottom: 1px solid var(--bs-border-color, #dee2e6);
512
- font-weight: 500;
513
- min-height: 36px;
514
- }
515
-
516
- :global(.cell-table-container .tabulator-row.tabulator-group:hover) {
517
- background-color: var(--bs-secondary-bg, #e9ecef);
518
- cursor: pointer;
519
- }
520
-
521
- :global(.cell-table-container .tabulator-row.tabulator-group span) {
522
- color: var(--bs-body-color, #212529);
523
- }
524
-
525
- /* Group toggle arrow */
526
- :global(.cell-table-container .tabulator-group-toggle) {
527
- margin-right: 0.5rem;
528
- }
529
-
530
- /* Scrollbar styling */
531
- :global(.cell-table-container .tabulator-tableholder::-webkit-scrollbar) {
532
- width: 8px;
533
- height: 8px;
534
- }
535
-
536
- :global(.cell-table-container .tabulator-tableholder::-webkit-scrollbar-track) {
537
- background: var(--bs-tertiary-bg, #f8f9fa);
538
- }
539
-
540
- :global(.cell-table-container .tabulator-tableholder::-webkit-scrollbar-thumb) {
541
- background: var(--bs-secondary-color, #6c757d);
542
- border-radius: 4px;
543
- }
544
-
545
- :global(.cell-table-container .tabulator-tableholder::-webkit-scrollbar-thumb:hover) {
546
- background: var(--bs-body-color, #212529);
547
- }
548
-
549
- /* Placeholder */
550
- :global(.cell-table-container .tabulator-placeholder) {
551
- color: var(--bs-secondary-color, #6c757d);
552
- font-style: italic;
553
- }
554
-
555
- /* Resize handle */
556
- :global(.cell-table-container .tabulator-col-resize-handle) {
557
- width: 6px;
558
- right: 0;
559
- }
560
-
561
- :global(.cell-table-container .tabulator-col-resize-handle:hover) {
562
- background-color: var(--bs-primary, #0d6efd);
563
- }
564
-
565
- /* Footer/pagination (if enabled) */
566
- :global(.cell-table-container .tabulator-footer) {
567
- background-color: var(--bs-tertiary-bg, #f8f9fa);
568
- border-top: 1px solid var(--bs-border-color, #dee2e6);
569
- padding: 0.5rem;
570
- }
571
-
572
- :global(.cell-table-container .tabulator-page) {
573
- padding: 0.25rem 0.5rem;
574
- margin: 0 0.125rem;
575
- border: 1px solid var(--bs-border-color, #dee2e6);
576
- border-radius: var(--bs-border-radius-sm, 0.25rem);
577
- background-color: var(--bs-body-bg, #fff);
578
- color: var(--bs-body-color, #212529);
579
- }
580
-
581
- :global(.cell-table-container .tabulator-page:hover) {
582
- background-color: var(--bs-tertiary-bg, #f8f9fa);
583
- }
584
-
585
- :global(.cell-table-container .tabulator-page.active) {
586
- background-color: var(--bs-primary, #0d6efd);
587
- border-color: var(--bs-primary, #0d6efd);
588
- color: white;
589
- }
590
-
591
- /* Badge styling in cells */
592
- :global(.cell-table-container .badge) {
593
- font-weight: 500;
594
- padding: 0.25em 0.5em;
595
- border-radius: var(--bs-border-radius-sm, 0.25rem);
596
- }
597
- </style>
@@ -1,40 +0,0 @@
1
- import { TabulatorFull as Tabulator } from 'tabulator-tables';
2
- import 'tabulator-tables/dist/css/tabulator_bootstrap5.min.css';
3
- import type { CellTableProps, CellData, RowSelectionEvent, RowClickEvent, RowDblClickEvent, RowContextMenuEvent, DataChangeEvent } from './types';
4
- interface Props extends CellTableProps {
5
- /** Row selection change event */
6
- onselectionchange?: (event: RowSelectionEvent) => void;
7
- /** Row click event */
8
- onrowclick?: (event: RowClickEvent) => void;
9
- /** Row double-click event */
10
- onrowdblclick?: (event: RowDblClickEvent) => void;
11
- /** Row context menu (right-click) event */
12
- onrowcontextmenu?: (event: RowContextMenuEvent) => void;
13
- /** Data change event (filter, sort, etc.) */
14
- ondatachange?: (event: DataChangeEvent) => void;
15
- }
16
- declare const CellTable: import("svelte").Component<Props, {
17
- getTable: () => Tabulator | null;
18
- getSelectedRows: () => CellData[];
19
- clearSelection: () => void;
20
- selectRow: (id: string) => void;
21
- scrollToRow: (id: string) => void;
22
- downloadCSV: (filename?: string) => void;
23
- downloadJSON: (filename?: string) => void;
24
- setFilter: (field: string, type: string, value: unknown) => void;
25
- clearFilters: () => void;
26
- redraw: () => void;
27
- collapseAll: () => void;
28
- expandAll: () => void;
29
- toggleHeaderFilters: (visible: boolean) => void;
30
- showColumn: (field: string) => void;
31
- hideColumn: (field: string) => void;
32
- getVisibleColumns: () => string[];
33
- getGroups: () => {
34
- key: string;
35
- count: number;
36
- }[];
37
- scrollToGroup: (key: string) => void;
38
- }, "">;
39
- type CellTable = ReturnType<typeof CellTable>;
40
- export default CellTable;