@smartnet360/svelte-components 0.0.112 → 0.0.114
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/core/CellTable/CellTable.svelte +35 -0
- package/dist/core/CellTable/CellTable.svelte.d.ts +5 -0
- package/dist/core/CellTable/CellTableDemo.svelte +125 -13
- package/dist/core/CellTable/CellTablePanel.svelte +125 -2
- package/dist/core/CellTable/CellTablePanel.svelte.d.ts +4 -0
- package/dist/core/CellTable/CellTableToolbar.svelte +37 -10
- package/dist/core/CellTable/CellTableToolbar.svelte.d.ts +8 -0
- package/package.json +1 -1
|
@@ -20,6 +20,13 @@
|
|
|
20
20
|
cellDataSorter
|
|
21
21
|
} from './column-config';
|
|
22
22
|
|
|
23
|
+
// Type for Tabulator group component (not exported from tabulator-tables)
|
|
24
|
+
interface TabulatorGroup {
|
|
25
|
+
getKey(): string | number | boolean;
|
|
26
|
+
getRows(): RowComponent[];
|
|
27
|
+
scrollTo(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
interface Props extends CellTableProps {
|
|
24
31
|
/** Row selection change event */
|
|
25
32
|
onselectionchange?: (event: RowSelectionEvent) => void;
|
|
@@ -335,6 +342,34 @@
|
|
|
335
342
|
.map(col => col.getField())
|
|
336
343
|
.filter((field): field is string => !!field);
|
|
337
344
|
}
|
|
345
|
+
|
|
346
|
+
/** Get all group keys (when grouping is active) */
|
|
347
|
+
export function getGroups(): { key: string; count: number }[] {
|
|
348
|
+
if (!table || groupBy === 'none') return [];
|
|
349
|
+
try {
|
|
350
|
+
const groups = table.getGroups() as TabulatorGroup[];
|
|
351
|
+
return groups.map(g => ({
|
|
352
|
+
key: String(g.getKey()),
|
|
353
|
+
count: g.getRows().length
|
|
354
|
+
}));
|
|
355
|
+
} catch {
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Scroll to a specific group by key */
|
|
361
|
+
export function scrollToGroup(key: string): void {
|
|
362
|
+
if (!table || groupBy === 'none') return;
|
|
363
|
+
try {
|
|
364
|
+
const groups = table.getGroups() as TabulatorGroup[];
|
|
365
|
+
const group = groups.find(g => String(g.getKey()) === key);
|
|
366
|
+
if (group) {
|
|
367
|
+
group.scrollTo();
|
|
368
|
+
}
|
|
369
|
+
} catch (e) {
|
|
370
|
+
console.warn('Failed to scroll to group:', e);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
338
373
|
</script>
|
|
339
374
|
|
|
340
375
|
<div class="cell-table-container">
|
|
@@ -28,6 +28,11 @@ declare const CellTable: import("svelte").Component<Props, {
|
|
|
28
28
|
showColumn: (field: string) => void;
|
|
29
29
|
hideColumn: (field: string) => void;
|
|
30
30
|
getVisibleColumns: () => string[];
|
|
31
|
+
getGroups: () => {
|
|
32
|
+
key: string;
|
|
33
|
+
count: number;
|
|
34
|
+
}[];
|
|
35
|
+
scrollToGroup: (key: string) => void;
|
|
31
36
|
}, "">;
|
|
32
37
|
type CellTable = ReturnType<typeof CellTable>;
|
|
33
38
|
export default CellTable;
|
|
@@ -10,6 +10,33 @@
|
|
|
10
10
|
let groupBy: CellTableGroupField = $state('none');
|
|
11
11
|
let columnPreset: ColumnPreset = $state('default');
|
|
12
12
|
let searchTerm = $state('');
|
|
13
|
+
|
|
14
|
+
// Loading states
|
|
15
|
+
let isCreating = $state(false);
|
|
16
|
+
let isReloading = $state(false);
|
|
17
|
+
let isLoading = $derived(isCreating || isReloading);
|
|
18
|
+
|
|
19
|
+
// Check if 5G-3500 already exists for the searched site
|
|
20
|
+
let has5G3500 = $derived.by(() => {
|
|
21
|
+
const siteId = searchTerm.trim();
|
|
22
|
+
if (!siteId) return false;
|
|
23
|
+
return demoCells.some(cell =>
|
|
24
|
+
cell.siteId.toLowerCase() === siteId.toLowerCase() &&
|
|
25
|
+
cell.fband === '5G-3500'
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Create button disabled state
|
|
30
|
+
let createDisabled = $derived(!searchTerm.trim() || isLoading || has5G3500);
|
|
31
|
+
|
|
32
|
+
// Tooltip for create button
|
|
33
|
+
let createTooltip = $derived.by(() => {
|
|
34
|
+
if (!searchTerm.trim()) return 'Enter a site ID first';
|
|
35
|
+
if (has5G3500) return '5G-3500 already exists for this site';
|
|
36
|
+
if (isCreating) return 'Creating...';
|
|
37
|
+
if (isReloading) return 'Reloading...';
|
|
38
|
+
return 'Create 5G-3500 cell for this site';
|
|
39
|
+
});
|
|
13
40
|
|
|
14
41
|
function handleSelectionChange(event: RowSelectionEvent) {
|
|
15
42
|
console.log('Selection changed:', event.ids);
|
|
@@ -40,6 +67,64 @@
|
|
|
40
67
|
handleSearch();
|
|
41
68
|
}
|
|
42
69
|
}
|
|
70
|
+
|
|
71
|
+
// ============================================
|
|
72
|
+
// PLACEHOLDER FUNCTIONS - Replace with your API calls
|
|
73
|
+
// ============================================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create a new 5G-3500 cell for the given site
|
|
77
|
+
* TODO: Replace with your actual API call
|
|
78
|
+
*/
|
|
79
|
+
async function createCellForSite(siteId: string): Promise<void> {
|
|
80
|
+
// Simulate API call - replace with your implementation
|
|
81
|
+
console.log(`Creating 5G-3500 cell for site: ${siteId}`);
|
|
82
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
83
|
+
console.log(`Cell created for site: ${siteId}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Reload site data after creation
|
|
88
|
+
* TODO: Replace with your actual API call
|
|
89
|
+
*/
|
|
90
|
+
async function reloadSiteData(siteId: string): Promise<typeof demoCells> {
|
|
91
|
+
// Simulate API call - replace with your implementation
|
|
92
|
+
console.log(`Reloading data for site: ${siteId}`);
|
|
93
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
94
|
+
// For demo, just return filtered data
|
|
95
|
+
const allCells = generateCellsFromPreset(datasetSize);
|
|
96
|
+
return allCells.filter(cell =>
|
|
97
|
+
cell.siteId.toLowerCase().includes(siteId.toLowerCase())
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ============================================
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Handle create cell button click
|
|
105
|
+
*/
|
|
106
|
+
async function handleCreateCell() {
|
|
107
|
+
const siteId = searchTerm.trim();
|
|
108
|
+
if (!siteId || isLoading || has5G3500) return;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Step 1: Create the cell
|
|
112
|
+
isCreating = true;
|
|
113
|
+
await createCellForSite(siteId);
|
|
114
|
+
isCreating = false;
|
|
115
|
+
|
|
116
|
+
// Step 2: Reload the data
|
|
117
|
+
isReloading = true;
|
|
118
|
+
demoCells = await reloadSiteData(siteId);
|
|
119
|
+
isReloading = false;
|
|
120
|
+
|
|
121
|
+
console.log('Cell created and data reloaded successfully');
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('Error creating cell:', error);
|
|
124
|
+
isCreating = false;
|
|
125
|
+
isReloading = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
43
128
|
</script>
|
|
44
129
|
|
|
45
130
|
<div class="cell-table-page vh-100 d-flex flex-column">
|
|
@@ -75,26 +160,53 @@
|
|
|
75
160
|
headerFilters={true}
|
|
76
161
|
showDetailsSidebar={true}
|
|
77
162
|
sidebarWidth={320}
|
|
163
|
+
showScrollSpy={true}
|
|
78
164
|
title="Cell Data"
|
|
79
165
|
onselectionchange={handleSelectionChange}
|
|
80
166
|
>
|
|
81
167
|
{#snippet headerSearch()}
|
|
82
|
-
<div class="
|
|
83
|
-
<input
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
168
|
+
<div class="d-flex align-items-center gap-2">
|
|
169
|
+
<div class="input-group input-group-sm" style="width: 180px;">
|
|
170
|
+
<input
|
|
171
|
+
type="text"
|
|
172
|
+
class="form-control"
|
|
173
|
+
placeholder="Search site or cell..."
|
|
174
|
+
bind:value={searchTerm}
|
|
175
|
+
onkeydown={handleSearchKeydown}
|
|
176
|
+
disabled={isLoading}
|
|
177
|
+
/>
|
|
178
|
+
<button
|
|
179
|
+
class="btn btn-outline-primary"
|
|
180
|
+
type="button"
|
|
181
|
+
onclick={handleSearch}
|
|
182
|
+
title="Search"
|
|
183
|
+
aria-label="Search"
|
|
184
|
+
disabled={isLoading}
|
|
185
|
+
>
|
|
186
|
+
{#if isReloading}
|
|
187
|
+
<span class="spinner-border spinner-border-sm" role="status"></span>
|
|
188
|
+
{:else}
|
|
189
|
+
<i class="bi bi-search"></i>
|
|
190
|
+
{/if}
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
90
193
|
<button
|
|
91
|
-
class="btn btn-
|
|
194
|
+
class="btn btn-sm"
|
|
195
|
+
class:btn-outline-success={!has5G3500}
|
|
196
|
+
class:btn-success={has5G3500}
|
|
92
197
|
type="button"
|
|
93
|
-
onclick={
|
|
94
|
-
title=
|
|
95
|
-
aria-label="
|
|
198
|
+
onclick={handleCreateCell}
|
|
199
|
+
title={createTooltip}
|
|
200
|
+
aria-label="Create 5G-3500 cell"
|
|
201
|
+
disabled={createDisabled}
|
|
96
202
|
>
|
|
97
|
-
|
|
203
|
+
{#if isCreating}
|
|
204
|
+
<span class="spinner-border spinner-border-sm" role="status"></span>
|
|
205
|
+
{:else if has5G3500}
|
|
206
|
+
<i class="bi bi-check-lg"></i>
|
|
207
|
+
{:else}
|
|
208
|
+
<i class="bi bi-plus-lg"></i>
|
|
209
|
+
{/if}
|
|
98
210
|
</button>
|
|
99
211
|
</div>
|
|
100
212
|
{/snippet}
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
showToolbar?: boolean;
|
|
33
33
|
/** Show export buttons */
|
|
34
34
|
showExport?: boolean;
|
|
35
|
+
/** Show JSON export button (requires showExport=true) */
|
|
36
|
+
showJsonExport?: boolean;
|
|
35
37
|
/** Technology color mapping */
|
|
36
38
|
techColors?: TechColorMap;
|
|
37
39
|
/** Status color mapping */
|
|
@@ -48,6 +50,8 @@
|
|
|
48
50
|
persistSettings?: boolean;
|
|
49
51
|
/** Storage key prefix for persisted settings */
|
|
50
52
|
storageKey?: string;
|
|
53
|
+
/** Show scrollspy navigation bar for quick group navigation */
|
|
54
|
+
showScrollSpy?: boolean;
|
|
51
55
|
/** Bindable reference to table methods */
|
|
52
56
|
tableRef?: { redraw: () => void } | null;
|
|
53
57
|
/** Row selection change event */
|
|
@@ -75,6 +79,7 @@
|
|
|
75
79
|
height = '100%',
|
|
76
80
|
showToolbar = true,
|
|
77
81
|
showExport = true,
|
|
82
|
+
showJsonExport = false,
|
|
78
83
|
techColors,
|
|
79
84
|
statusColors,
|
|
80
85
|
headerFilters = true,
|
|
@@ -83,6 +88,7 @@
|
|
|
83
88
|
sidebarWidth = 320,
|
|
84
89
|
persistSettings = true,
|
|
85
90
|
storageKey = 'cell-table',
|
|
91
|
+
showScrollSpy = false,
|
|
86
92
|
tableRef = $bindable(null),
|
|
87
93
|
onselectionchange,
|
|
88
94
|
onrowclick,
|
|
@@ -97,13 +103,15 @@
|
|
|
97
103
|
const STORAGE_KEY_GROUP = `${storageKey}-groupBy`;
|
|
98
104
|
const STORAGE_KEY_COLUMNS = `${storageKey}-visibleColumns`;
|
|
99
105
|
const STORAGE_KEY_FILTERS = `${storageKey}-filtersVisible`;
|
|
106
|
+
const STORAGE_KEY_SCROLLSPY = `${storageKey}-scrollSpyEnabled`;
|
|
100
107
|
|
|
101
108
|
// Load persisted settings
|
|
102
109
|
function loadPersistedSettings() {
|
|
103
|
-
if (!persistSettings || typeof localStorage === 'undefined') return { columns: null, filtersVisible: true };
|
|
110
|
+
if (!persistSettings || typeof localStorage === 'undefined') return { columns: null, filtersVisible: true, scrollSpyEnabled: showScrollSpy };
|
|
104
111
|
|
|
105
112
|
let columns: string[] | null = null;
|
|
106
113
|
let filters = true;
|
|
114
|
+
let scrollSpy = showScrollSpy;
|
|
107
115
|
|
|
108
116
|
try {
|
|
109
117
|
const savedGroup = localStorage.getItem(STORAGE_KEY_GROUP);
|
|
@@ -120,10 +128,15 @@
|
|
|
120
128
|
if (savedFilters !== null) {
|
|
121
129
|
filters = savedFilters === 'true';
|
|
122
130
|
}
|
|
131
|
+
|
|
132
|
+
const savedScrollSpy = localStorage.getItem(STORAGE_KEY_SCROLLSPY);
|
|
133
|
+
if (savedScrollSpy !== null) {
|
|
134
|
+
scrollSpy = savedScrollSpy === 'true';
|
|
135
|
+
}
|
|
123
136
|
} catch (e) {
|
|
124
137
|
console.warn('Failed to load CellTable settings:', e);
|
|
125
138
|
}
|
|
126
|
-
return { columns, filtersVisible: filters };
|
|
139
|
+
return { columns, filtersVisible: filters, scrollSpyEnabled: scrollSpy };
|
|
127
140
|
}
|
|
128
141
|
|
|
129
142
|
// Save group setting
|
|
@@ -156,6 +169,16 @@
|
|
|
156
169
|
}
|
|
157
170
|
}
|
|
158
171
|
|
|
172
|
+
// Save scrollspy state
|
|
173
|
+
function saveScrollSpyState(enabled: boolean) {
|
|
174
|
+
if (!persistSettings || typeof localStorage === 'undefined') return;
|
|
175
|
+
try {
|
|
176
|
+
localStorage.setItem(STORAGE_KEY_SCROLLSPY, String(enabled));
|
|
177
|
+
} catch (e) {
|
|
178
|
+
console.warn('Failed to save scrollspy state:', e);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
159
182
|
let cellTable: CellTable;
|
|
160
183
|
let selectedCount = $state(0);
|
|
161
184
|
let selectedRows = $state<CellData[]>([]);
|
|
@@ -171,6 +194,10 @@
|
|
|
171
194
|
const persistedSettings = loadPersistedSettings();
|
|
172
195
|
let filtersVisible = $state(persistedSettings.filtersVisible);
|
|
173
196
|
let visibleColumns = $state<string[]>(persistedSettings.columns ?? getPresetVisibleFields(columnPreset));
|
|
197
|
+
|
|
198
|
+
// ScrollSpy state - initialize from persisted settings
|
|
199
|
+
let scrollSpyGroups = $state<{ key: string; count: number }[]>([]);
|
|
200
|
+
let scrollSpyEnabled = $state(persistedSettings.scrollSpyEnabled);
|
|
174
201
|
|
|
175
202
|
// Update visible columns when preset changes (but not from storage load)
|
|
176
203
|
$effect(() => {
|
|
@@ -208,11 +235,42 @@
|
|
|
208
235
|
|
|
209
236
|
function handleDataChange(event: DataChangeEvent) {
|
|
210
237
|
filteredCount = event.filteredCount;
|
|
238
|
+
// Update scrollspy groups when data changes
|
|
239
|
+
updateScrollSpyGroups();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function updateScrollSpyGroups() {
|
|
243
|
+
if (scrollSpyEnabled && groupBy !== 'none') {
|
|
244
|
+
// Small delay to ensure table has updated
|
|
245
|
+
setTimeout(() => {
|
|
246
|
+
scrollSpyGroups = cellTable?.getGroups() ?? [];
|
|
247
|
+
}, 50);
|
|
248
|
+
} else {
|
|
249
|
+
scrollSpyGroups = [];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function handleScrollToGroup(key: string) {
|
|
254
|
+
cellTable?.scrollToGroup(key);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function handleToggleScrollSpy() {
|
|
258
|
+
scrollSpyEnabled = !scrollSpyEnabled;
|
|
259
|
+
saveScrollSpyState(scrollSpyEnabled);
|
|
260
|
+
if (scrollSpyEnabled && groupBy !== 'none') {
|
|
261
|
+
updateScrollSpyGroups();
|
|
262
|
+
} else {
|
|
263
|
+
scrollSpyGroups = [];
|
|
264
|
+
}
|
|
211
265
|
}
|
|
212
266
|
|
|
213
267
|
function handleGroupChange(group: CellTableGroupField) {
|
|
214
268
|
groupBy = group;
|
|
215
269
|
saveGroupSetting(group);
|
|
270
|
+
// Update scrollspy groups after grouping changes
|
|
271
|
+
if (scrollSpyEnabled) {
|
|
272
|
+
setTimeout(() => updateScrollSpyGroups(), 100);
|
|
273
|
+
}
|
|
216
274
|
}
|
|
217
275
|
|
|
218
276
|
function handlePresetChange(preset: ColumnPreset) {
|
|
@@ -353,6 +411,7 @@
|
|
|
353
411
|
{filteredCount}
|
|
354
412
|
{selectedCount}
|
|
355
413
|
{showExport}
|
|
414
|
+
{showJsonExport}
|
|
356
415
|
ongroupchange={handleGroupChange}
|
|
357
416
|
onpresetchange={handlePresetChange}
|
|
358
417
|
onexportcsv={handleExportCSV}
|
|
@@ -366,9 +425,40 @@
|
|
|
366
425
|
{visibleColumns}
|
|
367
426
|
oncolumnvisibilitychange={handleColumnVisibilityChange}
|
|
368
427
|
onresetcolumns={handleResetColumns}
|
|
428
|
+
{scrollSpyEnabled}
|
|
429
|
+
showScrollSpyToggle={showScrollSpy}
|
|
430
|
+
ontogglescrollspy={handleToggleScrollSpy}
|
|
369
431
|
/>
|
|
370
432
|
{/if}
|
|
371
433
|
|
|
434
|
+
<!-- ScrollSpy Navigation Bar -->
|
|
435
|
+
{#if scrollSpyEnabled && groupBy !== 'none' && scrollSpyGroups.length > 0}
|
|
436
|
+
<div class="scrollspy-bar d-flex align-items-center gap-2 px-3 py-2 bg-body-tertiary border-bottom overflow-auto">
|
|
437
|
+
<span class="text-muted small me-1">
|
|
438
|
+
<i class="bi bi-signpost-split"></i> Jump to:
|
|
439
|
+
</span>
|
|
440
|
+
{#each scrollSpyGroups as group (group.key)}
|
|
441
|
+
{@const bgColor = groupBy === 'tech'
|
|
442
|
+
? (techColors?.[group.key] ?? DEFAULT_TECH_COLORS[group.key] ?? '#6c757d')
|
|
443
|
+
: groupBy === 'fband'
|
|
444
|
+
? (FBAND_COLORS[group.key] ?? '#6c757d')
|
|
445
|
+
: groupBy === 'status'
|
|
446
|
+
? (statusColors?.[group.key] ?? DEFAULT_STATUS_COLORS[group.key] ?? '#6c757d')
|
|
447
|
+
: '#6c757d'}
|
|
448
|
+
<button
|
|
449
|
+
type="button"
|
|
450
|
+
class="btn btn-sm scrollspy-badge"
|
|
451
|
+
style="background-color: {bgColor}; border-color: {bgColor}; color: white;"
|
|
452
|
+
onclick={() => handleScrollToGroup(group.key)}
|
|
453
|
+
title="Scroll to {group.key} ({group.count} cells)"
|
|
454
|
+
>
|
|
455
|
+
<span class="badge rounded-pill bg-light text-dark me-1">{group.count}</span>
|
|
456
|
+
{group.key}
|
|
457
|
+
</button>
|
|
458
|
+
{/each}
|
|
459
|
+
</div>
|
|
460
|
+
{/if}
|
|
461
|
+
|
|
372
462
|
<!-- Main content with optional sidebar -->
|
|
373
463
|
<div class="content-area d-flex flex-grow-1 overflow-hidden">
|
|
374
464
|
<!-- Table -->
|
|
@@ -571,4 +661,37 @@
|
|
|
571
661
|
.panel-footer {
|
|
572
662
|
min-height: 48px;
|
|
573
663
|
}
|
|
664
|
+
|
|
665
|
+
/* ScrollSpy bar styling */
|
|
666
|
+
.scrollspy-bar {
|
|
667
|
+
min-height: 40px;
|
|
668
|
+
flex-wrap: nowrap;
|
|
669
|
+
scrollbar-width: thin;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.scrollspy-bar::-webkit-scrollbar {
|
|
673
|
+
height: 4px;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.scrollspy-bar::-webkit-scrollbar-thumb {
|
|
677
|
+
background: var(--bs-secondary-color, #6c757d);
|
|
678
|
+
border-radius: 2px;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.scrollspy-badge {
|
|
682
|
+
white-space: nowrap;
|
|
683
|
+
font-size: 0.75rem;
|
|
684
|
+
padding: 0.25rem 0.5rem;
|
|
685
|
+
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.scrollspy-badge:hover {
|
|
689
|
+
transform: translateY(-1px);
|
|
690
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
691
|
+
filter: brightness(1.1);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.scrollspy-badge:active {
|
|
695
|
+
transform: translateY(0);
|
|
696
|
+
}
|
|
574
697
|
</style>
|
|
@@ -17,6 +17,8 @@ interface Props {
|
|
|
17
17
|
showToolbar?: boolean;
|
|
18
18
|
/** Show export buttons */
|
|
19
19
|
showExport?: boolean;
|
|
20
|
+
/** Show JSON export button (requires showExport=true) */
|
|
21
|
+
showJsonExport?: boolean;
|
|
20
22
|
/** Technology color mapping */
|
|
21
23
|
techColors?: TechColorMap;
|
|
22
24
|
/** Status color mapping */
|
|
@@ -33,6 +35,8 @@ interface Props {
|
|
|
33
35
|
persistSettings?: boolean;
|
|
34
36
|
/** Storage key prefix for persisted settings */
|
|
35
37
|
storageKey?: string;
|
|
38
|
+
/** Show scrollspy navigation bar for quick group navigation */
|
|
39
|
+
showScrollSpy?: boolean;
|
|
36
40
|
/** Bindable reference to table methods */
|
|
37
41
|
tableRef?: {
|
|
38
42
|
redraw: () => void;
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
selectedCount?: number;
|
|
17
17
|
/** Show export buttons */
|
|
18
18
|
showExport?: boolean;
|
|
19
|
+
/** Show JSON export button (requires showExport=true) */
|
|
20
|
+
showJsonExport?: boolean;
|
|
19
21
|
/** Show grouping dropdown */
|
|
20
22
|
showGrouping?: boolean;
|
|
21
23
|
/** Show preset dropdown */
|
|
@@ -46,6 +48,12 @@
|
|
|
46
48
|
oncolumnvisibilitychange?: (field: string, visible: boolean) => void;
|
|
47
49
|
/** Reset columns to preset default */
|
|
48
50
|
onresetcolumns?: () => void;
|
|
51
|
+
/** Whether scrollspy is enabled */
|
|
52
|
+
scrollSpyEnabled?: boolean;
|
|
53
|
+
/** Show scrollspy toggle button */
|
|
54
|
+
showScrollSpyToggle?: boolean;
|
|
55
|
+
/** Toggle scrollspy event */
|
|
56
|
+
ontogglescrollspy?: () => void;
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
let {
|
|
@@ -55,6 +63,7 @@
|
|
|
55
63
|
filteredCount = 0,
|
|
56
64
|
selectedCount = 0,
|
|
57
65
|
showExport = true,
|
|
66
|
+
showJsonExport = false,
|
|
58
67
|
showGrouping = true,
|
|
59
68
|
showPresets = true,
|
|
60
69
|
ongroupchange,
|
|
@@ -69,7 +78,10 @@
|
|
|
69
78
|
columnMeta = [],
|
|
70
79
|
visibleColumns = [],
|
|
71
80
|
oncolumnvisibilitychange,
|
|
72
|
-
onresetcolumns
|
|
81
|
+
onresetcolumns,
|
|
82
|
+
scrollSpyEnabled = false,
|
|
83
|
+
showScrollSpyToggle = false,
|
|
84
|
+
ontogglescrollspy
|
|
73
85
|
}: Props = $props();
|
|
74
86
|
|
|
75
87
|
const groupOptions: { value: CellTableGroupField; label: string }[] = [
|
|
@@ -192,6 +204,19 @@
|
|
|
192
204
|
|
|
193
205
|
<!-- Actions -->
|
|
194
206
|
<div class="toolbar-actions d-flex align-items-center gap-2">
|
|
207
|
+
{#if showScrollSpyToggle}
|
|
208
|
+
<button
|
|
209
|
+
type="button"
|
|
210
|
+
class="btn btn-sm"
|
|
211
|
+
class:btn-outline-secondary={!scrollSpyEnabled}
|
|
212
|
+
class:btn-secondary={scrollSpyEnabled}
|
|
213
|
+
onclick={ontogglescrollspy}
|
|
214
|
+
title={scrollSpyEnabled ? 'Hide quick navigation' : 'Show quick navigation'}
|
|
215
|
+
aria-label={scrollSpyEnabled ? 'Hide quick navigation' : 'Show quick navigation'}
|
|
216
|
+
>
|
|
217
|
+
<i class="bi bi-signpost-split"></i>
|
|
218
|
+
</button>
|
|
219
|
+
{/if}
|
|
195
220
|
{#if ontogglefilters}
|
|
196
221
|
<button
|
|
197
222
|
type="button"
|
|
@@ -228,15 +253,17 @@
|
|
|
228
253
|
<i class="bi bi-filetype-csv"></i>
|
|
229
254
|
<span class="d-none d-md-inline ms-1">CSV</span>
|
|
230
255
|
</button>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
256
|
+
{#if showJsonExport}
|
|
257
|
+
<button
|
|
258
|
+
type="button"
|
|
259
|
+
class="btn btn-sm btn-outline-primary"
|
|
260
|
+
onclick={onexportjson}
|
|
261
|
+
title="Export to JSON"
|
|
262
|
+
>
|
|
263
|
+
<i class="bi bi-filetype-json"></i>
|
|
264
|
+
<span class="d-none d-md-inline ms-1">JSON</span>
|
|
265
|
+
</button>
|
|
266
|
+
{/if}
|
|
240
267
|
</div>
|
|
241
268
|
{/if}
|
|
242
269
|
</div>
|
|
@@ -13,6 +13,8 @@ interface Props {
|
|
|
13
13
|
selectedCount?: number;
|
|
14
14
|
/** Show export buttons */
|
|
15
15
|
showExport?: boolean;
|
|
16
|
+
/** Show JSON export button (requires showExport=true) */
|
|
17
|
+
showJsonExport?: boolean;
|
|
16
18
|
/** Show grouping dropdown */
|
|
17
19
|
showGrouping?: boolean;
|
|
18
20
|
/** Show preset dropdown */
|
|
@@ -43,6 +45,12 @@ interface Props {
|
|
|
43
45
|
oncolumnvisibilitychange?: (field: string, visible: boolean) => void;
|
|
44
46
|
/** Reset columns to preset default */
|
|
45
47
|
onresetcolumns?: () => void;
|
|
48
|
+
/** Whether scrollspy is enabled */
|
|
49
|
+
scrollSpyEnabled?: boolean;
|
|
50
|
+
/** Show scrollspy toggle button */
|
|
51
|
+
showScrollSpyToggle?: boolean;
|
|
52
|
+
/** Toggle scrollspy event */
|
|
53
|
+
ontogglescrollspy?: () => void;
|
|
46
54
|
}
|
|
47
55
|
declare const CellTableToolbar: import("svelte").Component<Props, {}, "">;
|
|
48
56
|
type CellTableToolbar = ReturnType<typeof CellTableToolbar>;
|