@smartnet360/svelte-components 0.0.133 → 0.0.135

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.
Files changed (40) hide show
  1. package/dist/core/CellHistory/CellHistoryPanel.svelte +178 -0
  2. package/dist/core/CellHistory/CellHistoryPanel.svelte.d.ts +4 -0
  3. package/dist/core/CellHistory/column-config.d.ts +26 -0
  4. package/dist/core/CellHistory/column-config.js +120 -0
  5. package/dist/core/CellHistory/index.d.ts +9 -0
  6. package/dist/core/CellHistory/index.js +10 -0
  7. package/dist/core/CellHistory/transformers.d.ts +16 -0
  8. package/dist/core/CellHistory/transformers.js +76 -0
  9. package/dist/core/CellHistory/types.d.ts +54 -0
  10. package/dist/core/CellHistory/types.js +6 -0
  11. package/dist/core/CellTable/CellHistoryDemo.svelte +1 -0
  12. package/dist/core/CellTable/CellTable.svelte +7 -5
  13. package/dist/core/CellTable/CellTablePanel.svelte +82 -14
  14. package/dist/core/CellTable/CellTablePanel.svelte.d.ts +2 -0
  15. package/dist/core/CellTable/column-config.js +46 -58
  16. package/dist/core/CellTableV2/CellTable.svelte +601 -0
  17. package/dist/core/CellTableV2/CellTable.svelte.d.ts +43 -0
  18. package/dist/core/CellTableV2/CellTablePanel.svelte +685 -0
  19. package/dist/core/CellTableV2/CellTablePanel.svelte.d.ts +98 -0
  20. package/dist/core/CellTableV2/CellTableToolbar.svelte +322 -0
  21. package/dist/core/CellTableV2/CellTableToolbar.svelte.d.ts +59 -0
  22. package/dist/core/CellTableV2/ColumnPicker.svelte +214 -0
  23. package/dist/core/CellTableV2/ColumnPicker.svelte.d.ts +26 -0
  24. package/dist/core/CellTableV2/column-config.d.ts +120 -0
  25. package/dist/core/CellTableV2/column-config.js +671 -0
  26. package/dist/core/CellTableV2/composables/index.d.ts +12 -0
  27. package/dist/core/CellTableV2/composables/index.js +9 -0
  28. package/dist/core/CellTableV2/composables/useColumnVisibility.svelte.d.ts +45 -0
  29. package/dist/core/CellTableV2/composables/useColumnVisibility.svelte.js +98 -0
  30. package/dist/core/CellTableV2/composables/usePersistence.svelte.d.ts +28 -0
  31. package/dist/core/CellTableV2/composables/usePersistence.svelte.js +101 -0
  32. package/dist/core/CellTableV2/composables/useScrollSpy.svelte.d.ts +44 -0
  33. package/dist/core/CellTableV2/composables/useScrollSpy.svelte.js +91 -0
  34. package/dist/core/CellTableV2/index.d.ts +12 -0
  35. package/dist/core/CellTableV2/index.js +14 -0
  36. package/dist/core/CellTableV2/types.d.ts +172 -0
  37. package/dist/core/CellTableV2/types.js +6 -0
  38. package/dist/core/index.d.ts +2 -0
  39. package/dist/core/index.js +5 -0
  40. package/package.json +1 -1
@@ -0,0 +1,98 @@
1
+ /**
2
+ * CellTablePanel V2 - Composable-based Cell Table Panel
3
+ *
4
+ * Uses composables for reusable logic:
5
+ * - usePersistence: localStorage read/write
6
+ * - useColumnVisibility: column preset and visibility management
7
+ * - useScrollSpy: group navigation
8
+ */
9
+ import type { Snippet } from 'svelte';
10
+ import type { ColumnDefinition } from 'tabulator-tables';
11
+ import type { ColumnMetadataOptions } from './column-config';
12
+ import type { CellData, CellTableGroupField, ColumnPreset, RowSelectionEvent, RowClickEvent, RowDblClickEvent, TechColorMap, StatusColorMap, GroupOption } from './types';
13
+ interface Props {
14
+ /** Cell data array to display */
15
+ cells: CellData[];
16
+ /** Initial grouping field */
17
+ groupBy?: CellTableGroupField;
18
+ /** Custom grouping options (overrides default tech/fband/status options) */
19
+ groupOptions?: GroupOption[];
20
+ /** Initial column preset */
21
+ columnPreset?: ColumnPreset;
22
+ /** Custom column definitions (overrides columnPreset when provided) */
23
+ customColumns?: ColumnDefinition[];
24
+ /** Column metadata filter options */
25
+ columnMetadataOptions?: ColumnMetadataOptions;
26
+ /** Enable row selection */
27
+ selectable?: boolean;
28
+ /** Enable multi-row selection */
29
+ multiSelect?: boolean;
30
+ /** Panel height (CSS value) */
31
+ height?: string;
32
+ /** Show toolbar */
33
+ showToolbar?: boolean;
34
+ /** Show column presets dropdown and column picker (set false for simple tables) */
35
+ showColumnPresets?: boolean;
36
+ /** Show export buttons */
37
+ showExport?: boolean;
38
+ /** Show JSON export button (requires showExport=true) */
39
+ showJsonExport?: boolean;
40
+ /** Technology color mapping */
41
+ techColors?: TechColorMap;
42
+ /** Status color mapping */
43
+ statusColors?: StatusColorMap;
44
+ /** Enable header filters */
45
+ headerFilters?: boolean;
46
+ /** Panel title */
47
+ title?: string;
48
+ /** Show details sidebar */
49
+ showDetailsSidebar?: boolean;
50
+ /** Sidebar width in pixels */
51
+ sidebarWidth?: number;
52
+ /** Persist settings to localStorage */
53
+ persistSettings?: boolean;
54
+ /** Storage key prefix for persisted settings */
55
+ storageKey?: string;
56
+ /** Enable Tabulator persistence for column widths, order, visibility */
57
+ persistLayout?: boolean;
58
+ /** Show scrollspy navigation bar for quick group navigation */
59
+ showScrollSpy?: boolean;
60
+ /** Bindable reference to table methods */
61
+ tableRef?: {
62
+ redraw: () => void;
63
+ } | null;
64
+ /** Row selection change event */
65
+ onselectionchange?: (event: RowSelectionEvent) => void;
66
+ /** Row click event */
67
+ onrowclick?: (event: RowClickEvent) => void;
68
+ /** Row double-click event */
69
+ onrowdblclick?: (event: RowDblClickEvent) => void;
70
+ /** Custom header search slot (appears next to title) */
71
+ headerSearch?: Snippet;
72
+ /** Custom header actions slot */
73
+ headerActions?: Snippet;
74
+ /** Custom footer slot */
75
+ footer?: Snippet<[{
76
+ selectedRows: CellData[];
77
+ selectedCount: number;
78
+ }]>;
79
+ /** Custom details sidebar content */
80
+ detailsContent?: Snippet<[{
81
+ cell: CellData | null;
82
+ closeSidebar: () => void;
83
+ }]>;
84
+ /** Custom context menu content (right-click on row) */
85
+ contextMenu?: Snippet<[{
86
+ row: CellData;
87
+ closeMenu: () => void;
88
+ }]>;
89
+ }
90
+ declare const CellTablePanel: import("svelte").Component<Props, {
91
+ getSelectedRows: () => CellData[];
92
+ clearSelection: () => void;
93
+ scrollToRow: (id: string) => void;
94
+ redraw: () => void;
95
+ openSidebar: () => void;
96
+ }, "groupBy" | "tableRef">;
97
+ type CellTablePanel = ReturnType<typeof CellTablePanel>;
98
+ export default CellTablePanel;
@@ -0,0 +1,322 @@
1
+ <script lang="ts">
2
+ import type { CellTableGroupField, ColumnPreset, GroupOption } from './types';
3
+ import type { ColumnMeta } from './column-config';
4
+ import ColumnPicker from './ColumnPicker.svelte';
5
+
6
+ interface Props {
7
+ /** Current grouping field */
8
+ groupBy?: CellTableGroupField;
9
+ /** Current column preset */
10
+ columnPreset?: ColumnPreset;
11
+ /** Total row count */
12
+ totalCount?: number;
13
+ /** Filtered row count */
14
+ filteredCount?: number;
15
+ /** Selected row count */
16
+ selectedCount?: number;
17
+ /** Show export buttons */
18
+ showExport?: boolean;
19
+ /** Show JSON export button (requires showExport=true) */
20
+ showJsonExport?: boolean;
21
+ /** Show grouping dropdown */
22
+ showGrouping?: boolean;
23
+ /** Custom grouping options (overrides default options) */
24
+ groupOptions?: GroupOption[];
25
+ /** Show preset dropdown */
26
+ showPresets?: boolean;
27
+ /** Group change event */
28
+ ongroupchange?: (group: CellTableGroupField) => void;
29
+ /** Preset change event */
30
+ onpresetchange?: (preset: ColumnPreset) => void;
31
+ /** Export CSV event */
32
+ onexportcsv?: () => void;
33
+ /** Export JSON event */
34
+ onexportjson?: () => void;
35
+ /** Clear filters event */
36
+ onclearfilters?: () => void;
37
+ /** Collapse all groups event */
38
+ oncollapseall?: () => void;
39
+ /** Expand all groups event */
40
+ onexpandall?: () => void;
41
+ /** Whether header filters are visible */
42
+ filtersVisible?: boolean;
43
+ /** Toggle filters event */
44
+ ontogglefilters?: () => void;
45
+ /** All available columns metadata */
46
+ columnMeta?: ColumnMeta[];
47
+ /** Currently visible column fields */
48
+ visibleColumns?: string[];
49
+ /** Column visibility change event */
50
+ oncolumnvisibilitychange?: (field: string, visible: boolean) => void;
51
+ /** Reset columns to preset default */
52
+ onresetcolumns?: () => void;
53
+ /** Whether scrollspy is enabled */
54
+ scrollSpyEnabled?: boolean;
55
+ /** Show scrollspy toggle button */
56
+ showScrollSpyToggle?: boolean;
57
+ /** Toggle scrollspy event */
58
+ ontogglescrollspy?: () => void;
59
+ }
60
+
61
+ let {
62
+ groupBy = 'none',
63
+ columnPreset = 'default',
64
+ totalCount = 0,
65
+ filteredCount = 0,
66
+ selectedCount = 0,
67
+ showExport = true,
68
+ showJsonExport = false,
69
+ showGrouping = true,
70
+ groupOptions: customGroupOptions,
71
+ showPresets = true,
72
+ ongroupchange,
73
+ onpresetchange,
74
+ onexportcsv,
75
+ onexportjson,
76
+ onclearfilters,
77
+ oncollapseall,
78
+ onexpandall,
79
+ filtersVisible = true,
80
+ ontogglefilters,
81
+ columnMeta = [],
82
+ visibleColumns = [],
83
+ oncolumnvisibilitychange,
84
+ onresetcolumns,
85
+ scrollSpyEnabled = false,
86
+ showScrollSpyToggle = false,
87
+ ontogglescrollspy
88
+ }: Props = $props();
89
+
90
+ const defaultGroupOptions: GroupOption[] = [
91
+ { value: 'none', label: 'No Grouping' },
92
+ { value: 'tech', label: 'Technology' },
93
+ { value: 'fband', label: 'Frequency Band' },
94
+ { value: 'frq', label: 'Frequency' },
95
+ { value: 'status', label: 'Status' },
96
+ { value: 'siteId', label: 'Site ID' }
97
+ ];
98
+
99
+ // Use custom options if provided, otherwise use defaults
100
+ const groupOptions = customGroupOptions ?? defaultGroupOptions;
101
+
102
+ const presetOptions: { value: ColumnPreset; label: string }[] = [
103
+ { value: 'default', label: 'Default' },
104
+ { value: 'compact', label: 'Compact' },
105
+ { value: 'full', label: 'All Columns' },
106
+ { value: 'physical', label: 'Physical' },
107
+ { value: 'network', label: 'Network' },
108
+ { value: 'planning', label: 'Planning' },
109
+ { value: 'compare', label: 'Compare (Atoll/NW)' },
110
+ ];
111
+
112
+ function handleGroupChange(e: Event) {
113
+ const value = (e.target as HTMLSelectElement).value;
114
+ ongroupchange?.(value as CellTableGroupField);
115
+ }
116
+
117
+ function handlePresetChange(e: Event) {
118
+ const value = (e.target as HTMLSelectElement).value as ColumnPreset;
119
+ onpresetchange?.(value);
120
+ }
121
+ </script>
122
+
123
+ <div class="cell-table-toolbar d-flex align-items-center gap-3 p-2 bg-body-tertiary border-bottom">
124
+ <!-- Stats -->
125
+ <div class="toolbar-stats d-flex align-items-center gap-2">
126
+ <span class="badge bg-secondary">
127
+ {filteredCount} / {totalCount} cells
128
+ </span>
129
+ {#if selectedCount > 0}
130
+ <span class="badge bg-primary">
131
+ {selectedCount} selected
132
+ </span>
133
+ {/if}
134
+ </div>
135
+
136
+ <div class="vr"></div>
137
+
138
+ <!-- Grouping -->
139
+ {#if showGrouping}
140
+ <div class="toolbar-group d-flex align-items-center gap-2">
141
+ <label for="group-select" class="form-label mb-0 text-muted small">
142
+ <i class="bi bi-collection"></i> Group:
143
+ </label>
144
+ <select
145
+ id="group-select"
146
+ class="form-select form-select-sm"
147
+ style="width: auto;"
148
+ value={groupBy}
149
+ onchange={handleGroupChange}
150
+ >
151
+ {#each groupOptions as option}
152
+ <option value={option.value}>{option.label}</option>
153
+ {/each}
154
+ </select>
155
+ {#if groupBy !== 'none'}
156
+ <div class="btn-group ms-2">
157
+ <button
158
+ type="button"
159
+ class="btn btn-sm btn-outline-secondary"
160
+ onclick={oncollapseall}
161
+ title="Collapse all groups"
162
+ aria-label="Collapse all groups"
163
+ >
164
+ <i class="bi bi-chevron-contract"></i>
165
+ </button>
166
+ <button
167
+ type="button"
168
+ class="btn btn-sm btn-outline-secondary"
169
+ onclick={onexpandall}
170
+ title="Expand all groups"
171
+ aria-label="Expand all groups"
172
+ >
173
+ <i class="bi bi-chevron-expand"></i>
174
+ </button>
175
+ </div>
176
+ {/if}
177
+ </div>
178
+ {/if}
179
+
180
+ <!-- Presets -->
181
+ {#if showPresets}
182
+ <div class="toolbar-preset d-flex align-items-center gap-2">
183
+ <label for="preset-select" class="form-label mb-0 text-muted small">
184
+ <i class="bi bi-layout-three-columns"></i> Columns:
185
+ </label>
186
+ <select
187
+ id="preset-select"
188
+ class="form-select form-select-sm"
189
+ style="width: auto;"
190
+ value={columnPreset}
191
+ onchange={handlePresetChange}
192
+ >
193
+ {#each presetOptions as option}
194
+ <option value={option.value}>{option.label}</option>
195
+ {/each}
196
+ </select>
197
+ {#if columnMeta.length > 0}
198
+ <ColumnPicker
199
+ columns={columnMeta}
200
+ {visibleColumns}
201
+ presetName={presetOptions.find(p => p.value === columnPreset)?.label ?? 'Default'}
202
+ onchange={oncolumnvisibilitychange}
203
+ onreset={onresetcolumns}
204
+ />
205
+ {/if}
206
+ </div>
207
+ {/if}
208
+
209
+ <div class="flex-grow-1"></div>
210
+
211
+ <!-- Actions -->
212
+ <div class="toolbar-actions d-flex align-items-center gap-2">
213
+ {#if showScrollSpyToggle}
214
+ <button
215
+ type="button"
216
+ class="btn btn-sm"
217
+ class:btn-outline-secondary={!scrollSpyEnabled}
218
+ class:btn-secondary={scrollSpyEnabled}
219
+ onclick={ontogglescrollspy}
220
+ title={scrollSpyEnabled ? 'Hide quick navigation' : 'Show quick navigation'}
221
+ aria-label={scrollSpyEnabled ? 'Hide quick navigation' : 'Show quick navigation'}
222
+ >
223
+ <i class="bi bi-signpost-split"></i>
224
+ </button>
225
+ {/if}
226
+ {#if ontogglefilters}
227
+ <button
228
+ type="button"
229
+ class="btn btn-sm"
230
+ class:btn-outline-secondary={!filtersVisible}
231
+ class:btn-secondary={filtersVisible}
232
+ onclick={ontogglefilters}
233
+ title={filtersVisible ? 'Hide filters' : 'Show filters'}
234
+ aria-label={filtersVisible ? 'Hide filters' : 'Show filters'}
235
+ >
236
+ <i class="bi bi-funnel"></i>
237
+ </button>
238
+ {/if}
239
+ {#if onclearfilters}
240
+ <button
241
+ type="button"
242
+ class="btn btn-sm btn-outline-secondary"
243
+ onclick={onclearfilters}
244
+ title="Clear all filters"
245
+ >
246
+ <i class="bi bi-x-circle"></i>
247
+ <span class="d-none d-md-inline ms-1">Clear Filters</span>
248
+ </button>
249
+ {/if}
250
+
251
+ {#if showExport}
252
+ <div class="btn-group">
253
+ <button
254
+ type="button"
255
+ class="btn btn-sm btn-outline-primary"
256
+ onclick={onexportcsv}
257
+ title="Export to CSV"
258
+ >
259
+ <i class="bi bi-filetype-csv"></i>
260
+ <span class="d-none d-md-inline ms-1">CSV</span>
261
+ </button>
262
+ {#if showJsonExport}
263
+ <button
264
+ type="button"
265
+ class="btn btn-sm btn-outline-primary"
266
+ onclick={onexportjson}
267
+ title="Export to JSON"
268
+ >
269
+ <i class="bi bi-filetype-json"></i>
270
+ <span class="d-none d-md-inline ms-1">JSON</span>
271
+ </button>
272
+ {/if}
273
+ </div>
274
+ {/if}
275
+ </div>
276
+ </div>
277
+
278
+ <style>
279
+ .cell-table-toolbar {
280
+ min-height: 48px;
281
+ flex-wrap: wrap;
282
+ }
283
+
284
+ .toolbar-stats .badge {
285
+ font-size: 0.75rem;
286
+ font-weight: 500;
287
+ }
288
+
289
+ .form-select-sm {
290
+ padding-top: 0.25rem;
291
+ padding-bottom: 0.25rem;
292
+ font-size: 0.8125rem;
293
+ }
294
+
295
+ .btn-sm {
296
+ padding: 0.25rem 0.5rem;
297
+ font-size: 0.8125rem;
298
+ }
299
+
300
+ @media (max-width: 768px) {
301
+ .cell-table-toolbar {
302
+ flex-direction: column;
303
+ align-items: stretch !important;
304
+ gap: 0.5rem !important;
305
+ }
306
+
307
+ .toolbar-stats,
308
+ .toolbar-group,
309
+ .toolbar-preset,
310
+ .toolbar-actions {
311
+ justify-content: space-between;
312
+ }
313
+
314
+ .vr {
315
+ display: none;
316
+ }
317
+
318
+ .flex-grow-1 {
319
+ display: none;
320
+ }
321
+ }
322
+ </style>
@@ -0,0 +1,59 @@
1
+ import type { CellTableGroupField, ColumnPreset, GroupOption } from './types';
2
+ import type { ColumnMeta } from './column-config';
3
+ interface Props {
4
+ /** Current grouping field */
5
+ groupBy?: CellTableGroupField;
6
+ /** Current column preset */
7
+ columnPreset?: ColumnPreset;
8
+ /** Total row count */
9
+ totalCount?: number;
10
+ /** Filtered row count */
11
+ filteredCount?: number;
12
+ /** Selected row count */
13
+ selectedCount?: number;
14
+ /** Show export buttons */
15
+ showExport?: boolean;
16
+ /** Show JSON export button (requires showExport=true) */
17
+ showJsonExport?: boolean;
18
+ /** Show grouping dropdown */
19
+ showGrouping?: boolean;
20
+ /** Custom grouping options (overrides default options) */
21
+ groupOptions?: GroupOption[];
22
+ /** Show preset dropdown */
23
+ showPresets?: boolean;
24
+ /** Group change event */
25
+ ongroupchange?: (group: CellTableGroupField) => void;
26
+ /** Preset change event */
27
+ onpresetchange?: (preset: ColumnPreset) => void;
28
+ /** Export CSV event */
29
+ onexportcsv?: () => void;
30
+ /** Export JSON event */
31
+ onexportjson?: () => void;
32
+ /** Clear filters event */
33
+ onclearfilters?: () => void;
34
+ /** Collapse all groups event */
35
+ oncollapseall?: () => void;
36
+ /** Expand all groups event */
37
+ onexpandall?: () => void;
38
+ /** Whether header filters are visible */
39
+ filtersVisible?: boolean;
40
+ /** Toggle filters event */
41
+ ontogglefilters?: () => void;
42
+ /** All available columns metadata */
43
+ columnMeta?: ColumnMeta[];
44
+ /** Currently visible column fields */
45
+ visibleColumns?: string[];
46
+ /** Column visibility change event */
47
+ oncolumnvisibilitychange?: (field: string, visible: boolean) => void;
48
+ /** Reset columns to preset default */
49
+ onresetcolumns?: () => void;
50
+ /** Whether scrollspy is enabled */
51
+ scrollSpyEnabled?: boolean;
52
+ /** Show scrollspy toggle button */
53
+ showScrollSpyToggle?: boolean;
54
+ /** Toggle scrollspy event */
55
+ ontogglescrollspy?: () => void;
56
+ }
57
+ declare const CellTableToolbar: import("svelte").Component<Props, {}, "">;
58
+ type CellTableToolbar = ReturnType<typeof CellTableToolbar>;
59
+ export default CellTableToolbar;
@@ -0,0 +1,214 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ColumnPicker - Column visibility customization dropdown
4
+ *
5
+ * Shows a dropdown panel with checkboxes to toggle individual column visibility.
6
+ * Works alongside presets - user can customize which columns are visible.
7
+ */
8
+
9
+ interface ColumnInfo {
10
+ field: string;
11
+ title: string;
12
+ group: string;
13
+ }
14
+
15
+ interface Props {
16
+ /** All available columns with their metadata */
17
+ columns: ColumnInfo[];
18
+ /** Currently visible column fields */
19
+ visibleColumns: string[];
20
+ /** Callback when column visibility changes */
21
+ onchange?: (field: string, visible: boolean) => void;
22
+ /** Callback to reset to preset defaults */
23
+ onreset?: () => void;
24
+ /** Current preset name for display */
25
+ presetName?: string;
26
+ }
27
+
28
+ let {
29
+ columns = [],
30
+ visibleColumns = [],
31
+ onchange,
32
+ onreset,
33
+ presetName = 'Default'
34
+ }: Props = $props();
35
+
36
+ let isOpen = $state(false);
37
+ let dropdownRef: HTMLDivElement;
38
+
39
+ // Group columns by category
40
+ const groupedColumns = $derived.by(() => {
41
+ const groups: Record<string, ColumnInfo[]> = {};
42
+ for (const col of columns) {
43
+ if (!groups[col.group]) {
44
+ groups[col.group] = [];
45
+ }
46
+ groups[col.group].push(col);
47
+ }
48
+ return groups;
49
+ });
50
+
51
+ const groupOrder = ['Core', 'Physical', 'Network', 'Atoll', 'Position', 'Planning'];
52
+
53
+ function toggleDropdown() {
54
+ isOpen = !isOpen;
55
+ }
56
+
57
+ function handleCheckboxChange(field: string, event: Event) {
58
+ const checked = (event.target as HTMLInputElement).checked;
59
+ onchange?.(field, checked);
60
+ }
61
+
62
+ function handleReset() {
63
+ onreset?.();
64
+ }
65
+
66
+ function handleSelectAll() {
67
+ columns.forEach(col => onchange?.(col.field, true));
68
+ }
69
+
70
+ function handleDeselectAll() {
71
+ // Keep at least core columns visible
72
+ columns.forEach(col => {
73
+ const isCore = col.group === 'Core';
74
+ onchange?.(col.field, isCore);
75
+ });
76
+ }
77
+
78
+ // Close dropdown when clicking outside
79
+ function handleClickOutside(event: MouseEvent) {
80
+ if (dropdownRef && !dropdownRef.contains(event.target as Node)) {
81
+ isOpen = false;
82
+ }
83
+ }
84
+
85
+ $effect(() => {
86
+ if (isOpen) {
87
+ document.addEventListener('click', handleClickOutside);
88
+ return () => document.removeEventListener('click', handleClickOutside);
89
+ }
90
+ });
91
+ </script>
92
+
93
+ <div class="column-picker position-relative" bind:this={dropdownRef}>
94
+ <button
95
+ type="button"
96
+ class="btn btn-sm btn-outline-secondary"
97
+ onclick={toggleDropdown}
98
+ title="Customize columns"
99
+ aria-label="Customize columns"
100
+ aria-expanded={isOpen}
101
+ >
102
+ <i class="bi bi-pencil"></i>
103
+ </button>
104
+
105
+ {#if isOpen}
106
+ <div class="column-picker-dropdown position-absolute end-0 mt-1 bg-white border rounded shadow-sm"
107
+ style="z-index: 1050; width: 280px; max-height: 400px;">
108
+
109
+ <!-- Header -->
110
+ <div class="d-flex align-items-center justify-content-between p-2 border-bottom bg-light">
111
+ <span class="small fw-medium">
112
+ <i class="bi bi-layout-three-columns text-primary"></i>
113
+ Customize "{presetName}"
114
+ </span>
115
+ <button
116
+ type="button"
117
+ class="btn-close btn-close-sm"
118
+ onclick={() => isOpen = false}
119
+ aria-label="Close"
120
+ ></button>
121
+ </div>
122
+
123
+ <!-- Quick actions -->
124
+ <div class="d-flex gap-2 p-2 border-bottom">
125
+ <button
126
+ type="button"
127
+ class="btn btn-sm btn-outline-secondary flex-grow-1"
128
+ onclick={handleSelectAll}
129
+ >
130
+ Select All
131
+ </button>
132
+ <button
133
+ type="button"
134
+ class="btn btn-sm btn-outline-secondary flex-grow-1"
135
+ onclick={handleDeselectAll}
136
+ >
137
+ Core Only
138
+ </button>
139
+ </div>
140
+
141
+ <!-- Scrollable column list -->
142
+ <div class="column-list overflow-auto" style="max-height: 280px;">
143
+ {#each groupOrder as groupName}
144
+ {#if groupedColumns[groupName]}
145
+ <div class="column-group">
146
+ <div class="px-2 py-1 bg-body-tertiary small fw-medium text-muted border-bottom">
147
+ {groupName}
148
+ </div>
149
+ {#each groupedColumns[groupName] as col}
150
+ <label class="d-flex align-items-center px-2 py-1 column-item">
151
+ <input
152
+ type="checkbox"
153
+ class="form-check-input me-2 mt-0"
154
+ checked={visibleColumns.includes(col.field)}
155
+ onchange={(e) => handleCheckboxChange(col.field, e)}
156
+ />
157
+ <span class="small">{col.title}</span>
158
+ <code class="ms-auto small text-muted">{col.field}</code>
159
+ </label>
160
+ {/each}
161
+ </div>
162
+ {/if}
163
+ {/each}
164
+ </div>
165
+
166
+ <!-- Footer with reset -->
167
+ <div class="p-2 border-top bg-light">
168
+ <button
169
+ type="button"
170
+ class="btn btn-sm btn-outline-primary w-100"
171
+ onclick={handleReset}
172
+ >
173
+ <i class="bi bi-arrow-counterclockwise"></i>
174
+ Reset to "{presetName}" Default
175
+ </button>
176
+ </div>
177
+ </div>
178
+ {/if}
179
+ </div>
180
+
181
+ <style>
182
+ .column-picker-dropdown {
183
+ animation: fadeIn 0.15s ease-out;
184
+ }
185
+
186
+ @keyframes fadeIn {
187
+ from {
188
+ opacity: 0;
189
+ transform: translateY(-4px);
190
+ }
191
+ to {
192
+ opacity: 1;
193
+ transform: translateY(0);
194
+ }
195
+ }
196
+
197
+ .column-item {
198
+ cursor: pointer;
199
+ transition: background-color 0.1s;
200
+ }
201
+
202
+ .column-item:hover {
203
+ background-color: var(--bs-tertiary-bg, #f8f9fa);
204
+ }
205
+
206
+ .form-check-input {
207
+ cursor: pointer;
208
+ }
209
+
210
+ .btn-close-sm {
211
+ font-size: 0.65rem;
212
+ padding: 0.25rem;
213
+ }
214
+ </style>