@smartnet360/svelte-components 0.0.51 → 0.0.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apps/site-check/transforms.js +2 -2
- package/dist/core/Charts/ChartCard.svelte +6 -1
- package/dist/core/Charts/ChartComponent.svelte +8 -3
- package/dist/core/Charts/GlobalControls.svelte +116 -14
- package/dist/core/Charts/adapt.js +1 -1
- package/dist/core/Charts/charts.model.d.ts +3 -0
- package/dist/core/FeatureRegistry/index.js +1 -1
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/map/controls/MapControl.svelte +204 -0
- package/dist/map/controls/MapControl.svelte.d.ts +17 -0
- package/dist/map/controls/SiteFilterControl.svelte +126 -0
- package/dist/map/controls/SiteFilterControl.svelte.d.ts +16 -0
- package/dist/map/demo/DemoMap.svelte +98 -0
- package/dist/map/demo/DemoMap.svelte.d.ts +12 -0
- package/dist/map/demo/demo-data.d.ts +12 -0
- package/dist/map/demo/demo-data.js +220 -0
- package/dist/map/hooks/useCellData.d.ts +14 -0
- package/dist/map/hooks/useCellData.js +29 -0
- package/dist/map/hooks/useMapbox.d.ts +14 -0
- package/dist/map/hooks/useMapbox.js +29 -0
- package/dist/map/index.d.ts +27 -0
- package/dist/map/index.js +47 -0
- package/dist/map/layers/CellsLayer.svelte +242 -0
- package/dist/map/layers/CellsLayer.svelte.d.ts +21 -0
- package/dist/map/layers/CoverageLayer.svelte +37 -0
- package/dist/map/layers/CoverageLayer.svelte.d.ts +9 -0
- package/dist/map/layers/LayerBase.d.ts +42 -0
- package/dist/map/layers/LayerBase.js +58 -0
- package/dist/map/layers/SitesLayer.svelte +282 -0
- package/dist/map/layers/SitesLayer.svelte.d.ts +19 -0
- package/dist/map/providers/CellDataProvider.svelte +43 -0
- package/dist/map/providers/CellDataProvider.svelte.d.ts +12 -0
- package/dist/map/providers/MapboxProvider.svelte +38 -0
- package/dist/map/providers/MapboxProvider.svelte.d.ts +9 -0
- package/dist/map/providers/providerHelpers.d.ts +17 -0
- package/dist/map/providers/providerHelpers.js +26 -0
- package/dist/map/stores/cellDataStore.d.ts +21 -0
- package/dist/map/stores/cellDataStore.js +53 -0
- package/dist/map/stores/interactions.d.ts +20 -0
- package/dist/map/stores/interactions.js +33 -0
- package/dist/map/stores/mapStore.d.ts +8 -0
- package/dist/map/stores/mapStore.js +10 -0
- package/dist/map/types.d.ts +115 -0
- package/dist/map/types.js +10 -0
- package/dist/map/utils/geojson.d.ts +20 -0
- package/dist/map/utils/geojson.js +78 -0
- package/dist/map/utils/mapboxHelpers.d.ts +51 -0
- package/dist/map/utils/mapboxHelpers.js +98 -0
- package/dist/map/utils/math.d.ts +40 -0
- package/dist/map/utils/math.js +95 -0
- package/dist/map/utils/siteTreeUtils.d.ts +27 -0
- package/dist/map/utils/siteTreeUtils.js +164 -0
- package/package.json +1 -1
- package/dist/core/Map/Map.svelte +0 -312
- package/dist/core/Map/Map.svelte.d.ts +0 -230
- package/dist/core/Map/index.d.ts +0 -9
- package/dist/core/Map/index.js +0 -9
- package/dist/core/Map/mapSettings.d.ts +0 -147
- package/dist/core/Map/mapSettings.js +0 -226
- package/dist/core/Map/mapStore.d.ts +0 -73
- package/dist/core/Map/mapStore.js +0 -136
- package/dist/core/Map/types.d.ts +0 -72
- package/dist/core/Map/types.js +0 -32
|
@@ -229,7 +229,7 @@ export function buildTreeNodes(data, grouping = { level0: 'site', level1: 'azimu
|
|
|
229
229
|
sector: record.sector,
|
|
230
230
|
azimuth: record.azimuth
|
|
231
231
|
},
|
|
232
|
-
defaultChecked:
|
|
232
|
+
defaultChecked: false // Start unselected
|
|
233
233
|
};
|
|
234
234
|
level1Node.children.push(cellNode);
|
|
235
235
|
});
|
|
@@ -293,7 +293,7 @@ function build2LevelTree(data, grouping) {
|
|
|
293
293
|
sector: record.sector,
|
|
294
294
|
azimuth: record.azimuth
|
|
295
295
|
},
|
|
296
|
-
defaultChecked:
|
|
296
|
+
defaultChecked: false // Start unselected
|
|
297
297
|
};
|
|
298
298
|
level0Node.children.push(cellNode);
|
|
299
299
|
});
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { adaptPlotlyLayout, addMarkersToLayout, type ContainerSize } from './adapt.js';
|
|
9
9
|
import { getKPIValues, type ProcessedChartData } from './data-processor.js';
|
|
10
10
|
import { log } from '../logger';
|
|
11
|
+
import { checkHealth, getMessage } from '../FeatureRegistry';
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
13
14
|
chart: ChartModel;
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
let chartDiv: HTMLElement;
|
|
40
41
|
let containerSize = $state<ContainerSize>({ width: 0, height: 0 });
|
|
41
42
|
let chartInitialized = $state(false); // Track if chart has been created
|
|
43
|
+
let isHealthy = $state(checkHealth('charts'));
|
|
42
44
|
|
|
43
45
|
function handleContextMenu(event: MouseEvent) {
|
|
44
46
|
event.preventDefault();
|
|
@@ -268,6 +270,7 @@
|
|
|
268
270
|
}
|
|
269
271
|
|
|
270
272
|
onMount(() => {
|
|
273
|
+
|
|
271
274
|
log('📈 ChartCard mounted', {
|
|
272
275
|
chartTitle: chart.title,
|
|
273
276
|
leftKPIs: chart.yLeft.length,
|
|
@@ -286,7 +289,9 @@
|
|
|
286
289
|
height: rect.height
|
|
287
290
|
});
|
|
288
291
|
}
|
|
289
|
-
|
|
292
|
+
if(!isHealthy){
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
290
295
|
renderChart();
|
|
291
296
|
|
|
292
297
|
// Set up ResizeObserver with debouncing to prevent excessive re-renders
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import GlobalControls from './GlobalControls.svelte';
|
|
8
8
|
import { getPreprocessedData, type ProcessedChartData } from './data-processor.js';
|
|
9
9
|
import { log } from '../logger';
|
|
10
|
+
import { checkHealth, getMessage } from '../FeatureRegistry';
|
|
10
11
|
|
|
11
12
|
interface Props {
|
|
12
13
|
layout: Layout;
|
|
@@ -56,7 +57,7 @@
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
let { layout, data, mode, markers, plotlyLayout, enableAdaptation = true, showGlobalControls = true, persistSettings = false }: Props = $props();
|
|
59
|
-
|
|
60
|
+
let isHealthy = $state(checkHealth('charts'));
|
|
60
61
|
// Log component initialization
|
|
61
62
|
$effect(() => {
|
|
62
63
|
log('📊 ChartComponent initialized', {
|
|
@@ -89,6 +90,9 @@
|
|
|
89
90
|
},
|
|
90
91
|
hoverMode: {
|
|
91
92
|
mode: layout.hoverMode ?? 'x' // Default to 'x' if not specified
|
|
93
|
+
},
|
|
94
|
+
adaptive: {
|
|
95
|
+
enabled: enableAdaptation // Initialize from prop, always present
|
|
92
96
|
}
|
|
93
97
|
});
|
|
94
98
|
|
|
@@ -273,6 +277,7 @@
|
|
|
273
277
|
}
|
|
274
278
|
|
|
275
279
|
onMount(() => {
|
|
280
|
+
|
|
276
281
|
const handleKeydown = (event: KeyboardEvent) => {
|
|
277
282
|
if (event.key === 'Escape') {
|
|
278
283
|
if (zoomedChart) {
|
|
@@ -329,7 +334,7 @@
|
|
|
329
334
|
{processedData}
|
|
330
335
|
{markers}
|
|
331
336
|
{plotlyLayout}
|
|
332
|
-
{
|
|
337
|
+
enableAdaptation={globalControls.adaptive?.enabled ?? true}
|
|
333
338
|
sectionId={section.id}
|
|
334
339
|
sectionMovingAverage={section.movingAverage}
|
|
335
340
|
layoutMovingAverage={layout.movingAverage}
|
|
@@ -449,7 +454,7 @@
|
|
|
449
454
|
{processedData}
|
|
450
455
|
{markers}
|
|
451
456
|
{plotlyLayout}
|
|
452
|
-
{
|
|
457
|
+
enableAdaptation={globalControls.adaptive?.enabled ?? true}
|
|
453
458
|
sectionId={activeZoom.section.id}
|
|
454
459
|
sectionMovingAverage={activeZoom.section.movingAverage}
|
|
455
460
|
layoutMovingAverage={layout.movingAverage}
|
|
@@ -102,6 +102,18 @@
|
|
|
102
102
|
onUpdate(newControls);
|
|
103
103
|
saveSettings(newControls);
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
function updateAdaptive(updates: Partial<NonNullable<GlobalChartControls['adaptive']>>) {
|
|
107
|
+
const newControls = {
|
|
108
|
+
...controls,
|
|
109
|
+
adaptive: {
|
|
110
|
+
...controls.adaptive!,
|
|
111
|
+
...updates
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
onUpdate(newControls);
|
|
115
|
+
saveSettings(newControls);
|
|
116
|
+
}
|
|
105
117
|
</script>
|
|
106
118
|
|
|
107
119
|
<!-- Floating Controls Container -->
|
|
@@ -146,7 +158,11 @@
|
|
|
146
158
|
checked={controls.markers.enabled}
|
|
147
159
|
onchange={() => updateMarkers({ enabled: !controls.markers!.enabled })}
|
|
148
160
|
/>
|
|
149
|
-
<label
|
|
161
|
+
<label
|
|
162
|
+
class="btn btn-outline-primary btn-sm"
|
|
163
|
+
for="markersToggle"
|
|
164
|
+
title="Show or hide data point markers on charts"
|
|
165
|
+
>
|
|
150
166
|
Markers
|
|
151
167
|
</label>
|
|
152
168
|
</div>
|
|
@@ -162,17 +178,41 @@
|
|
|
162
178
|
checked={controls.legend.enabled}
|
|
163
179
|
onchange={() => updateLegend({ enabled: !controls.legend!.enabled })}
|
|
164
180
|
/>
|
|
165
|
-
<label
|
|
181
|
+
<label
|
|
182
|
+
class="btn btn-outline-primary btn-sm"
|
|
183
|
+
for="legendToggle"
|
|
184
|
+
title="Show or hide the chart legend with series names"
|
|
185
|
+
>
|
|
166
186
|
Legend
|
|
167
187
|
</label>
|
|
168
188
|
</div>
|
|
169
189
|
{/if}
|
|
190
|
+
|
|
191
|
+
<!-- Adaptive Toggle (Always Present - Not Optional) -->
|
|
192
|
+
<div class="control-group-inline">
|
|
193
|
+
<input
|
|
194
|
+
type="checkbox"
|
|
195
|
+
class="btn-check"
|
|
196
|
+
id="adaptiveToggle"
|
|
197
|
+
checked={controls.adaptive?.enabled ?? true}
|
|
198
|
+
onchange={() => updateAdaptive({ enabled: !(controls.adaptive?.enabled ?? true) })}
|
|
199
|
+
/>
|
|
200
|
+
<label
|
|
201
|
+
class="btn btn-outline-primary btn-sm"
|
|
202
|
+
for="adaptiveToggle"
|
|
203
|
+
title="Enable adaptive chart display based on chart size and data density"
|
|
204
|
+
>
|
|
205
|
+
Adaptive
|
|
206
|
+
</label>
|
|
207
|
+
</div>
|
|
170
208
|
</div>
|
|
171
209
|
|
|
172
210
|
<!-- Hover Mode Controls -->
|
|
173
211
|
{#if controls.hoverMode}
|
|
174
212
|
<div class="control-group">
|
|
175
|
-
<div class="control-label"
|
|
213
|
+
<div class="control-label" title="Control how hover tooltips appear when you move your mouse over the chart">
|
|
214
|
+
Hover Mode
|
|
215
|
+
</div>
|
|
176
216
|
<div class="btn-group btn-group-sm" role="group" aria-label="Hover Mode">
|
|
177
217
|
<input
|
|
178
218
|
type="radio"
|
|
@@ -182,7 +222,13 @@
|
|
|
182
222
|
checked={controls.hoverMode.mode === 'x'}
|
|
183
223
|
onchange={() => updateHoverMode({ mode: 'x' })}
|
|
184
224
|
/>
|
|
185
|
-
<label
|
|
225
|
+
<label
|
|
226
|
+
class="btn btn-outline-primary"
|
|
227
|
+
for="hoverModeX"
|
|
228
|
+
title="Show all data points at the same X-axis position"
|
|
229
|
+
>
|
|
230
|
+
X-Axis
|
|
231
|
+
</label>
|
|
186
232
|
|
|
187
233
|
<!-- <input
|
|
188
234
|
type="radio"
|
|
@@ -202,7 +248,13 @@
|
|
|
202
248
|
checked={controls.hoverMode.mode === 'closest'}
|
|
203
249
|
onchange={() => updateHoverMode({ mode: 'closest' })}
|
|
204
250
|
/>
|
|
205
|
-
<label
|
|
251
|
+
<label
|
|
252
|
+
class="btn btn-outline-primary"
|
|
253
|
+
for="hoverModeClosest"
|
|
254
|
+
title="Show only the single closest data point to your cursor"
|
|
255
|
+
>
|
|
256
|
+
Closest
|
|
257
|
+
</label>
|
|
206
258
|
|
|
207
259
|
<input
|
|
208
260
|
type="radio"
|
|
@@ -212,7 +264,13 @@
|
|
|
212
264
|
checked={controls.hoverMode.mode === 'x unified'}
|
|
213
265
|
onchange={() => updateHoverMode({ mode: 'x unified' })}
|
|
214
266
|
/>
|
|
215
|
-
<label
|
|
267
|
+
<label
|
|
268
|
+
class="btn btn-outline-primary"
|
|
269
|
+
for="hoverModeXUnified"
|
|
270
|
+
title="Show all data points at the same X position with a single unified tooltip"
|
|
271
|
+
>
|
|
272
|
+
X-Unified
|
|
273
|
+
</label>
|
|
216
274
|
|
|
217
275
|
<!-- <input
|
|
218
276
|
type="radio"
|
|
@@ -232,7 +290,13 @@
|
|
|
232
290
|
checked={controls.hoverMode.mode === false}
|
|
233
291
|
onchange={() => updateHoverMode({ mode: false })}
|
|
234
292
|
/>
|
|
235
|
-
<label
|
|
293
|
+
<label
|
|
294
|
+
class="btn btn-outline-primary"
|
|
295
|
+
for="hoverModeFalse"
|
|
296
|
+
title="Disable hover tooltips completely"
|
|
297
|
+
>
|
|
298
|
+
Off
|
|
299
|
+
</label>
|
|
236
300
|
</div>
|
|
237
301
|
</div>
|
|
238
302
|
{/if}
|
|
@@ -248,7 +312,11 @@
|
|
|
248
312
|
checked={controls.movingAverage.enabled}
|
|
249
313
|
onchange={() => updateMovingAverage({ enabled: !controls.movingAverage!.enabled })}
|
|
250
314
|
/>
|
|
251
|
-
<label
|
|
315
|
+
<label
|
|
316
|
+
class="btn btn-outline-primary btn-sm"
|
|
317
|
+
for="maToggle"
|
|
318
|
+
title="Apply smoothing to chart data using moving average calculations"
|
|
319
|
+
>
|
|
252
320
|
Moving Average
|
|
253
321
|
</label>
|
|
254
322
|
|
|
@@ -264,7 +332,13 @@
|
|
|
264
332
|
checked={controls.movingAverage.windowOverride === undefined}
|
|
265
333
|
onchange={() => updateMovingAverage({ windowOverride: undefined })}
|
|
266
334
|
/>
|
|
267
|
-
<label
|
|
335
|
+
<label
|
|
336
|
+
class="btn btn-outline-primary"
|
|
337
|
+
for="maWindowAuto"
|
|
338
|
+
title="Default size from settings"
|
|
339
|
+
>
|
|
340
|
+
Auto
|
|
341
|
+
</label>
|
|
268
342
|
|
|
269
343
|
<input
|
|
270
344
|
type="radio"
|
|
@@ -274,7 +348,13 @@
|
|
|
274
348
|
checked={controls.movingAverage.windowOverride === 7}
|
|
275
349
|
onchange={() => updateMovingAverage({ windowOverride: 7 })}
|
|
276
350
|
/>
|
|
277
|
-
<label
|
|
351
|
+
<label
|
|
352
|
+
class="btn btn-outline-primary"
|
|
353
|
+
for="maWindow7"
|
|
354
|
+
title="Use a 7-period moving average window"
|
|
355
|
+
>
|
|
356
|
+
7
|
|
357
|
+
</label>
|
|
278
358
|
|
|
279
359
|
<input
|
|
280
360
|
type="radio"
|
|
@@ -284,7 +364,13 @@
|
|
|
284
364
|
checked={controls.movingAverage.windowOverride === 14}
|
|
285
365
|
onchange={() => updateMovingAverage({ windowOverride: 14 })}
|
|
286
366
|
/>
|
|
287
|
-
<label
|
|
367
|
+
<label
|
|
368
|
+
class="btn btn-outline-primary"
|
|
369
|
+
for="maWindow14"
|
|
370
|
+
title="Use a 14-period moving average window"
|
|
371
|
+
>
|
|
372
|
+
14
|
|
373
|
+
</label>
|
|
288
374
|
|
|
289
375
|
<input
|
|
290
376
|
type="radio"
|
|
@@ -294,7 +380,13 @@
|
|
|
294
380
|
checked={controls.movingAverage.windowOverride === 24}
|
|
295
381
|
onchange={() => updateMovingAverage({ windowOverride: 24 })}
|
|
296
382
|
/>
|
|
297
|
-
<label
|
|
383
|
+
<label
|
|
384
|
+
class="btn btn-outline-primary"
|
|
385
|
+
for="maWindow24"
|
|
386
|
+
title="Use a 24-period moving average window"
|
|
387
|
+
>
|
|
388
|
+
24
|
|
389
|
+
</label>
|
|
298
390
|
|
|
299
391
|
<input
|
|
300
392
|
type="radio"
|
|
@@ -304,7 +396,13 @@
|
|
|
304
396
|
checked={controls.movingAverage.windowOverride === 30}
|
|
305
397
|
onchange={() => updateMovingAverage({ windowOverride: 30 })}
|
|
306
398
|
/>
|
|
307
|
-
<label
|
|
399
|
+
<label
|
|
400
|
+
class="btn btn-outline-primary"
|
|
401
|
+
for="maWindow30"
|
|
402
|
+
title="Use a 30-period moving average window"
|
|
403
|
+
>
|
|
404
|
+
30
|
|
405
|
+
</label>
|
|
308
406
|
</div>
|
|
309
407
|
|
|
310
408
|
<!-- Show Original Toggle Button -->
|
|
@@ -315,7 +413,11 @@
|
|
|
315
413
|
checked={controls.movingAverage.showOriginal}
|
|
316
414
|
onchange={() => updateMovingAverage({ showOriginal: !controls.movingAverage!.showOriginal })}
|
|
317
415
|
/>
|
|
318
|
-
<label
|
|
416
|
+
<label
|
|
417
|
+
class="btn btn-outline-primary btn-sm ms-2"
|
|
418
|
+
for="showOriginal"
|
|
419
|
+
title="Display both the original data and the moving average together"
|
|
420
|
+
>
|
|
319
421
|
Show Original
|
|
320
422
|
</label>
|
|
321
423
|
</div>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
function adaptHoverBehavior(layout, containerSize, chartInfo, originalHoverMode) {
|
|
10
10
|
const { width, height } = containerSize;
|
|
11
11
|
const isTiny = width < 250 || height < 200;
|
|
12
|
-
const isSmall = width <
|
|
12
|
+
const isSmall = width < 250 || height < 200;
|
|
13
13
|
const isMedium = width < 600 || height < 400;
|
|
14
14
|
const totalSeries = chartInfo.leftSeriesCount + chartInfo.rightSeriesCount;
|
|
15
15
|
// Only override hover mode in critical cases for performance/UX
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export * from './Settings/index.js';
|
|
|
11
11
|
// Logger utility for debugging and monitoring
|
|
12
12
|
export * from './logger/index.js';
|
|
13
13
|
// Map component - Mapbox GL + Deck.GL integration
|
|
14
|
-
|
|
14
|
+
// TODO: Moved to top-level src/lib/map module
|
|
15
|
+
// export * from './Map/index.js';
|
|
15
16
|
// FeatureRegistry - Component access management
|
|
16
17
|
export * from './FeatureRegistry/index.js';
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,5 +2,7 @@
|
|
|
2
2
|
// This approach keeps the main index clean and allows for easy expansion
|
|
3
3
|
// Core components (Desktop orchestration + Charts + TreeView)
|
|
4
4
|
export * from './core/index.js';
|
|
5
|
+
// Map components (Mapbox cellular visualization)
|
|
6
|
+
export * from './map/index.js';
|
|
5
7
|
// Complete applications
|
|
6
8
|
export * from './apps/index.js';
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* MapControl - Reusable wrapper for Mapbox custom controls
|
|
4
|
+
*
|
|
5
|
+
* Creates a custom control that can be positioned anywhere on the map
|
|
6
|
+
* and contain any content via slots.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* <MapControl position="top-left" title="My Control">
|
|
10
|
+
* <div>Custom content here</div>
|
|
11
|
+
* </MapControl>
|
|
12
|
+
*/
|
|
13
|
+
import { onMount, onDestroy } from 'svelte';
|
|
14
|
+
import mapboxgl from 'mapbox-gl';
|
|
15
|
+
import { tryUseMapbox } from '../hooks/useMapbox';
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
/** Position on the map */
|
|
19
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
20
|
+
/** Control title (shown in header) */
|
|
21
|
+
title?: string;
|
|
22
|
+
/** Is the control collapsible? */
|
|
23
|
+
collapsible?: boolean;
|
|
24
|
+
/** Initial collapsed state */
|
|
25
|
+
initiallyCollapsed?: boolean;
|
|
26
|
+
/** Custom CSS class for the container */
|
|
27
|
+
className?: string;
|
|
28
|
+
/** Child content */
|
|
29
|
+
children?: import('svelte').Snippet;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
position = 'top-left',
|
|
34
|
+
title,
|
|
35
|
+
collapsible = true,
|
|
36
|
+
initiallyCollapsed = false,
|
|
37
|
+
className = '',
|
|
38
|
+
children
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
const mapStore = tryUseMapbox();
|
|
42
|
+
|
|
43
|
+
if (!mapStore) {
|
|
44
|
+
console.error('MapControl: No map context available. Make sure MapControl is used inside MapboxProvider.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let map: mapboxgl.Map | null = null;
|
|
48
|
+
let controlElement: HTMLDivElement;
|
|
49
|
+
let collapsed = $state(initiallyCollapsed);
|
|
50
|
+
let control: mapboxgl.IControl | null = null;
|
|
51
|
+
|
|
52
|
+
onMount(() => {
|
|
53
|
+
if (!mapStore) {
|
|
54
|
+
console.error('MapControl: Cannot mount - no map store available');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const unsub = mapStore.subscribe((m) => {
|
|
59
|
+
if (!m) return;
|
|
60
|
+
map = m;
|
|
61
|
+
addControl();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return () => {
|
|
65
|
+
unsub();
|
|
66
|
+
removeControl();
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
onDestroy(() => {
|
|
71
|
+
removeControl();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
function addControl() {
|
|
75
|
+
if (!map || control) return;
|
|
76
|
+
|
|
77
|
+
// Create a custom Mapbox control
|
|
78
|
+
control = {
|
|
79
|
+
onAdd: () => {
|
|
80
|
+
return controlElement;
|
|
81
|
+
},
|
|
82
|
+
onRemove: () => {
|
|
83
|
+
// Cleanup handled in onDestroy
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
map.addControl(control, position);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function removeControl() {
|
|
91
|
+
if (map && control) {
|
|
92
|
+
try {
|
|
93
|
+
map.removeControl(control);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
// Control may already be removed
|
|
96
|
+
}
|
|
97
|
+
control = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function toggleCollapse() {
|
|
102
|
+
collapsed = !collapsed;
|
|
103
|
+
}
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<div
|
|
107
|
+
bind:this={controlElement}
|
|
108
|
+
class="mapboxgl-ctrl mapboxgl-ctrl-group map-control-container {className}"
|
|
109
|
+
>
|
|
110
|
+
{#if title}
|
|
111
|
+
<div class="map-control-header">
|
|
112
|
+
<span class="map-control-title">{title}</span>
|
|
113
|
+
{#if collapsible}
|
|
114
|
+
<button
|
|
115
|
+
class="map-control-toggle"
|
|
116
|
+
onclick={toggleCollapse}
|
|
117
|
+
aria-label={collapsed ? 'Expand' : 'Collapse'}
|
|
118
|
+
title={collapsed ? 'Expand' : 'Collapse'}
|
|
119
|
+
>
|
|
120
|
+
<i class="bi bi-chevron-{collapsed ? 'down' : 'up'}"></i>
|
|
121
|
+
</button>
|
|
122
|
+
{/if}
|
|
123
|
+
</div>
|
|
124
|
+
{/if}
|
|
125
|
+
|
|
126
|
+
{#if !collapsed}
|
|
127
|
+
<div class="map-control-content">
|
|
128
|
+
{#if children}
|
|
129
|
+
{@render children()}
|
|
130
|
+
{/if}
|
|
131
|
+
</div>
|
|
132
|
+
{/if}
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<style>
|
|
136
|
+
.map-control-container {
|
|
137
|
+
background: white;
|
|
138
|
+
border-radius: 4px;
|
|
139
|
+
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
|
140
|
+
overflow: hidden;
|
|
141
|
+
max-width: 300px;
|
|
142
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.map-control-header {
|
|
146
|
+
display: flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
justify-content: space-between;
|
|
149
|
+
padding: 8px 12px;
|
|
150
|
+
background: #f8f9fa;
|
|
151
|
+
border-bottom: 1px solid #dee2e6;
|
|
152
|
+
font-weight: 600;
|
|
153
|
+
font-size: 13px;
|
|
154
|
+
color: #212529;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.map-control-title {
|
|
158
|
+
flex: 1;
|
|
159
|
+
user-select: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.map-control-toggle {
|
|
163
|
+
background: none;
|
|
164
|
+
border: none;
|
|
165
|
+
padding: 4px;
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
color: #6c757d;
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
justify-content: center;
|
|
171
|
+
border-radius: 3px;
|
|
172
|
+
transition: all 0.2s;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.map-control-toggle:hover {
|
|
176
|
+
background: rgba(0, 0, 0, 0.05);
|
|
177
|
+
color: #212529;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.map-control-content {
|
|
181
|
+
padding: 12px;
|
|
182
|
+
max-height: 400px;
|
|
183
|
+
overflow-y: auto;
|
|
184
|
+
font-size: 13px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Custom scrollbar */
|
|
188
|
+
.map-control-content::-webkit-scrollbar {
|
|
189
|
+
width: 8px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.map-control-content::-webkit-scrollbar-track {
|
|
193
|
+
background: #f1f1f1;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.map-control-content::-webkit-scrollbar-thumb {
|
|
197
|
+
background: #888;
|
|
198
|
+
border-radius: 4px;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.map-control-content::-webkit-scrollbar-thumb:hover {
|
|
202
|
+
background: #555;
|
|
203
|
+
}
|
|
204
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Position on the map */
|
|
3
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
4
|
+
/** Control title (shown in header) */
|
|
5
|
+
title?: string;
|
|
6
|
+
/** Is the control collapsible? */
|
|
7
|
+
collapsible?: boolean;
|
|
8
|
+
/** Initial collapsed state */
|
|
9
|
+
initiallyCollapsed?: boolean;
|
|
10
|
+
/** Custom CSS class for the container */
|
|
11
|
+
className?: string;
|
|
12
|
+
/** Child content */
|
|
13
|
+
children?: import('svelte').Snippet;
|
|
14
|
+
}
|
|
15
|
+
declare const MapControl: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type MapControl = ReturnType<typeof MapControl>;
|
|
17
|
+
export default MapControl;
|