@smartnet360/svelte-components 0.0.40 → 0.0.42

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 (31) hide show
  1. package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte +0 -5
  2. package/dist/apps/antenna-pattern/components/DbNotification.svelte +1 -1
  3. package/dist/apps/antenna-pattern/components/chart-engines/PolarBarChart.svelte +2 -0
  4. package/dist/apps/antenna-pattern/components/chart-engines/PolarBarChart.svelte.d.ts +1 -0
  5. package/dist/apps/site-check/helper.js +3 -0
  6. package/dist/core/Charts/ChartCard.svelte +9 -12
  7. package/dist/core/Charts/ChartCard.svelte.d.ts +5 -21
  8. package/dist/core/Charts/ChartComponent.svelte +2 -2
  9. package/dist/core/Charts/editor/ChartLayoutEditor.svelte +8 -8
  10. package/dist/core/Charts/editor/GridPreview.svelte +0 -3
  11. package/dist/core/Charts/editor/KPIPicker.svelte +6 -7
  12. package/dist/core/Charts/editor/KPIPicker.svelte.d.ts +4 -20
  13. package/dist/core/Charts/editor/PropertiesPanel.svelte +6 -3
  14. package/dist/core/Charts/editor/PropertiesPanel.svelte.d.ts +8 -18
  15. package/dist/core/Map/Map.svelte +312 -0
  16. package/dist/core/Map/Map.svelte.d.ts +230 -0
  17. package/dist/core/Map/index.d.ts +9 -0
  18. package/dist/core/Map/index.js +9 -0
  19. package/dist/core/Map/mapSettings.d.ts +147 -0
  20. package/dist/core/Map/mapSettings.js +226 -0
  21. package/dist/core/Map/mapStore.d.ts +73 -0
  22. package/dist/core/Map/mapStore.js +136 -0
  23. package/dist/core/Map/types.d.ts +72 -0
  24. package/dist/core/Map/types.js +32 -0
  25. package/dist/core/Settings/FieldRenderer.svelte +19 -15
  26. package/dist/core/Settings/FieldRenderer.svelte.d.ts +12 -25
  27. package/dist/core/Settings/Settings.svelte +48 -29
  28. package/dist/core/Settings/Settings.svelte.d.ts +26 -20
  29. package/dist/core/index.d.ts +1 -0
  30. package/dist/core/index.js +2 -0
  31. package/package.json +12 -9
@@ -139,11 +139,6 @@
139
139
  ant1MechanicalTilt = 0;
140
140
  ant2ElectricalTilt = 0;
141
141
  ant2MechanicalTilt = 0;
142
-
143
- // Update chart if initialized
144
- if (chartInitialized) {
145
- await updateChart();
146
- }
147
142
  }
148
143
 
149
144
  console.log('Data refreshed from settings modal');
@@ -6,7 +6,7 @@
6
6
  let visible = false;
7
7
  let message = '';
8
8
  let type: 'success' | 'error' | 'info' = 'info';
9
- let timer: number;
9
+ let timer: ReturnType<typeof setTimeout> | undefined;
10
10
 
11
11
  const unsubscribe = dataOperationStatus.subscribe(status => {
12
12
  if (!status.operation) return;
@@ -15,6 +15,7 @@
15
15
  selectedAntenna2?: Antenna | null;
16
16
  viewMode?: 'single' | 'compare';
17
17
  patternType?: 'horizontal' | 'vertical';
18
+ patternDisplayMode?: 'normalized' | 'gain-adjusted';
18
19
  ant1ElectricalTilt?: number;
19
20
  ant1MechanicalTilt?: number;
20
21
  ant2ElectricalTilt?: number;
@@ -27,6 +28,7 @@
27
28
  selectedAntenna2 = null,
28
29
  viewMode = 'single',
29
30
  patternType = 'vertical',
31
+ patternDisplayMode = 'normalized',
30
32
  ant1ElectricalTilt = 0,
31
33
  ant1MechanicalTilt = 0,
32
34
  ant2ElectricalTilt = 0,
@@ -4,6 +4,7 @@ interface Props {
4
4
  selectedAntenna2?: Antenna | null;
5
5
  viewMode?: 'single' | 'compare';
6
6
  patternType?: 'horizontal' | 'vertical';
7
+ patternDisplayMode?: 'normalized' | 'gain-adjusted';
7
8
  ant1ElectricalTilt?: number;
8
9
  ant1MechanicalTilt?: number;
9
10
  ant2ElectricalTilt?: number;
@@ -21,6 +21,9 @@ export function expandLayoutForCells(baseLayout, data, stylingConfig) {
21
21
  // Deep clone the layout structure and expand KPIs
22
22
  const expandedLayout = {
23
23
  layoutName: baseLayout.layoutName,
24
+ hoverMode: baseLayout.hoverMode, // Preserve hover mode from base layout
25
+ coloredHover: baseLayout.coloredHover, // Preserve colored hover setting
26
+ movingAverage: baseLayout.movingAverage, // Preserve moving average config
24
27
  sections: baseLayout.sections.map((section) => ({
25
28
  ...section,
26
29
  charts: section.charts.map((chart) => ({
@@ -1,7 +1,7 @@
1
1
  <svelte:options runes={true} />
2
2
 
3
3
  <script lang="ts">
4
- import { onMount, createEventDispatcher } from 'svelte';
4
+ import { onMount } from 'svelte';
5
5
  import Plotly from 'plotly.js-dist-min';
6
6
  import type { Chart as ChartModel, ChartMarker, MovingAverageConfig, HoverMode } from './charts.model.js';
7
7
  import { createTimeSeriesTraceWithMA, getYAxisTitle, createDefaultPlotlyLayout } from './data-utils.js';
@@ -9,15 +9,6 @@
9
9
  import { getKPIValues, type ProcessedChartData } from './data-processor.js';
10
10
  import { log } from '../logger';
11
11
 
12
- const dispatch = createEventDispatcher<{
13
- chartcontextmenu: {
14
- chart: ChartModel;
15
- sectionId?: string;
16
- clientX: number;
17
- clientY: number;
18
- };
19
- }>();
20
-
21
12
  interface Props {
22
13
  chart: ChartModel;
23
14
  processedData: ProcessedChartData; // Pre-processed KPI values and timestamps
@@ -33,9 +24,15 @@
33
24
  runtimeShowOriginal?: boolean; // Runtime control for showing original lines
34
25
  runtimeShowMarkers?: boolean; // Runtime control for showing markers (default: true)
35
26
  runtimeShowLegend?: boolean; // Runtime control for showing legend (default: true)
27
+ onchartcontextmenu?: (detail: {
28
+ chart: ChartModel;
29
+ sectionId?: string;
30
+ clientX: number;
31
+ clientY: number;
32
+ }) => void;
36
33
  }
37
34
 
38
- let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, layoutHoverMode, layoutColoredHover = true, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true }: Props = $props();
35
+ let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, layoutHoverMode, layoutColoredHover = true, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true, onchartcontextmenu }: Props = $props();
39
36
 
40
37
  // Chart container div and state
41
38
  let chartDiv: HTMLElement;
@@ -44,7 +41,7 @@
44
41
 
45
42
  function handleContextMenu(event: MouseEvent) {
46
43
  event.preventDefault();
47
- dispatch('chartcontextmenu', {
44
+ onchartcontextmenu?.({
48
45
  chart,
49
46
  sectionId,
50
47
  clientX: event.clientX,
@@ -15,29 +15,13 @@ interface Props {
15
15
  runtimeShowOriginal?: boolean;
16
16
  runtimeShowMarkers?: boolean;
17
17
  runtimeShowLegend?: boolean;
18
- }
19
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
20
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
21
- $$bindings?: Bindings;
22
- } & Exports;
23
- (internal: unknown, props: Props & {
24
- $$events?: Events;
25
- $$slots?: Slots;
26
- }): Exports & {
27
- $set?: any;
28
- $on?: any;
29
- };
30
- z_$$bindings?: Bindings;
31
- }
32
- declare const ChartCard: $$__sveltets_2_IsomorphicComponent<Props, {
33
- chartcontextmenu: CustomEvent<{
18
+ onchartcontextmenu?: (detail: {
34
19
  chart: ChartModel;
35
20
  sectionId?: string;
36
21
  clientX: number;
37
22
  clientY: number;
38
- }>;
39
- } & {
40
- [evt: string]: CustomEvent<any>;
41
- }, {}, {}, "">;
42
- type ChartCard = InstanceType<typeof ChartCard>;
23
+ }) => void;
24
+ }
25
+ declare const ChartCard: import("svelte").Component<Props, {}, "">;
26
+ type ChartCard = ReturnType<typeof ChartCard>;
43
27
  export default ChartCard;
@@ -333,7 +333,7 @@
333
333
  runtimeShowOriginal={globalControls.movingAverage?.showOriginal}
334
334
  runtimeShowMarkers={globalControls.markers?.enabled}
335
335
  runtimeShowLegend={globalControls.legend?.enabled}
336
- on:chartcontextmenu={(event) => handleChartContextMenu(event.detail, section)}
336
+ onchartcontextmenu={(detail) => handleChartContextMenu(detail, section)}
337
337
  />
338
338
  </div>
339
339
  {/each}
@@ -475,7 +475,7 @@
475
475
  runtimeShowOriginal={globalControls.movingAverage?.showOriginal}
476
476
  runtimeShowMarkers={globalControls.markers?.enabled}
477
477
  runtimeShowLegend={globalControls.legend?.enabled}
478
- on:chartcontextmenu={(event) => handleChartContextMenu(event.detail, activeZoom.section)}
478
+ onchartcontextmenu={(detail) => handleChartContextMenu(detail, activeZoom.section)}
479
479
  />
480
480
  </div>
481
481
  </div>
@@ -73,18 +73,18 @@
73
73
  input.click();
74
74
  }
75
75
 
76
- function handleOpenKPIPicker(event: CustomEvent<{ sectionId: string; chartIndex: number; side: 'yLeft' | 'yRight' }>) {
77
- kpiPickerContext = event.detail;
76
+ function handleOpenKPIPicker(detail: { sectionId: string; chartIndex: number; side: 'yLeft' | 'yRight' }) {
77
+ kpiPickerContext = detail;
78
78
  showKPIPicker = true;
79
79
  }
80
80
 
81
- function handleKPISelected(event: CustomEvent<any>) {
81
+ function handleKPISelected(kpi: KPI) {
82
82
  if (kpiPickerContext) {
83
83
  editorStore.addKPI(
84
84
  kpiPickerContext.sectionId,
85
85
  kpiPickerContext.chartIndex,
86
86
  kpiPickerContext.side,
87
- event.detail
87
+ kpi
88
88
  );
89
89
  }
90
90
  showKPIPicker = false;
@@ -172,12 +172,12 @@
172
172
 
173
173
  <!-- Center Panel: Grid Preview -->
174
174
  <div class="panel-center flex-grow-1 bg-light overflow-auto">
175
- <GridPreview on:openkpipicker={handleOpenKPIPicker} />
175
+ <GridPreview />
176
176
  </div>
177
177
 
178
178
  <!-- Right Panel: Properties -->
179
179
  <div class="panel-right border-start bg-white overflow-auto" style="width: 320px; min-width: 320px;">
180
- <PropertiesPanel on:openkpipicker={handleOpenKPIPicker} />
180
+ <PropertiesPanel onopenkpipicker={handleOpenKPIPicker} />
181
181
  </div>
182
182
  </div>
183
183
  {/if}
@@ -188,8 +188,8 @@
188
188
  <KPIPicker
189
189
  show={showKPIPicker}
190
190
  {availableKPIs}
191
- on:select={handleKPISelected}
192
- on:close={() => { showKPIPicker = false; kpiPickerContext = null; }}
191
+ onselect={handleKPISelected}
192
+ onclose={() => { showKPIPicker = false; kpiPickerContext = null; }}
193
193
  />
194
194
  {/if}
195
195
 
@@ -1,12 +1,9 @@
1
1
  <svelte:options runes={true} />
2
2
 
3
3
  <script lang="ts">
4
- import { createEventDispatcher } from 'svelte';
5
4
  import { editorStore, currentLayout, selection } from './editorState.js';
6
5
  import type { ChartGrid } from '../charts.model.js';
7
6
 
8
- const dispatch = createEventDispatcher();
9
-
10
7
  function getGridDimensions(grid: ChartGrid): { rows: number; cols: number } {
11
8
  const map: Record<ChartGrid, { rows: number; cols: number }> = {
12
9
  '2x2': { rows: 2, cols: 2 },
@@ -1,17 +1,16 @@
1
1
  <svelte:options runes={true} />
2
2
 
3
3
  <script lang="ts">
4
- import { createEventDispatcher } from 'svelte';
5
4
  import type { KPI } from '../charts.model.js';
6
5
 
7
6
  interface Props {
8
7
  show: boolean;
9
8
  availableKPIs: KPI[];
9
+ onselect?: (kpi: KPI) => void;
10
+ onclose?: () => void;
10
11
  }
11
12
 
12
- let { show, availableKPIs }: Props = $props();
13
-
14
- const dispatch = createEventDispatcher();
13
+ let { show, availableKPIs, onselect, onclose }: Props = $props();
15
14
 
16
15
  let searchQuery = $state('');
17
16
  let selectedKPI = $state<KPI | null>(null);
@@ -36,7 +35,7 @@
36
35
 
37
36
  function handleConfirm() {
38
37
  if (selectedKPI) {
39
- dispatch('select', selectedKPI);
38
+ onselect?.(selectedKPI);
40
39
  handleClose();
41
40
  }
42
41
  }
@@ -44,7 +43,7 @@
44
43
  function handleClose() {
45
44
  searchQuery = '';
46
45
  selectedKPI = null;
47
- dispatch('close');
46
+ onclose?.();
48
47
  }
49
48
 
50
49
  function handleKeydown(e: KeyboardEvent) {
@@ -56,7 +55,7 @@
56
55
  }
57
56
  </script>
58
57
 
59
- <svelte:window on:keydown={handleKeydown} />
58
+ <svelte:window onkeydown={handleKeydown} />
60
59
 
61
60
  {#if show}
62
61
  <!-- Bootstrap Modal -->
@@ -2,25 +2,9 @@ import type { KPI } from '../charts.model.js';
2
2
  interface Props {
3
3
  show: boolean;
4
4
  availableKPIs: KPI[];
5
+ onselect?: (kpi: KPI) => void;
6
+ onclose?: () => void;
5
7
  }
6
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
7
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
8
- $$bindings?: Bindings;
9
- } & Exports;
10
- (internal: unknown, props: Props & {
11
- $$events?: Events;
12
- $$slots?: Slots;
13
- }): Exports & {
14
- $set?: any;
15
- $on?: any;
16
- };
17
- z_$$bindings?: Bindings;
18
- }
19
- declare const KpiPicker: $$__sveltets_2_IsomorphicComponent<Props, {
20
- select: CustomEvent<any>;
21
- close: CustomEvent<any>;
22
- } & {
23
- [evt: string]: CustomEvent<any>;
24
- }, {}, {}, "">;
25
- type KpiPicker = InstanceType<typeof KpiPicker>;
8
+ declare const KpiPicker: import("svelte").Component<Props, {}, "">;
9
+ type KpiPicker = ReturnType<typeof KpiPicker>;
26
10
  export default KpiPicker;
@@ -1,17 +1,20 @@
1
1
  <svelte:options runes={true} />
2
2
 
3
3
  <script lang="ts">
4
- import { createEventDispatcher } from 'svelte';
5
4
  import { editorStore, currentLayout, selection, selectedItem } from './editorState.js';
6
5
  import type { ChartGrid, Section, Chart } from '../charts.model.js';
7
6
 
8
- const dispatch = createEventDispatcher();
7
+ interface Props {
8
+ onopenkpipicker?: (detail: { sectionId: string; chartIndex: number; side: 'yLeft' | 'yRight' }) => void;
9
+ }
10
+
11
+ let { onopenkpipicker }: Props = $props();
9
12
 
10
13
  const gridOptions: ChartGrid[] = ['2x2', '3x3', '4x4', '1x2', '1x4', '1x8'];
11
14
 
12
15
  function handleOpenKPIPicker(side: 'yLeft' | 'yRight') {
13
16
  if ($selection.type === 'chart' && $selection.sectionId && $selection.chartIndex !== undefined) {
14
- dispatch('openkpipicker', {
17
+ onopenkpipicker?.({
15
18
  sectionId: $selection.sectionId,
16
19
  chartIndex: $selection.chartIndex,
17
20
  side
@@ -1,20 +1,10 @@
1
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
- $$bindings?: Bindings;
4
- } & Exports;
5
- (internal: unknown, props: {
6
- $$events?: Events;
7
- $$slots?: Slots;
8
- }): Exports & {
9
- $set?: any;
10
- $on?: any;
11
- };
12
- z_$$bindings?: Bindings;
1
+ interface Props {
2
+ onopenkpipicker?: (detail: {
3
+ sectionId: string;
4
+ chartIndex: number;
5
+ side: 'yLeft' | 'yRight';
6
+ }) => void;
13
7
  }
14
- declare const PropertiesPanel: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
- openkpipicker: CustomEvent<any>;
16
- } & {
17
- [evt: string]: CustomEvent<any>;
18
- }, {}, {}, "">;
19
- type PropertiesPanel = InstanceType<typeof PropertiesPanel>;
8
+ declare const PropertiesPanel: import("svelte").Component<Props, {}, "">;
9
+ type PropertiesPanel = ReturnType<typeof PropertiesPanel>;
20
10
  export default PropertiesPanel;
@@ -0,0 +1,312 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import mapboxgl from 'mapbox-gl';
4
+ import { MapboxOverlay } from '@deck.gl/mapbox';
5
+ import type { Layer } from '@deck.gl/core';
6
+ import { createMapStore, type MapStore } from './mapStore';
7
+ import { createSettingsStore } from '../Settings/store';
8
+ import { mapSettingsSchema } from './mapSettings';
9
+ import { MAP_STYLES, DEFAULT_MAP_OPTIONS, type MapOptions } from './types';
10
+
11
+ interface Props {
12
+ accessToken: string;
13
+ initialOptions?: Partial<MapOptions>;
14
+ namespace?: string;
15
+ layers?: Layer[];
16
+ mapStore?: MapStore;
17
+ onMapLoaded?: (map: mapboxgl.Map) => void;
18
+ }
19
+
20
+ let {
21
+ accessToken,
22
+ initialOptions = {},
23
+ namespace = 'map',
24
+ layers = [],
25
+ mapStore = $bindable(),
26
+ onMapLoaded
27
+ }: Props = $props();
28
+
29
+ // Container element
30
+ let container = $state<HTMLDivElement>();
31
+ let map = $state<mapboxgl.Map>();
32
+ let overlay = $state<MapboxOverlay>();
33
+ let styleLoaded = $state(false);
34
+
35
+ // Create stores if not provided
36
+ if (!mapStore) {
37
+ mapStore = createMapStore();
38
+ }
39
+
40
+ // Create settings store
41
+ const settings = createSettingsStore(mapSettingsSchema, namespace);
42
+
43
+ // Reactive: Update layers when prop changes
44
+ $effect(() => {
45
+ if (mapStore && layers.length > 0) {
46
+ mapStore.setLayers(layers);
47
+ }
48
+ });
49
+
50
+ // Reactive: Update map style when settings change
51
+ $effect(() => {
52
+ if (!map || !styleLoaded || !$settings.appearance.style) return;
53
+
54
+ const styleUrl = MAP_STYLES[$settings.appearance.style as keyof typeof MAP_STYLES];
55
+ if (styleUrl && map.getStyle()?.name !== styleUrl) {
56
+ styleLoaded = false; // Mark as not loaded during transition
57
+ map.setStyle(styleUrl);
58
+ }
59
+ });
60
+
61
+ // Reactive: Update interaction handlers
62
+ $effect(() => {
63
+ const currentMap = map;
64
+ if (!currentMap) return;
65
+
66
+ const handlers = [
67
+ { name: 'dragRotate', value: $settings.interaction.dragRotate },
68
+ { name: 'touchZoomRotate', value: $settings.interaction.touchZoomRotate },
69
+ { name: 'scrollZoom', value: $settings.interaction.scrollZoom },
70
+ { name: 'doubleClickZoom', value: $settings.interaction.doubleClickZoom },
71
+ { name: 'dragPan', value: $settings.interaction.dragPan }
72
+ ];
73
+
74
+ handlers.forEach(({ name, value }) => {
75
+ const handler = currentMap[name as keyof mapboxgl.Map] as any;
76
+ if (handler) {
77
+ value ? handler.enable() : handler.disable();
78
+ }
79
+ });
80
+ });
81
+
82
+ // Reactive: Update pitch and bearing
83
+ $effect(() => {
84
+ if (!map || !styleLoaded) return;
85
+
86
+ map.setPitch($settings.view.pitch as number);
87
+ map.setBearing($settings.view.bearing as number);
88
+ });
89
+
90
+ // Reactive: Toggle 3D buildings
91
+ $effect(() => {
92
+ if (!map || !styleLoaded) return;
93
+
94
+ if ($settings.appearance.show3dBuildings) {
95
+ add3dBuildings();
96
+ } else if (map.getLayer('3d-buildings')) {
97
+ map.removeLayer('3d-buildings');
98
+ }
99
+ });
100
+
101
+ // Reactive: Toggle terrain
102
+ $effect(() => {
103
+ if (!map || !styleLoaded) return;
104
+
105
+ if ($settings.appearance.showTerrain) {
106
+ addTerrain();
107
+ } else if (map.getTerrain()) {
108
+ map.setTerrain(null);
109
+ }
110
+ });
111
+
112
+ // One-time initialization using onMount (recommended for async/DOM-dependent setup)
113
+ onMount(() => {
114
+ if (!container) return;
115
+
116
+ // Set Mapbox access token
117
+ mapboxgl.accessToken = accessToken;
118
+
119
+ // Merge default options with initial options and settings
120
+ const options: MapOptions = {
121
+ ...DEFAULT_MAP_OPTIONS,
122
+ ...initialOptions,
123
+ accessToken
124
+ };
125
+
126
+ // Initialize Mapbox map
127
+ map = new mapboxgl.Map({
128
+ container,
129
+ style: MAP_STYLES[$settings.appearance.style as keyof typeof MAP_STYLES] || options.style,
130
+ center: options.center,
131
+ zoom: options.zoom,
132
+ pitch: $settings.view.pitch as number,
133
+ bearing: $settings.view.bearing as number,
134
+ minZoom: $settings.view.minZoom as number,
135
+ maxZoom: $settings.view.maxZoom as number,
136
+ antialias: $settings.performance.antialias as boolean,
137
+ maxTileCacheSize: $settings.performance.maxTileCacheSize as number,
138
+ fadeDuration: $settings.performance.fadeDuration as number
139
+ });
140
+
141
+ // Add controls based on settings
142
+ if ($settings.controls.showNavigationControls) {
143
+ map.addControl(new mapboxgl.NavigationControl(), 'top-right');
144
+ }
145
+
146
+ if ($settings.controls.showScaleControl) {
147
+ map.addControl(new mapboxgl.ScaleControl(), 'bottom-left');
148
+ }
149
+
150
+ if ($settings.controls.showFullscreenControl) {
151
+ map.addControl(new mapboxgl.FullscreenControl(), 'top-right');
152
+ }
153
+
154
+ if ($settings.controls.showGeolocateControl) {
155
+ map.addControl(
156
+ new mapboxgl.GeolocateControl({
157
+ positionOptions: { enableHighAccuracy: true },
158
+ trackUserLocation: true,
159
+ showUserHeading: true
160
+ }),
161
+ 'top-right'
162
+ );
163
+ }
164
+
165
+ // Initialize Deck.GL overlay
166
+ overlay = new MapboxOverlay({
167
+ interleaved: true,
168
+ layers: layers
169
+ });
170
+
171
+ map.addControl(overlay as any);
172
+
173
+ // Store map and overlay instances
174
+ if (mapStore) {
175
+ mapStore.setMap(map);
176
+ mapStore.setOverlay(overlay);
177
+ mapStore.setLayers(layers);
178
+ }
179
+
180
+ // Map load event
181
+ map.on('load', () => {
182
+ if (!map) return;
183
+
184
+ styleLoaded = true;
185
+
186
+ if (mapStore) {
187
+ mapStore.setLoaded(true);
188
+ }
189
+
190
+ // Add 3D buildings if enabled
191
+ if ($settings.appearance.show3dBuildings) {
192
+ add3dBuildings();
193
+ }
194
+
195
+ // Add terrain if enabled
196
+ if ($settings.appearance.showTerrain) {
197
+ addTerrain();
198
+ }
199
+
200
+ // Call user callback
201
+ if (onMapLoaded) {
202
+ onMapLoaded(map);
203
+ }
204
+ });
205
+
206
+ // Track style loading for dynamic style changes
207
+ map.on('style.load', () => {
208
+ styleLoaded = true;
209
+ });
210
+
211
+ // Track view state changes
212
+ map.on('move', () => {
213
+ if (!map || !mapStore) return;
214
+
215
+ mapStore.updateViewState({
216
+ center: map.getCenter().toArray() as [number, number],
217
+ zoom: map.getZoom(),
218
+ pitch: map.getPitch(),
219
+ bearing: map.getBearing()
220
+ });
221
+ });
222
+
223
+ // Cleanup on destroy
224
+ return () => {
225
+ if (map) {
226
+ map.remove();
227
+ }
228
+ };
229
+ });
230
+
231
+ function add3dBuildings() {
232
+ if (!map) return;
233
+ if (!map.getLayer('3d-buildings')) {
234
+ const mapLayers = map.getStyle().layers;
235
+ const labelLayerId = mapLayers?.find(
236
+ (layer) => layer.type === 'symbol' && layer.layout?.['text-field']
237
+ )?.id;
238
+
239
+ map.addLayer(
240
+ {
241
+ id: '3d-buildings',
242
+ source: 'composite',
243
+ 'source-layer': 'building',
244
+ filter: ['==', 'extrude', 'true'],
245
+ type: 'fill-extrusion',
246
+ minzoom: 15,
247
+ paint: {
248
+ 'fill-extrusion-color': '#aaa',
249
+ 'fill-extrusion-height': [
250
+ 'interpolate',
251
+ ['linear'],
252
+ ['zoom'],
253
+ 15,
254
+ 0,
255
+ 15.05,
256
+ ['get', 'height']
257
+ ],
258
+ 'fill-extrusion-base': [
259
+ 'interpolate',
260
+ ['linear'],
261
+ ['zoom'],
262
+ 15,
263
+ 0,
264
+ 15.05,
265
+ ['get', 'min_height']
266
+ ],
267
+ 'fill-extrusion-opacity': 0.6
268
+ }
269
+ },
270
+ labelLayerId
271
+ );
272
+ }
273
+ }
274
+
275
+ function addTerrain() {
276
+ if (!map) return;
277
+ if (!map.getSource('mapbox-dem')) {
278
+ map.addSource('mapbox-dem', {
279
+ type: 'raster-dem',
280
+ url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
281
+ tileSize: 512,
282
+ maxzoom: 14
283
+ });
284
+
285
+ map.setTerrain({ source: 'mapbox-dem', exaggeration: 1.5 });
286
+ }
287
+ }
288
+
289
+ // Export settings store for external access
290
+ export { settings };
291
+ </script>
292
+
293
+
294
+
295
+ <div class="map-container">
296
+ <div bind:this={container} class="map"></div>
297
+ </div>
298
+
299
+ <style>
300
+ .map-container {
301
+ width: 100%;
302
+ height: 100%;
303
+ position: relative;
304
+ }
305
+
306
+ .map {
307
+ width: 100%;
308
+ height: 100%;
309
+ }
310
+
311
+ /* Import Mapbox CSS - This should be in your app's global CSS */
312
+ </style>