@smartnet360/svelte-components 0.0.51 → 0.0.54
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/apps/site-check/transforms.js +2 -2
- package/dist/core/Charts/ChartCard.svelte +6 -1
- package/dist/core/Charts/ChartComponent.svelte +8 -3
- package/dist/core/Charts/GlobalControls.svelte +116 -14
- package/dist/core/Charts/adapt.js +1 -1
- package/dist/core/Charts/charts.model.d.ts +3 -0
- package/dist/core/FeatureRegistry/index.js +1 -1
- package/dist/core/TreeView/TreeNode.svelte +40 -45
- package/dist/core/TreeView/TreeNode.svelte.d.ts +10 -0
- package/dist/core/TreeView/TreeView.svelte +14 -2
- package/dist/core/TreeView/TreeView.svelte.d.ts +10 -0
- package/dist/core/TreeView/tree-utils.d.ts +3 -0
- package/dist/core/TreeView/tree-utils.js +33 -9
- package/dist/core/TreeView/tree.store.js +49 -24
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/map/controls/MapControl.svelte +204 -0
- package/dist/map/controls/MapControl.svelte.d.ts +17 -0
- package/dist/map/controls/SiteFilterControl.svelte +126 -0
- package/dist/map/controls/SiteFilterControl.svelte.d.ts +16 -0
- package/dist/map/demo/DemoMap.svelte +98 -0
- package/dist/map/demo/DemoMap.svelte.d.ts +12 -0
- package/dist/map/demo/demo-data.d.ts +12 -0
- package/dist/map/demo/demo-data.js +220 -0
- package/dist/map/hooks/useCellData.d.ts +14 -0
- package/dist/map/hooks/useCellData.js +29 -0
- package/dist/map/hooks/useMapbox.d.ts +14 -0
- package/dist/map/hooks/useMapbox.js +29 -0
- package/dist/map/index.d.ts +27 -0
- package/dist/map/index.js +47 -0
- package/dist/map/layers/CellsLayer.svelte +242 -0
- package/dist/map/layers/CellsLayer.svelte.d.ts +21 -0
- package/dist/map/layers/CoverageLayer.svelte +37 -0
- package/dist/map/layers/CoverageLayer.svelte.d.ts +9 -0
- package/dist/map/layers/LayerBase.d.ts +42 -0
- package/dist/map/layers/LayerBase.js +58 -0
- package/dist/map/layers/SitesLayer.svelte +282 -0
- package/dist/map/layers/SitesLayer.svelte.d.ts +19 -0
- package/dist/map/providers/CellDataProvider.svelte +43 -0
- package/dist/map/providers/CellDataProvider.svelte.d.ts +12 -0
- package/dist/map/providers/MapboxProvider.svelte +38 -0
- package/dist/map/providers/MapboxProvider.svelte.d.ts +9 -0
- package/dist/map/providers/providerHelpers.d.ts +17 -0
- package/dist/map/providers/providerHelpers.js +26 -0
- package/dist/map/stores/cellDataStore.d.ts +21 -0
- package/dist/map/stores/cellDataStore.js +53 -0
- package/dist/map/stores/interactions.d.ts +20 -0
- package/dist/map/stores/interactions.js +33 -0
- package/dist/map/stores/mapStore.d.ts +8 -0
- package/dist/map/stores/mapStore.js +10 -0
- package/dist/map/types.d.ts +115 -0
- package/dist/map/types.js +10 -0
- package/dist/map/utils/geojson.d.ts +20 -0
- package/dist/map/utils/geojson.js +78 -0
- package/dist/map/utils/mapboxHelpers.d.ts +51 -0
- package/dist/map/utils/mapboxHelpers.js +98 -0
- package/dist/map/utils/math.d.ts +40 -0
- package/dist/map/utils/math.js +95 -0
- package/dist/map/utils/siteTreeUtils.d.ts +27 -0
- package/dist/map/utils/siteTreeUtils.js +164 -0
- package/package.json +1 -1
- package/dist/core/Map/Map.svelte +0 -312
- package/dist/core/Map/Map.svelte.d.ts +0 -230
- package/dist/core/Map/index.d.ts +0 -9
- package/dist/core/Map/index.js +0 -9
- package/dist/core/Map/mapSettings.d.ts +0 -147
- package/dist/core/Map/mapSettings.js +0 -226
- package/dist/core/Map/mapStore.d.ts +0 -73
- package/dist/core/Map/mapStore.js +0 -136
- package/dist/core/Map/types.d.ts +0 -72
- package/dist/core/Map/types.js +0 -32
|
@@ -229,7 +229,7 @@ export function buildTreeNodes(data, grouping = { level0: 'site', level1: 'azimu
|
|
|
229
229
|
sector: record.sector,
|
|
230
230
|
azimuth: record.azimuth
|
|
231
231
|
},
|
|
232
|
-
defaultChecked:
|
|
232
|
+
defaultChecked: false // Start unselected
|
|
233
233
|
};
|
|
234
234
|
level1Node.children.push(cellNode);
|
|
235
235
|
});
|
|
@@ -293,7 +293,7 @@ function build2LevelTree(data, grouping) {
|
|
|
293
293
|
sector: record.sector,
|
|
294
294
|
azimuth: record.azimuth
|
|
295
295
|
},
|
|
296
|
-
defaultChecked:
|
|
296
|
+
defaultChecked: false // Start unselected
|
|
297
297
|
};
|
|
298
298
|
level0Node.children.push(cellNode);
|
|
299
299
|
});
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { adaptPlotlyLayout, addMarkersToLayout, type ContainerSize } from './adapt.js';
|
|
9
9
|
import { getKPIValues, type ProcessedChartData } from './data-processor.js';
|
|
10
10
|
import { log } from '../logger';
|
|
11
|
+
import { checkHealth, getMessage } from '../FeatureRegistry';
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
13
14
|
chart: ChartModel;
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
let chartDiv: HTMLElement;
|
|
40
41
|
let containerSize = $state<ContainerSize>({ width: 0, height: 0 });
|
|
41
42
|
let chartInitialized = $state(false); // Track if chart has been created
|
|
43
|
+
let isHealthy = $state(checkHealth('charts'));
|
|
42
44
|
|
|
43
45
|
function handleContextMenu(event: MouseEvent) {
|
|
44
46
|
event.preventDefault();
|
|
@@ -268,6 +270,7 @@
|
|
|
268
270
|
}
|
|
269
271
|
|
|
270
272
|
onMount(() => {
|
|
273
|
+
|
|
271
274
|
log('📈 ChartCard mounted', {
|
|
272
275
|
chartTitle: chart.title,
|
|
273
276
|
leftKPIs: chart.yLeft.length,
|
|
@@ -286,7 +289,9 @@
|
|
|
286
289
|
height: rect.height
|
|
287
290
|
});
|
|
288
291
|
}
|
|
289
|
-
|
|
292
|
+
if(!isHealthy){
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
290
295
|
renderChart();
|
|
291
296
|
|
|
292
297
|
// Set up ResizeObserver with debouncing to prevent excessive re-renders
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import GlobalControls from './GlobalControls.svelte';
|
|
8
8
|
import { getPreprocessedData, type ProcessedChartData } from './data-processor.js';
|
|
9
9
|
import { log } from '../logger';
|
|
10
|
+
import { checkHealth, getMessage } from '../FeatureRegistry';
|
|
10
11
|
|
|
11
12
|
interface Props {
|
|
12
13
|
layout: Layout;
|
|
@@ -56,7 +57,7 @@
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
let { layout, data, mode, markers, plotlyLayout, enableAdaptation = true, showGlobalControls = true, persistSettings = false }: Props = $props();
|
|
59
|
-
|
|
60
|
+
let isHealthy = $state(checkHealth('charts'));
|
|
60
61
|
// Log component initialization
|
|
61
62
|
$effect(() => {
|
|
62
63
|
log('📊 ChartComponent initialized', {
|
|
@@ -89,6 +90,9 @@
|
|
|
89
90
|
},
|
|
90
91
|
hoverMode: {
|
|
91
92
|
mode: layout.hoverMode ?? 'x' // Default to 'x' if not specified
|
|
93
|
+
},
|
|
94
|
+
adaptive: {
|
|
95
|
+
enabled: enableAdaptation // Initialize from prop, always present
|
|
92
96
|
}
|
|
93
97
|
});
|
|
94
98
|
|
|
@@ -273,6 +277,7 @@
|
|
|
273
277
|
}
|
|
274
278
|
|
|
275
279
|
onMount(() => {
|
|
280
|
+
|
|
276
281
|
const handleKeydown = (event: KeyboardEvent) => {
|
|
277
282
|
if (event.key === 'Escape') {
|
|
278
283
|
if (zoomedChart) {
|
|
@@ -329,7 +334,7 @@
|
|
|
329
334
|
{processedData}
|
|
330
335
|
{markers}
|
|
331
336
|
{plotlyLayout}
|
|
332
|
-
{
|
|
337
|
+
enableAdaptation={globalControls.adaptive?.enabled ?? true}
|
|
333
338
|
sectionId={section.id}
|
|
334
339
|
sectionMovingAverage={section.movingAverage}
|
|
335
340
|
layoutMovingAverage={layout.movingAverage}
|
|
@@ -449,7 +454,7 @@
|
|
|
449
454
|
{processedData}
|
|
450
455
|
{markers}
|
|
451
456
|
{plotlyLayout}
|
|
452
|
-
{
|
|
457
|
+
enableAdaptation={globalControls.adaptive?.enabled ?? true}
|
|
453
458
|
sectionId={activeZoom.section.id}
|
|
454
459
|
sectionMovingAverage={activeZoom.section.movingAverage}
|
|
455
460
|
layoutMovingAverage={layout.movingAverage}
|
|
@@ -102,6 +102,18 @@
|
|
|
102
102
|
onUpdate(newControls);
|
|
103
103
|
saveSettings(newControls);
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
function updateAdaptive(updates: Partial<NonNullable<GlobalChartControls['adaptive']>>) {
|
|
107
|
+
const newControls = {
|
|
108
|
+
...controls,
|
|
109
|
+
adaptive: {
|
|
110
|
+
...controls.adaptive!,
|
|
111
|
+
...updates
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
onUpdate(newControls);
|
|
115
|
+
saveSettings(newControls);
|
|
116
|
+
}
|
|
105
117
|
</script>
|
|
106
118
|
|
|
107
119
|
<!-- Floating Controls Container -->
|
|
@@ -146,7 +158,11 @@
|
|
|
146
158
|
checked={controls.markers.enabled}
|
|
147
159
|
onchange={() => updateMarkers({ enabled: !controls.markers!.enabled })}
|
|
148
160
|
/>
|
|
149
|
-
<label
|
|
161
|
+
<label
|
|
162
|
+
class="btn btn-outline-primary btn-sm"
|
|
163
|
+
for="markersToggle"
|
|
164
|
+
title="Show or hide data point markers on charts"
|
|
165
|
+
>
|
|
150
166
|
Markers
|
|
151
167
|
</label>
|
|
152
168
|
</div>
|
|
@@ -162,17 +178,41 @@
|
|
|
162
178
|
checked={controls.legend.enabled}
|
|
163
179
|
onchange={() => updateLegend({ enabled: !controls.legend!.enabled })}
|
|
164
180
|
/>
|
|
165
|
-
<label
|
|
181
|
+
<label
|
|
182
|
+
class="btn btn-outline-primary btn-sm"
|
|
183
|
+
for="legendToggle"
|
|
184
|
+
title="Show or hide the chart legend with series names"
|
|
185
|
+
>
|
|
166
186
|
Legend
|
|
167
187
|
</label>
|
|
168
188
|
</div>
|
|
169
189
|
{/if}
|
|
190
|
+
|
|
191
|
+
<!-- Adaptive Toggle (Always Present - Not Optional) -->
|
|
192
|
+
<div class="control-group-inline">
|
|
193
|
+
<input
|
|
194
|
+
type="checkbox"
|
|
195
|
+
class="btn-check"
|
|
196
|
+
id="adaptiveToggle"
|
|
197
|
+
checked={controls.adaptive?.enabled ?? true}
|
|
198
|
+
onchange={() => updateAdaptive({ enabled: !(controls.adaptive?.enabled ?? true) })}
|
|
199
|
+
/>
|
|
200
|
+
<label
|
|
201
|
+
class="btn btn-outline-primary btn-sm"
|
|
202
|
+
for="adaptiveToggle"
|
|
203
|
+
title="Enable adaptive chart display based on chart size and data density"
|
|
204
|
+
>
|
|
205
|
+
Adaptive
|
|
206
|
+
</label>
|
|
207
|
+
</div>
|
|
170
208
|
</div>
|
|
171
209
|
|
|
172
210
|
<!-- Hover Mode Controls -->
|
|
173
211
|
{#if controls.hoverMode}
|
|
174
212
|
<div class="control-group">
|
|
175
|
-
<div class="control-label"
|
|
213
|
+
<div class="control-label" title="Control how hover tooltips appear when you move your mouse over the chart">
|
|
214
|
+
Hover Mode
|
|
215
|
+
</div>
|
|
176
216
|
<div class="btn-group btn-group-sm" role="group" aria-label="Hover Mode">
|
|
177
217
|
<input
|
|
178
218
|
type="radio"
|
|
@@ -182,7 +222,13 @@
|
|
|
182
222
|
checked={controls.hoverMode.mode === 'x'}
|
|
183
223
|
onchange={() => updateHoverMode({ mode: 'x' })}
|
|
184
224
|
/>
|
|
185
|
-
<label
|
|
225
|
+
<label
|
|
226
|
+
class="btn btn-outline-primary"
|
|
227
|
+
for="hoverModeX"
|
|
228
|
+
title="Show all data points at the same X-axis position"
|
|
229
|
+
>
|
|
230
|
+
X-Axis
|
|
231
|
+
</label>
|
|
186
232
|
|
|
187
233
|
<!-- <input
|
|
188
234
|
type="radio"
|
|
@@ -202,7 +248,13 @@
|
|
|
202
248
|
checked={controls.hoverMode.mode === 'closest'}
|
|
203
249
|
onchange={() => updateHoverMode({ mode: 'closest' })}
|
|
204
250
|
/>
|
|
205
|
-
<label
|
|
251
|
+
<label
|
|
252
|
+
class="btn btn-outline-primary"
|
|
253
|
+
for="hoverModeClosest"
|
|
254
|
+
title="Show only the single closest data point to your cursor"
|
|
255
|
+
>
|
|
256
|
+
Closest
|
|
257
|
+
</label>
|
|
206
258
|
|
|
207
259
|
<input
|
|
208
260
|
type="radio"
|
|
@@ -212,7 +264,13 @@
|
|
|
212
264
|
checked={controls.hoverMode.mode === 'x unified'}
|
|
213
265
|
onchange={() => updateHoverMode({ mode: 'x unified' })}
|
|
214
266
|
/>
|
|
215
|
-
<label
|
|
267
|
+
<label
|
|
268
|
+
class="btn btn-outline-primary"
|
|
269
|
+
for="hoverModeXUnified"
|
|
270
|
+
title="Show all data points at the same X position with a single unified tooltip"
|
|
271
|
+
>
|
|
272
|
+
X-Unified
|
|
273
|
+
</label>
|
|
216
274
|
|
|
217
275
|
<!-- <input
|
|
218
276
|
type="radio"
|
|
@@ -232,7 +290,13 @@
|
|
|
232
290
|
checked={controls.hoverMode.mode === false}
|
|
233
291
|
onchange={() => updateHoverMode({ mode: false })}
|
|
234
292
|
/>
|
|
235
|
-
<label
|
|
293
|
+
<label
|
|
294
|
+
class="btn btn-outline-primary"
|
|
295
|
+
for="hoverModeFalse"
|
|
296
|
+
title="Disable hover tooltips completely"
|
|
297
|
+
>
|
|
298
|
+
Off
|
|
299
|
+
</label>
|
|
236
300
|
</div>
|
|
237
301
|
</div>
|
|
238
302
|
{/if}
|
|
@@ -248,7 +312,11 @@
|
|
|
248
312
|
checked={controls.movingAverage.enabled}
|
|
249
313
|
onchange={() => updateMovingAverage({ enabled: !controls.movingAverage!.enabled })}
|
|
250
314
|
/>
|
|
251
|
-
<label
|
|
315
|
+
<label
|
|
316
|
+
class="btn btn-outline-primary btn-sm"
|
|
317
|
+
for="maToggle"
|
|
318
|
+
title="Apply smoothing to chart data using moving average calculations"
|
|
319
|
+
>
|
|
252
320
|
Moving Average
|
|
253
321
|
</label>
|
|
254
322
|
|
|
@@ -264,7 +332,13 @@
|
|
|
264
332
|
checked={controls.movingAverage.windowOverride === undefined}
|
|
265
333
|
onchange={() => updateMovingAverage({ windowOverride: undefined })}
|
|
266
334
|
/>
|
|
267
|
-
<label
|
|
335
|
+
<label
|
|
336
|
+
class="btn btn-outline-primary"
|
|
337
|
+
for="maWindowAuto"
|
|
338
|
+
title="Default size from settings"
|
|
339
|
+
>
|
|
340
|
+
Auto
|
|
341
|
+
</label>
|
|
268
342
|
|
|
269
343
|
<input
|
|
270
344
|
type="radio"
|
|
@@ -274,7 +348,13 @@
|
|
|
274
348
|
checked={controls.movingAverage.windowOverride === 7}
|
|
275
349
|
onchange={() => updateMovingAverage({ windowOverride: 7 })}
|
|
276
350
|
/>
|
|
277
|
-
<label
|
|
351
|
+
<label
|
|
352
|
+
class="btn btn-outline-primary"
|
|
353
|
+
for="maWindow7"
|
|
354
|
+
title="Use a 7-period moving average window"
|
|
355
|
+
>
|
|
356
|
+
7
|
|
357
|
+
</label>
|
|
278
358
|
|
|
279
359
|
<input
|
|
280
360
|
type="radio"
|
|
@@ -284,7 +364,13 @@
|
|
|
284
364
|
checked={controls.movingAverage.windowOverride === 14}
|
|
285
365
|
onchange={() => updateMovingAverage({ windowOverride: 14 })}
|
|
286
366
|
/>
|
|
287
|
-
<label
|
|
367
|
+
<label
|
|
368
|
+
class="btn btn-outline-primary"
|
|
369
|
+
for="maWindow14"
|
|
370
|
+
title="Use a 14-period moving average window"
|
|
371
|
+
>
|
|
372
|
+
14
|
|
373
|
+
</label>
|
|
288
374
|
|
|
289
375
|
<input
|
|
290
376
|
type="radio"
|
|
@@ -294,7 +380,13 @@
|
|
|
294
380
|
checked={controls.movingAverage.windowOverride === 24}
|
|
295
381
|
onchange={() => updateMovingAverage({ windowOverride: 24 })}
|
|
296
382
|
/>
|
|
297
|
-
<label
|
|
383
|
+
<label
|
|
384
|
+
class="btn btn-outline-primary"
|
|
385
|
+
for="maWindow24"
|
|
386
|
+
title="Use a 24-period moving average window"
|
|
387
|
+
>
|
|
388
|
+
24
|
|
389
|
+
</label>
|
|
298
390
|
|
|
299
391
|
<input
|
|
300
392
|
type="radio"
|
|
@@ -304,7 +396,13 @@
|
|
|
304
396
|
checked={controls.movingAverage.windowOverride === 30}
|
|
305
397
|
onchange={() => updateMovingAverage({ windowOverride: 30 })}
|
|
306
398
|
/>
|
|
307
|
-
<label
|
|
399
|
+
<label
|
|
400
|
+
class="btn btn-outline-primary"
|
|
401
|
+
for="maWindow30"
|
|
402
|
+
title="Use a 30-period moving average window"
|
|
403
|
+
>
|
|
404
|
+
30
|
|
405
|
+
</label>
|
|
308
406
|
</div>
|
|
309
407
|
|
|
310
408
|
<!-- Show Original Toggle Button -->
|
|
@@ -315,7 +413,11 @@
|
|
|
315
413
|
checked={controls.movingAverage.showOriginal}
|
|
316
414
|
onchange={() => updateMovingAverage({ showOriginal: !controls.movingAverage!.showOriginal })}
|
|
317
415
|
/>
|
|
318
|
-
<label
|
|
416
|
+
<label
|
|
417
|
+
class="btn btn-outline-primary btn-sm ms-2"
|
|
418
|
+
for="showOriginal"
|
|
419
|
+
title="Display both the original data and the moving average together"
|
|
420
|
+
>
|
|
319
421
|
Show Original
|
|
320
422
|
</label>
|
|
321
423
|
</div>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
function adaptHoverBehavior(layout, containerSize, chartInfo, originalHoverMode) {
|
|
10
10
|
const { width, height } = containerSize;
|
|
11
11
|
const isTiny = width < 250 || height < 200;
|
|
12
|
-
const isSmall = width <
|
|
12
|
+
const isSmall = width < 250 || height < 200;
|
|
13
13
|
const isMedium = width < 600 || height < 400;
|
|
14
14
|
const totalSeries = chartInfo.leftSeriesCount + chartInfo.rightSeriesCount;
|
|
15
15
|
// Only override hover mode in critical cases for performance/UX
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { NodeState, TreeStoreValue } from './tree.model';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
3
4
|
import TreeNode from './TreeNode.svelte';
|
|
4
5
|
|
|
6
|
+
interface NodeSlotProps {
|
|
7
|
+
node: NodeState['node'];
|
|
8
|
+
state: {
|
|
9
|
+
checked: boolean;
|
|
10
|
+
indeterminate: boolean;
|
|
11
|
+
expanded: boolean;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
interface Props {
|
|
6
16
|
nodeState: NodeState;
|
|
7
17
|
store: TreeStoreValue;
|
|
8
18
|
showIndeterminate?: boolean;
|
|
19
|
+
children?: Snippet<[NodeSlotProps]>;
|
|
9
20
|
}
|
|
10
21
|
|
|
11
|
-
let { nodeState, store, showIndeterminate = true }: Props = $props();
|
|
22
|
+
let { nodeState, store, showIndeterminate = true, children }: Props = $props();
|
|
12
23
|
|
|
13
24
|
// Computed states
|
|
14
25
|
let isChecked = $derived(store.state.checkedPaths.has(nodeState.path));
|
|
@@ -25,8 +36,8 @@
|
|
|
25
36
|
.filter((node): node is NodeState => node !== undefined)
|
|
26
37
|
);
|
|
27
38
|
|
|
28
|
-
// Indentation based on level
|
|
29
|
-
let indentStyle = $derived(`
|
|
39
|
+
// Indentation based on level (20px per level for consistent visual hierarchy)
|
|
40
|
+
let indentStyle = $derived(`margin-left: ${nodeState.level * 20}px`);
|
|
30
41
|
|
|
31
42
|
function handleToggle() {
|
|
32
43
|
store.toggle(nodeState.path);
|
|
@@ -40,50 +51,52 @@
|
|
|
40
51
|
</script>
|
|
41
52
|
|
|
42
53
|
<div class="tree-node" style={indentStyle}>
|
|
43
|
-
<div class="
|
|
54
|
+
<div class="d-flex align-items-center gap-1">
|
|
44
55
|
<!-- Expand/Collapse Button -->
|
|
45
56
|
{#if hasChildren}
|
|
46
57
|
<button
|
|
47
58
|
type="button"
|
|
48
|
-
class="
|
|
59
|
+
class="expand-toggle"
|
|
60
|
+
style="width: 20px; height: 20px; font-size: 10px;"
|
|
49
61
|
onclick={handleExpandToggle}
|
|
50
62
|
aria-label={isExpanded ? 'Collapse' : 'Expand'}
|
|
51
63
|
>
|
|
52
|
-
{
|
|
53
|
-
<i class="bi bi-chevron-down"></i>
|
|
54
|
-
{:else}
|
|
55
|
-
<i class="bi bi-chevron-right"></i>
|
|
56
|
-
{/if}
|
|
64
|
+
{isExpanded ? '▼' : '▶'}
|
|
57
65
|
</button>
|
|
58
66
|
{:else}
|
|
59
|
-
<
|
|
67
|
+
<div class="expand-placeholder"></div>
|
|
60
68
|
{/if}
|
|
61
69
|
|
|
62
70
|
<!-- Checkbox -->
|
|
63
|
-
<div class="form-check">
|
|
71
|
+
<div class="form-check d-flex align-items-center flex-fill">
|
|
64
72
|
<input
|
|
65
73
|
type="checkbox"
|
|
66
|
-
class="form-check-input"
|
|
74
|
+
class="form-check-input me-2 my-0"
|
|
67
75
|
class:indeterminate={isIndeterminate}
|
|
68
76
|
checked={isChecked}
|
|
69
77
|
indeterminate={isIndeterminate}
|
|
70
78
|
onchange={handleToggle}
|
|
71
79
|
id={`checkbox-${nodeState.path}`}
|
|
72
80
|
/>
|
|
73
|
-
<label class="form-check-label" for={`checkbox-${nodeState.path}`}>
|
|
81
|
+
<label class="form-check-label user-select-none mb-0 lh-1" for={`checkbox-${nodeState.path}`}>
|
|
74
82
|
{#if nodeState.node.icon}
|
|
75
83
|
<span class="node-icon">{nodeState.node.icon}</span>
|
|
76
84
|
{/if}
|
|
77
85
|
<span class="node-label">{nodeState.node.label}</span>
|
|
78
86
|
</label>
|
|
79
87
|
</div>
|
|
88
|
+
|
|
89
|
+
<!-- Slot for additional controls -->
|
|
90
|
+
<div class="ms-auto">
|
|
91
|
+
{@render children?.({ node: nodeState.node, state: { checked: isChecked, indeterminate: isIndeterminate, expanded: isExpanded } })}
|
|
92
|
+
</div>
|
|
80
93
|
</div>
|
|
81
94
|
|
|
82
95
|
<!-- Recursive Children -->
|
|
83
96
|
{#if hasChildren && isExpanded}
|
|
84
97
|
<div class="tree-node-children">
|
|
85
98
|
{#each childNodes as childNode (childNode.path)}
|
|
86
|
-
<TreeNode nodeState={childNode} {store} {showIndeterminate} />
|
|
99
|
+
<TreeNode nodeState={childNode} {store} {showIndeterminate} {children} />
|
|
87
100
|
{/each}
|
|
88
101
|
</div>
|
|
89
102
|
{/if}
|
|
@@ -91,33 +104,26 @@
|
|
|
91
104
|
|
|
92
105
|
<style>
|
|
93
106
|
.tree-node {
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.tree-node-content {
|
|
98
|
-
display: flex;
|
|
99
|
-
align-items: center;
|
|
100
|
-
gap: 0.5rem;
|
|
101
|
-
padding: 0.25rem 0;
|
|
102
|
-
min-height: 2rem;
|
|
107
|
+
display: block;
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
.expand-toggle {
|
|
106
|
-
width:
|
|
107
|
-
height:
|
|
108
|
-
|
|
109
|
-
align-items: center;
|
|
110
|
-
justify-content: center;
|
|
111
|
+
width: 20px;
|
|
112
|
+
height: 20px;
|
|
113
|
+
font-size: 10px;
|
|
111
114
|
border: none;
|
|
112
115
|
background: none;
|
|
113
116
|
cursor: pointer;
|
|
114
117
|
color: #6c757d;
|
|
115
|
-
|
|
118
|
+
line-height: 1;
|
|
119
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
justify-content: center;
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
.expand-toggle:hover {
|
|
119
|
-
color:
|
|
120
|
-
background-color: #f8f9fa;
|
|
126
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
121
127
|
border-radius: 0.25rem;
|
|
122
128
|
}
|
|
123
129
|
|
|
@@ -127,21 +133,13 @@
|
|
|
127
133
|
}
|
|
128
134
|
|
|
129
135
|
.expand-placeholder {
|
|
130
|
-
width:
|
|
131
|
-
height:
|
|
136
|
+
width: 20px;
|
|
137
|
+
height: 20px;
|
|
132
138
|
display: inline-block;
|
|
133
139
|
}
|
|
134
140
|
|
|
135
|
-
.form-check {
|
|
136
|
-
display: flex;
|
|
137
|
-
align-items: center;
|
|
138
|
-
gap: 0.5rem;
|
|
139
|
-
margin: 0;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
141
|
.form-check-input {
|
|
143
142
|
cursor: pointer;
|
|
144
|
-
margin: 0;
|
|
145
143
|
flex-shrink: 0;
|
|
146
144
|
}
|
|
147
145
|
|
|
@@ -157,8 +155,6 @@
|
|
|
157
155
|
display: flex;
|
|
158
156
|
align-items: center;
|
|
159
157
|
gap: 0.5rem;
|
|
160
|
-
margin: 0;
|
|
161
|
-
user-select: none;
|
|
162
158
|
}
|
|
163
159
|
|
|
164
160
|
.node-icon {
|
|
@@ -168,6 +164,5 @@
|
|
|
168
164
|
|
|
169
165
|
.node-label {
|
|
170
166
|
font-size: 0.875rem;
|
|
171
|
-
line-height: 1.5;
|
|
172
167
|
}
|
|
173
168
|
</style>
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import type { NodeState, TreeStoreValue } from './tree.model';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
import TreeNode from './TreeNode.svelte';
|
|
4
|
+
interface NodeSlotProps {
|
|
5
|
+
node: NodeState['node'];
|
|
6
|
+
state: {
|
|
7
|
+
checked: boolean;
|
|
8
|
+
indeterminate: boolean;
|
|
9
|
+
expanded: boolean;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
3
12
|
interface Props {
|
|
4
13
|
nodeState: NodeState;
|
|
5
14
|
store: TreeStoreValue;
|
|
6
15
|
showIndeterminate?: boolean;
|
|
16
|
+
children?: Snippet<[NodeSlotProps]>;
|
|
7
17
|
}
|
|
8
18
|
declare const TreeNode: import("svelte").Component<Props, {}, "">;
|
|
9
19
|
type TreeNode = ReturnType<typeof TreeNode>;
|
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { TreeStoreValue } from './tree.model';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
3
4
|
import TreeNode from './TreeNode.svelte';
|
|
4
5
|
|
|
6
|
+
interface NodeSlotProps {
|
|
7
|
+
node: any;
|
|
8
|
+
state: {
|
|
9
|
+
checked: boolean;
|
|
10
|
+
indeterminate: boolean;
|
|
11
|
+
expanded: boolean;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
interface Props {
|
|
6
16
|
store: TreeStoreValue;
|
|
7
17
|
showIndeterminate?: boolean;
|
|
8
18
|
showControls?: boolean;
|
|
9
19
|
height?: string;
|
|
20
|
+
children?: Snippet<[NodeSlotProps]>;
|
|
10
21
|
}
|
|
11
22
|
|
|
12
23
|
let {
|
|
13
24
|
store,
|
|
14
25
|
showIndeterminate = true,
|
|
15
26
|
showControls = true,
|
|
16
|
-
height = '100%'
|
|
27
|
+
height = '100%',
|
|
28
|
+
children
|
|
17
29
|
}: Props = $props();
|
|
18
30
|
|
|
19
31
|
// Get root nodes
|
|
@@ -97,7 +109,7 @@
|
|
|
97
109
|
|
|
98
110
|
<div class="tree-nodes">
|
|
99
111
|
{#each rootNodes as rootNode (rootNode.path)}
|
|
100
|
-
<TreeNode nodeState={rootNode} {store} {showIndeterminate} />
|
|
112
|
+
<TreeNode nodeState={rootNode} {store} {showIndeterminate} {children} />
|
|
101
113
|
{/each}
|
|
102
114
|
</div>
|
|
103
115
|
</div>
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import type { TreeStoreValue } from './tree.model';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
interface NodeSlotProps {
|
|
4
|
+
node: any;
|
|
5
|
+
state: {
|
|
6
|
+
checked: boolean;
|
|
7
|
+
indeterminate: boolean;
|
|
8
|
+
expanded: boolean;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
2
11
|
interface Props {
|
|
3
12
|
store: TreeStoreValue;
|
|
4
13
|
showIndeterminate?: boolean;
|
|
5
14
|
showControls?: boolean;
|
|
6
15
|
height?: string;
|
|
16
|
+
children?: Snippet<[NodeSlotProps]>;
|
|
7
17
|
}
|
|
8
18
|
declare const TreeView: import("svelte").Component<Props, {}, "">;
|
|
9
19
|
type TreeView = ReturnType<typeof TreeView>;
|
|
@@ -28,6 +28,9 @@ export declare function getPathLevel(path: string, separator?: string): number;
|
|
|
28
28
|
export declare function flattenTree<T = any>(nodes: TreeNode<T>[], config: TreeConfig<T>, parentPath?: string, level?: number): Map<string, NodeState>;
|
|
29
29
|
/**
|
|
30
30
|
* Calculate which nodes should be indeterminate
|
|
31
|
+
* A node is indeterminate if:
|
|
32
|
+
* - It has children AND
|
|
33
|
+
* - Some (but not all) of its direct children are checked OR indeterminate
|
|
31
34
|
*/
|
|
32
35
|
export declare function calculateIndeterminateStates(nodes: Map<string, NodeState>, checkedPaths: Set<string>): Set<string>;
|
|
33
36
|
/**
|