@smartnet360/svelte-components 0.0.71 → 0.0.73

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 (34) hide show
  1. package/dist/map-v2/demo/DemoMap.svelte +29 -1
  2. package/dist/map-v2/demo/demo-repeaters.d.ts +13 -0
  3. package/dist/map-v2/demo/demo-repeaters.js +74 -0
  4. package/dist/map-v2/demo/index.d.ts +1 -0
  5. package/dist/map-v2/demo/index.js +1 -0
  6. package/dist/map-v2/features/repeaters/constants/radiusMultipliers.d.ts +26 -0
  7. package/dist/map-v2/features/repeaters/constants/radiusMultipliers.js +40 -0
  8. package/dist/map-v2/features/repeaters/constants/techBandZOrder.d.ts +25 -0
  9. package/dist/map-v2/features/repeaters/constants/techBandZOrder.js +43 -0
  10. package/dist/map-v2/features/repeaters/constants/zIndex.d.ts +11 -0
  11. package/dist/map-v2/features/repeaters/constants/zIndex.js +11 -0
  12. package/dist/map-v2/features/repeaters/controls/RepeaterFilterControl.svelte +172 -0
  13. package/dist/map-v2/features/repeaters/controls/RepeaterFilterControl.svelte.d.ts +18 -0
  14. package/dist/map-v2/features/repeaters/index.d.ts +16 -0
  15. package/dist/map-v2/features/repeaters/index.js +19 -0
  16. package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte +301 -0
  17. package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte.d.ts +10 -0
  18. package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte +259 -0
  19. package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte.d.ts +10 -0
  20. package/dist/map-v2/features/repeaters/stores/repeaterStoreContext.svelte.d.ts +69 -0
  21. package/dist/map-v2/features/repeaters/stores/repeaterStoreContext.svelte.js +222 -0
  22. package/dist/map-v2/features/repeaters/types.d.ts +59 -0
  23. package/dist/map-v2/features/repeaters/types.js +4 -0
  24. package/dist/map-v2/features/repeaters/utils/repeaterGeoJSON.d.ts +20 -0
  25. package/dist/map-v2/features/repeaters/utils/repeaterGeoJSON.js +90 -0
  26. package/dist/map-v2/features/repeaters/utils/repeaterTree.d.ts +23 -0
  27. package/dist/map-v2/features/repeaters/utils/repeaterTree.js +111 -0
  28. package/dist/map-v2/index.d.ts +2 -1
  29. package/dist/map-v2/index.js +5 -1
  30. package/dist/map-v2/shared/controls/FeatureSettingsControl.svelte +27 -21
  31. package/dist/map-v2/shared/controls/FeatureSettingsControl.svelte.d.ts +3 -0
  32. package/dist/map-v2/shared/controls/panels/RepeaterSettingsPanel.svelte +280 -5
  33. package/dist/map-v2/shared/controls/panels/RepeaterSettingsPanel.svelte.d.ts +17 -16
  34. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  /**
3
- * DemoMap - Complete demo of map-v2 site and cell features
3
+ * DemoMap - Complete demo of map-v2 site, cell, and repeater features
4
4
  *
5
5
  * Demonstrates:
6
6
  * - Simplified MapboxProvider with automatic initialization
@@ -11,6 +11,8 @@
11
11
  * - CellsLayer rendering cells with arc geometries
12
12
  * - CellFilterControl with dynamic hierarchical grouping
13
13
  * - CellStyleControl for cell visual settings
14
+ * - RepeatersLayer with tech:fband tree filtering
15
+ * - RepeaterLabelsLayer for repeater identification
14
16
  */
15
17
  import MapboxProvider from '../core/providers/MapboxProvider.svelte';
16
18
  import MapStyleControl from '../core/controls/MapStyleControl.svelte';
@@ -21,13 +23,18 @@
21
23
  import CellsLayer from '../features/cells/layers/CellsLayer.svelte';
22
24
  import CellLabelsLayer from '../features/cells/layers/CellLabelsLayer.svelte';
23
25
  import CellFilterControl from '../features/cells/controls/CellFilterControl.svelte';
26
+ import RepeatersLayer from '../features/repeaters/layers/RepeatersLayer.svelte';
27
+ import RepeaterLabelsLayer from '../features/repeaters/layers/RepeaterLabelsLayer.svelte';
28
+ import RepeaterFilterControl from '../features/repeaters/controls/RepeaterFilterControl.svelte';
24
29
  import FeatureSettingsControl from '../shared/controls/FeatureSettingsControl.svelte';
25
30
  import { createSiteStoreContext } from '../features/sites/stores/siteStoreContext.svelte';
26
31
  import { createCellStoreContext } from '../features/cells/stores/cellStoreContext.svelte';
32
+ import { createRepeaterStoreContext } from '../features/repeaters/stores/repeaterStoreContext.svelte';
27
33
  import { createViewportStore } from '../core/stores/viewportStore.svelte';
28
34
  import type { CellGroupingField, CellGroupingLabels } from '../features/cells/types';
29
35
  import { demoSites } from './demo-data';
30
36
  import { demoCells } from './demo-cells';
37
+ import { demoRepeaters } from './demo-repeaters';
31
38
  import { writable } from 'svelte/store';
32
39
 
33
40
  interface Props {
@@ -54,6 +61,9 @@
54
61
  // Create cell store with demo data
55
62
  const cellStore = createCellStoreContext(demoCells);
56
63
 
64
+ // Create repeater store with demo data
65
+ const repeaterStore = createRepeaterStoreContext(demoRepeaters);
66
+
57
67
  // Toggle whether control headers show icons (when collapsed) or always show text
58
68
  const useIconHeaders = true;
59
69
 
@@ -61,6 +71,7 @@
61
71
  const controlIcons = {
62
72
  mapStyle: 'layers',
63
73
  cellFilter: 'radioactive',
74
+ repeaterFilter: 'broadcast',
64
75
  siteFilter: 'broadcast-pin',
65
76
  siteSelection: 'check2-square',
66
77
  cellStyle: 'palette',
@@ -153,6 +164,12 @@
153
164
  <!-- Cell labels layer - renders tech-aware labels positioned along azimuth -->
154
165
  <CellLabelsLayer store={cellStore} namespace="demo-cells" />
155
166
 
167
+ <!-- Repeater layer - renders arc sectors with fixed 30° beamwidth -->
168
+ <RepeatersLayer store={repeaterStore} namespace="demo-repeaters" />
169
+
170
+ <!-- Repeater labels layer - renders labels positioned along azimuth -->
171
+ <RepeaterLabelsLayer store={repeaterStore} namespace="demo-repeaters" />
172
+
156
173
  <!-- Map style control - switch between map styles -->
157
174
  <MapStyleControl
158
175
  position="top-right"
@@ -175,6 +192,16 @@
175
192
  iconOnlyWhenCollapsed={useIconHeaders}
176
193
  />
177
194
 
195
+ <!-- Repeater filter control - Tech:FBand tree filtering -->
196
+ <RepeaterFilterControl
197
+ store={repeaterStore}
198
+ position="top-left"
199
+ title="Repeater Filter"
200
+ initiallyCollapsed={true}
201
+ icon={controlIcons.repeaterFilter}
202
+ iconOnlyWhenCollapsed={useIconHeaders}
203
+ />
204
+
178
205
  <!-- Site filter control - updates store.filteredSites -->
179
206
  <SiteFilterControl
180
207
  store={siteStore}
@@ -199,6 +226,7 @@
199
226
  <FeatureSettingsControl
200
227
  siteStore={siteStore}
201
228
  cellStore={cellStore}
229
+ repeaterStore={repeaterStore}
202
230
  position="top-right"
203
231
  title="Feature Settings"
204
232
  initiallyCollapsed={true}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Demo Repeater Data
3
+ *
4
+ * 15 repeaters strategically placed near demo sites
5
+ * Mix of 2G, 4G, and 5G technologies across various frequency bands
6
+ * Fixed 30° beamwidth
7
+ */
8
+ import type { Repeater } from '../features/repeaters/types';
9
+ /**
10
+ * Generate demo repeater data
11
+ * Creates 15 repeaters with various tech:fband combinations
12
+ */
13
+ export declare const demoRepeaters: Repeater[];
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Demo Repeater Data
3
+ *
4
+ * 15 repeaters strategically placed near demo sites
5
+ * Mix of 2G, 4G, and 5G technologies across various frequency bands
6
+ * Fixed 30° beamwidth
7
+ */
8
+ // Base location: San Francisco
9
+ const BASE_LAT = 37.7749;
10
+ const BASE_LNG = -122.4194;
11
+ // Repeater positions (distributed across demo area)
12
+ const REPEATER_POSITIONS = [
13
+ { lat: BASE_LAT + 0.015, lng: BASE_LNG - 0.020 },
14
+ { lat: BASE_LAT + 0.025, lng: BASE_LNG + 0.010 },
15
+ { lat: BASE_LAT - 0.010, lng: BASE_LNG + 0.025 },
16
+ { lat: BASE_LAT - 0.020, lng: BASE_LNG - 0.015 },
17
+ { lat: BASE_LAT + 0.005, lng: BASE_LNG + 0.030 },
18
+ { lat: BASE_LAT + 0.030, lng: BASE_LNG - 0.005 },
19
+ { lat: BASE_LAT - 0.025, lng: BASE_LNG + 0.015 },
20
+ { lat: BASE_LAT + 0.010, lng: BASE_LNG - 0.030 },
21
+ { lat: BASE_LAT - 0.015, lng: BASE_LNG - 0.025 },
22
+ { lat: BASE_LAT + 0.020, lng: BASE_LNG + 0.020 },
23
+ { lat: BASE_LAT - 0.005, lng: BASE_LNG - 0.010 },
24
+ { lat: BASE_LAT + 0.035, lng: BASE_LNG + 0.005 },
25
+ { lat: BASE_LAT - 0.030, lng: BASE_LNG + 0.020 },
26
+ { lat: BASE_LAT + 0.012, lng: BASE_LNG - 0.018 },
27
+ { lat: BASE_LAT - 0.018, lng: BASE_LNG + 0.012 }
28
+ ];
29
+ // Repeater tech-band combinations
30
+ const REPEATER_CONFIGS = [
31
+ // 2G repeaters
32
+ { tech: '2G', fband: 'GSM900', azimuth: 45 },
33
+ { tech: '2G', fband: 'GSM1800', azimuth: 180 },
34
+ { tech: '2G', fband: 'GSM900', azimuth: 270 },
35
+ // 4G repeaters
36
+ { tech: '4G', fband: 'LTE700', azimuth: 90 },
37
+ { tech: '4G', fband: 'LTE800', azimuth: 225 },
38
+ { tech: '4G', fband: 'LTE1800', azimuth: 135 },
39
+ { tech: '4G', fband: 'LTE2100', azimuth: 315 },
40
+ { tech: '4G', fband: 'LTE2600', azimuth: 60 },
41
+ // 5G repeaters
42
+ { tech: '5G', fband: '5G-700', azimuth: 150 },
43
+ { tech: '5G', fband: '5G-2100', azimuth: 210 },
44
+ { tech: '5G', fband: '5G-3500', azimuth: 330 },
45
+ { tech: '5G', fband: '5G-3500', azimuth: 0 },
46
+ { tech: '5G', fband: '5G-2100', azimuth: 120 },
47
+ { tech: '5G', fband: '5G-700', azimuth: 240 },
48
+ { tech: '5G', fband: '5G-3500', azimuth: 180 }
49
+ ];
50
+ /**
51
+ * Generate demo repeater data
52
+ * Creates 15 repeaters with various tech:fband combinations
53
+ */
54
+ export const demoRepeaters = REPEATER_POSITIONS.map((pos, idx) => {
55
+ const config = REPEATER_CONFIGS[idx];
56
+ const repeaterId = `REP-${String(idx + 1).padStart(3, '0')}`;
57
+ const donorCellId = `CELL-${String((idx * 7) % 100).padStart(3, '0')}`;
58
+ return {
59
+ repeaterId,
60
+ donorCellId,
61
+ donorCellName: `Cell-${config.tech}-${config.fband}-${idx + 1}`,
62
+ latitude: pos.lat,
63
+ longitude: pos.lng,
64
+ azimuth: config.azimuth,
65
+ beamwidth: 30, // Fixed 30° beamwidth
66
+ tech: config.tech,
67
+ fband: config.fband,
68
+ type: 'REPEATER',
69
+ height: 15 + (idx % 5) * 5, // Heights between 15-35m
70
+ factory_nbr: `FN-${String(1000 + idx).padStart(6, '0')}`,
71
+ provider: idx % 2 === 0 ? 'Operator-A' : 'Operator-B',
72
+ featureGroup: config.tech === '5G' ? 'High-Priority' : config.tech === '4G' ? 'Standard' : 'Legacy'
73
+ };
74
+ });
@@ -6,3 +6,4 @@
6
6
  export { default as DemoMap } from './DemoMap.svelte';
7
7
  export { demoSites } from './demo-data';
8
8
  export { demoCells } from './demo-cells';
9
+ export { demoRepeaters } from './demo-repeaters';
@@ -8,3 +8,4 @@ export { default as DemoMap } from './DemoMap.svelte';
8
8
  // Demo data
9
9
  export { demoSites } from './demo-data';
10
10
  export { demoCells } from './demo-cells';
11
+ export { demoRepeaters } from './demo-repeaters';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Radius multipliers for different repeater tech-band combinations
3
+ *
4
+ * These multipliers adjust the visual size of repeater arcs based on technology and frequency.
5
+ * Lower frequencies typically have larger coverage areas.
6
+ */
7
+ /**
8
+ * Tech:FBand key type for repeaters
9
+ */
10
+ export type RepeaterTechBandKey = string;
11
+ /**
12
+ * Radius multipliers by tech:fband
13
+ * Multiplier is applied to base radius to calculate final arc size
14
+ *
15
+ * Lower frequency = larger multiplier (wider coverage)
16
+ * Higher frequency = smaller multiplier (more focused coverage)
17
+ */
18
+ export declare const REPEATER_RADIUS_MULTIPLIER: Record<string, number>;
19
+ /**
20
+ * Get radius multiplier for a tech:fband combination
21
+ *
22
+ * @param tech - Technology (2G, 4G, 5G)
23
+ * @param fband - Frequency band (e.g., "LTE1800", "GSM900")
24
+ * @returns Radius multiplier (defaults to 1.0 if not found)
25
+ */
26
+ export declare function getRepeaterRadiusMultiplier(tech: string, fband: string): number;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Radius multipliers for different repeater tech-band combinations
3
+ *
4
+ * These multipliers adjust the visual size of repeater arcs based on technology and frequency.
5
+ * Lower frequencies typically have larger coverage areas.
6
+ */
7
+ /**
8
+ * Radius multipliers by tech:fband
9
+ * Multiplier is applied to base radius to calculate final arc size
10
+ *
11
+ * Lower frequency = larger multiplier (wider coverage)
12
+ * Higher frequency = smaller multiplier (more focused coverage)
13
+ */
14
+ export const REPEATER_RADIUS_MULTIPLIER = {
15
+ // 2G bands - largest coverage
16
+ '2G:GSM900': 1.2,
17
+ '2G:GSM1800': 1.0,
18
+ // 4G bands - medium coverage
19
+ '4G:LTE700': 1.3,
20
+ '4G:LTE800': 1.2,
21
+ '4G:LTE900': 1.1,
22
+ '4G:LTE1800': 0.8,
23
+ '4G:LTE2100': 0.7,
24
+ '4G:LTE2600': 0.6,
25
+ // 5G bands - smallest coverage (higher frequencies)
26
+ '5G:5G-700': 1.2,
27
+ '5G:5G-2100': 0.7,
28
+ '5G:5G-3500': 0.5,
29
+ };
30
+ /**
31
+ * Get radius multiplier for a tech:fband combination
32
+ *
33
+ * @param tech - Technology (2G, 4G, 5G)
34
+ * @param fband - Frequency band (e.g., "LTE1800", "GSM900")
35
+ * @returns Radius multiplier (defaults to 1.0 if not found)
36
+ */
37
+ export function getRepeaterRadiusMultiplier(tech, fband) {
38
+ const key = `${tech}:${fband}`;
39
+ return REPEATER_RADIUS_MULTIPLIER[key] ?? 1.0; // Default to 1.0x if unknown
40
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Z-Index ordering for repeater tech-band combinations
3
+ *
4
+ * Higher frequency bands are rendered on top when repeaters overlap.
5
+ * This follows the same pattern as cells.
6
+ */
7
+ /**
8
+ * Z-index ordering for tech:fband combinations
9
+ * Higher number = rendered on top
10
+ *
11
+ * Organized by frequency (low to high):
12
+ * - Lower frequencies (700-900) at bottom
13
+ * - Mid frequencies (1800-2100) in middle
14
+ * - Higher frequencies (2600-3500) on top
15
+ */
16
+ export declare const REPEATER_TECH_BAND_Z_ORDER: Record<string, number>;
17
+ /**
18
+ * Get z-order index for a tech:fband combination
19
+ * Used to set the sort key for layering overlapping repeaters
20
+ *
21
+ * @param tech - Technology (2G, 4G, 5G)
22
+ * @param fband - Frequency band (e.g., "LTE1800", "GSM900")
23
+ * @returns Z-order index (defaults to 0 if not found)
24
+ */
25
+ export declare function getRepeaterZOrder(tech: string, fband: string): number;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Z-Index ordering for repeater tech-band combinations
3
+ *
4
+ * Higher frequency bands are rendered on top when repeaters overlap.
5
+ * This follows the same pattern as cells.
6
+ */
7
+ /**
8
+ * Z-index ordering for tech:fband combinations
9
+ * Higher number = rendered on top
10
+ *
11
+ * Organized by frequency (low to high):
12
+ * - Lower frequencies (700-900) at bottom
13
+ * - Mid frequencies (1800-2100) in middle
14
+ * - Higher frequencies (2600-3500) on top
15
+ */
16
+ export const REPEATER_TECH_BAND_Z_ORDER = {
17
+ // 2G bands (lowest)
18
+ '2G:GSM900': 1,
19
+ '2G:GSM1800': 2,
20
+ // 4G bands (medium)
21
+ '4G:LTE700': 3,
22
+ '4G:LTE800': 4,
23
+ '4G:LTE900': 5,
24
+ '4G:LTE1800': 6,
25
+ '4G:LTE2100': 7,
26
+ '4G:LTE2600': 8,
27
+ // 5G bands (highest)
28
+ '5G:5G-700': 9,
29
+ '5G:5G-2100': 10,
30
+ '5G:5G-3500': 11,
31
+ };
32
+ /**
33
+ * Get z-order index for a tech:fband combination
34
+ * Used to set the sort key for layering overlapping repeaters
35
+ *
36
+ * @param tech - Technology (2G, 4G, 5G)
37
+ * @param fband - Frequency band (e.g., "LTE1800", "GSM900")
38
+ * @returns Z-order index (defaults to 0 if not found)
39
+ */
40
+ export function getRepeaterZOrder(tech, fband) {
41
+ const key = `${tech}:${fband}`;
42
+ return REPEATER_TECH_BAND_Z_ORDER[key] ?? 0;
43
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Z-Index constants for repeater layers
3
+ *
4
+ * Repeaters are rendered above cells but below labels
5
+ */
6
+ /** Fill layer z-index for repeater arcs */
7
+ export declare const REPEATER_FILL_Z_INDEX = 100;
8
+ /** Line layer z-index for repeater arc borders */
9
+ export declare const REPEATER_LINE_Z_INDEX = 101;
10
+ /** Label layer z-index for repeater labels */
11
+ export declare const REPEATER_LABEL_Z_INDEX = 102;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Z-Index constants for repeater layers
3
+ *
4
+ * Repeaters are rendered above cells but below labels
5
+ */
6
+ /** Fill layer z-index for repeater arcs */
7
+ export const REPEATER_FILL_Z_INDEX = 100;
8
+ /** Line layer z-index for repeater arc borders */
9
+ export const REPEATER_LINE_Z_INDEX = 101;
10
+ /** Label layer z-index for repeater labels */
11
+ export const REPEATER_LABEL_Z_INDEX = 102;
@@ -0,0 +1,172 @@
1
+ <script lang="ts">
2
+ /**
3
+ * RepeaterFilterControl - Tech:FBand hierarchical filter control for repeaters
4
+ *
5
+ * Features:
6
+ * - Fixed Tech→FBand two-level tree structure
7
+ * - TreeView with checkboxes for filtering
8
+ * - Color pickers on leaf nodes (tech:fband level customization)
9
+ * - Persists tree state to localStorage
10
+ */
11
+
12
+ import { onMount } from 'svelte';
13
+ import type { Writable } from 'svelte/store';
14
+ import MapControl from '../../../shared/controls/MapControl.svelte';
15
+ import TreeView from '../../../../core/TreeView/TreeView.svelte';
16
+ import { createTreeStore } from '../../../../core/TreeView/tree.store';
17
+ import { buildRepeaterTree, getFilteredRepeaters } from '../utils/repeaterTree';
18
+ import type { RepeaterStoreContext } from '../stores/repeaterStoreContext.svelte';
19
+ import type { TreeStoreValue } from '../../../../core/TreeView/tree.model';
20
+
21
+ interface Props {
22
+ /** Repeater store context */
23
+ store: RepeaterStoreContext;
24
+ /** Control position */
25
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
26
+ /** Control title */
27
+ title?: string;
28
+ /** Optional header icon */
29
+ icon?: string;
30
+ /** Show icon when collapsed (default: true) */
31
+ iconOnlyWhenCollapsed?: boolean;
32
+ /** Initially collapsed? */
33
+ initiallyCollapsed?: boolean;
34
+ }
35
+
36
+ let {
37
+ store,
38
+ position = 'top-left',
39
+ title = 'Repeater Filter',
40
+ icon = 'broadcast',
41
+ iconOnlyWhenCollapsed = true,
42
+ initiallyCollapsed = true
43
+ }: Props = $props();
44
+
45
+ let treeStore = $state<Writable<TreeStoreValue> | null>(null);
46
+
47
+ // Rebuild tree when repeaters change
48
+ function rebuildTree() {
49
+ // Build tree from all repeaters (not filtered)
50
+ const treeRoot = buildRepeaterTree(store.repeaters, store.techBandColorMap);
51
+
52
+ // Create or recreate tree store with persistence
53
+ treeStore = createTreeStore({
54
+ nodes: [treeRoot], // TreeView expects array of root nodes
55
+ namespace: 'cellular-repeater-filter',
56
+ persistState: true,
57
+ defaultExpandAll: false
58
+ });
59
+
60
+ // Subscribe to tree changes and update filtered repeaters
61
+ if (treeStore) {
62
+ const unsub = treeStore.subscribe((treeValue: TreeStoreValue) => {
63
+ const checkedPaths = treeValue.getCheckedPaths();
64
+
65
+ // Convert string[] to Set<string>
66
+ const checkedPathsSet = new Set(checkedPaths);
67
+ const selectedTechBands = getFilteredRepeaters(checkedPathsSet);
68
+
69
+ // Update visibility for each tech:fband combination
70
+ // First, get all tech:fband combinations from repeaters
71
+ const allTechBands = new Set<string>();
72
+ for (const repeater of store.repeaters) {
73
+ const key = `${repeater.tech}:${repeater.fband}`;
74
+ allTechBands.add(key);
75
+ }
76
+
77
+ // If only root checked, show all
78
+ if (selectedTechBands.size === 0 && checkedPathsSet.has('root')) {
79
+ // All visible - no toggle needed
80
+ } else {
81
+ // Toggle visibility based on selection
82
+ for (const techBand of allTechBands) {
83
+ const [tech, fband] = techBand.split(':');
84
+ const shouldBeVisible = selectedTechBands.has(techBand);
85
+ const isVisible = store.isTechBandVisible(tech, fband);
86
+
87
+ // Only toggle if state needs to change
88
+ if (shouldBeVisible !== isVisible) {
89
+ store.toggleTechBand(tech, fband);
90
+ }
91
+ }
92
+ }
93
+ });
94
+
95
+ return () => unsub();
96
+ }
97
+ }
98
+
99
+ // Handle color changes from tree
100
+ function handleColorChange(nodeId: string, color: string) {
101
+ // Extract tech:fband from path (format: "root:tech:fband")
102
+ const parts = nodeId.split(':');
103
+ if (parts.length >= 3) {
104
+ const tech = parts[1];
105
+ const fband = parts[2];
106
+ store.setTechBandColor(tech, fband, color);
107
+
108
+ // Rebuild tree to reflect new color
109
+ rebuildTree();
110
+ }
111
+ }
112
+
113
+ // Initialize on mount
114
+ onMount(() => {
115
+ rebuildTree();
116
+ });
117
+ </script>
118
+
119
+ <MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true} {initiallyCollapsed}>
120
+ {#if treeStore && $treeStore}
121
+ <div class="repeater-filter-tree">
122
+ <TreeView store={$treeStore!} showControls={false}>
123
+ {#snippet children({ node, state })}
124
+ <!-- Color picker for leaf nodes (tech:fband combinations) -->
125
+ {#if node.metadata?.color}
126
+ <input
127
+ type="color"
128
+ class="color-picker"
129
+ value={node.metadata.color}
130
+ oninput={(e) => handleColorChange(node.path, e.currentTarget.value)}
131
+ onclick={(e) => e.stopPropagation()}
132
+ title="Choose color for {node.label}"
133
+ />
134
+ {/if}
135
+ {/snippet}
136
+ </TreeView>
137
+
138
+ <div class="repeater-filter-stats">
139
+ <small class="text-muted">
140
+ Showing {store.filteredRepeaters.length} of {store.repeaters.length} repeaters
141
+ </small>
142
+ </div>
143
+ </div>
144
+ {:else}
145
+ <div class="text-muted text-center p-3" style="font-size: 0.875rem;">
146
+ Loading tree...
147
+ </div>
148
+ {/if}
149
+ </MapControl>
150
+
151
+ <style>
152
+ .repeater-filter-tree {
153
+ max-height: 400px;
154
+ overflow-y: auto;
155
+ }
156
+
157
+ .color-picker {
158
+ width: 24px;
159
+ height: 24px;
160
+ border: none;
161
+ border-radius: 3px;
162
+ cursor: pointer;
163
+ margin-left: 8px;
164
+ }
165
+
166
+ .repeater-filter-stats {
167
+ margin-top: 0.5rem;
168
+ padding-top: 0.5rem;
169
+ border-top: 1px solid var(--bs-border-color);
170
+ text-align: center;
171
+ }
172
+ </style>
@@ -0,0 +1,18 @@
1
+ import type { RepeaterStoreContext } from '../stores/repeaterStoreContext.svelte';
2
+ interface Props {
3
+ /** Repeater store context */
4
+ store: RepeaterStoreContext;
5
+ /** Control position */
6
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
7
+ /** Control title */
8
+ title?: string;
9
+ /** Optional header icon */
10
+ icon?: string;
11
+ /** Show icon when collapsed (default: true) */
12
+ iconOnlyWhenCollapsed?: boolean;
13
+ /** Initially collapsed? */
14
+ initiallyCollapsed?: boolean;
15
+ }
16
+ declare const RepeaterFilterControl: import("svelte").Component<Props, {}, "">;
17
+ type RepeaterFilterControl = ReturnType<typeof RepeaterFilterControl>;
18
+ export default RepeaterFilterControl;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Repeaters Feature - Public API exports
3
+ *
4
+ * Barrel export for all public-facing repeater functionality
5
+ */
6
+ export type { Repeater, RepeaterTreeNode } from './types';
7
+ export type { RepeaterStoreValue, RepeaterStoreContext } from './stores/repeaterStoreContext.svelte';
8
+ export { createRepeaterStoreContext } from './stores/repeaterStoreContext.svelte';
9
+ export { default as RepeatersLayer } from './layers/RepeatersLayer.svelte';
10
+ export { default as RepeaterLabelsLayer } from './layers/RepeaterLabelsLayer.svelte';
11
+ export { default as RepeaterFilterControl } from './controls/RepeaterFilterControl.svelte';
12
+ export { repeatersToGeoJSON } from './utils/repeaterGeoJSON';
13
+ export { buildRepeaterTree, getFilteredRepeaters } from './utils/repeaterTree';
14
+ export { REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX } from './constants/zIndex';
15
+ export { REPEATER_RADIUS_MULTIPLIER, getRepeaterRadiusMultiplier } from './constants/radiusMultipliers';
16
+ export { REPEATER_TECH_BAND_Z_ORDER, getRepeaterZOrder } from './constants/techBandZOrder';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Repeaters Feature - Public API exports
3
+ *
4
+ * Barrel export for all public-facing repeater functionality
5
+ */
6
+ // Store
7
+ export { createRepeaterStoreContext } from './stores/repeaterStoreContext.svelte';
8
+ // Layers
9
+ export { default as RepeatersLayer } from './layers/RepeatersLayer.svelte';
10
+ export { default as RepeaterLabelsLayer } from './layers/RepeaterLabelsLayer.svelte';
11
+ // Controls
12
+ export { default as RepeaterFilterControl } from './controls/RepeaterFilterControl.svelte';
13
+ // Utilities
14
+ export { repeatersToGeoJSON } from './utils/repeaterGeoJSON';
15
+ export { buildRepeaterTree, getFilteredRepeaters } from './utils/repeaterTree';
16
+ // Constants
17
+ export { REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX } from './constants/zIndex';
18
+ export { REPEATER_RADIUS_MULTIPLIER, getRepeaterRadiusMultiplier } from './constants/radiusMultipliers';
19
+ export { REPEATER_TECH_BAND_Z_ORDER, getRepeaterZOrder } from './constants/techBandZOrder';