@smartnet360/svelte-components 0.0.134 → 0.0.136
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/CellHistory/CellHistoryPanel.svelte +178 -0
- package/dist/core/CellHistory/CellHistoryPanel.svelte.d.ts +4 -0
- package/dist/core/CellHistory/column-config.d.ts +26 -0
- package/dist/core/CellHistory/column-config.js +120 -0
- package/dist/core/CellHistory/index.d.ts +9 -0
- package/dist/core/CellHistory/index.js +10 -0
- package/dist/core/CellHistory/transformers.d.ts +16 -0
- package/dist/core/CellHistory/transformers.js +76 -0
- package/dist/core/CellHistory/types.d.ts +54 -0
- package/dist/core/CellHistory/types.js +6 -0
- package/dist/core/{CellTable → CellTableV2}/CellTable.svelte +7 -3
- package/dist/core/{CellTable → CellTableV2}/CellTable.svelte.d.ts +3 -0
- package/dist/core/{CellTable → CellTableV2}/CellTablePanel.svelte +87 -235
- package/dist/core/{CellTable → CellTableV2}/CellTablePanel.svelte.d.ts +16 -2
- package/dist/core/{CellTable → CellTableV2}/CellTableToolbar.svelte +0 -1
- package/dist/core/{CellTable → CellTableV2}/column-config.d.ts +11 -30
- package/dist/core/{CellTable → CellTableV2}/column-config.js +21 -220
- package/dist/core/CellTableV2/composables/index.d.ts +12 -0
- package/dist/core/CellTableV2/composables/index.js +9 -0
- package/dist/core/CellTableV2/composables/useColumnVisibility.svelte.d.ts +45 -0
- package/dist/core/CellTableV2/composables/useColumnVisibility.svelte.js +98 -0
- package/dist/core/CellTableV2/composables/usePersistence.svelte.d.ts +28 -0
- package/dist/core/CellTableV2/composables/usePersistence.svelte.js +101 -0
- package/dist/core/CellTableV2/composables/useScrollSpy.svelte.d.ts +44 -0
- package/dist/core/CellTableV2/composables/useScrollSpy.svelte.js +91 -0
- package/dist/core/CellTableV2/index.d.ts +12 -0
- package/dist/core/CellTableV2/index.js +14 -0
- package/dist/core/{CellTable → CellTableV2}/types.d.ts +3 -3
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +3 -1
- package/package.json +1 -1
- package/dist/core/CellTable/CellHistoryDemo.svelte +0 -240
- package/dist/core/CellTable/CellHistoryDemo.svelte.d.ts +0 -3
- package/dist/core/CellTable/CellTableDemo.svelte +0 -336
- package/dist/core/CellTable/CellTableDemo.svelte.d.ts +0 -3
- package/dist/core/CellTable/history-api-helper.d.ts +0 -79
- package/dist/core/CellTable/history-api-helper.js +0 -83
- package/dist/core/CellTable/index.d.ts +0 -15
- package/dist/core/CellTable/index.js +0 -20
- /package/dist/core/{CellTable → CellTableV2}/CellTableToolbar.svelte.d.ts +0 -0
- /package/dist/core/{CellTable → CellTableV2}/ColumnPicker.svelte +0 -0
- /package/dist/core/{CellTable → CellTableV2}/ColumnPicker.svelte.d.ts +0 -0
- /package/dist/core/{CellTable → CellTableV2}/types.js +0 -0
|
@@ -60,8 +60,6 @@ export const COLUMN_GROUPS = {
|
|
|
60
60
|
atoll: ['atollET', 'atollMT', 'atollPW', 'atollRS', 'atollBW'],
|
|
61
61
|
position: ['latitude', 'longitude', 'siteLatitude', 'siteLongitude', 'dx', 'dy'],
|
|
62
62
|
compare: ['compareET', 'comparePW', 'compareRS', 'compareBW'],
|
|
63
|
-
kpi: ['kpiTraffic', 'kpiThroughput', 'kpiAvailability', 'kpiSuccessRate'],
|
|
64
|
-
history: ['configDate', 'frequency', 'antennaChange', 'etiltChange', 'mtiltChange', 'powerChange'],
|
|
65
63
|
};
|
|
66
64
|
/**
|
|
67
65
|
* Create a technology badge formatter
|
|
@@ -168,98 +166,6 @@ export function createCompareFormatter(atollField, nwtField) {
|
|
|
168
166
|
return `<span class="badge" style="background-color: ${bgColor}; color: ${textColor}; font-size: 0.75rem; font-weight: normal;">${atollStr} | ${nwtStr}</span>`;
|
|
169
167
|
};
|
|
170
168
|
}
|
|
171
|
-
/**
|
|
172
|
-
* Create a history change formatter for showing current vs previous values
|
|
173
|
-
* Highlights changes in yellow/amber, unchanged values in muted style
|
|
174
|
-
*
|
|
175
|
-
* @param currentField - Field name for current value
|
|
176
|
-
* @param prevField - Field name for previous value
|
|
177
|
-
*/
|
|
178
|
-
export function createHistoryChangeFormatter(currentField, prevField) {
|
|
179
|
-
return (cell) => {
|
|
180
|
-
const row = cell.getRow().getData();
|
|
181
|
-
const currentVal = row[currentField];
|
|
182
|
-
const prevVal = row[prevField];
|
|
183
|
-
const currentStr = currentVal !== null && currentVal !== undefined ? String(currentVal) : '-';
|
|
184
|
-
const prevStr = prevVal !== null && prevVal !== undefined ? String(prevVal) : '-';
|
|
185
|
-
const hasChanged = currentVal !== prevVal;
|
|
186
|
-
if (hasChanged) {
|
|
187
|
-
// Value changed - highlight with amber/warning color
|
|
188
|
-
return `<span class="badge bg-warning text-dark" style="font-size: 0.75rem; font-weight: 500;" title="Changed from: ${prevStr}">${currentStr}</span>`;
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
// No change - muted style
|
|
192
|
-
return `<span class="text-muted">${currentStr}</span>`;
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Format ISO date string to YYYY.MM.DD format for history display
|
|
198
|
-
*/
|
|
199
|
-
export function historyDateFormatter(cell) {
|
|
200
|
-
const value = cell.getValue();
|
|
201
|
-
if (!value)
|
|
202
|
-
return '-';
|
|
203
|
-
try {
|
|
204
|
-
const date = new Date(value);
|
|
205
|
-
const year = date.getFullYear();
|
|
206
|
-
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
207
|
-
const day = String(date.getDate()).padStart(2, '0');
|
|
208
|
-
return `${year}-${month}-${day}`;
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
return value;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
export function createSparklineFormatter(options = {}) {
|
|
215
|
-
const { width = 80, height = 24, lineColor = '#0d6efd', fillColor = 'rgba(13,110,253,0.2)', showDots = false, showLastValue = true, unit = '', decimals = 1 } = options;
|
|
216
|
-
return (cell) => {
|
|
217
|
-
const data = cell.getValue();
|
|
218
|
-
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
219
|
-
return '<span class="text-muted">—</span>';
|
|
220
|
-
}
|
|
221
|
-
const values = data.filter(v => typeof v === 'number' && !isNaN(v));
|
|
222
|
-
if (values.length === 0) {
|
|
223
|
-
return '<span class="text-muted">—</span>';
|
|
224
|
-
}
|
|
225
|
-
const min = Math.min(...values);
|
|
226
|
-
const max = Math.max(...values);
|
|
227
|
-
const range = max - min || 1;
|
|
228
|
-
const padding = 2;
|
|
229
|
-
const chartWidth = showLastValue ? width - 35 : width - 4;
|
|
230
|
-
const chartHeight = height - 4;
|
|
231
|
-
// Generate SVG path
|
|
232
|
-
const points = values.map((v, i) => {
|
|
233
|
-
const x = padding + (i / (values.length - 1 || 1)) * (chartWidth - padding * 2);
|
|
234
|
-
const y = padding + (1 - (v - min) / range) * (chartHeight - padding * 2);
|
|
235
|
-
return { x, y, v };
|
|
236
|
-
});
|
|
237
|
-
// Line path
|
|
238
|
-
const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x.toFixed(1)} ${p.y.toFixed(1)}`).join(' ');
|
|
239
|
-
// Fill path (closed polygon)
|
|
240
|
-
const fillPath = `${linePath} L ${points[points.length - 1].x.toFixed(1)} ${chartHeight - padding} L ${padding} ${chartHeight - padding} Z`;
|
|
241
|
-
// Dots
|
|
242
|
-
const dots = showDots
|
|
243
|
-
? points.map(p => `<circle cx="${p.x.toFixed(1)}" cy="${p.y.toFixed(1)}" r="1.5" fill="${lineColor}"/>`).join('')
|
|
244
|
-
: '';
|
|
245
|
-
// Last value text
|
|
246
|
-
const lastValue = values[values.length - 1];
|
|
247
|
-
const valueText = showLastValue
|
|
248
|
-
? `<text x="${width - 2}" y="${height / 2 + 4}" text-anchor="end" font-size="10" fill="#333">${lastValue.toFixed(decimals)}${unit}</text>`
|
|
249
|
-
: '';
|
|
250
|
-
// Trend indicator (comparing last vs first)
|
|
251
|
-
const trend = values.length > 1 ? values[values.length - 1] - values[0] : 0;
|
|
252
|
-
const trendColor = trend >= 0 ? '#198754' : '#dc3545';
|
|
253
|
-
return `
|
|
254
|
-
<svg width="${width}" height="${height}" style="vertical-align: middle;">
|
|
255
|
-
<path d="${fillPath}" fill="${fillColor}" />
|
|
256
|
-
<path d="${linePath}" fill="none" stroke="${lineColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
257
|
-
${dots}
|
|
258
|
-
${valueText}
|
|
259
|
-
</svg>
|
|
260
|
-
`.trim().replace(/\s+/g, ' ');
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
169
|
/**
|
|
264
170
|
* Custom sorter for fband - extracts numeric portion and sorts numerically
|
|
265
171
|
* Examples: LTE700 → 700, GSM900 → 900, LTE1800 → 1800, 5G-3500 → 3500
|
|
@@ -365,49 +271,6 @@ export function getAllColumns(techColors = DEFAULT_TECH_COLORS, statusColors = D
|
|
|
365
271
|
width: 120,
|
|
366
272
|
...headerFilterParams,
|
|
367
273
|
},
|
|
368
|
-
{
|
|
369
|
-
title: 'Config Date',
|
|
370
|
-
field: 'configDate',
|
|
371
|
-
width: 120,
|
|
372
|
-
formatter: historyDateFormatter,
|
|
373
|
-
...headerFilterParams,
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
title: 'Freq (MHz)',
|
|
377
|
-
field: 'frequency',
|
|
378
|
-
width: 100,
|
|
379
|
-
hozAlign: 'right',
|
|
380
|
-
...headerFilterParams,
|
|
381
|
-
},
|
|
382
|
-
// History change columns (current vs previous, highlights changes)
|
|
383
|
-
{
|
|
384
|
-
title: 'Antenna',
|
|
385
|
-
field: 'antennaChange',
|
|
386
|
-
width: 160,
|
|
387
|
-
formatter: createHistoryChangeFormatter('antenna', 'prevAntenna'),
|
|
388
|
-
...headerFilterParams,
|
|
389
|
-
},
|
|
390
|
-
{
|
|
391
|
-
title: 'E-Tilt',
|
|
392
|
-
field: 'etiltChange',
|
|
393
|
-
width: 80,
|
|
394
|
-
hozAlign: 'center',
|
|
395
|
-
formatter: createHistoryChangeFormatter('electricalTilt', 'prevElectricalTilt'),
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
title: 'M-Tilt',
|
|
399
|
-
field: 'mtiltChange',
|
|
400
|
-
width: 80,
|
|
401
|
-
hozAlign: 'center',
|
|
402
|
-
formatter: createHistoryChangeFormatter('mechanicalTilt', 'prevMechanicalTilt'),
|
|
403
|
-
},
|
|
404
|
-
{
|
|
405
|
-
title: 'Power',
|
|
406
|
-
field: 'powerChange',
|
|
407
|
-
width: 80,
|
|
408
|
-
hozAlign: 'center',
|
|
409
|
-
formatter: createHistoryChangeFormatter('power', 'prevPower'),
|
|
410
|
-
},
|
|
411
274
|
// Physical columns
|
|
412
275
|
{
|
|
413
276
|
title: 'Antenna',
|
|
@@ -589,63 +452,6 @@ export function getAllColumns(techColors = DEFAULT_TECH_COLORS, statusColors = D
|
|
|
589
452
|
formatter: createCompareFormatter('atollBW', 'nwBW'),
|
|
590
453
|
headerTooltip: 'Atoll BW | Network BW',
|
|
591
454
|
},
|
|
592
|
-
// KPI Sparkline columns
|
|
593
|
-
{
|
|
594
|
-
title: 'Traffic',
|
|
595
|
-
field: 'kpiTraffic',
|
|
596
|
-
width: 120,
|
|
597
|
-
hozAlign: 'center',
|
|
598
|
-
formatter: createSparklineFormatter({
|
|
599
|
-
unit: '',
|
|
600
|
-
lineColor: '#0d6efd',
|
|
601
|
-
fillColor: 'rgba(13,110,253,0.15)',
|
|
602
|
-
decimals: 0
|
|
603
|
-
}),
|
|
604
|
-
headerTooltip: 'Traffic volume trend (GB)',
|
|
605
|
-
headerSort: false,
|
|
606
|
-
},
|
|
607
|
-
{
|
|
608
|
-
title: 'Throughput',
|
|
609
|
-
field: 'kpiThroughput',
|
|
610
|
-
width: 120,
|
|
611
|
-
hozAlign: 'center',
|
|
612
|
-
formatter: createSparklineFormatter({
|
|
613
|
-
unit: '',
|
|
614
|
-
lineColor: '#6f42c1',
|
|
615
|
-
fillColor: 'rgba(111,66,193,0.15)',
|
|
616
|
-
decimals: 1
|
|
617
|
-
}),
|
|
618
|
-
headerTooltip: 'Throughput trend (Mbps)',
|
|
619
|
-
headerSort: false,
|
|
620
|
-
},
|
|
621
|
-
{
|
|
622
|
-
title: 'Avail %',
|
|
623
|
-
field: 'kpiAvailability',
|
|
624
|
-
width: 120,
|
|
625
|
-
hozAlign: 'center',
|
|
626
|
-
formatter: createSparklineFormatter({
|
|
627
|
-
unit: '%',
|
|
628
|
-
lineColor: '#198754',
|
|
629
|
-
fillColor: 'rgba(25,135,84,0.15)',
|
|
630
|
-
decimals: 1
|
|
631
|
-
}),
|
|
632
|
-
headerTooltip: 'Availability trend (%)',
|
|
633
|
-
headerSort: false,
|
|
634
|
-
},
|
|
635
|
-
{
|
|
636
|
-
title: 'Success %',
|
|
637
|
-
field: 'kpiSuccessRate',
|
|
638
|
-
width: 120,
|
|
639
|
-
hozAlign: 'center',
|
|
640
|
-
formatter: createSparklineFormatter({
|
|
641
|
-
unit: '%',
|
|
642
|
-
lineColor: '#fd7e14',
|
|
643
|
-
fillColor: 'rgba(253,126,20,0.15)',
|
|
644
|
-
decimals: 1
|
|
645
|
-
}),
|
|
646
|
-
headerTooltip: 'Success rate trend (%)',
|
|
647
|
-
headerSort: false,
|
|
648
|
-
},
|
|
649
455
|
// Position columns
|
|
650
456
|
{
|
|
651
457
|
title: 'Latitude',
|
|
@@ -740,13 +546,6 @@ export function getColumnsForPreset(preset, techColors = DEFAULT_TECH_COLORS, st
|
|
|
740
546
|
case 'compare':
|
|
741
547
|
visibleFields = [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.compare];
|
|
742
548
|
break;
|
|
743
|
-
case 'kpi':
|
|
744
|
-
visibleFields = [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.kpi];
|
|
745
|
-
break;
|
|
746
|
-
case 'history':
|
|
747
|
-
// Simplified view for config history - just cellName, date, and config fields
|
|
748
|
-
visibleFields = ['cellName', ...COLUMN_GROUPS.history];
|
|
749
|
-
break;
|
|
750
549
|
case 'default':
|
|
751
550
|
default:
|
|
752
551
|
visibleFields = [
|
|
@@ -778,16 +577,16 @@ export function getGroupHeaderFormatter(groupField) {
|
|
|
778
577
|
}
|
|
779
578
|
/**
|
|
780
579
|
* Get column metadata for the column picker UI
|
|
580
|
+
* @param options Filter options for including/excluding column groups
|
|
781
581
|
*/
|
|
782
|
-
export function getColumnMetadata() {
|
|
783
|
-
|
|
582
|
+
export function getColumnMetadata(options) {
|
|
583
|
+
const allColumns = [
|
|
784
584
|
// Core
|
|
785
585
|
{ field: 'siteId', title: 'Site ID', group: 'Core' },
|
|
786
586
|
{ field: 'txId', title: 'TX ID', group: 'Core' },
|
|
787
587
|
{ field: 'cellName', title: 'Cell Name', group: 'Core' },
|
|
788
588
|
{ field: 'tech', title: 'Technology', group: 'Core' },
|
|
789
589
|
{ field: 'fband', title: 'Band', group: 'Core' },
|
|
790
|
-
{ field: 'rru', title: 'RRU', group: 'Core' },
|
|
791
590
|
{ field: 'frq', title: 'Frequency', group: 'Core' },
|
|
792
591
|
{ field: 'status', title: 'Status', group: 'Core' },
|
|
793
592
|
{ field: 'onAirDate', title: 'On Air Date', group: 'Core' },
|
|
@@ -816,19 +615,10 @@ export function getColumnMetadata() {
|
|
|
816
615
|
{ field: 'dlEarfn', title: 'DL EARFCN', group: 'Network' },
|
|
817
616
|
{ field: 'bcch', title: 'BCCH', group: 'Network' },
|
|
818
617
|
{ field: 'pci', title: 'PCI', group: 'Network' },
|
|
618
|
+
{ field: 'rru', title: 'RRU', group: 'Network' },
|
|
619
|
+
{ field: 'cellID', title: 'Cell ID', group: 'Network' },
|
|
620
|
+
{ field: 'cellId2G', title: 'Cell ID 2G', group: 'Network' },
|
|
819
621
|
{ field: 'ctrlid', title: 'Ctrl ID', group: 'Network' },
|
|
820
|
-
// History
|
|
821
|
-
{ field: 'configDate', title: 'Config Date', group: 'History' },
|
|
822
|
-
{ field: 'frequency', title: 'Freq (MHz)', group: 'History' },
|
|
823
|
-
{ field: 'antennaChange', title: 'Antenna (Δ)', group: 'History' },
|
|
824
|
-
{ field: 'etiltChange', title: 'E-Tilt (Δ)', group: 'History' },
|
|
825
|
-
{ field: 'mtiltChange', title: 'M-Tilt (Δ)', group: 'History' },
|
|
826
|
-
{ field: 'powerChange', title: 'Power (Δ)', group: 'History' },
|
|
827
|
-
// KPI Trends
|
|
828
|
-
{ field: 'kpiTraffic', title: 'Traffic', group: 'KPI' },
|
|
829
|
-
{ field: 'kpiThroughput', title: 'Throughput', group: 'KPI' },
|
|
830
|
-
{ field: 'kpiAvailability', title: 'Availability', group: 'KPI' },
|
|
831
|
-
{ field: 'kpiSuccessRate', title: 'Success Rate', group: 'KPI' },
|
|
832
622
|
// Position
|
|
833
623
|
// { field: 'latitude', title: 'Latitude', group: 'Position' },
|
|
834
624
|
// { field: 'longitude', title: 'Longitude', group: 'Position' },
|
|
@@ -841,6 +631,21 @@ export function getColumnMetadata() {
|
|
|
841
631
|
{ field: 'comment', title: 'Comment', group: 'Planning' },
|
|
842
632
|
// { field: 'customSubgroup', title: 'Subgroup', group: 'Planning' },
|
|
843
633
|
];
|
|
634
|
+
// Apply filtering if options provided
|
|
635
|
+
const { include, exclude } = options ?? {};
|
|
636
|
+
return allColumns.filter(col => {
|
|
637
|
+
// If include is specified, only include those groups
|
|
638
|
+
if (include && include.length > 0) {
|
|
639
|
+
if (!include.includes(col.group)) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// Apply exclude filter
|
|
644
|
+
if (exclude && exclude.includes(col.group)) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
return true;
|
|
648
|
+
});
|
|
844
649
|
}
|
|
845
650
|
/**
|
|
846
651
|
* Get default visible columns for a preset
|
|
@@ -859,10 +664,6 @@ export function getPresetVisibleFields(preset) {
|
|
|
859
664
|
return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.planning];
|
|
860
665
|
case 'compare':
|
|
861
666
|
return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.compare];
|
|
862
|
-
case 'kpi':
|
|
863
|
-
return [...COLUMN_GROUPS.core, ...COLUMN_GROUPS.kpi];
|
|
864
|
-
case 'history':
|
|
865
|
-
return ['cellName', ...COLUMN_GROUPS.history];
|
|
866
667
|
case 'default':
|
|
867
668
|
default:
|
|
868
669
|
return ['siteId', 'txId', 'cellName', 'tech', 'fband', 'frq', 'status', 'azimuth', 'height', 'antenna'];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CellTableV2 Composables
|
|
3
|
+
*
|
|
4
|
+
* Reusable logic hooks for cell table functionality.
|
|
5
|
+
* These composables use Svelte 5 runes and can be shared across different panel types.
|
|
6
|
+
*/
|
|
7
|
+
export { usePersistence } from './usePersistence.svelte';
|
|
8
|
+
export type { UsePersistenceOptions, UsePersistenceReturn } from './usePersistence.svelte';
|
|
9
|
+
export { useColumnVisibility } from './useColumnVisibility.svelte';
|
|
10
|
+
export type { UseColumnVisibilityOptions, UseColumnVisibilityReturn } from './useColumnVisibility.svelte';
|
|
11
|
+
export { useScrollSpy } from './useScrollSpy.svelte';
|
|
12
|
+
export type { UseScrollSpyOptions, UseScrollSpyReturn, ScrollSpyGroup, CellTableRef } from './useScrollSpy.svelte';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CellTableV2 Composables
|
|
3
|
+
*
|
|
4
|
+
* Reusable logic hooks for cell table functionality.
|
|
5
|
+
* These composables use Svelte 5 runes and can be shared across different panel types.
|
|
6
|
+
*/
|
|
7
|
+
export { usePersistence } from './usePersistence.svelte';
|
|
8
|
+
export { useColumnVisibility } from './useColumnVisibility.svelte';
|
|
9
|
+
export { useScrollSpy } from './useScrollSpy.svelte';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useColumnVisibility - Column visibility management composable
|
|
3
|
+
*
|
|
4
|
+
* Manages column preset selection and per-preset column visibility customization.
|
|
5
|
+
* Uses Svelte 5 runes for reactive state.
|
|
6
|
+
*/
|
|
7
|
+
import type { ColumnPreset } from '../types';
|
|
8
|
+
import type { ColumnMeta } from '../column-config';
|
|
9
|
+
export interface UseColumnVisibilityOptions {
|
|
10
|
+
/** Base storage key for persistence */
|
|
11
|
+
storageKey: string;
|
|
12
|
+
/** Column metadata for the column picker */
|
|
13
|
+
columnMeta: ColumnMeta[];
|
|
14
|
+
/** Initial column preset */
|
|
15
|
+
initialPreset?: ColumnPreset;
|
|
16
|
+
/** Whether to persist settings to localStorage */
|
|
17
|
+
persistSettings?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface UseColumnVisibilityReturn {
|
|
20
|
+
/** Current column preset (reactive) */
|
|
21
|
+
readonly columnPreset: ColumnPreset;
|
|
22
|
+
/** Currently visible column fields (reactive) */
|
|
23
|
+
readonly visibleColumns: string[];
|
|
24
|
+
/** Column metadata */
|
|
25
|
+
readonly columnMeta: ColumnMeta[];
|
|
26
|
+
/** Load saved columns for a preset (or default if none saved) */
|
|
27
|
+
loadColumnsForPreset: (preset: ColumnPreset) => string[];
|
|
28
|
+
/** Set the column preset and load its columns */
|
|
29
|
+
setPreset: (preset: ColumnPreset) => void;
|
|
30
|
+
/** Toggle a single column's visibility */
|
|
31
|
+
toggleColumn: (field: string, visible: boolean) => void;
|
|
32
|
+
/** Reset current preset to its default columns */
|
|
33
|
+
resetToDefaults: () => void;
|
|
34
|
+
/** Apply column visibility to a CellTable instance */
|
|
35
|
+
applyToTable: (table: {
|
|
36
|
+
showColumn: (f: string) => void;
|
|
37
|
+
hideColumn: (f: string) => void;
|
|
38
|
+
} | undefined) => void;
|
|
39
|
+
/** Save current visibility state */
|
|
40
|
+
saveVisibility: () => void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create column visibility management for a cell table
|
|
44
|
+
*/
|
|
45
|
+
export declare function useColumnVisibility(options: UseColumnVisibilityOptions): UseColumnVisibilityReturn;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useColumnVisibility - Column visibility management composable
|
|
3
|
+
*
|
|
4
|
+
* Manages column preset selection and per-preset column visibility customization.
|
|
5
|
+
* Uses Svelte 5 runes for reactive state.
|
|
6
|
+
*/
|
|
7
|
+
import { getPresetVisibleFields } from '../column-config';
|
|
8
|
+
import { usePersistence } from './usePersistence.svelte';
|
|
9
|
+
/**
|
|
10
|
+
* Create column visibility management for a cell table
|
|
11
|
+
*/
|
|
12
|
+
export function useColumnVisibility(options) {
|
|
13
|
+
const { storageKey, columnMeta, initialPreset = 'default', persistSettings = true } = options;
|
|
14
|
+
// Persistence utilities
|
|
15
|
+
const persistence = usePersistence({ storageKey, enabled: persistSettings });
|
|
16
|
+
// Storage key for per-preset column visibility
|
|
17
|
+
function getColumnsKey(preset) {
|
|
18
|
+
return `visibleColumns-${preset}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Load saved columns for a preset (or default if none saved)
|
|
22
|
+
*/
|
|
23
|
+
function loadColumnsForPreset(preset) {
|
|
24
|
+
const saved = persistence.load(getColumnsKey(preset), null);
|
|
25
|
+
if (saved && Array.isArray(saved)) {
|
|
26
|
+
return saved;
|
|
27
|
+
}
|
|
28
|
+
return getPresetVisibleFields(preset);
|
|
29
|
+
}
|
|
30
|
+
// Load initial preset from storage
|
|
31
|
+
const savedPreset = persistence.loadString('columnPreset', initialPreset);
|
|
32
|
+
// Reactive state
|
|
33
|
+
let _columnPreset = $state(savedPreset);
|
|
34
|
+
let _visibleColumns = $state(loadColumnsForPreset(savedPreset));
|
|
35
|
+
/**
|
|
36
|
+
* Save current column visibility for current preset
|
|
37
|
+
*/
|
|
38
|
+
function saveVisibility() {
|
|
39
|
+
persistence.save(getColumnsKey(_columnPreset), _visibleColumns);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Set the column preset and load its saved columns
|
|
43
|
+
*/
|
|
44
|
+
function setPreset(preset) {
|
|
45
|
+
_columnPreset = preset;
|
|
46
|
+
_visibleColumns = loadColumnsForPreset(preset);
|
|
47
|
+
persistence.saveString('columnPreset', preset);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Toggle a single column's visibility
|
|
51
|
+
*/
|
|
52
|
+
function toggleColumn(field, visible) {
|
|
53
|
+
if (visible) {
|
|
54
|
+
if (!_visibleColumns.includes(field)) {
|
|
55
|
+
_visibleColumns = [..._visibleColumns, field];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
_visibleColumns = _visibleColumns.filter(f => f !== field);
|
|
60
|
+
}
|
|
61
|
+
saveVisibility();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Reset current preset to its default columns
|
|
65
|
+
*/
|
|
66
|
+
function resetToDefaults() {
|
|
67
|
+
_visibleColumns = getPresetVisibleFields(_columnPreset);
|
|
68
|
+
// Remove saved customization
|
|
69
|
+
persistence.remove(getColumnsKey(_columnPreset));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Apply column visibility to a CellTable instance
|
|
73
|
+
*/
|
|
74
|
+
function applyToTable(table) {
|
|
75
|
+
if (!table)
|
|
76
|
+
return;
|
|
77
|
+
columnMeta.forEach(col => {
|
|
78
|
+
if (_visibleColumns.includes(col.field)) {
|
|
79
|
+
table.showColumn(col.field);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
table.hideColumn(col.field);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// Return with getters for reactivity
|
|
87
|
+
return {
|
|
88
|
+
get columnPreset() { return _columnPreset; },
|
|
89
|
+
get visibleColumns() { return _visibleColumns; },
|
|
90
|
+
columnMeta,
|
|
91
|
+
loadColumnsForPreset,
|
|
92
|
+
setPreset,
|
|
93
|
+
toggleColumn,
|
|
94
|
+
resetToDefaults,
|
|
95
|
+
applyToTable,
|
|
96
|
+
saveVisibility
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePersistence - localStorage persistence utility
|
|
3
|
+
*
|
|
4
|
+
* Provides namespaced read/write/remove functions for persisting UI state.
|
|
5
|
+
* Does not use reactive state - just utility functions.
|
|
6
|
+
*/
|
|
7
|
+
export interface UsePersistenceOptions {
|
|
8
|
+
/** Base storage key prefix for namespacing */
|
|
9
|
+
storageKey: string;
|
|
10
|
+
/** Whether persistence is enabled */
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface UsePersistenceReturn {
|
|
14
|
+
/** Load a value from localStorage */
|
|
15
|
+
load: <T>(key: string, defaultValue: T) => T;
|
|
16
|
+
/** Save a value to localStorage */
|
|
17
|
+
save: <T>(key: string, value: T) => void;
|
|
18
|
+
/** Remove a value from localStorage */
|
|
19
|
+
remove: (key: string) => void;
|
|
20
|
+
/** Load a string value (no JSON parsing) */
|
|
21
|
+
loadString: (key: string, defaultValue: string) => string;
|
|
22
|
+
/** Save a string value (no JSON stringifying) */
|
|
23
|
+
saveString: (key: string, value: string) => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create persistence utilities for a given storage namespace
|
|
27
|
+
*/
|
|
28
|
+
export declare function usePersistence(options: UsePersistenceOptions): UsePersistenceReturn;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePersistence - localStorage persistence utility
|
|
3
|
+
*
|
|
4
|
+
* Provides namespaced read/write/remove functions for persisting UI state.
|
|
5
|
+
* Does not use reactive state - just utility functions.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Create persistence utilities for a given storage namespace
|
|
9
|
+
*/
|
|
10
|
+
export function usePersistence(options) {
|
|
11
|
+
const { storageKey, enabled = true } = options;
|
|
12
|
+
/**
|
|
13
|
+
* Get full storage key with namespace
|
|
14
|
+
*/
|
|
15
|
+
function getKey(key) {
|
|
16
|
+
return `${storageKey}-${key}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Load a JSON value from localStorage
|
|
20
|
+
*/
|
|
21
|
+
function load(key, defaultValue) {
|
|
22
|
+
if (!enabled || typeof localStorage === 'undefined') {
|
|
23
|
+
return defaultValue;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const saved = localStorage.getItem(getKey(key));
|
|
27
|
+
if (saved !== null) {
|
|
28
|
+
return JSON.parse(saved);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
console.warn(`Failed to load ${key}:`, e);
|
|
33
|
+
}
|
|
34
|
+
return defaultValue;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Save a JSON value to localStorage
|
|
38
|
+
*/
|
|
39
|
+
function save(key, value) {
|
|
40
|
+
if (!enabled || typeof localStorage === 'undefined') {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
localStorage.setItem(getKey(key), JSON.stringify(value));
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
console.warn(`Failed to save ${key}:`, e);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Remove a value from localStorage
|
|
52
|
+
*/
|
|
53
|
+
function remove(key) {
|
|
54
|
+
if (!enabled || typeof localStorage === 'undefined') {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
localStorage.removeItem(getKey(key));
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
console.warn(`Failed to remove ${key}:`, e);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Load a string value (no JSON parsing)
|
|
66
|
+
*/
|
|
67
|
+
function loadString(key, defaultValue) {
|
|
68
|
+
if (!enabled || typeof localStorage === 'undefined') {
|
|
69
|
+
return defaultValue;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const saved = localStorage.getItem(getKey(key));
|
|
73
|
+
return saved ?? defaultValue;
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
console.warn(`Failed to load string ${key}:`, e);
|
|
77
|
+
return defaultValue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Save a string value (no JSON stringifying)
|
|
82
|
+
*/
|
|
83
|
+
function saveString(key, value) {
|
|
84
|
+
if (!enabled || typeof localStorage === 'undefined') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
localStorage.setItem(getKey(key), value);
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
console.warn(`Failed to save string ${key}:`, e);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
load,
|
|
96
|
+
save,
|
|
97
|
+
remove,
|
|
98
|
+
loadString,
|
|
99
|
+
saveString
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useScrollSpy - ScrollSpy navigation composable
|
|
3
|
+
*
|
|
4
|
+
* Manages group-based scroll navigation for grouped tables.
|
|
5
|
+
* Uses Svelte 5 runes for reactive state.
|
|
6
|
+
*/
|
|
7
|
+
export interface ScrollSpyGroup {
|
|
8
|
+
key: string;
|
|
9
|
+
count: number;
|
|
10
|
+
}
|
|
11
|
+
export interface CellTableRef {
|
|
12
|
+
getGroups: () => ScrollSpyGroup[];
|
|
13
|
+
scrollToGroup: (key: string) => void;
|
|
14
|
+
}
|
|
15
|
+
export interface UseScrollSpyOptions {
|
|
16
|
+
/** Base storage key for persistence */
|
|
17
|
+
storageKey: string;
|
|
18
|
+
/** Initial enabled state */
|
|
19
|
+
initialEnabled?: boolean;
|
|
20
|
+
/** Whether to persist settings */
|
|
21
|
+
persistSettings?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface UseScrollSpyReturn {
|
|
24
|
+
/** ScrollSpy groups (reactive) */
|
|
25
|
+
readonly groups: ScrollSpyGroup[];
|
|
26
|
+
/** Whether scrollspy is enabled (reactive) */
|
|
27
|
+
readonly enabled: boolean;
|
|
28
|
+
/** Toggle scrollspy on/off */
|
|
29
|
+
toggle: () => void;
|
|
30
|
+
/** Enable scrollspy */
|
|
31
|
+
enable: () => void;
|
|
32
|
+
/** Disable scrollspy */
|
|
33
|
+
disable: () => void;
|
|
34
|
+
/** Update groups from table data */
|
|
35
|
+
updateGroups: (table: CellTableRef | undefined) => void;
|
|
36
|
+
/** Scroll to a specific group */
|
|
37
|
+
scrollToGroup: (table: CellTableRef | undefined, key: string) => void;
|
|
38
|
+
/** Clear groups */
|
|
39
|
+
clearGroups: () => void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create scrollspy navigation for a grouped table
|
|
43
|
+
*/
|
|
44
|
+
export declare function useScrollSpy(options: UseScrollSpyOptions): UseScrollSpyReturn;
|