@smartnet360/svelte-components 0.0.57 → 0.0.58

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 (44) hide show
  1. package/dist/map-v2/core/components/ViewportSync.svelte +79 -0
  2. package/dist/map-v2/core/components/ViewportSync.svelte.d.ts +8 -0
  3. package/dist/map-v2/core/index.d.ts +2 -0
  4. package/dist/map-v2/core/index.js +3 -0
  5. package/dist/map-v2/core/stores/viewportStore.svelte.d.ts +51 -0
  6. package/dist/map-v2/core/stores/viewportStore.svelte.js +120 -0
  7. package/dist/map-v2/demo/DemoMap.svelte +30 -3
  8. package/dist/map-v2/demo/demo-cells.d.ts +12 -0
  9. package/dist/map-v2/demo/demo-cells.js +114 -0
  10. package/dist/map-v2/demo/index.d.ts +1 -0
  11. package/dist/map-v2/demo/index.js +1 -0
  12. package/dist/map-v2/features/cells/constants/colors.d.ts +7 -0
  13. package/dist/map-v2/features/cells/constants/colors.js +21 -0
  14. package/dist/map-v2/features/cells/constants/radiusMultipliers.d.ts +8 -0
  15. package/dist/map-v2/features/cells/constants/radiusMultipliers.js +22 -0
  16. package/dist/map-v2/features/cells/constants/statusStyles.d.ts +7 -0
  17. package/dist/map-v2/features/cells/constants/statusStyles.js +49 -0
  18. package/dist/map-v2/features/cells/constants/zIndex.d.ts +14 -0
  19. package/dist/map-v2/features/cells/constants/zIndex.js +28 -0
  20. package/dist/map-v2/features/cells/controls/CellFilterControl.svelte +242 -0
  21. package/dist/map-v2/features/cells/controls/CellFilterControl.svelte.d.ts +14 -0
  22. package/dist/map-v2/features/cells/controls/CellStyleControl.svelte +139 -0
  23. package/dist/map-v2/features/cells/controls/CellStyleControl.svelte.d.ts +14 -0
  24. package/dist/map-v2/features/cells/index.d.ts +20 -0
  25. package/dist/map-v2/features/cells/index.js +24 -0
  26. package/dist/map-v2/features/cells/layers/CellsLayer.svelte +195 -0
  27. package/dist/map-v2/features/cells/layers/CellsLayer.svelte.d.ts +10 -0
  28. package/dist/map-v2/features/cells/stores/cellStoreContext.svelte.d.ts +46 -0
  29. package/dist/map-v2/features/cells/stores/cellStoreContext.svelte.js +137 -0
  30. package/dist/map-v2/features/cells/types.d.ts +99 -0
  31. package/dist/map-v2/features/cells/types.js +12 -0
  32. package/dist/map-v2/features/cells/utils/arcGeometry.d.ts +36 -0
  33. package/dist/map-v2/features/cells/utils/arcGeometry.js +55 -0
  34. package/dist/map-v2/features/cells/utils/cellGeoJSON.d.ts +22 -0
  35. package/dist/map-v2/features/cells/utils/cellGeoJSON.js +81 -0
  36. package/dist/map-v2/features/cells/utils/cellTree.d.ts +25 -0
  37. package/dist/map-v2/features/cells/utils/cellTree.js +226 -0
  38. package/dist/map-v2/features/cells/utils/techBandParser.d.ts +11 -0
  39. package/dist/map-v2/features/cells/utils/techBandParser.js +17 -0
  40. package/dist/map-v2/features/cells/utils/zoomScaling.d.ts +42 -0
  41. package/dist/map-v2/features/cells/utils/zoomScaling.js +53 -0
  42. package/dist/map-v2/index.d.ts +3 -2
  43. package/dist/map-v2/index.js +6 -2
  44. package/package.json +1 -1
@@ -0,0 +1,242 @@
1
+ <script lang="ts">
2
+ /**
3
+ * CellFilterControl - Dynamic hierarchical filter control for cells
4
+ *
5
+ * Features:
6
+ * - Level1 & Level2 grouping dropdown selectors
7
+ * - Dynamic tree rebuild on grouping config change
8
+ * - TreeView with checkboxes for filtering
9
+ * - Color pickers on leaf nodes (group-level customization)
10
+ * - Persists tree state and grouping config to localStorage
11
+ */
12
+
13
+ import { onMount } from 'svelte';
14
+ import type { Writable } from 'svelte/store';
15
+ import MapControl from '../../../shared/controls/MapControl.svelte';
16
+ import TreeView from '../../../../core/TreeView/TreeView.svelte';
17
+ import { createTreeStore } from '../../../../core/TreeView/tree.store';
18
+ import { buildCellTree, getFilteredCells } from '../utils/cellTree';
19
+ import type { CellStoreContext } from '../stores/cellStoreContext.svelte';
20
+ import type { CellGroupingField } from '../types';
21
+ import type { TreeStoreValue } from '../../../../core/TreeView/tree.model';
22
+
23
+ interface Props {
24
+ /** Cell store context */
25
+ store: CellStoreContext;
26
+ /** Control position */
27
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
28
+ /** Control title */
29
+ title?: string;
30
+ /** Initially collapsed? */
31
+ initiallyCollapsed?: boolean;
32
+ }
33
+
34
+ let {
35
+ store,
36
+ position = 'top-left',
37
+ title = 'Cell Filter',
38
+ initiallyCollapsed = false
39
+ }: Props = $props();
40
+
41
+ // Grouping options (excluding 'none' from level1)
42
+ const GROUPING_OPTIONS: Exclude<CellGroupingField, 'none'>[] = [
43
+ 'tech',
44
+ 'band',
45
+ 'status',
46
+ 'siteId',
47
+ 'customSubgroup',
48
+ 'type',
49
+ 'planner'
50
+ ];
51
+
52
+ // Level2 options include 'none' for flat tree
53
+ const LEVEL2_OPTIONS: CellGroupingField[] = ['none', ...GROUPING_OPTIONS];
54
+
55
+ let treeStore = $state<Writable<TreeStoreValue> | null>(null);
56
+ let level1 = $state<Exclude<CellGroupingField, 'none'>>(store.groupingConfig.level1);
57
+ let level2 = $state<CellGroupingField>(store.groupingConfig.level2);
58
+
59
+ // Rebuild tree when grouping config or cells change
60
+ function rebuildTree() {
61
+ if (store.cells.length === 0) return;
62
+
63
+ console.log('CellFilterControl: Building tree with config:', { level1, level2 });
64
+
65
+ const treeNodes = buildCellTree(
66
+ store.cells,
67
+ { level1, level2 },
68
+ store.groupColorMap
69
+ );
70
+
71
+ // Create or recreate tree store
72
+ treeStore = createTreeStore({
73
+ nodes: [treeNodes],
74
+ namespace: 'cellular-cell-filter',
75
+ persistState: true,
76
+ defaultExpandAll: false
77
+ });
78
+
79
+ console.log('CellFilterControl: Tree store created');
80
+
81
+ // Subscribe to tree changes and update filtered cells
82
+ if (treeStore) {
83
+ const unsub = treeStore.subscribe((treeValue: TreeStoreValue) => {
84
+ const checkedPaths = treeValue.getCheckedPaths();
85
+ console.log('TreeStore updated, checked paths:', checkedPaths.length);
86
+
87
+ // Convert string[] to Set<string>
88
+ const checkedPathsSet = new Set(checkedPaths);
89
+ const newFilteredCells = getFilteredCells(checkedPathsSet, store.cells);
90
+ console.log('Filtered cells count:', newFilteredCells.length, 'of', store.cells.length);
91
+
92
+ // Update the cell store directly
93
+ store.setFilteredCells(newFilteredCells);
94
+ });
95
+
96
+ return () => unsub();
97
+ }
98
+ }
99
+
100
+ onMount(() => {
101
+ console.log('CellFilterControl: Mounted with', store.cells.length, 'cells');
102
+ rebuildTree();
103
+ });
104
+
105
+ // Handle grouping config changes
106
+ function handleLevel1Change(event: Event) {
107
+ const target = event.currentTarget as HTMLSelectElement;
108
+ level1 = target.value as Exclude<CellGroupingField, 'none'>;
109
+
110
+ // Update store config
111
+ store.setGroupingConfig({ level1, level2 });
112
+
113
+ // Rebuild tree
114
+ rebuildTree();
115
+ }
116
+
117
+ function handleLevel2Change(event: Event) {
118
+ const target = event.currentTarget as HTMLSelectElement;
119
+ level2 = target.value as CellGroupingField;
120
+
121
+ // Update store config
122
+ store.setGroupingConfig({ level1, level2 });
123
+
124
+ // Rebuild tree
125
+ rebuildTree();
126
+ }
127
+
128
+ function handleColorChange(groupKey: string, color: string) {
129
+ store.setGroupColor(groupKey, color);
130
+ }
131
+
132
+ // Label mappings for display
133
+ const FIELD_LABELS: Record<CellGroupingField | 'none', string> = {
134
+ tech: 'Technology',
135
+ band: 'Frequency Band',
136
+ status: 'Status',
137
+ siteId: 'Site ID',
138
+ customSubgroup: 'Custom Subgroup',
139
+ type: 'Type',
140
+ planner: 'Planner',
141
+ none: 'None (2-Level Tree)'
142
+ };
143
+ </script>
144
+
145
+ <MapControl {position} {title} collapsible={true} {initiallyCollapsed}>
146
+ <div class="cell-filter-control">
147
+ <!-- Grouping Configuration -->
148
+ <div class="grouping-config">
149
+ <div class="mb-2">
150
+ <label for="level1-select" class="form-label small mb-1">Level 1 Grouping</label>
151
+ <select
152
+ id="level1-select"
153
+ class="form-select form-select-sm"
154
+ value={level1}
155
+ onchange={handleLevel1Change}
156
+ >
157
+ {#each GROUPING_OPTIONS as option}
158
+ <option value={option}>{FIELD_LABELS[option]}</option>
159
+ {/each}
160
+ </select>
161
+ </div>
162
+
163
+ <div class="mb-3">
164
+ <label for="level2-select" class="form-label small mb-1">Level 2 Grouping</label>
165
+ <select
166
+ id="level2-select"
167
+ class="form-select form-select-sm"
168
+ value={level2}
169
+ onchange={handleLevel2Change}
170
+ >
171
+ {#each LEVEL2_OPTIONS as option}
172
+ <option value={option}>{FIELD_LABELS[option]}</option>
173
+ {/each}
174
+ </select>
175
+ </div>
176
+ </div>
177
+
178
+ <!-- Tree View -->
179
+ {#if treeStore && $treeStore}
180
+ <div class="cell-filter-tree">
181
+ <TreeView store={$treeStore} showControls={false}>
182
+ {#snippet children({ node, state })}
183
+ <!-- Custom node rendering with color picker for leaf nodes -->
184
+ {#if node.metadata?.isLeafGroup}
185
+ <input
186
+ type="color"
187
+ class="color-picker"
188
+ value={node.metadata.color || '#888888'}
189
+ oninput={(e) => handleColorChange(node.id, e.currentTarget.value)}
190
+ onclick={(e) => e.stopPropagation()}
191
+ title="Choose color for this cell group"
192
+ />
193
+ {/if}
194
+ {/snippet}
195
+ </TreeView>
196
+
197
+ <div class="cell-filter-stats">
198
+ <small class="text-muted">
199
+ Showing {store.filteredCells.length} of {store.cells.length} cells
200
+ </small>
201
+ </div>
202
+ </div>
203
+ {:else}
204
+ <div class="text-muted small">Loading cells...</div>
205
+ {/if}
206
+ </div>
207
+ </MapControl>
208
+
209
+ <style>
210
+ .cell-filter-control {
211
+ min-width: 280px;
212
+ max-width: 320px;
213
+ }
214
+
215
+ .grouping-config {
216
+ padding-bottom: 0.5rem;
217
+ border-bottom: 1px solid #dee2e6;
218
+ margin-bottom: 0.75rem;
219
+ }
220
+
221
+ .cell-filter-tree {
222
+ max-height: 400px;
223
+ overflow-y: auto;
224
+ }
225
+
226
+ .cell-filter-stats {
227
+ margin-top: 8px;
228
+ padding-top: 8px;
229
+ border-top: 1px solid #dee2e6;
230
+ text-align: center;
231
+ }
232
+
233
+ .color-picker {
234
+ width: 24px;
235
+ height: 24px;
236
+ border: 1px solid #ccc;
237
+ border-radius: 4px;
238
+ cursor: pointer;
239
+ margin-left: 8px;
240
+ vertical-align: middle;
241
+ }
242
+ </style>
@@ -0,0 +1,14 @@
1
+ import type { CellStoreContext } from '../stores/cellStoreContext.svelte';
2
+ interface Props {
3
+ /** Cell store context */
4
+ store: CellStoreContext;
5
+ /** Control position */
6
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
7
+ /** Control title */
8
+ title?: string;
9
+ /** Initially collapsed? */
10
+ initiallyCollapsed?: boolean;
11
+ }
12
+ declare const CellFilterControl: import("svelte").Component<Props, {}, "">;
13
+ type CellFilterControl = ReturnType<typeof CellFilterControl>;
14
+ export default CellFilterControl;
@@ -0,0 +1,139 @@
1
+ <script lang="ts">
2
+ /**
3
+ * CellStyleControl - Visual settings control for cells
4
+ *
5
+ * Sliders for:
6
+ * - Base radius
7
+ * - Line width
8
+ * - Fill opacity
9
+ * - Show/hide cells
10
+ */
11
+
12
+ import MapControl from '../../../shared/controls/MapControl.svelte';
13
+ import type { CellStoreContext } from '../stores/cellStoreContext.svelte';
14
+
15
+ interface Props {
16
+ /** Cell store context */
17
+ store: CellStoreContext;
18
+ /** Control position */
19
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
20
+ /** Control title */
21
+ title?: string;
22
+ /** Initially collapsed? */
23
+ initiallyCollapsed?: boolean;
24
+ }
25
+
26
+ let {
27
+ store,
28
+ position = 'bottom-left',
29
+ title = 'Cell Display',
30
+ initiallyCollapsed = false
31
+ }: Props = $props();
32
+ </script>
33
+
34
+ <MapControl {position} {title} collapsible={true} {initiallyCollapsed}>
35
+ <div class="cell-style-controls">
36
+ <!-- Show cells toggle -->
37
+ <div class="control-row">
38
+ <label for="cell-show-toggle" class="control-label">
39
+ Show Cells
40
+ </label>
41
+ <div class="form-check form-switch">
42
+ <input
43
+ id="cell-show-toggle"
44
+ type="checkbox"
45
+ class="form-check-input"
46
+ role="switch"
47
+ bind:checked={store.showCells}
48
+ />
49
+ </div>
50
+ </div>
51
+
52
+ <!-- Base radius slider -->
53
+ <div class="control-row">
54
+ <label for="cell-radius-slider" class="control-label">
55
+ Base Radius: <strong>{store.baseRadius}m</strong>
56
+ </label>
57
+ <input
58
+ id="cell-radius-slider"
59
+ type="range"
60
+ class="form-range"
61
+ min="100"
62
+ max="2000"
63
+ step="50"
64
+ bind:value={store.baseRadius}
65
+ />
66
+ </div>
67
+
68
+ <!-- Line width slider -->
69
+ <div class="control-row">
70
+ <label for="cell-line-width-slider" class="control-label">
71
+ Line Width: <strong>{store.lineWidth}px</strong>
72
+ </label>
73
+ <input
74
+ id="cell-line-width-slider"
75
+ type="range"
76
+ class="form-range"
77
+ min="1"
78
+ max="5"
79
+ step="0.5"
80
+ bind:value={store.lineWidth}
81
+ />
82
+ </div>
83
+
84
+ <!-- Fill opacity slider -->
85
+ <div class="control-row">
86
+ <label for="cell-opacity-slider" class="control-label">
87
+ Fill Opacity: <strong>{Math.round(store.fillOpacity * 100)}%</strong>
88
+ </label>
89
+ <input
90
+ id="cell-opacity-slider"
91
+ type="range"
92
+ class="form-range"
93
+ min="0"
94
+ max="1"
95
+ step="0.1"
96
+ bind:value={store.fillOpacity}
97
+ />
98
+ </div>
99
+ </div>
100
+ </MapControl>
101
+
102
+ <style>
103
+ .cell-style-controls {
104
+ min-width: 250px;
105
+ padding: 0.5rem 0;
106
+ }
107
+
108
+ .control-row {
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: space-between;
112
+ gap: 1rem;
113
+ margin-bottom: 1rem;
114
+ }
115
+
116
+ .control-row:last-child {
117
+ margin-bottom: 0;
118
+ }
119
+
120
+ .control-label {
121
+ font-size: 0.875rem;
122
+ font-weight: 500;
123
+ margin: 0;
124
+ white-space: nowrap;
125
+ }
126
+
127
+ .form-range {
128
+ flex: 1;
129
+ min-width: 120px;
130
+ }
131
+
132
+ .form-check {
133
+ margin: 0;
134
+ }
135
+
136
+ .form-switch .form-check-input {
137
+ cursor: pointer;
138
+ }
139
+ </style>
@@ -0,0 +1,14 @@
1
+ import type { CellStoreContext } from '../stores/cellStoreContext.svelte';
2
+ interface Props {
3
+ /** Cell store context */
4
+ store: CellStoreContext;
5
+ /** Control position */
6
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
7
+ /** Control title */
8
+ title?: string;
9
+ /** Initially collapsed? */
10
+ initiallyCollapsed?: boolean;
11
+ }
12
+ declare const CellStyleControl: import("svelte").Component<Props, {}, "">;
13
+ type CellStyleControl = ReturnType<typeof CellStyleControl>;
14
+ export default CellStyleControl;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Cell Feature - Public API
3
+ *
4
+ * Exports all cell-related types, components, utilities, and constants
5
+ */
6
+ export type { Cell, CellStatus, CellStatusStyle, TechnologyBandKey, ParsedTechBand, CellGroupingField, CellTreeConfig } from './types';
7
+ export { DEFAULT_CELL_TREE_CONFIG } from './types';
8
+ export { TECHNOLOGY_BAND_COLORS } from './constants/colors';
9
+ export { RADIUS_MULTIPLIER } from './constants/radiusMultipliers';
10
+ export { Z_INDEX_BY_BAND, CELL_FILL_Z_INDEX, CELL_LINE_Z_INDEX } from './constants/zIndex';
11
+ export { DEFAULT_STATUS_STYLES } from './constants/statusStyles';
12
+ export { createCellStoreContext, type CellStoreContext, type CellStoreValue } from './stores/cellStoreContext.svelte';
13
+ export { default as CellsLayer } from './layers/CellsLayer.svelte';
14
+ export { default as CellFilterControl } from './controls/CellFilterControl.svelte';
15
+ export { default as CellStyleControl } from './controls/CellStyleControl.svelte';
16
+ export { parseTechBand } from './utils/techBandParser';
17
+ export { getZoomFactor, calculateRadius } from './utils/zoomScaling';
18
+ export { createArcPolygon } from './utils/arcGeometry';
19
+ export { buildCellTree, getFilteredCells } from './utils/cellTree';
20
+ export { cellsToGeoJSON } from './utils/cellGeoJSON';
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Cell Feature - Public API
3
+ *
4
+ * Exports all cell-related types, components, utilities, and constants
5
+ */
6
+ export { DEFAULT_CELL_TREE_CONFIG } from './types';
7
+ // Constants
8
+ export { TECHNOLOGY_BAND_COLORS } from './constants/colors';
9
+ export { RADIUS_MULTIPLIER } from './constants/radiusMultipliers';
10
+ export { Z_INDEX_BY_BAND, CELL_FILL_Z_INDEX, CELL_LINE_Z_INDEX } from './constants/zIndex';
11
+ export { DEFAULT_STATUS_STYLES } from './constants/statusStyles';
12
+ // Store
13
+ export { createCellStoreContext } from './stores/cellStoreContext.svelte';
14
+ // Layers
15
+ export { default as CellsLayer } from './layers/CellsLayer.svelte';
16
+ // Controls
17
+ export { default as CellFilterControl } from './controls/CellFilterControl.svelte';
18
+ export { default as CellStyleControl } from './controls/CellStyleControl.svelte';
19
+ // Utilities
20
+ export { parseTechBand } from './utils/techBandParser';
21
+ export { getZoomFactor, calculateRadius } from './utils/zoomScaling';
22
+ export { createArcPolygon } from './utils/arcGeometry';
23
+ export { buildCellTree, getFilteredCells } from './utils/cellTree';
24
+ export { cellsToGeoJSON } from './utils/cellGeoJSON';
@@ -0,0 +1,195 @@
1
+ <script lang="ts">
2
+ /**
3
+ * CellsLayer - Renders cell sectors as arc polygons
4
+ *
5
+ * Features:
6
+ * - Mapbox fill layer (colored arcs by tech-band)
7
+ * - Mapbox line layer (borders styled by status)
8
+ * - Zoom-reactive: Regenerates arcs on zoomend
9
+ * - Updates when filteredCells or store settings change
10
+ */
11
+
12
+ import { getContext, onDestroy, onMount } from 'svelte';
13
+ import type { Map as MapboxMap } from 'mapbox-gl';
14
+ import type { CellStoreContext } from '../stores/cellStoreContext.svelte';
15
+ import { useMapbox } from '../../../core/hooks/useMapbox';
16
+ import { cellsToGeoJSON } from '../utils/cellGeoJSON';
17
+ import { CELL_FILL_Z_INDEX, CELL_LINE_Z_INDEX } from '../constants/zIndex';
18
+
19
+ interface Props {
20
+ /** Cell store context */
21
+ store: CellStoreContext;
22
+ /** Unique namespace for layer IDs */
23
+ namespace: string;
24
+ }
25
+
26
+ let { store, namespace }: Props = $props();
27
+
28
+ const FILL_LAYER_ID = `${namespace}-cells-fill`;
29
+ const LINE_LAYER_ID = `${namespace}-cells-line`;
30
+ const SOURCE_ID = `${namespace}-cells`;
31
+
32
+ // Get map from mapbox hook
33
+ const mapStore = useMapbox();
34
+
35
+ let map = $state<MapboxMap | null>(null);
36
+ let mounted = $state(false);
37
+
38
+ // Zoom change handler
39
+ function handleZoomEnd() {
40
+ if (!map) return;
41
+ const newZoom = map.getZoom();
42
+ store.setCurrentZoom(newZoom);
43
+ }
44
+
45
+ onMount(() => {
46
+ console.log('CellsLayer: onMount, waiting for map...');
47
+
48
+ // Subscribe to map store
49
+ const unsubscribe = mapStore.subscribe((mapInstance) => {
50
+ if (mapInstance && !map) {
51
+ console.log('CellsLayer: Map available, initializing...');
52
+ map = mapInstance;
53
+ mounted = true;
54
+
55
+ // Set initial zoom
56
+ store.setCurrentZoom(mapInstance.getZoom());
57
+
58
+ // Listen to zoom changes
59
+ mapInstance.on('zoomend', handleZoomEnd);
60
+ }
61
+ });
62
+
63
+ return () => {
64
+ unsubscribe();
65
+ };
66
+ });
67
+
68
+ onDestroy(() => {
69
+ if (!map) return;
70
+
71
+ map.off('zoomend', handleZoomEnd);
72
+
73
+ // Clean up layers and source
74
+ if (map.getLayer(LINE_LAYER_ID)) {
75
+ map.removeLayer(LINE_LAYER_ID);
76
+ }
77
+ if (map.getLayer(FILL_LAYER_ID)) {
78
+ map.removeLayer(FILL_LAYER_ID);
79
+ }
80
+ if (map.getSource(SOURCE_ID)) {
81
+ map.removeSource(SOURCE_ID);
82
+ }
83
+ });
84
+
85
+ // Reactive: Update GeoJSON when cells/zoom/filters/settings change
86
+ $effect(() => {
87
+ console.log('CellsLayer $effect triggered:', {
88
+ mounted,
89
+ hasMap: !!map,
90
+ showCells: store.showCells,
91
+ filteredCellsCount: store.filteredCells.length,
92
+ currentZoom: store.currentZoom,
93
+ baseRadius: store.baseRadius
94
+ });
95
+
96
+ if (!mounted || !map || !store.showCells) {
97
+ // Remove layers if showCells is false
98
+ if (mounted && map) {
99
+ console.log('CellsLayer: Removing layers (showCells=false or not mounted)');
100
+ if (map.getLayer(LINE_LAYER_ID)) {
101
+ map.removeLayer(LINE_LAYER_ID);
102
+ }
103
+ if (map.getLayer(FILL_LAYER_ID)) {
104
+ map.removeLayer(FILL_LAYER_ID);
105
+ }
106
+ if (map.getSource(SOURCE_ID)) {
107
+ map.removeSource(SOURCE_ID);
108
+ }
109
+ }
110
+ return;
111
+ }
112
+
113
+ // Generate GeoJSON from filtered cells
114
+ const geoJSON = cellsToGeoJSON(
115
+ store.filteredCells,
116
+ store.currentZoom,
117
+ store.baseRadius,
118
+ store.groupColorMap
119
+ );
120
+
121
+ console.log('CellsLayer: Generated GeoJSON:', {
122
+ featureCount: geoJSON.features.length,
123
+ firstFeature: geoJSON.features[0]
124
+ });
125
+
126
+ // Update or create source
127
+ const source = map.getSource(SOURCE_ID);
128
+ if (source && source.type === 'geojson') {
129
+ console.log('CellsLayer: Updating existing source');
130
+ source.setData(geoJSON);
131
+ } else {
132
+ console.log('CellsLayer: Creating new source');
133
+ map.addSource(SOURCE_ID, {
134
+ type: 'geojson',
135
+ data: geoJSON
136
+ });
137
+ }
138
+
139
+ // Add fill layer if not exists
140
+ if (!map.getLayer(FILL_LAYER_ID)) {
141
+ console.log('CellsLayer: Creating fill layer');
142
+ map.addLayer({
143
+ id: FILL_LAYER_ID,
144
+ type: 'fill',
145
+ source: SOURCE_ID,
146
+ paint: {
147
+ 'fill-color': [
148
+ 'coalesce',
149
+ ['get', 'groupColor'],
150
+ ['get', 'techBandColor'],
151
+ '#888888' // Fallback
152
+ ],
153
+ 'fill-opacity': store.fillOpacity
154
+ },
155
+ metadata: {
156
+ zIndex: CELL_FILL_Z_INDEX
157
+ }
158
+ });
159
+ } else {
160
+ // Update fill opacity
161
+ console.log('CellsLayer: Updating fill opacity:', store.fillOpacity);
162
+ map.setPaintProperty(FILL_LAYER_ID, 'fill-opacity', store.fillOpacity);
163
+ }
164
+
165
+ // Add line layer if not exists
166
+ if (!map.getLayer(LINE_LAYER_ID)) {
167
+ console.log('CellsLayer: Creating line layer');
168
+ map.addLayer({
169
+ id: LINE_LAYER_ID,
170
+ type: 'line',
171
+ source: SOURCE_ID,
172
+ paint: {
173
+ 'line-color': ['get', 'lineColor'],
174
+ 'line-width': store.lineWidth,
175
+ 'line-opacity': ['get', 'lineOpacity'],
176
+ 'line-dasharray': [
177
+ 'case',
178
+ ['has', 'dashArray'],
179
+ ['get', 'dashArray'],
180
+ ['literal', [1, 0]] // Solid line default
181
+ ]
182
+ },
183
+ metadata: {
184
+ zIndex: CELL_LINE_Z_INDEX
185
+ }
186
+ });
187
+ } else {
188
+ // Update line width
189
+ console.log('CellsLayer: Updating line width:', store.lineWidth);
190
+ map.setPaintProperty(LINE_LAYER_ID, 'line-width', store.lineWidth);
191
+ }
192
+ });
193
+ </script>
194
+
195
+ <!-- This component doesn't render DOM, only Mapbox layers -->
@@ -0,0 +1,10 @@
1
+ import type { CellStoreContext } from '../stores/cellStoreContext.svelte';
2
+ interface Props {
3
+ /** Cell store context */
4
+ store: CellStoreContext;
5
+ /** Unique namespace for layer IDs */
6
+ namespace: string;
7
+ }
8
+ declare const CellsLayer: import("svelte").Component<Props, {}, "">;
9
+ type CellsLayer = ReturnType<typeof CellsLayer>;
10
+ export default CellsLayer;