@smartnet360/svelte-components 0.0.100 โ†’ 0.0.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/apps/site-check/SiteCheck.svelte +54 -272
  2. package/dist/apps/site-check/SiteCheckControls.svelte +294 -0
  3. package/dist/apps/site-check/SiteCheckControls.svelte.d.ts +30 -0
  4. package/dist/map-v2/demo/DemoMap.svelte +39 -7
  5. package/dist/map-v2/features/cells/utils/cellGeoJSON.js +1 -0
  6. package/dist/map-v2/shared/controls/FeatureSelectionControl.svelte +20 -25
  7. package/dist/map-v2/shared/controls/FeatureSelectionControl.svelte.d.ts +2 -4
  8. package/dist/map-v3/demo/DemoMap.svelte +31 -5
  9. package/dist/map-v3/demo/demo-cells.js +51 -22
  10. package/dist/map-v3/features/cells/layers/CellsLayer.svelte +29 -9
  11. package/dist/map-v3/features/cells/logic/geometry.js +3 -0
  12. package/dist/map-v3/features/cells/stores/cell.data.svelte.d.ts +27 -0
  13. package/dist/map-v3/features/cells/stores/cell.data.svelte.js +65 -0
  14. package/dist/map-v3/features/selection/components/FeatureSelectionControl.svelte +82 -65
  15. package/dist/map-v3/features/selection/components/FeatureSelectionControl.svelte.d.ts +5 -9
  16. package/dist/map-v3/features/selection/index.d.ts +1 -2
  17. package/dist/map-v3/features/selection/index.js +0 -1
  18. package/dist/map-v3/features/selection/stores/selection.store.svelte.d.ts +44 -15
  19. package/dist/map-v3/features/selection/stores/selection.store.svelte.js +163 -40
  20. package/dist/map-v3/features/selection/types.d.ts +4 -2
  21. package/dist/shared/ResizableSplitPanel.svelte +175 -0
  22. package/dist/shared/ResizableSplitPanel.svelte.d.ts +17 -0
  23. package/package.json +1 -1
  24. package/dist/map-v3/features/selection/layers/SelectionHighlightLayers.svelte +0 -209
  25. package/dist/map-v3/features/selection/layers/SelectionHighlightLayers.svelte.d.ts +0 -13
@@ -7,7 +7,9 @@
7
7
  import { expandLayoutForCells } from './helper';
8
8
  import { log } from '../../core/logger';
9
9
  import type {ChartMarker, Mode } from '../../index.js';
10
- import { checkHealth, getMessage } from '../../core/FeatureRegistry';
10
+ import { checkHealth } from '../../core/FeatureRegistry';
11
+ import SiteCheckControls from './SiteCheckControls.svelte';
12
+ import ResizableSplitPanel from '../../shared/ResizableSplitPanel.svelte';
11
13
 
12
14
  interface Props {
13
15
  rawData: CellTrafficRecord[];
@@ -31,28 +33,6 @@
31
33
  cellStyling = defaultCellStyling, initialGrouping = defaultTreeGrouping,
32
34
  showGroupingSelector = true, useSectorLineStyles = false, onSearch, searchPlaceholder = "Search...", plotlyLayout }: Props = $props();
33
35
 
34
- // Search state
35
- let searchTerm = $state('');
36
-
37
- // Controls visibility state (starts expanded)
38
- let controlsExpanded = $state(true);
39
-
40
- // Handlers
41
- function handleSearch() {
42
- if (onSearch) {
43
- onSearch(searchTerm);
44
- log('๐Ÿ” Search triggered:', searchTerm);
45
- }
46
- }
47
-
48
- function handleClearSearch() {
49
- searchTerm = '';
50
- if (onSearch) {
51
- onSearch('');
52
- log('๐Ÿงน Search cleared');
53
- }
54
- }
55
-
56
36
  // Check feature health
57
37
  let isHealthy = $state(checkHealth('sitecheck'));
58
38
 
@@ -68,37 +48,7 @@
68
48
  // Single Level 1 select mode - only one Level 1 node per parent at a time (radio behavior)
69
49
  let singleLevel1Select = $state(false);
70
50
 
71
- // Available field options for grouping levels
72
- const fieldOptions: { value: TreeGroupField; label: string }[] = [
73
- { value: 'site', label: 'Site' },
74
- { value: 'band', label: 'Band' },
75
- { value: 'azimuth', label: 'Azimuth' },
76
- { value: 'sector', label: 'Sector' },
77
- { value: 'cellName', label: 'Cell Name' }
78
- ];
79
-
80
- // Handlers for level changes
81
- function handleLevel0Change(value: TreeGroupField) {
82
- // Clear level1 if it conflicts with new level0
83
- const newLevel1 = treeGrouping.level1 === value ? null : treeGrouping.level1;
84
- treeGrouping = {
85
- level0: value,
86
- level1: newLevel1
87
- };
88
- }
89
-
90
- function handleLevel1Change(value: TreeGroupField | 'none') {
91
- const newLevel1 = value === 'none' ? null : value;
92
- treeGrouping = {
93
- level0: treeGrouping.level0,
94
- level1: newLevel1
95
- };
96
- }
97
-
98
- // Get available options for level1 (exclude level0)
99
- let availableLevel1Options = $derived.by(() => {
100
- return fieldOptions.filter(opt => opt.value !== treeGrouping.level0);
101
- }); let treeStore = $state<ReturnType<typeof createTreeStore> | null>(null);
51
+ let treeStore = $state<ReturnType<typeof createTreeStore> | null>(null);
102
52
 
103
53
  // Rebuild tree whenever treeGrouping, singleRootSelect, or singleLevel1Select changes
104
54
  $effect(() => {
@@ -106,10 +56,11 @@
106
56
  log('๐Ÿ”„ Rebuilding tree with grouping', { treeGrouping, singleRootSelect, singleLevel1Select });
107
57
 
108
58
  // Clear any existing localStorage data to prevent stale state
109
- const storageKey = 'site-check:treeState';
59
+ // This includes both tree state AND chart settings to avoid cell mismatches
110
60
  if (typeof window !== 'undefined') {
111
- localStorage.removeItem(storageKey);
112
- log('๐Ÿงน Cleared localStorage:', storageKey);
61
+ localStorage.removeItem('site-check:treeState');
62
+ localStorage.removeItem('charts:globalControls');
63
+ log('๐Ÿงน Cleared localStorage: tree state and chart settings');
113
64
  }
114
65
 
115
66
  // Build tree nodes from raw data with custom grouping
@@ -250,227 +201,58 @@
250
201
  </script>
251
202
 
252
203
  <div class="container-fluid vh-100 d-flex flex-column">
253
- <!-- Main Content -->
254
- <div class="row flex-grow-1" style="min-height: 0;">
255
- <!-- Left: Tree View -->
256
- <div class="col-lg-3 col-md-4 border-end bg-white d-flex flex-column" style="min-height: 0; height: 100%;">
257
- <!-- Collapsible Controls Toggle -->
258
- {#if onSearch || showGroupingSelector}
259
- <button
260
- class="controls-toggle w-100 text-start p-2 bg-light border-bottom d-flex align-items-center flex-shrink-0"
261
- onclick={() => controlsExpanded = !controlsExpanded}
262
- aria-expanded={controlsExpanded}
263
- aria-label="Toggle controls"
264
- >
265
- <i class="bi bi-sliders me-2"></i>
266
- <span class="fw-semibold small">Controls</span>
267
- <i class="bi ms-auto"
268
- class:bi-chevron-down={controlsExpanded}
269
- class:bi-chevron-right={!controlsExpanded}>
270
- </i>
271
- </button>
272
- {/if}
273
-
274
- <!-- Collapsible Controls Content -->
275
- {#if controlsExpanded}
276
- <!-- Search Box -->
277
- {#if onSearch}
278
- <div class="p-3 border-bottom flex-shrink-0">
279
- <label for="searchInput" class="form-label small fw-semibold mb-2">
280
- Search
281
- </label>
282
- <div class="input-group input-group-sm">
283
- <input
284
- type="text"
285
- id="searchInput"
286
- class="form-control"
287
- placeholder={searchPlaceholder}
288
- bind:value={searchTerm}
289
- onkeydown={(e) => {
290
- if (e.key === 'Enter') {
291
- handleSearch();
292
- }
293
- }}
294
- />
295
- {#if searchTerm}
296
- <button
297
- class="btn btn-outline-secondary"
298
- type="button"
299
- onclick={handleClearSearch}
300
- title="Clear search"
301
- aria-label="Clear search"
302
- >
303
- <i class="bi bi-x-lg"></i>
304
- </button>
305
- {/if}
306
- <button
307
- class="btn btn-primary"
308
- type="button"
309
- onclick={handleSearch}
310
- title="Search"
311
- aria-label="Search"
312
- >
313
- <i class="bi bi-search"></i>
314
- </button>
315
- </div>
316
- </div>
317
- {/if}
204
+ <ResizableSplitPanel namespace="site-check" defaultLeftWidth={25}>
205
+ {#snippet left()}
206
+ <div class="bg-white d-flex flex-column" style="height: 100%;">
207
+ <!-- Controls -->
208
+ <SiteCheckControls
209
+ {treeGrouping}
210
+ {colorDimension}
211
+ {singleRootSelect}
212
+ {singleLevel1Select}
213
+ treeStore={$treeStore}
214
+ {showGroupingSelector}
215
+ {onSearch}
216
+ {searchPlaceholder}
217
+ onGroupingChange={(g) => (treeGrouping = g)}
218
+ onColorDimensionChange={(d) => (colorDimension = d)}
219
+ onSingleRootSelectChange={(e) => (singleRootSelect = e)}
220
+ onSingleLevel1SelectChange={(e) => (singleLevel1Select = e)}
221
+ />
318
222
 
319
- <!-- Grouping Selector -->
320
- {#if showGroupingSelector}
321
- <div class="p-3 border-bottom flex-shrink-0">
322
- <div class="small fw-semibold mb-2">Tree Grouping</div>
323
-
324
- <div class="row g-2 mb-2">
325
- <!-- Level 0 (Mandatory) -->
326
- <div class="col-4">
327
- <label for="level0Select" class="form-label small mb-1">Level 0</label>
328
- <select
329
- id="level0Select"
330
- class="form-select form-select-sm"
331
- value={treeGrouping.level0}
332
- onchange={(e) => handleLevel0Change(e.currentTarget.value as TreeGroupField)}
333
- >
334
- {#each fieldOptions as option}
335
- <option value={option.value}>{option.label}</option>
336
- {/each}
337
- </select>
338
- </div>
339
-
340
- <!-- Level 1 (Optional) -->
341
- <div class="col-4">
342
- <label for="level1Select" class="form-label small mb-1">Level 1</label>
343
- <select
344
- id="level1Select"
345
- class="form-select form-select-sm"
346
- value={treeGrouping.level1 ?? 'none'}
347
- onchange={(e) => handleLevel1Change(e.currentTarget.value as TreeGroupField | 'none')}
348
- >
349
- <option value="none">None</option>
350
- {#each availableLevel1Options as option}
351
- <option value={option.value}>{option.label}</option>
352
- {/each}
353
- </select>
354
- </div>
355
-
356
- <!-- Color By -->
357
- <div class="col-4">
358
- <label for="colorDimensionSelect" class="form-label small mb-1">Color By</label>
359
- <select
360
- id="colorDimensionSelect"
361
- class="form-select form-select-sm"
362
- value={colorDimension}
363
- onchange={(e) => {
364
- colorDimension = e.currentTarget.value as ColorDimension;
365
- log('๐ŸŽจ Color dimension changed:', colorDimension);
366
- }}
367
- >
368
- <option value="band">Band</option>
369
- <option value="site">Site</option>
370
- <option value="sector">Sector</option>
371
- <option value="cellName">Cell Name</option>
372
- </select>
373
- </div>
374
- </div>
375
-
376
- <!-- Single Root Select Toggle -->
377
- <div class="form-check mt-2">
378
- <input
379
- class="form-check-input"
380
- type="checkbox"
381
- id="singleRootSelectCheck"
382
- checked={singleRootSelect}
383
- onchange={(e) => {
384
- singleRootSelect = e.currentTarget.checked;
385
- log('๐Ÿ”˜ Single root select mode:', singleRootSelect);
386
-
387
- // When enabling single root mode, uncheck all roots except the first one
388
- if (singleRootSelect && treeStore) {
389
- const store = $treeStore;
390
- if (store) {
391
- const checkedRoots = store.state.rootPaths.filter(path =>
392
- store.state.checkedPaths.has(path)
393
- );
394
- if (checkedRoots.length > 1) {
395
- log('๐Ÿ”˜ Multiple roots selected, keeping only first one:', checkedRoots[0]);
396
- // Uncheck all except the first
397
- for (let i = 1; i < checkedRoots.length; i++) {
398
- store.toggle(checkedRoots[i]);
399
- }
400
- }
401
- }
402
- }
403
- }}
404
- />
405
- <label class="form-check-label small" for="singleRootSelectCheck">
406
- Single selection on level 0
407
- </label>
408
- </div>
409
-
410
- <!-- Single Level 1 Select Toggle -->
411
- <div class="form-check mt-2">
412
- <input
413
- class="form-check-input"
414
- type="checkbox"
415
- id="singleLevel1SelectCheck"
416
- checked={singleLevel1Select}
417
- onchange={(e) => {
418
- singleLevel1Select = e.currentTarget.checked;
419
- log('๐Ÿ”˜ Single Level 1 select mode:', singleLevel1Select);
420
- }}
421
- />
422
- <label class="form-check-label small" for="singleLevel1SelectCheck">
423
- Single selection on level 1
424
- </label>
223
+ <!-- Tree View -->
224
+ <div class="flex-grow-1" style="min-height: 0; overflow: hidden;">
225
+ {#if treeStore}
226
+ <TreeView store={$treeStore!} showControls={true} showIndeterminate={true} height="100%" />
227
+ {/if}
425
228
  </div>
426
229
  </div>
427
- {/if}
428
- {/if} <!-- Tree View -->
429
- <div class="flex-grow-1" style="min-height: 0; overflow: hidden;">
430
- {#if treeStore}
431
- <TreeView store={$treeStore!} showControls={true} showIndeterminate={true} height="100%" />
230
+ {/snippet}
231
+
232
+ {#snippet right()}
233
+ <div class="bg-light d-flex flex-column" style="height: 100%;">
234
+ {#if chartData.length > 0}
235
+ <ChartComponent
236
+ layout={chartLayout}
237
+ data={chartData}
238
+ {mode}
239
+ {markers}
240
+ showGlobalControls={true}
241
+ enableAdaptation={true}
242
+ {plotlyLayout}
243
+ persistSettings={true}
244
+ />
245
+ {:else}
246
+ <div class="d-flex align-items-center justify-content-center h-100">
247
+ <div class="text-center text-muted">
248
+ <h5>No Data Selected</h5>
249
+ </div>
250
+ </div>
432
251
  {/if}
433
252
  </div>
434
- </div>
435
-
436
- <!-- Right: Charts -->
437
- <div class="col-lg-9 col-md-8 bg-light d-flex flex-column" style="min-height: 0; height: 100%; overflow: hidden;">
438
- {#if chartData.length > 0}
439
- <ChartComponent
440
- layout={chartLayout}
441
- data={chartData}
442
- mode={mode}
443
- markers={markers}
444
- showGlobalControls={true}
445
- enableAdaptation={true}
446
- plotlyLayout={plotlyLayout}
447
- persistSettings={true}
448
- />
449
- {:else}
450
- <div class="d-flex align-items-center justify-content-center h-100">
451
- <div class="text-center text-muted">
452
- <h5>No Data Selected</h5>
453
- </div>
454
- </div>
455
- {/if}
456
- </div>
457
- </div>
253
+ {/snippet}
254
+ </ResizableSplitPanel>
458
255
  </div>
459
256
 
460
- <style>
461
- .controls-toggle {
462
- cursor: pointer;
463
- border: none;
464
- transition: background-color 0.2s;
465
- }
466
-
467
- .controls-toggle:hover {
468
- background-color: #e9ecef !important;
469
- }
470
257
 
471
- .controls-toggle:focus {
472
- outline: 2px solid #0d6efd;
473
- outline-offset: -2px;
474
- }
475
- </style>
476
258
 
@@ -0,0 +1,294 @@
1
+ <svelte:options runes={true} />
2
+
3
+ <script lang="ts">
4
+ import { log } from '../../core/logger';
5
+ import type { TreeGroupingConfig, TreeGroupField, ColorDimension } from './index';
6
+
7
+ interface Props {
8
+ /** Current tree grouping configuration */
9
+ treeGrouping: TreeGroupingConfig;
10
+ /** Current color dimension */
11
+ colorDimension: ColorDimension;
12
+ /** Single root select mode */
13
+ singleRootSelect: boolean;
14
+ /** Single level 1 select mode */
15
+ singleLevel1Select: boolean;
16
+ /** Tree store for enforcing single root selection */
17
+ treeStore?: any;
18
+ /** Show grouping selector controls */
19
+ showGroupingSelector?: boolean;
20
+ /** Optional search callback */
21
+ onSearch?: (searchTerm: string) => void;
22
+ /** Search placeholder text */
23
+ searchPlaceholder?: string;
24
+ /** Callback when grouping changes */
25
+ onGroupingChange?: (grouping: TreeGroupingConfig) => void;
26
+ /** Callback when color dimension changes */
27
+ onColorDimensionChange?: (dimension: ColorDimension) => void;
28
+ /** Callback when single root select changes */
29
+ onSingleRootSelectChange?: (enabled: boolean) => void;
30
+ /** Callback when single level 1 select changes */
31
+ onSingleLevel1SelectChange?: (enabled: boolean) => void;
32
+ }
33
+
34
+ let {
35
+ treeGrouping,
36
+ colorDimension,
37
+ singleRootSelect,
38
+ singleLevel1Select,
39
+ treeStore,
40
+ showGroupingSelector = true,
41
+ onSearch,
42
+ searchPlaceholder = 'Search...',
43
+ onGroupingChange,
44
+ onColorDimensionChange,
45
+ onSingleRootSelectChange,
46
+ onSingleLevel1SelectChange
47
+ }: Props = $props();
48
+
49
+ // Local state
50
+ let searchTerm = $state('');
51
+ let controlsExpanded = $state(true);
52
+
53
+ // Available field options for grouping levels
54
+ const fieldOptions: { value: TreeGroupField; label: string }[] = [
55
+ { value: 'site', label: 'Site' },
56
+ { value: 'band', label: 'Band' },
57
+ { value: 'azimuth', label: 'Azimuth' },
58
+ { value: 'sector', label: 'Sector' },
59
+ { value: 'cellName', label: 'Cell Name' }
60
+ ];
61
+
62
+ // Get available options for level1 (exclude level0)
63
+ let availableLevel1Options = $derived.by(() => {
64
+ return fieldOptions.filter(opt => opt.value !== treeGrouping.level0);
65
+ });
66
+
67
+ // Handlers
68
+ function handleSearch() {
69
+ if (onSearch) {
70
+ onSearch(searchTerm);
71
+ log('๐Ÿ” Search triggered:', searchTerm);
72
+ }
73
+ }
74
+
75
+ function handleClearSearch() {
76
+ searchTerm = '';
77
+ if (onSearch) {
78
+ onSearch('');
79
+ log('๐Ÿงน Search cleared');
80
+ }
81
+ }
82
+
83
+ function handleLevel0Change(value: TreeGroupField) {
84
+ const newLevel1 = treeGrouping.level1 === value ? null : treeGrouping.level1;
85
+ const newGrouping = {
86
+ level0: value,
87
+ level1: newLevel1
88
+ };
89
+ onGroupingChange?.(newGrouping);
90
+ }
91
+
92
+ function handleLevel1Change(value: TreeGroupField | 'none') {
93
+ const newLevel1 = value === 'none' ? null : value;
94
+ const newGrouping = {
95
+ level0: treeGrouping.level0,
96
+ level1: newLevel1
97
+ };
98
+ onGroupingChange?.(newGrouping);
99
+ }
100
+
101
+ function handleColorDimensionChange(dimension: ColorDimension) {
102
+ onColorDimensionChange?.(dimension);
103
+ log('๐ŸŽจ Color dimension changed:', dimension);
104
+ }
105
+
106
+ function handleSingleRootSelectChange(enabled: boolean) {
107
+ onSingleRootSelectChange?.(enabled);
108
+ log('๐Ÿ”˜ Single root select mode:', enabled);
109
+
110
+ // When enabling single root mode, uncheck all roots except the first one
111
+ if (enabled && treeStore) {
112
+ const store = treeStore;
113
+ if (store) {
114
+ const checkedRoots = store.state.rootPaths.filter((path: string) =>
115
+ store.state.checkedPaths.has(path)
116
+ );
117
+ if (checkedRoots.length > 1) {
118
+ log('๐Ÿ”˜ Multiple roots selected, keeping only first one:', checkedRoots[0]);
119
+ for (let i = 1; i < checkedRoots.length; i++) {
120
+ store.toggle(checkedRoots[i]);
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ function handleSingleLevel1SelectChange(enabled: boolean) {
128
+ onSingleLevel1SelectChange?.(enabled);
129
+ log('๐Ÿ”˜ Single Level 1 select mode:', enabled);
130
+ }
131
+ </script>
132
+
133
+ {#if onSearch || showGroupingSelector}
134
+ <!-- Collapsible Controls Toggle -->
135
+ <button
136
+ class="controls-toggle w-100 text-start p-2 bg-light border-bottom d-flex align-items-center flex-shrink-0"
137
+ onclick={() => (controlsExpanded = !controlsExpanded)}
138
+ aria-expanded={controlsExpanded}
139
+ aria-label="Toggle controls"
140
+ >
141
+ <i class="bi bi-sliders me-2"></i>
142
+ <span class="fw-semibold small">Controls</span>
143
+ <i
144
+ class="bi ms-auto"
145
+ class:bi-chevron-down={controlsExpanded}
146
+ class:bi-chevron-right={!controlsExpanded}
147
+ ></i>
148
+ </button>
149
+
150
+ <!-- Collapsible Controls Content -->
151
+ {#if controlsExpanded}
152
+ <!-- Search Box -->
153
+ {#if onSearch}
154
+ <div class="p-3 border-bottom flex-shrink-0">
155
+ <label for="searchInput" class="form-label small fw-semibold mb-2"> Search </label>
156
+ <div class="input-group input-group-sm">
157
+ <input
158
+ type="text"
159
+ id="searchInput"
160
+ class="form-control"
161
+ placeholder={searchPlaceholder}
162
+ bind:value={searchTerm}
163
+ onkeydown={(e) => {
164
+ if (e.key === 'Enter') {
165
+ handleSearch();
166
+ }
167
+ }}
168
+ />
169
+ {#if searchTerm}
170
+ <button
171
+ class="btn btn-outline-secondary"
172
+ type="button"
173
+ onclick={handleClearSearch}
174
+ title="Clear search"
175
+ aria-label="Clear search"
176
+ >
177
+ <i class="bi bi-x-lg"></i>
178
+ </button>
179
+ {/if}
180
+ <button
181
+ class="btn btn-primary"
182
+ type="button"
183
+ onclick={handleSearch}
184
+ title="Search"
185
+ aria-label="Search"
186
+ >
187
+ <i class="bi bi-search"></i>
188
+ </button>
189
+ </div>
190
+ </div>
191
+ {/if}
192
+
193
+ <!-- Grouping Selector -->
194
+ {#if showGroupingSelector}
195
+ <div class="p-3 border-bottom flex-shrink-0">
196
+ <div class="small fw-semibold mb-2">Tree Grouping</div>
197
+
198
+ <div class="row g-2 mb-2">
199
+ <!-- Level 0 (Mandatory) -->
200
+ <div class="col-4">
201
+ <label for="level0Select" class="form-label small mb-1">Level 0</label>
202
+ <select
203
+ id="level0Select"
204
+ class="form-select form-select-sm"
205
+ value={treeGrouping.level0}
206
+ onchange={(e) => handleLevel0Change(e.currentTarget.value as TreeGroupField)}
207
+ >
208
+ {#each fieldOptions as option}
209
+ <option value={option.value}>{option.label}</option>
210
+ {/each}
211
+ </select>
212
+ </div>
213
+
214
+ <!-- Level 1 (Optional) -->
215
+ <div class="col-4">
216
+ <label for="level1Select" class="form-label small mb-1">Level 1</label>
217
+ <select
218
+ id="level1Select"
219
+ class="form-select form-select-sm"
220
+ value={treeGrouping.level1 ?? 'none'}
221
+ onchange={(e) => handleLevel1Change(e.currentTarget.value as TreeGroupField | 'none')}
222
+ >
223
+ <option value="none">None</option>
224
+ {#each availableLevel1Options as option}
225
+ <option value={option.value}>{option.label}</option>
226
+ {/each}
227
+ </select>
228
+ </div>
229
+
230
+ <!-- Color By -->
231
+ <div class="col-4">
232
+ <label for="colorDimensionSelect" class="form-label small mb-1">Color By</label>
233
+ <select
234
+ id="colorDimensionSelect"
235
+ class="form-select form-select-sm"
236
+ value={colorDimension}
237
+ onchange={(e) => handleColorDimensionChange(e.currentTarget.value as ColorDimension)}
238
+ >
239
+ <option value="band">Band</option>
240
+ <option value="site">Site</option>
241
+ <option value="sector">Sector</option>
242
+ <option value="cellName">Cell Name</option>
243
+ </select>
244
+ </div>
245
+ </div>
246
+
247
+ <!-- Single Root Select Toggle -->
248
+ <div class="form-check mt-2">
249
+ <input
250
+ class="form-check-input"
251
+ type="checkbox"
252
+ id="singleRootSelectCheck"
253
+ checked={singleRootSelect}
254
+ onchange={(e) => handleSingleRootSelectChange(e.currentTarget.checked)}
255
+ />
256
+ <label class="form-check-label small" for="singleRootSelectCheck">
257
+ Single selection on level 0
258
+ </label>
259
+ </div>
260
+
261
+ <!-- Single Level 1 Select Toggle -->
262
+ <div class="form-check mt-2">
263
+ <input
264
+ class="form-check-input"
265
+ type="checkbox"
266
+ id="singleLevel1SelectCheck"
267
+ checked={singleLevel1Select}
268
+ onchange={(e) => handleSingleLevel1SelectChange(e.currentTarget.checked)}
269
+ />
270
+ <label class="form-check-label small" for="singleLevel1SelectCheck">
271
+ Single selection on level 1
272
+ </label>
273
+ </div>
274
+ </div>
275
+ {/if}
276
+ {/if}
277
+ {/if}
278
+
279
+ <style>
280
+ .controls-toggle {
281
+ cursor: pointer;
282
+ border: none;
283
+ transition: background-color 0.2s;
284
+ }
285
+
286
+ .controls-toggle:hover {
287
+ background-color: #e9ecef !important;
288
+ }
289
+
290
+ .controls-toggle:focus {
291
+ outline: 2px solid #0d6efd;
292
+ outline-offset: -2px;
293
+ }
294
+ </style>
@@ -0,0 +1,30 @@
1
+ import type { TreeGroupingConfig, ColorDimension } from './index';
2
+ interface Props {
3
+ /** Current tree grouping configuration */
4
+ treeGrouping: TreeGroupingConfig;
5
+ /** Current color dimension */
6
+ colorDimension: ColorDimension;
7
+ /** Single root select mode */
8
+ singleRootSelect: boolean;
9
+ /** Single level 1 select mode */
10
+ singleLevel1Select: boolean;
11
+ /** Tree store for enforcing single root selection */
12
+ treeStore?: any;
13
+ /** Show grouping selector controls */
14
+ showGroupingSelector?: boolean;
15
+ /** Optional search callback */
16
+ onSearch?: (searchTerm: string) => void;
17
+ /** Search placeholder text */
18
+ searchPlaceholder?: string;
19
+ /** Callback when grouping changes */
20
+ onGroupingChange?: (grouping: TreeGroupingConfig) => void;
21
+ /** Callback when color dimension changes */
22
+ onColorDimensionChange?: (dimension: ColorDimension) => void;
23
+ /** Callback when single root select changes */
24
+ onSingleRootSelectChange?: (enabled: boolean) => void;
25
+ /** Callback when single level 1 select changes */
26
+ onSingleLevel1SelectChange?: (enabled: boolean) => void;
27
+ }
28
+ declare const SiteCheckControls: import("svelte").Component<Props, {}, "">;
29
+ type SiteCheckControls = ReturnType<typeof SiteCheckControls>;
30
+ export default SiteCheckControls;