@smartnet360/svelte-components 0.0.21 → 0.0.23
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/antenna-pattern/utils/msi-parser.js +18 -1
- package/dist/cellular/CellularChartsView.svelte +293 -0
- package/dist/cellular/CellularChartsView.svelte.d.ts +7 -0
- package/dist/cellular/HierarchicalTree.svelte +469 -0
- package/dist/cellular/HierarchicalTree.svelte.d.ts +9 -0
- package/dist/cellular/SiteTree.svelte +286 -0
- package/dist/cellular/SiteTree.svelte.d.ts +11 -0
- package/dist/cellular/cellular-transforms.d.ts +25 -0
- package/dist/cellular/cellular-transforms.js +129 -0
- package/dist/cellular/cellular.model.d.ts +63 -0
- package/dist/cellular/cellular.model.js +6 -0
- package/dist/cellular/index.d.ts +11 -0
- package/dist/cellular/index.js +11 -0
- package/dist/cellular/mock-cellular-data.d.ts +13 -0
- package/dist/cellular/mock-cellular-data.js +241 -0
- package/dist/core/Charts/ChartCard.svelte +65 -16
- package/dist/core/Charts/ChartCard.svelte.d.ts +2 -0
- package/dist/core/Charts/ChartComponent.svelte +166 -34
- package/dist/core/Charts/ChartComponent.svelte.d.ts +1 -0
- package/dist/core/Charts/GlobalControls.svelte +188 -0
- package/dist/core/Charts/GlobalControls.svelte.d.ts +8 -0
- package/dist/core/Charts/charts.model.d.ts +7 -0
- package/dist/core/TreeChartView/TreeChartView.svelte +208 -0
- package/dist/core/TreeChartView/TreeChartView.svelte.d.ts +42 -0
- package/dist/core/TreeChartView/index.d.ts +7 -0
- package/dist/core/TreeChartView/index.js +7 -0
- package/dist/core/TreeView/TreeNode.svelte +173 -0
- package/dist/core/TreeView/TreeNode.svelte.d.ts +10 -0
- package/dist/core/TreeView/TreeView.svelte +163 -0
- package/dist/core/TreeView/TreeView.svelte.d.ts +10 -0
- package/dist/core/TreeView/index.d.ts +48 -0
- package/dist/core/TreeView/index.js +50 -0
- package/dist/core/TreeView/tree-utils.d.ts +56 -0
- package/dist/core/TreeView/tree-utils.js +194 -0
- package/dist/core/TreeView/tree.model.d.ts +104 -0
- package/dist/core/TreeView/tree.model.js +5 -0
- package/dist/core/TreeView/tree.store.d.ts +10 -0
- package/dist/core/TreeView/tree.store.js +225 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/package.json +1 -1
@@ -0,0 +1,188 @@
|
|
1
|
+
<svelte:options runes={true} />
|
2
|
+
|
3
|
+
<script lang="ts">
|
4
|
+
import type { GlobalChartControls } from './charts.model.js';
|
5
|
+
|
6
|
+
interface Props {
|
7
|
+
controls: GlobalChartControls;
|
8
|
+
onUpdate: (controls: GlobalChartControls) => void;
|
9
|
+
}
|
10
|
+
|
11
|
+
let { controls, onUpdate }: Props = $props();
|
12
|
+
|
13
|
+
function updateControls(updates: Partial<GlobalChartControls>) {
|
14
|
+
onUpdate({
|
15
|
+
...controls,
|
16
|
+
...updates
|
17
|
+
});
|
18
|
+
}
|
19
|
+
|
20
|
+
function updateMovingAverage(updates: Partial<NonNullable<GlobalChartControls['movingAverage']>>) {
|
21
|
+
onUpdate({
|
22
|
+
...controls,
|
23
|
+
movingAverage: {
|
24
|
+
...controls.movingAverage!,
|
25
|
+
...updates
|
26
|
+
}
|
27
|
+
});
|
28
|
+
}
|
29
|
+
</script>
|
30
|
+
|
31
|
+
<div class="global-controls">
|
32
|
+
<div class="controls-section">
|
33
|
+
<!-- <span class="controls-label">Display Controls:</span> -->
|
34
|
+
|
35
|
+
<!-- Moving Average Controls -->
|
36
|
+
{#if controls.movingAverage}
|
37
|
+
<div class="control-group">
|
38
|
+
<!-- MA Enable Toggle Button -->
|
39
|
+
<input
|
40
|
+
type="checkbox"
|
41
|
+
class="btn-check"
|
42
|
+
id="maToggle"
|
43
|
+
checked={controls.movingAverage.enabled}
|
44
|
+
onchange={() => updateMovingAverage({ enabled: !controls.movingAverage!.enabled })}
|
45
|
+
/>
|
46
|
+
<label class="btn btn-outline-primary btn-sm" for="maToggle">
|
47
|
+
Moving Average
|
48
|
+
</label>
|
49
|
+
|
50
|
+
{#if controls.movingAverage.enabled}
|
51
|
+
<div class="control-subgroup">
|
52
|
+
<!-- MA Window Size -->
|
53
|
+
<div class="btn-group btn-group-sm" role="group" aria-label="MA Window">
|
54
|
+
<input
|
55
|
+
type="radio"
|
56
|
+
class="btn-check"
|
57
|
+
name="maWindow"
|
58
|
+
id="maWindowAuto"
|
59
|
+
checked={controls.movingAverage.windowOverride === undefined}
|
60
|
+
onchange={() => updateMovingAverage({ windowOverride: undefined })}
|
61
|
+
/>
|
62
|
+
<label class="btn btn-outline-primary" for="maWindowAuto">Auto</label>
|
63
|
+
|
64
|
+
<input
|
65
|
+
type="radio"
|
66
|
+
class="btn-check"
|
67
|
+
name="maWindow"
|
68
|
+
id="maWindow7"
|
69
|
+
checked={controls.movingAverage.windowOverride === 7}
|
70
|
+
onchange={() => updateMovingAverage({ windowOverride: 7 })}
|
71
|
+
/>
|
72
|
+
<label class="btn btn-outline-primary" for="maWindow7">7</label>
|
73
|
+
|
74
|
+
<input
|
75
|
+
type="radio"
|
76
|
+
class="btn-check"
|
77
|
+
name="maWindow"
|
78
|
+
id="maWindow14"
|
79
|
+
checked={controls.movingAverage.windowOverride === 14}
|
80
|
+
onchange={() => updateMovingAverage({ windowOverride: 14 })}
|
81
|
+
/>
|
82
|
+
<label class="btn btn-outline-primary" for="maWindow14">14</label>
|
83
|
+
|
84
|
+
<input
|
85
|
+
type="radio"
|
86
|
+
class="btn-check"
|
87
|
+
name="maWindow"
|
88
|
+
id="maWindow24"
|
89
|
+
checked={controls.movingAverage.windowOverride === 24}
|
90
|
+
onchange={() => updateMovingAverage({ windowOverride: 24 })}
|
91
|
+
/>
|
92
|
+
<label class="btn btn-outline-primary" for="maWindow24">24</label>
|
93
|
+
|
94
|
+
<input
|
95
|
+
type="radio"
|
96
|
+
class="btn-check"
|
97
|
+
name="maWindow"
|
98
|
+
id="maWindow30"
|
99
|
+
checked={controls.movingAverage.windowOverride === 30}
|
100
|
+
onchange={() => updateMovingAverage({ windowOverride: 30 })}
|
101
|
+
/>
|
102
|
+
<label class="btn btn-outline-primary" for="maWindow30">30</label>
|
103
|
+
</div>
|
104
|
+
|
105
|
+
<!-- Show Original Toggle Button -->
|
106
|
+
<input
|
107
|
+
type="checkbox"
|
108
|
+
class="btn-check"
|
109
|
+
id="showOriginal"
|
110
|
+
checked={controls.movingAverage.showOriginal}
|
111
|
+
onchange={() => updateMovingAverage({ showOriginal: !controls.movingAverage!.showOriginal })}
|
112
|
+
/>
|
113
|
+
<label class="btn btn-outline-primary btn-sm ms-2" for="showOriginal">
|
114
|
+
Show Original
|
115
|
+
</label>
|
116
|
+
</div>
|
117
|
+
{/if}
|
118
|
+
</div>
|
119
|
+
{/if}
|
120
|
+
|
121
|
+
<!-- Future controls can be added here -->
|
122
|
+
<!-- Example:
|
123
|
+
<div class="control-group">
|
124
|
+
<div class="form-check form-check-inline">
|
125
|
+
<input type="checkbox" id="markersToggle" />
|
126
|
+
<label for="markersToggle">Markers</label>
|
127
|
+
</div>
|
128
|
+
</div>
|
129
|
+
-->
|
130
|
+
</div>
|
131
|
+
</div>
|
132
|
+
|
133
|
+
<style>
|
134
|
+
/* Global Controls Section */
|
135
|
+
.global-controls {
|
136
|
+
flex-shrink: 0;
|
137
|
+
background-color: #f8f9fa;
|
138
|
+
border-bottom: 1px solid #dee2e6;
|
139
|
+
padding: 0.5rem 1rem;
|
140
|
+
font-size: 0.875rem;
|
141
|
+
}
|
142
|
+
|
143
|
+
.controls-section {
|
144
|
+
display: flex;
|
145
|
+
align-items: center;
|
146
|
+
gap: 1.5rem;
|
147
|
+
flex-wrap: wrap;
|
148
|
+
}
|
149
|
+
|
150
|
+
.controls-label {
|
151
|
+
font-weight: 600;
|
152
|
+
color: #495057;
|
153
|
+
margin-right: 0.5rem;
|
154
|
+
}
|
155
|
+
|
156
|
+
.control-group {
|
157
|
+
display: flex;
|
158
|
+
align-items: center;
|
159
|
+
gap: 0.75rem;
|
160
|
+
}
|
161
|
+
|
162
|
+
.control-subgroup {
|
163
|
+
display: flex;
|
164
|
+
align-items: center;
|
165
|
+
gap: 0.5rem;
|
166
|
+
padding-left: 0.5rem;
|
167
|
+
border-left: 2px solid #dee2e6;
|
168
|
+
}
|
169
|
+
|
170
|
+
.global-controls :global(.btn-group-sm .btn) {
|
171
|
+
padding: 0.25rem 0.75rem;
|
172
|
+
font-size: 0.75rem;
|
173
|
+
font-weight: 500;
|
174
|
+
}
|
175
|
+
|
176
|
+
/* Responsive adjustments */
|
177
|
+
@media (max-width: 768px) {
|
178
|
+
.controls-section {
|
179
|
+
flex-direction: column;
|
180
|
+
align-items: flex-start;
|
181
|
+
gap: 0.75rem;
|
182
|
+
}
|
183
|
+
|
184
|
+
.control-subgroup {
|
185
|
+
flex-wrap: wrap;
|
186
|
+
}
|
187
|
+
}
|
188
|
+
</style>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import type { GlobalChartControls } from './charts.model.js';
|
2
|
+
interface Props {
|
3
|
+
controls: GlobalChartControls;
|
4
|
+
onUpdate: (controls: GlobalChartControls) => void;
|
5
|
+
}
|
6
|
+
declare const GlobalControls: import("svelte").Component<Props, {}, "">;
|
7
|
+
type GlobalControls = ReturnType<typeof GlobalControls>;
|
8
|
+
export default GlobalControls;
|
@@ -43,3 +43,10 @@ export interface ChartMarker {
|
|
43
43
|
showLabel?: boolean;
|
44
44
|
category?: 'release' | 'incident' | 'maintenance' | 'other';
|
45
45
|
}
|
46
|
+
export interface GlobalChartControls {
|
47
|
+
movingAverage?: {
|
48
|
+
enabled: boolean;
|
49
|
+
windowOverride?: number;
|
50
|
+
showOriginal?: boolean;
|
51
|
+
};
|
52
|
+
}
|
@@ -0,0 +1,208 @@
|
|
1
|
+
<svelte:options runes={true} />
|
2
|
+
|
3
|
+
<script lang="ts">
|
4
|
+
/**
|
5
|
+
* TreeChartView - Generic component combining TreeView with ChartComponent
|
6
|
+
*
|
7
|
+
* This is a layout component that provides:
|
8
|
+
* - Left sidebar with TreeView
|
9
|
+
* - Right panel with ChartComponent
|
10
|
+
* - Empty state handling
|
11
|
+
* - Responsive layout
|
12
|
+
*
|
13
|
+
* All data transformation logic should be done externally by the consumer.
|
14
|
+
*/
|
15
|
+
|
16
|
+
import type { Layout } from '../Charts/charts.model.js';
|
17
|
+
import type { TreeStoreValue } from '../TreeView/tree.model.js';
|
18
|
+
import ChartComponent from '../Charts/ChartComponent.svelte';
|
19
|
+
import { TreeView } from '../TreeView/index.js';
|
20
|
+
|
21
|
+
interface Props {
|
22
|
+
/** Tree store (created externally with createTreeStore) */
|
23
|
+
treeStore: TreeStoreValue;
|
24
|
+
|
25
|
+
/** Chart data array (consumer's responsibility to filter based on tree state) */
|
26
|
+
chartData: any[];
|
27
|
+
|
28
|
+
/** Chart layout configuration */
|
29
|
+
chartLayout: Layout;
|
30
|
+
|
31
|
+
/** Number of total selectable items */
|
32
|
+
totalItems: number;
|
33
|
+
|
34
|
+
/** Number of currently visible items */
|
35
|
+
visibleItems: number;
|
36
|
+
|
37
|
+
/** Chart area title */
|
38
|
+
title?: string;
|
39
|
+
|
40
|
+
/** Chart display mode */
|
41
|
+
mode?: 'tabs' | 'scrollspy';
|
42
|
+
|
43
|
+
/** Show global chart controls */
|
44
|
+
showGlobalControls?: boolean;
|
45
|
+
|
46
|
+
/** Enable chart adaptation */
|
47
|
+
enableAdaptation?: boolean;
|
48
|
+
|
49
|
+
/** Show tree controls (expand/collapse all, etc.) */
|
50
|
+
showTreeControls?: boolean;
|
51
|
+
|
52
|
+
/** Tree sidebar width */
|
53
|
+
treeWidth?: string;
|
54
|
+
|
55
|
+
/** Empty state message */
|
56
|
+
emptyMessage?: string;
|
57
|
+
}
|
58
|
+
|
59
|
+
let {
|
60
|
+
treeStore,
|
61
|
+
chartData,
|
62
|
+
chartLayout,
|
63
|
+
totalItems,
|
64
|
+
visibleItems,
|
65
|
+
title = 'Charts',
|
66
|
+
mode = 'tabs',
|
67
|
+
showGlobalControls = true,
|
68
|
+
enableAdaptation = true,
|
69
|
+
showTreeControls = true,
|
70
|
+
treeWidth = '300px',
|
71
|
+
emptyMessage = 'No items selected. Select items from the tree on the left to display charts.'
|
72
|
+
}: Props = $props();
|
73
|
+
</script>
|
74
|
+
|
75
|
+
<div class="tree-chart-view">
|
76
|
+
<!-- Left: Tree Selector -->
|
77
|
+
<aside class="tree-sidebar" style:width={treeWidth}>
|
78
|
+
<TreeView
|
79
|
+
store={treeStore}
|
80
|
+
showControls={showTreeControls}
|
81
|
+
height="100%"
|
82
|
+
/>
|
83
|
+
</aside>
|
84
|
+
|
85
|
+
<!-- Right: Charts Area -->
|
86
|
+
<main class="charts-area">
|
87
|
+
<div class="charts-header">
|
88
|
+
<h5 class="mb-0">{title}</h5>
|
89
|
+
<div class="stats">
|
90
|
+
<span class="badge bg-primary">
|
91
|
+
{visibleItems} of {totalItems} selected
|
92
|
+
</span>
|
93
|
+
</div>
|
94
|
+
</div>
|
95
|
+
|
96
|
+
<div class="charts-container">
|
97
|
+
{#if visibleItems === 0}
|
98
|
+
<div class="empty-state">
|
99
|
+
<div class="empty-state-content">
|
100
|
+
<i class="bi bi-info-circle" style="font-size: 3rem; color: #6c757d;"></i>
|
101
|
+
<h4 class="mt-3">No Items Selected</h4>
|
102
|
+
<p class="text-muted">
|
103
|
+
{emptyMessage}
|
104
|
+
</p>
|
105
|
+
</div>
|
106
|
+
</div>
|
107
|
+
{:else}
|
108
|
+
<ChartComponent
|
109
|
+
layout={chartLayout}
|
110
|
+
data={chartData}
|
111
|
+
{mode}
|
112
|
+
{showGlobalControls}
|
113
|
+
{enableAdaptation}
|
114
|
+
/>
|
115
|
+
{/if}
|
116
|
+
</div>
|
117
|
+
</main>
|
118
|
+
</div>
|
119
|
+
|
120
|
+
<style>
|
121
|
+
.tree-chart-view {
|
122
|
+
width: 100%;
|
123
|
+
height: 100%;
|
124
|
+
display: flex;
|
125
|
+
gap: 0;
|
126
|
+
background-color: #fff;
|
127
|
+
}
|
128
|
+
|
129
|
+
.tree-sidebar {
|
130
|
+
flex-shrink: 0;
|
131
|
+
height: 100%;
|
132
|
+
overflow: hidden;
|
133
|
+
border-right: 1px solid #dee2e6;
|
134
|
+
}
|
135
|
+
|
136
|
+
.charts-area {
|
137
|
+
flex: 1;
|
138
|
+
display: flex;
|
139
|
+
flex-direction: column;
|
140
|
+
min-width: 0;
|
141
|
+
height: 100%;
|
142
|
+
}
|
143
|
+
|
144
|
+
.charts-header {
|
145
|
+
padding: 1rem;
|
146
|
+
border-bottom: 1px solid #dee2e6;
|
147
|
+
background-color: #f8f9fa;
|
148
|
+
display: flex;
|
149
|
+
justify-content: space-between;
|
150
|
+
align-items: center;
|
151
|
+
flex-shrink: 0;
|
152
|
+
}
|
153
|
+
|
154
|
+
.charts-header h5 {
|
155
|
+
margin: 0;
|
156
|
+
color: #495057;
|
157
|
+
}
|
158
|
+
|
159
|
+
.stats {
|
160
|
+
display: flex;
|
161
|
+
gap: 0.5rem;
|
162
|
+
}
|
163
|
+
|
164
|
+
.charts-container {
|
165
|
+
flex: 1;
|
166
|
+
min-height: 0;
|
167
|
+
overflow: hidden;
|
168
|
+
}
|
169
|
+
|
170
|
+
.empty-state {
|
171
|
+
display: flex;
|
172
|
+
align-items: center;
|
173
|
+
justify-content: center;
|
174
|
+
height: 100%;
|
175
|
+
width: 100%;
|
176
|
+
}
|
177
|
+
|
178
|
+
.empty-state-content {
|
179
|
+
text-align: center;
|
180
|
+
max-width: 400px;
|
181
|
+
padding: 2rem;
|
182
|
+
}
|
183
|
+
|
184
|
+
.empty-state-content h4 {
|
185
|
+
color: #495057;
|
186
|
+
margin-bottom: 0.5rem;
|
187
|
+
}
|
188
|
+
|
189
|
+
/* Responsive layout */
|
190
|
+
@media (max-width: 992px) {
|
191
|
+
.tree-sidebar {
|
192
|
+
width: 250px !important;
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
@media (max-width: 768px) {
|
197
|
+
.tree-chart-view {
|
198
|
+
flex-direction: column;
|
199
|
+
}
|
200
|
+
|
201
|
+
.tree-sidebar {
|
202
|
+
width: 100% !important;
|
203
|
+
height: 300px;
|
204
|
+
border-right: none;
|
205
|
+
border-bottom: 1px solid #dee2e6;
|
206
|
+
}
|
207
|
+
}
|
208
|
+
</style>
|
@@ -0,0 +1,42 @@
|
|
1
|
+
/**
|
2
|
+
* TreeChartView - Generic component combining TreeView with ChartComponent
|
3
|
+
*
|
4
|
+
* This is a layout component that provides:
|
5
|
+
* - Left sidebar with TreeView
|
6
|
+
* - Right panel with ChartComponent
|
7
|
+
* - Empty state handling
|
8
|
+
* - Responsive layout
|
9
|
+
*
|
10
|
+
* All data transformation logic should be done externally by the consumer.
|
11
|
+
*/
|
12
|
+
import type { Layout } from '../Charts/charts.model.js';
|
13
|
+
import type { TreeStoreValue } from '../TreeView/tree.model.js';
|
14
|
+
interface Props {
|
15
|
+
/** Tree store (created externally with createTreeStore) */
|
16
|
+
treeStore: TreeStoreValue;
|
17
|
+
/** Chart data array (consumer's responsibility to filter based on tree state) */
|
18
|
+
chartData: any[];
|
19
|
+
/** Chart layout configuration */
|
20
|
+
chartLayout: Layout;
|
21
|
+
/** Number of total selectable items */
|
22
|
+
totalItems: number;
|
23
|
+
/** Number of currently visible items */
|
24
|
+
visibleItems: number;
|
25
|
+
/** Chart area title */
|
26
|
+
title?: string;
|
27
|
+
/** Chart display mode */
|
28
|
+
mode?: 'tabs' | 'scrollspy';
|
29
|
+
/** Show global chart controls */
|
30
|
+
showGlobalControls?: boolean;
|
31
|
+
/** Enable chart adaptation */
|
32
|
+
enableAdaptation?: boolean;
|
33
|
+
/** Show tree controls (expand/collapse all, etc.) */
|
34
|
+
showTreeControls?: boolean;
|
35
|
+
/** Tree sidebar width */
|
36
|
+
treeWidth?: string;
|
37
|
+
/** Empty state message */
|
38
|
+
emptyMessage?: string;
|
39
|
+
}
|
40
|
+
declare const TreeChartView: import("svelte").Component<Props, {}, "">;
|
41
|
+
type TreeChartView = ReturnType<typeof TreeChartView>;
|
42
|
+
export default TreeChartView;
|
@@ -0,0 +1,7 @@
|
|
1
|
+
/**
|
2
|
+
* TreeChartView - Combined Layout Component
|
3
|
+
*
|
4
|
+
* Provides a split-screen layout combining TreeView (left) with ChartComponent (right).
|
5
|
+
* This is a LAYOUT component - all data transformation is the consumer's responsibility.
|
6
|
+
*/
|
7
|
+
export { default as TreeChartView } from './TreeChartView.svelte';
|
@@ -0,0 +1,7 @@
|
|
1
|
+
/**
|
2
|
+
* TreeChartView - Combined Layout Component
|
3
|
+
*
|
4
|
+
* Provides a split-screen layout combining TreeView (left) with ChartComponent (right).
|
5
|
+
* This is a LAYOUT component - all data transformation is the consumer's responsibility.
|
6
|
+
*/
|
7
|
+
export { default as TreeChartView } from './TreeChartView.svelte';
|
@@ -0,0 +1,173 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import type { NodeState, TreeStoreValue } from './tree.model';
|
3
|
+
import TreeNode from './TreeNode.svelte';
|
4
|
+
|
5
|
+
interface Props {
|
6
|
+
nodeState: NodeState;
|
7
|
+
store: TreeStoreValue;
|
8
|
+
showIndeterminate?: boolean;
|
9
|
+
}
|
10
|
+
|
11
|
+
let { nodeState, store, showIndeterminate = true }: Props = $props();
|
12
|
+
|
13
|
+
// Computed states
|
14
|
+
let isChecked = $derived(store.state.checkedPaths.has(nodeState.path));
|
15
|
+
let isIndeterminate = $derived(
|
16
|
+
showIndeterminate && store.state.indeterminatePaths.has(nodeState.path)
|
17
|
+
);
|
18
|
+
let isExpanded = $derived(store.state.expandedPaths.has(nodeState.path));
|
19
|
+
let hasChildren = $derived(nodeState.childPaths.length > 0);
|
20
|
+
|
21
|
+
// Get child node states
|
22
|
+
let childNodes = $derived(
|
23
|
+
nodeState.childPaths
|
24
|
+
.map(path => store.state.nodes.get(path))
|
25
|
+
.filter((node): node is NodeState => node !== undefined)
|
26
|
+
);
|
27
|
+
|
28
|
+
// Indentation based on level
|
29
|
+
let indentStyle = $derived(`padding-left: ${nodeState.level * 1.5}rem`);
|
30
|
+
|
31
|
+
function handleToggle() {
|
32
|
+
store.toggle(nodeState.path);
|
33
|
+
}
|
34
|
+
|
35
|
+
function handleExpandToggle() {
|
36
|
+
if (hasChildren) {
|
37
|
+
store.toggleExpand(nodeState.path);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
</script>
|
41
|
+
|
42
|
+
<div class="tree-node" style={indentStyle}>
|
43
|
+
<div class="tree-node-content">
|
44
|
+
<!-- Expand/Collapse Button -->
|
45
|
+
{#if hasChildren}
|
46
|
+
<button
|
47
|
+
type="button"
|
48
|
+
class="btn btn-sm btn-link expand-toggle p-0"
|
49
|
+
onclick={handleExpandToggle}
|
50
|
+
aria-label={isExpanded ? 'Collapse' : 'Expand'}
|
51
|
+
>
|
52
|
+
{#if isExpanded}
|
53
|
+
<i class="bi bi-chevron-down"></i>
|
54
|
+
{:else}
|
55
|
+
<i class="bi bi-chevron-right"></i>
|
56
|
+
{/if}
|
57
|
+
</button>
|
58
|
+
{:else}
|
59
|
+
<span class="expand-placeholder"></span>
|
60
|
+
{/if}
|
61
|
+
|
62
|
+
<!-- Checkbox -->
|
63
|
+
<div class="form-check">
|
64
|
+
<input
|
65
|
+
type="checkbox"
|
66
|
+
class="form-check-input"
|
67
|
+
class:indeterminate={isIndeterminate}
|
68
|
+
checked={isChecked}
|
69
|
+
indeterminate={isIndeterminate}
|
70
|
+
onchange={handleToggle}
|
71
|
+
id={`checkbox-${nodeState.path}`}
|
72
|
+
/>
|
73
|
+
<label class="form-check-label" for={`checkbox-${nodeState.path}`}>
|
74
|
+
{#if nodeState.node.icon}
|
75
|
+
<span class="node-icon">{nodeState.node.icon}</span>
|
76
|
+
{/if}
|
77
|
+
<span class="node-label">{nodeState.node.label}</span>
|
78
|
+
</label>
|
79
|
+
</div>
|
80
|
+
</div>
|
81
|
+
|
82
|
+
<!-- Recursive Children -->
|
83
|
+
{#if hasChildren && isExpanded}
|
84
|
+
<div class="tree-node-children">
|
85
|
+
{#each childNodes as childNode (childNode.path)}
|
86
|
+
<TreeNode nodeState={childNode} {store} {showIndeterminate} />
|
87
|
+
{/each}
|
88
|
+
</div>
|
89
|
+
{/if}
|
90
|
+
</div>
|
91
|
+
|
92
|
+
<style>
|
93
|
+
.tree-node {
|
94
|
+
position: relative;
|
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;
|
103
|
+
}
|
104
|
+
|
105
|
+
.expand-toggle {
|
106
|
+
width: 1.5rem;
|
107
|
+
height: 1.5rem;
|
108
|
+
display: flex;
|
109
|
+
align-items: center;
|
110
|
+
justify-content: center;
|
111
|
+
border: none;
|
112
|
+
background: none;
|
113
|
+
cursor: pointer;
|
114
|
+
color: #6c757d;
|
115
|
+
text-decoration: none;
|
116
|
+
}
|
117
|
+
|
118
|
+
.expand-toggle:hover {
|
119
|
+
color: #495057;
|
120
|
+
background-color: #f8f9fa;
|
121
|
+
border-radius: 0.25rem;
|
122
|
+
}
|
123
|
+
|
124
|
+
.expand-toggle:focus {
|
125
|
+
outline: 2px solid #0d6efd;
|
126
|
+
outline-offset: 2px;
|
127
|
+
}
|
128
|
+
|
129
|
+
.expand-placeholder {
|
130
|
+
width: 1.5rem;
|
131
|
+
height: 1.5rem;
|
132
|
+
display: inline-block;
|
133
|
+
}
|
134
|
+
|
135
|
+
.form-check {
|
136
|
+
display: flex;
|
137
|
+
align-items: center;
|
138
|
+
gap: 0.5rem;
|
139
|
+
margin: 0;
|
140
|
+
}
|
141
|
+
|
142
|
+
.form-check-input {
|
143
|
+
cursor: pointer;
|
144
|
+
margin: 0;
|
145
|
+
flex-shrink: 0;
|
146
|
+
}
|
147
|
+
|
148
|
+
/* Indeterminate checkbox styling */
|
149
|
+
.form-check-input.indeterminate {
|
150
|
+
background-color: #0d6efd;
|
151
|
+
border-color: #0d6efd;
|
152
|
+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
|
153
|
+
}
|
154
|
+
|
155
|
+
.form-check-label {
|
156
|
+
cursor: pointer;
|
157
|
+
display: flex;
|
158
|
+
align-items: center;
|
159
|
+
gap: 0.5rem;
|
160
|
+
margin: 0;
|
161
|
+
user-select: none;
|
162
|
+
}
|
163
|
+
|
164
|
+
.node-icon {
|
165
|
+
font-size: 1.1rem;
|
166
|
+
line-height: 1;
|
167
|
+
}
|
168
|
+
|
169
|
+
.node-label {
|
170
|
+
font-size: 0.875rem;
|
171
|
+
line-height: 1.5;
|
172
|
+
}
|
173
|
+
</style>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import type { NodeState, TreeStoreValue } from './tree.model';
|
2
|
+
import TreeNode from './TreeNode.svelte';
|
3
|
+
interface Props {
|
4
|
+
nodeState: NodeState;
|
5
|
+
store: TreeStoreValue;
|
6
|
+
showIndeterminate?: boolean;
|
7
|
+
}
|
8
|
+
declare const TreeNode: import("svelte").Component<Props, {}, "">;
|
9
|
+
type TreeNode = ReturnType<typeof TreeNode>;
|
10
|
+
export default TreeNode;
|