maplibre-gl-layer-control-tidop 0.0.1

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/index.mjs ADDED
@@ -0,0 +1,3499 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ class CustomLayerRegistry {
5
+ constructor() {
6
+ __publicField(this, "adapters", /* @__PURE__ */ new Map());
7
+ __publicField(this, "changeListeners", []);
8
+ __publicField(this, "unsubscribers", /* @__PURE__ */ new Map());
9
+ }
10
+ /**
11
+ * Register a custom layer adapter.
12
+ * @param adapter The adapter to register
13
+ */
14
+ register(adapter) {
15
+ this.adapters.set(adapter.type, adapter);
16
+ if (adapter.onLayerChange) {
17
+ const unsubscribe = adapter.onLayerChange((event, layerId) => {
18
+ this.notifyChange(event, layerId);
19
+ });
20
+ this.unsubscribers.set(adapter.type, unsubscribe);
21
+ }
22
+ }
23
+ /**
24
+ * Unregister an adapter by type.
25
+ * @param type The adapter type to unregister
26
+ */
27
+ unregister(type) {
28
+ const unsubscribe = this.unsubscribers.get(type);
29
+ if (unsubscribe) {
30
+ unsubscribe();
31
+ this.unsubscribers.delete(type);
32
+ }
33
+ this.adapters.delete(type);
34
+ }
35
+ /**
36
+ * Get all custom layer IDs across all adapters.
37
+ * @returns Array of layer IDs
38
+ */
39
+ getAllLayerIds() {
40
+ const ids = [];
41
+ this.adapters.forEach((adapter) => {
42
+ ids.push(...adapter.getLayerIds());
43
+ });
44
+ return ids;
45
+ }
46
+ /**
47
+ * Check if a layer ID is managed by any adapter.
48
+ * @param layerId The layer ID to check
49
+ * @returns true if the layer is managed by an adapter
50
+ */
51
+ hasLayer(layerId) {
52
+ for (const adapter of this.adapters.values()) {
53
+ if (adapter.getLayerIds().includes(layerId)) {
54
+ return true;
55
+ }
56
+ }
57
+ return false;
58
+ }
59
+ /**
60
+ * Get the adapter responsible for a specific layer.
61
+ * @param layerId The layer ID
62
+ * @returns The adapter or null if not found
63
+ */
64
+ getAdapterForLayer(layerId) {
65
+ for (const adapter of this.adapters.values()) {
66
+ if (adapter.getLayerIds().includes(layerId)) {
67
+ return adapter;
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+ /**
73
+ * Get the state of a custom layer.
74
+ * @param layerId The layer ID
75
+ * @returns The layer state or null if not found
76
+ */
77
+ getLayerState(layerId) {
78
+ const adapter = this.getAdapterForLayer(layerId);
79
+ return adapter ? adapter.getLayerState(layerId) : null;
80
+ }
81
+ /**
82
+ * Set visibility of a custom layer.
83
+ * @param layerId The layer ID
84
+ * @param visible Whether the layer should be visible
85
+ * @returns true if the operation was handled by an adapter
86
+ */
87
+ setVisibility(layerId, visible) {
88
+ const adapter = this.getAdapterForLayer(layerId);
89
+ if (adapter) {
90
+ adapter.setVisibility(layerId, visible);
91
+ return true;
92
+ }
93
+ return false;
94
+ }
95
+ /**
96
+ * Set opacity of a custom layer.
97
+ * @param layerId The layer ID
98
+ * @param opacity The opacity value (0-1)
99
+ * @returns true if the operation was handled by an adapter
100
+ */
101
+ setOpacity(layerId, opacity) {
102
+ const adapter = this.getAdapterForLayer(layerId);
103
+ if (adapter) {
104
+ adapter.setOpacity(layerId, opacity);
105
+ return true;
106
+ }
107
+ return false;
108
+ }
109
+ /**
110
+ * Get the symbol type for a custom layer (for UI display).
111
+ * @param layerId The layer ID
112
+ * @returns The symbol type or null if not available
113
+ */
114
+ getSymbolType(layerId) {
115
+ const adapter = this.getAdapterForLayer(layerId);
116
+ if (adapter && adapter.getSymbolType) {
117
+ return adapter.getSymbolType(layerId);
118
+ }
119
+ return null;
120
+ }
121
+ /**
122
+ * Get the bounds of a custom layer (for zoom-to-layer).
123
+ * @param layerId The layer ID
124
+ * @returns The bounds [west, south, east, north] or null if not available
125
+ */
126
+ getBounds(layerId) {
127
+ const adapter = this.getAdapterForLayer(layerId);
128
+ if (adapter && adapter.getBounds) {
129
+ return adapter.getBounds(layerId);
130
+ }
131
+ return null;
132
+ }
133
+ /**
134
+ * Get native MapLibre layer IDs for a custom layer.
135
+ * @param layerId The custom layer ID
136
+ * @returns Array of native layer IDs, or null if not available
137
+ */
138
+ getNativeLayerIds(layerId) {
139
+ const adapter = this.getAdapterForLayer(layerId);
140
+ if (adapter && adapter.getNativeLayerIds) {
141
+ return adapter.getNativeLayerIds(layerId);
142
+ }
143
+ return null;
144
+ }
145
+ /**
146
+ * Remove a custom layer through its adapter.
147
+ * @param layerId The layer ID to remove
148
+ * @returns true if the operation was handled by an adapter
149
+ */
150
+ removeLayer(layerId) {
151
+ const adapter = this.getAdapterForLayer(layerId);
152
+ if (adapter && adapter.removeLayer) {
153
+ adapter.removeLayer(layerId);
154
+ return true;
155
+ }
156
+ return false;
157
+ }
158
+ /**
159
+ * Subscribe to layer changes across all adapters.
160
+ * @param callback Function called when layers are added or removed
161
+ * @returns Unsubscribe function
162
+ */
163
+ onChange(callback) {
164
+ this.changeListeners.push(callback);
165
+ return () => {
166
+ const idx = this.changeListeners.indexOf(callback);
167
+ if (idx >= 0) {
168
+ this.changeListeners.splice(idx, 1);
169
+ }
170
+ };
171
+ }
172
+ notifyChange(event, layerId) {
173
+ this.changeListeners.forEach((cb) => cb(event, layerId));
174
+ }
175
+ /**
176
+ * Clean up all subscriptions and adapters.
177
+ */
178
+ destroy() {
179
+ this.unsubscribers.forEach((unsub) => unsub());
180
+ this.unsubscribers.clear();
181
+ this.adapters.clear();
182
+ this.changeListeners = [];
183
+ }
184
+ }
185
+ function getOpacityProperty(layerType) {
186
+ switch (layerType) {
187
+ case "fill":
188
+ return "fill-opacity";
189
+ case "line":
190
+ return "line-opacity";
191
+ case "circle":
192
+ return "circle-opacity";
193
+ case "symbol":
194
+ return ["icon-opacity", "text-opacity"];
195
+ case "raster":
196
+ return "raster-opacity";
197
+ case "background":
198
+ return "background-opacity";
199
+ case "heatmap":
200
+ return "heatmap-opacity";
201
+ case "fill-extrusion":
202
+ return "fill-extrusion-opacity";
203
+ case "hillshade":
204
+ return "hillshade-exaggeration";
205
+ case "color-relief":
206
+ return null;
207
+ default:
208
+ return `${layerType}-opacity`;
209
+ }
210
+ }
211
+ function getLayerOpacity(map, layerId, layerType) {
212
+ const opacityProp = getOpacityProperty(layerType);
213
+ if (opacityProp === null) {
214
+ return 1;
215
+ }
216
+ if (Array.isArray(opacityProp)) {
217
+ const opacity2 = map.getPaintProperty(layerId, opacityProp[0]);
218
+ return opacity2 !== void 0 && opacity2 !== null ? opacity2 : 1;
219
+ }
220
+ const opacity = map.getPaintProperty(layerId, opacityProp);
221
+ return opacity !== void 0 && opacity !== null ? opacity : 1;
222
+ }
223
+ function setLayerOpacity(map, layerId, layerType, opacity) {
224
+ const opacityProp = getOpacityProperty(layerType);
225
+ if (opacityProp === null) {
226
+ return;
227
+ }
228
+ if (Array.isArray(opacityProp)) {
229
+ opacityProp.forEach((prop) => {
230
+ map.setPaintProperty(layerId, prop, opacity);
231
+ });
232
+ } else {
233
+ map.setPaintProperty(layerId, opacityProp, opacity);
234
+ }
235
+ }
236
+ function isStyleableLayerType(layerType) {
237
+ return [
238
+ "fill",
239
+ "line",
240
+ "circle",
241
+ "symbol",
242
+ "raster",
243
+ "heatmap",
244
+ "fill-extrusion",
245
+ "hillshade"
246
+ ].includes(layerType);
247
+ }
248
+ function getLayerType(map, layerId) {
249
+ try {
250
+ const layer = map.getLayer(layerId);
251
+ return layer ? layer.type : null;
252
+ } catch (error) {
253
+ console.warn(`Failed to get layer type for ${layerId}:`, error);
254
+ return null;
255
+ }
256
+ }
257
+ function clonePaintValue(value) {
258
+ if (Array.isArray(value)) {
259
+ return value.map((item) => clonePaintValue(item));
260
+ }
261
+ if (value && typeof value === "object") {
262
+ try {
263
+ return JSON.parse(JSON.stringify(value));
264
+ } catch (error) {
265
+ return value;
266
+ }
267
+ }
268
+ return value;
269
+ }
270
+ function cacheOriginalLayerStyle(map, layerId, originalStyles) {
271
+ var _a;
272
+ if (originalStyles.has(layerId)) {
273
+ return;
274
+ }
275
+ try {
276
+ const layer = map.getLayer(layerId);
277
+ if (!layer) {
278
+ return;
279
+ }
280
+ const paint = {};
281
+ const style = map.getStyle();
282
+ const layerDef = (_a = style.layers) == null ? void 0 : _a.find((l) => l.id === layerId);
283
+ if (layer.type === "raster") {
284
+ const rasterDefaults = {
285
+ "raster-opacity": 1,
286
+ "raster-brightness-min": 0,
287
+ "raster-brightness-max": 1,
288
+ "raster-saturation": 0,
289
+ "raster-contrast": 0,
290
+ "raster-hue-rotate": 0
291
+ };
292
+ Object.assign(paint, rasterDefaults);
293
+ if (layerDef && "paint" in layerDef && layerDef.paint) {
294
+ Object.entries(layerDef.paint).forEach(([prop, value]) => {
295
+ if (prop.startsWith("raster-")) {
296
+ paint[prop] = clonePaintValue(value);
297
+ }
298
+ });
299
+ }
300
+ } else {
301
+ if (layerDef && "paint" in layerDef && layerDef.paint) {
302
+ Object.entries(layerDef.paint).forEach(([prop, value]) => {
303
+ paint[prop] = clonePaintValue(value);
304
+ });
305
+ }
306
+ }
307
+ originalStyles.set(layerId, { paint });
308
+ } catch (error) {
309
+ console.warn(`Failed to cache original style for ${layerId}:`, error);
310
+ }
311
+ }
312
+ function restoreOriginalStyle(map, layerId, originalStyles) {
313
+ const original = originalStyles.get(layerId);
314
+ if (!original) {
315
+ return {};
316
+ }
317
+ const applied = {};
318
+ Object.entries(original.paint).forEach(([property, value]) => {
319
+ try {
320
+ const restoredValue = clonePaintValue(value);
321
+ map.setPaintProperty(layerId, property, restoredValue);
322
+ applied[property] = restoredValue;
323
+ } catch (error) {
324
+ console.warn(`Failed to restore ${property} for ${layerId}:`, error);
325
+ }
326
+ });
327
+ return applied;
328
+ }
329
+ function rgbToHex(r, g, b) {
330
+ const clamp2 = (v) => Math.max(0, Math.min(255, Math.round(v)));
331
+ const toHex = (v) => {
332
+ const hex = clamp2(v).toString(16);
333
+ return hex.length === 1 ? `0${hex}` : hex;
334
+ };
335
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
336
+ }
337
+ function normalizeColor(value) {
338
+ if (typeof value === "string") {
339
+ if (value.startsWith("#")) {
340
+ if (value.length === 4) {
341
+ const r = value[1];
342
+ const g = value[2];
343
+ const b = value[3];
344
+ return `#${r}${r}${g}${g}${b}${b}`;
345
+ }
346
+ return value;
347
+ }
348
+ if (value.startsWith("rgb")) {
349
+ const match = value.match(/\d+/g);
350
+ if (match && match.length >= 3) {
351
+ const [r, g, b] = match.map((num) => parseInt(num, 10));
352
+ return rgbToHex(r, g, b);
353
+ }
354
+ }
355
+ } else if (Array.isArray(value) && value.length >= 3) {
356
+ return rgbToHex(value[0], value[1], value[2]);
357
+ }
358
+ return "#3388ff";
359
+ }
360
+ function formatNumericValue(value, step) {
361
+ let decimals = 0;
362
+ if (step && Number(step) !== 1) {
363
+ const stepNumber = Number(step);
364
+ if (stepNumber > 0 && stepNumber < 1) {
365
+ decimals = Math.min(4, Math.ceil(Math.abs(Math.log10(stepNumber))));
366
+ }
367
+ }
368
+ return value.toFixed(decimals);
369
+ }
370
+ function clamp(value, min, max) {
371
+ return Math.max(min, Math.min(max, value));
372
+ }
373
+ const COLOR_PROPERTY_MAP = {
374
+ fill: ["fill-color", "fill-outline-color"],
375
+ line: ["line-color"],
376
+ circle: ["circle-color", "circle-stroke-color"],
377
+ symbol: ["icon-color", "text-color"],
378
+ background: ["background-color"],
379
+ heatmap: ["heatmap-color"],
380
+ "fill-extrusion": ["fill-extrusion-color"]
381
+ };
382
+ function extractColorFromExpression(expression) {
383
+ if (!Array.isArray(expression) || expression.length === 0) return null;
384
+ for (const item of expression) {
385
+ if (typeof item === "string") {
386
+ if (item.startsWith("#") || item.startsWith("rgb") || item.startsWith("hsl")) {
387
+ return normalizeColor(item);
388
+ }
389
+ } else if (Array.isArray(item)) {
390
+ const result = extractColorFromExpression(item);
391
+ if (result) return result;
392
+ }
393
+ }
394
+ return null;
395
+ }
396
+ function getLayerColor(map, layerId, layerType) {
397
+ var _a;
398
+ const propertyNames = COLOR_PROPERTY_MAP[layerType];
399
+ if (!propertyNames) return null;
400
+ for (const propertyName of propertyNames) {
401
+ try {
402
+ const runtimeColor = map.getPaintProperty(layerId, propertyName);
403
+ if (runtimeColor) {
404
+ if (typeof runtimeColor === "string") {
405
+ return normalizeColor(runtimeColor);
406
+ }
407
+ if (Array.isArray(runtimeColor)) {
408
+ const extracted = extractColorFromExpression(runtimeColor);
409
+ if (extracted) return extracted;
410
+ }
411
+ }
412
+ } catch {
413
+ }
414
+ const style = map.getStyle();
415
+ const layer = (_a = style == null ? void 0 : style.layers) == null ? void 0 : _a.find(
416
+ (l) => l.id === layerId
417
+ );
418
+ if (layer && "paint" in layer && layer.paint) {
419
+ const paintColor = layer.paint[propertyName];
420
+ if (paintColor) {
421
+ if (typeof paintColor === "string") {
422
+ return normalizeColor(paintColor);
423
+ }
424
+ if (Array.isArray(paintColor)) {
425
+ const extracted = extractColorFromExpression(paintColor);
426
+ if (extracted) return extracted;
427
+ }
428
+ }
429
+ }
430
+ }
431
+ return null;
432
+ }
433
+ function getLayerColorFromSpec(layer) {
434
+ const propertyNames = COLOR_PROPERTY_MAP[layer.type];
435
+ if (!propertyNames) return null;
436
+ for (const propertyName of propertyNames) {
437
+ if ("paint" in layer && layer.paint) {
438
+ const paintColor = layer.paint[propertyName];
439
+ if (paintColor) {
440
+ if (typeof paintColor === "string") {
441
+ return normalizeColor(paintColor);
442
+ }
443
+ if (Array.isArray(paintColor)) {
444
+ const extracted = extractColorFromExpression(paintColor);
445
+ if (extracted) return extracted;
446
+ }
447
+ }
448
+ }
449
+ }
450
+ return null;
451
+ }
452
+ function darkenColor(hexColor, amount) {
453
+ let hex = hexColor.replace("#", "");
454
+ if (hex.length === 3) {
455
+ hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
456
+ }
457
+ const r = Math.max(
458
+ 0,
459
+ parseInt(hex.slice(0, 2), 16) - Math.round(255 * amount)
460
+ );
461
+ const g = Math.max(
462
+ 0,
463
+ parseInt(hex.slice(2, 4), 16) - Math.round(255 * amount)
464
+ );
465
+ const b = Math.max(
466
+ 0,
467
+ parseInt(hex.slice(4, 6), 16) - Math.round(255 * amount)
468
+ );
469
+ return rgbToHex(r, g, b);
470
+ }
471
+ function createFillSymbol(size, color) {
472
+ const padding = 2;
473
+ const borderColor = darkenColor(color, 0.3);
474
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
475
+ <rect x="${padding}" y="${padding}" width="${size - padding * 2}" height="${size - padding * 2}"
476
+ fill="${color}" stroke="${borderColor}" stroke-width="1" rx="1"/>
477
+ </svg>`;
478
+ }
479
+ function createLineSymbol(size, color, strokeWidth = 2) {
480
+ const y = size / 2;
481
+ const padding = 2;
482
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
483
+ <line x1="${padding}" y1="${y}" x2="${size - padding}" y2="${y}"
484
+ stroke="${color}" stroke-width="${strokeWidth}" stroke-linecap="round"/>
485
+ </svg>`;
486
+ }
487
+ function createCircleSymbol(size, color) {
488
+ const cx = size / 2;
489
+ const cy = size / 2;
490
+ const r = size / 2 - 3;
491
+ const borderColor = darkenColor(color, 0.3);
492
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
493
+ <circle cx="${cx}" cy="${cy}" r="${r}" fill="${color}"
494
+ stroke="${borderColor}" stroke-width="1"/>
495
+ </svg>`;
496
+ }
497
+ function createMarkerSymbol(size, color) {
498
+ const borderColor = darkenColor(color, 0.3);
499
+ const cx = size / 2;
500
+ const pinWidth = size * 0.5;
501
+ const pinHeight = size * 0.7;
502
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
503
+ <path d="M${cx} ${size - 2}
504
+ L${cx - pinWidth / 2} ${size - pinHeight}
505
+ A${pinWidth / 2} ${pinWidth / 2} 0 1 1 ${cx + pinWidth / 2} ${size - pinHeight}
506
+ Z"
507
+ fill="${color}" stroke="${borderColor}" stroke-width="1"/>
508
+ <circle cx="${cx}" cy="${size - pinHeight - pinWidth / 4}" r="${pinWidth / 5}" fill="white"/>
509
+ </svg>`;
510
+ }
511
+ function createRasterSymbol(size) {
512
+ const padding = 2;
513
+ const id = `rasterGrad_${Math.random().toString(36).slice(2, 9)}`;
514
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
515
+ <defs>
516
+ <linearGradient id="${id}" x1="0%" y1="0%" x2="100%" y2="100%">
517
+ <stop offset="0%" stop-color="#e0e0e0"/>
518
+ <stop offset="50%" stop-color="#808080"/>
519
+ <stop offset="100%" stop-color="#404040"/>
520
+ </linearGradient>
521
+ </defs>
522
+ <rect x="${padding}" y="${padding}" width="${size - padding * 2}" height="${size - padding * 2}"
523
+ fill="url(#${id})" rx="1"/>
524
+ </svg>`;
525
+ }
526
+ function createBackgroundSymbol(size, color) {
527
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
528
+ <rect x="1" y="1" width="${size - 2}" height="${size - 2}" fill="${color}" rx="2"/>
529
+ <rect x="3" y="3" width="${size - 6}" height="${size - 6}" fill="none"
530
+ stroke="white" stroke-width="1" stroke-opacity="0.5" rx="1"/>
531
+ </svg>`;
532
+ }
533
+ function createHeatmapSymbol(size) {
534
+ const padding = 2;
535
+ const id = `heatmapGrad_${Math.random().toString(36).slice(2, 9)}`;
536
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
537
+ <defs>
538
+ <radialGradient id="${id}" cx="50%" cy="50%" r="50%">
539
+ <stop offset="0%" stop-color="#ffff00"/>
540
+ <stop offset="50%" stop-color="#ff8800"/>
541
+ <stop offset="100%" stop-color="#ff0000"/>
542
+ </radialGradient>
543
+ </defs>
544
+ <rect x="${padding}" y="${padding}" width="${size - padding * 2}" height="${size - padding * 2}"
545
+ fill="url(#${id})" rx="1"/>
546
+ </svg>`;
547
+ }
548
+ function createHillshadeSymbol(size) {
549
+ const padding = 2;
550
+ const id = `hillshadeGrad_${Math.random().toString(36).slice(2, 9)}`;
551
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
552
+ <defs>
553
+ <linearGradient id="${id}" x1="0%" y1="0%" x2="100%" y2="100%">
554
+ <stop offset="0%" stop-color="#ffffff"/>
555
+ <stop offset="100%" stop-color="#666666"/>
556
+ </linearGradient>
557
+ </defs>
558
+ <rect x="${padding}" y="${padding}" width="${size - padding * 2}" height="${size - padding * 2}"
559
+ fill="url(#${id})" rx="1"/>
560
+ </svg>`;
561
+ }
562
+ function createFillExtrusionSymbol(size, color) {
563
+ const borderColor = darkenColor(color, 0.3);
564
+ const topColor = color;
565
+ const sideColor = darkenColor(color, 0.2);
566
+ const depth = 3;
567
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
568
+ <polygon points="${2 + depth},2 ${size - 2},2 ${size - 2},${size - 2 - depth} ${size - 2 - depth},${size - 2} 2,${size - 2} 2,${2 + depth}"
569
+ fill="${topColor}" stroke="${borderColor}" stroke-width="1"/>
570
+ <polygon points="2,${2 + depth} ${2 + depth},2 ${2 + depth},${size - 2 - depth} 2,${size - 2}"
571
+ fill="${sideColor}" stroke="${borderColor}" stroke-width="0.5"/>
572
+ <polygon points="${2 + depth},${size - 2 - depth} ${size - 2},${size - 2 - depth} ${size - 2 - depth},${size - 2} 2,${size - 2}"
573
+ fill="${sideColor}" stroke="${borderColor}" stroke-width="0.5"/>
574
+ </svg>`;
575
+ }
576
+ function createDefaultSymbol(size, color) {
577
+ const padding = 2;
578
+ const borderColor = darkenColor(color, 0.3);
579
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
580
+ <rect x="${padding}" y="${padding}" width="${size - padding * 2}" height="${size - padding * 2}"
581
+ fill="${color}" stroke="${borderColor}" stroke-width="1"/>
582
+ </svg>`;
583
+ }
584
+ function createCOGSymbol(size, color) {
585
+ const padding = 2;
586
+ const borderColor = darkenColor(color, 0.3);
587
+ const cellSize = (size - padding * 2) / 2;
588
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
589
+ <rect x="${padding}" y="${padding}" width="${cellSize}" height="${cellSize}" fill="${color}" stroke="${borderColor}" stroke-width="0.5"/>
590
+ <rect x="${padding + cellSize}" y="${padding}" width="${cellSize}" height="${cellSize}" fill="${color}" stroke="${borderColor}" stroke-width="0.5" opacity="0.8"/>
591
+ <rect x="${padding}" y="${padding + cellSize}" width="${cellSize}" height="${cellSize}" fill="${color}" stroke="${borderColor}" stroke-width="0.5" opacity="0.6"/>
592
+ <rect x="${padding + cellSize}" y="${padding + cellSize}" width="${cellSize}" height="${cellSize}" fill="${color}" stroke="${borderColor}" stroke-width="0.5" opacity="0.4"/>
593
+ </svg>`;
594
+ }
595
+ function createZarrSymbol(size, color) {
596
+ const padding = 2;
597
+ const borderColor = darkenColor(color, 0.3);
598
+ const innerSize = size - padding * 2;
599
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
600
+ <rect x="${padding + 2}" y="${padding}" width="${innerSize - 2}" height="${innerSize - 2}" fill="${darkenColor(color, 0.2)}" stroke="${borderColor}" stroke-width="0.5" rx="1"/>
601
+ <rect x="${padding + 1}" y="${padding + 1}" width="${innerSize - 2}" height="${innerSize - 2}" fill="${darkenColor(color, 0.1)}" stroke="${borderColor}" stroke-width="0.5" rx="1"/>
602
+ <rect x="${padding}" y="${padding + 2}" width="${innerSize - 2}" height="${innerSize - 2}" fill="${color}" stroke="${borderColor}" stroke-width="0.5" rx="1"/>
603
+ <line x1="${padding + 3}" y1="${padding + 5}" x2="${padding + innerSize - 5}" y2="${padding + 5}" stroke="${borderColor}" stroke-width="0.5" opacity="0.5"/>
604
+ <line x1="${padding + 3}" y1="${padding + 8}" x2="${padding + innerSize - 5}" y2="${padding + 8}" stroke="${borderColor}" stroke-width="0.5" opacity="0.5"/>
605
+ </svg>`;
606
+ }
607
+ function createCustomRasterSymbol(size, color) {
608
+ const padding = 2;
609
+ const borderColor = darkenColor(color, 0.3);
610
+ const id = `customRasterGrad_${Math.random().toString(36).slice(2, 9)}`;
611
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
612
+ <defs>
613
+ <linearGradient id="${id}" x1="0%" y1="0%" x2="100%" y2="100%">
614
+ <stop offset="0%" stop-color="${color}"/>
615
+ <stop offset="100%" stop-color="${darkenColor(color, 0.4)}"/>
616
+ </linearGradient>
617
+ </defs>
618
+ <rect x="${padding}" y="${padding}" width="${size - padding * 2}" height="${size - padding * 2}"
619
+ fill="url(#${id})" stroke="${borderColor}" stroke-width="1" rx="1"/>
620
+ <line x1="${size / 2}" y1="${padding}" x2="${size / 2}" y2="${size - padding}" stroke="${borderColor}" stroke-width="0.5" opacity="0.3"/>
621
+ <line x1="${padding}" y1="${size / 2}" x2="${size - padding}" y2="${size / 2}" stroke="${borderColor}" stroke-width="0.5" opacity="0.3"/>
622
+ </svg>`;
623
+ }
624
+ function createStackedLayersSymbol(size) {
625
+ const colors = ["#a8d4a8", "#8ec4e8", "#d4c4a8"];
626
+ const borderColor = "#666666";
627
+ return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
628
+ <rect x="4" y="1" width="${size - 6}" height="${size - 6}" fill="${colors[2]}" stroke="${borderColor}" stroke-width="0.75" rx="1"/>
629
+ <rect x="2" y="3" width="${size - 6}" height="${size - 6}" fill="${colors[1]}" stroke="${borderColor}" stroke-width="0.75" rx="1"/>
630
+ <rect x="0" y="5" width="${size - 6}" height="${size - 6}" fill="${colors[0]}" stroke="${borderColor}" stroke-width="0.75" rx="1"/>
631
+ </svg>`;
632
+ }
633
+ function createLayerSymbolSVG(layerType, color, options = {}) {
634
+ const size = options.size || 16;
635
+ const strokeWidth = options.strokeWidth || 2;
636
+ const fillColor = color || "#888888";
637
+ switch (layerType) {
638
+ case "fill":
639
+ return createFillSymbol(size, fillColor);
640
+ case "line":
641
+ return createLineSymbol(size, fillColor, strokeWidth);
642
+ case "circle":
643
+ return createCircleSymbol(size, fillColor);
644
+ case "symbol":
645
+ return createMarkerSymbol(size, fillColor);
646
+ case "raster":
647
+ return createRasterSymbol(size);
648
+ case "background":
649
+ return createBackgroundSymbol(size, fillColor);
650
+ case "heatmap":
651
+ return createHeatmapSymbol(size);
652
+ case "hillshade":
653
+ return createHillshadeSymbol(size);
654
+ case "fill-extrusion":
655
+ return createFillExtrusionSymbol(size, fillColor);
656
+ case "background-group":
657
+ return createStackedLayersSymbol(size);
658
+ case "cog":
659
+ return createCOGSymbol(size, fillColor);
660
+ case "zarr":
661
+ return createZarrSymbol(size, fillColor);
662
+ case "custom-raster":
663
+ return createCustomRasterSymbol(size, fillColor);
664
+ default:
665
+ return createDefaultSymbol(size, fillColor);
666
+ }
667
+ }
668
+ function createBackgroundGroupSymbolSVG(size = 16) {
669
+ return createStackedLayersSymbol(size);
670
+ }
671
+ class LayerControl {
672
+ constructor(options = {}) {
673
+ __publicField(this, "map");
674
+ __publicField(this, "mapContainer");
675
+ __publicField(this, "container");
676
+ __publicField(this, "button");
677
+ __publicField(this, "panel");
678
+ // Panel positioning
679
+ __publicField(this, "resizeHandler", null);
680
+ __publicField(this, "mapResizeHandler", null);
681
+ // State management
682
+ __publicField(this, "state");
683
+ __publicField(this, "targetLayers");
684
+ __publicField(this, "styleEditors");
685
+ __publicField(this, "initialSourceIds", null);
686
+ __publicField(this, "initialLayerIds", null);
687
+ // Panel width management
688
+ __publicField(this, "minPanelWidth");
689
+ __publicField(this, "maxPanelWidth");
690
+ __publicField(this, "maxPanelHeight");
691
+ __publicField(this, "showStyleEditor");
692
+ __publicField(this, "showOpacitySlider");
693
+ __publicField(this, "showLayerSymbol");
694
+ __publicField(this, "excludeDrawnLayers");
695
+ __publicField(this, "excludeLayerPatterns");
696
+ __publicField(this, "customLayerRegistry", null);
697
+ __publicField(this, "customLayerUnsubscribe", null);
698
+ __publicField(this, "removedCustomLayerIds", /* @__PURE__ */ new Set());
699
+ __publicField(this, "nativeLayerGroups", /* @__PURE__ */ new Map());
700
+ __publicField(this, "basemapStyleUrl", null);
701
+ __publicField(this, "basemapLayerIds", null);
702
+ __publicField(this, "widthSliderEl", null);
703
+ __publicField(this, "widthThumbEl", null);
704
+ __publicField(this, "widthValueEl", null);
705
+ __publicField(this, "isWidthSliderActive", false);
706
+ __publicField(this, "widthDragRectWidth", null);
707
+ __publicField(this, "widthDragStartX", null);
708
+ __publicField(this, "widthDragStartWidth", null);
709
+ __publicField(this, "widthFrame", null);
710
+ // Context menu and drag-drop
711
+ __publicField(this, "contextMenuEl", null);
712
+ __publicField(this, "enableContextMenu");
713
+ __publicField(this, "enableDragAndDrop");
714
+ __publicField(this, "onLayerRename");
715
+ __publicField(this, "onLayerReorder");
716
+ __publicField(this, "onLayerRemove");
717
+ this.minPanelWidth = options.panelMinWidth || 240;
718
+ this.maxPanelWidth = options.panelMaxWidth || 420;
719
+ this.maxPanelHeight = options.panelMaxHeight || 600;
720
+ this.showStyleEditor = options.showStyleEditor !== false;
721
+ this.showOpacitySlider = options.showOpacitySlider !== false;
722
+ this.showLayerSymbol = options.showLayerSymbol !== false;
723
+ this.excludeDrawnLayers = options.excludeDrawnLayers !== false;
724
+ this.excludeLayerPatterns = this.wildcardPatternsToRegex(options.excludeLayers || []);
725
+ this.enableContextMenu = options.enableContextMenu !== false;
726
+ this.enableDragAndDrop = options.enableDragAndDrop !== false;
727
+ this.onLayerRename = options.onLayerRename;
728
+ this.onLayerReorder = options.onLayerReorder;
729
+ this.onLayerRemove = options.onLayerRemove;
730
+ this.state = {
731
+ collapsed: options.collapsed !== false,
732
+ panelWidth: options.panelWidth || 350,
733
+ activeStyleEditor: null,
734
+ layerStates: options.layerStates || {},
735
+ originalStyles: /* @__PURE__ */ new Map(),
736
+ userInteractingWithSlider: false,
737
+ backgroundLegendOpen: false,
738
+ backgroundLayerVisibility: /* @__PURE__ */ new Map(),
739
+ onlyRenderedFilter: false,
740
+ contextMenu: {
741
+ visible: false,
742
+ targetLayerId: null,
743
+ x: 0,
744
+ y: 0
745
+ },
746
+ renamingLayerId: null,
747
+ customLayerNames: /* @__PURE__ */ new Map(),
748
+ drag: {
749
+ active: false,
750
+ layerId: null,
751
+ startY: 0,
752
+ currentY: 0,
753
+ placeholder: null,
754
+ draggedElement: null
755
+ },
756
+ isStyleOperationInProgress: false
757
+ };
758
+ this.targetLayers = options.layers || Object.keys(this.state.layerStates);
759
+ this.styleEditors = /* @__PURE__ */ new Map();
760
+ if (options.customLayerAdapters && options.customLayerAdapters.length > 0) {
761
+ this.customLayerRegistry = new CustomLayerRegistry();
762
+ options.customLayerAdapters.forEach((adapter) => {
763
+ this.customLayerRegistry.register(adapter);
764
+ });
765
+ }
766
+ this.basemapStyleUrl = options.basemapStyleUrl || null;
767
+ }
768
+ /**
769
+ * Called when the control is added to the map
770
+ */
771
+ onAdd(map) {
772
+ this.map = map;
773
+ this.mapContainer = map.getContainer();
774
+ const style = this.map.getStyle();
775
+ if (style && style.sources) {
776
+ this.initialSourceIds = new Set(Object.keys(style.sources));
777
+ } else {
778
+ this.initialSourceIds = /* @__PURE__ */ new Set();
779
+ }
780
+ if (style && style.layers) {
781
+ this.initialLayerIds = new Set(style.layers.map((layer) => layer.id));
782
+ } else {
783
+ this.initialLayerIds = /* @__PURE__ */ new Set();
784
+ }
785
+ this.container = this.createContainer();
786
+ this.button = this.createToggleButton();
787
+ this.panel = this.createPanel();
788
+ this.container.appendChild(this.button);
789
+ this.mapContainer.appendChild(this.panel);
790
+ if (this.enableContextMenu) {
791
+ this.contextMenuEl = this.createContextMenu();
792
+ this.mapContainer.appendChild(this.contextMenuEl);
793
+ }
794
+ this.updateWidthDisplay();
795
+ this.setupEventListeners();
796
+ if (this.basemapStyleUrl && !this.basemapLayerIds) {
797
+ this.fetchBasemapStyle().then(() => {
798
+ if (Object.keys(this.state.layerStates).length === 0) {
799
+ this.autoDetectLayers();
800
+ }
801
+ this.buildLayerItems();
802
+ }).catch((error) => {
803
+ console.warn("Failed to fetch basemap style, falling back to heuristic detection:", error);
804
+ if (Object.keys(this.state.layerStates).length === 0) {
805
+ this.autoDetectLayers();
806
+ }
807
+ this.buildLayerItems();
808
+ });
809
+ } else {
810
+ if (Object.keys(this.state.layerStates).length === 0) {
811
+ this.autoDetectLayers();
812
+ }
813
+ this.buildLayerItems();
814
+ }
815
+ if (!this.state.collapsed) {
816
+ requestAnimationFrame(() => {
817
+ this.updatePanelPosition();
818
+ });
819
+ }
820
+ return this.container;
821
+ }
822
+ /**
823
+ * Fetch the basemap style JSON and extract layer IDs.
824
+ * This provides reliable distinction between basemap and user-added layers.
825
+ */
826
+ async fetchBasemapStyle() {
827
+ if (!this.basemapStyleUrl) return;
828
+ try {
829
+ const response = await fetch(this.basemapStyleUrl);
830
+ if (!response.ok) {
831
+ throw new Error(`HTTP error! status: ${response.status}`);
832
+ }
833
+ const styleJson = await response.json();
834
+ if (styleJson && Array.isArray(styleJson.layers)) {
835
+ this.basemapLayerIds = new Set(
836
+ styleJson.layers.map((layer) => layer.id)
837
+ );
838
+ } else {
839
+ this.basemapLayerIds = /* @__PURE__ */ new Set();
840
+ }
841
+ } catch (error) {
842
+ console.warn("Failed to fetch basemap style from URL:", this.basemapStyleUrl, error);
843
+ throw error;
844
+ }
845
+ }
846
+ /**
847
+ * Called when the control is removed from the map
848
+ */
849
+ onRemove() {
850
+ var _a, _b, _c;
851
+ if (this.customLayerUnsubscribe) {
852
+ this.customLayerUnsubscribe();
853
+ this.customLayerUnsubscribe = null;
854
+ }
855
+ if (this.customLayerRegistry) {
856
+ this.customLayerRegistry.destroy();
857
+ this.customLayerRegistry = null;
858
+ }
859
+ if (this.resizeHandler) {
860
+ window.removeEventListener("resize", this.resizeHandler);
861
+ this.resizeHandler = null;
862
+ }
863
+ if (this.mapResizeHandler) {
864
+ this.map.off("resize", this.mapResizeHandler);
865
+ this.mapResizeHandler = null;
866
+ }
867
+ if (this.contextMenuEl) {
868
+ (_a = this.contextMenuEl.parentNode) == null ? void 0 : _a.removeChild(this.contextMenuEl);
869
+ this.contextMenuEl = null;
870
+ }
871
+ this.cleanupDragState();
872
+ (_b = this.panel.parentNode) == null ? void 0 : _b.removeChild(this.panel);
873
+ (_c = this.container.parentNode) == null ? void 0 : _c.removeChild(this.container);
874
+ }
875
+ /**
876
+ * Auto-detect layers from the map and populate layerStates
877
+ */
878
+ autoDetectLayers() {
879
+ const style = this.map.getStyle();
880
+ if (!style || !style.layers) {
881
+ return;
882
+ }
883
+ const allLayerIds = style.layers.map((layer) => layer.id);
884
+ if (this.targetLayers.length === 0) {
885
+ const userAddedLayers = [];
886
+ const backgroundLayerIds = [];
887
+ const useBasemapStyleDetection = this.basemapLayerIds !== null && this.basemapLayerIds.size > 0;
888
+ const userAddedSourceIds = useBasemapStyleDetection ? /* @__PURE__ */ new Set() : this.detectUserAddedSources();
889
+ allLayerIds.forEach((layerId) => {
890
+ const layer = this.map.getLayer(layerId);
891
+ if (!layer) return;
892
+ if (this.excludeDrawnLayers && this.isDrawnLayer(layerId)) {
893
+ backgroundLayerIds.push(layerId);
894
+ return;
895
+ }
896
+ if (this.isExcludedByPattern(layerId)) {
897
+ backgroundLayerIds.push(layerId);
898
+ return;
899
+ }
900
+ if (useBasemapStyleDetection) {
901
+ if (this.basemapLayerIds.has(layerId)) {
902
+ backgroundLayerIds.push(layerId);
903
+ } else {
904
+ userAddedLayers.push(layerId);
905
+ }
906
+ } else {
907
+ const sourceId = layer.source;
908
+ if (sourceId && userAddedSourceIds.has(sourceId)) {
909
+ userAddedLayers.push(layerId);
910
+ } else {
911
+ backgroundLayerIds.push(layerId);
912
+ }
913
+ }
914
+ });
915
+ if (backgroundLayerIds.length > 0) {
916
+ this.state.layerStates["Background"] = {
917
+ visible: true,
918
+ opacity: 1,
919
+ name: "Background"
920
+ };
921
+ }
922
+ userAddedLayers.forEach((layerId) => {
923
+ const layer = this.map.getLayer(layerId);
924
+ if (!layer) return;
925
+ const visibility = this.map.getLayoutProperty(layerId, "visibility");
926
+ const isVisible = visibility !== "none";
927
+ const layerType = layer.type;
928
+ const opacity = getLayerOpacity(this.map, layerId, layerType);
929
+ const friendlyName = this.generateFriendlyName(layerId);
930
+ this.state.layerStates[layerId] = {
931
+ visible: isVisible,
932
+ opacity,
933
+ name: friendlyName
934
+ };
935
+ });
936
+ } else {
937
+ const userLayers = [];
938
+ const basemapLayers = [];
939
+ allLayerIds.forEach((layerId) => {
940
+ if (this.isExcludedByPattern(layerId)) {
941
+ basemapLayers.push(layerId);
942
+ return;
943
+ }
944
+ if (this.targetLayers.includes(layerId)) {
945
+ userLayers.push(layerId);
946
+ } else {
947
+ basemapLayers.push(layerId);
948
+ }
949
+ });
950
+ if (basemapLayers.length > 0) {
951
+ this.state.layerStates["Background"] = {
952
+ visible: true,
953
+ opacity: 1,
954
+ name: "Background"
955
+ };
956
+ }
957
+ userLayers.forEach((layerId) => {
958
+ const layer = this.map.getLayer(layerId);
959
+ if (!layer) return;
960
+ const visibility = this.map.getLayoutProperty(layerId, "visibility");
961
+ const isVisible = visibility !== "none";
962
+ const layerType = layer.type;
963
+ const opacity = getLayerOpacity(this.map, layerId, layerType);
964
+ const friendlyName = this.generateFriendlyName(layerId);
965
+ this.state.layerStates[layerId] = {
966
+ visible: isVisible,
967
+ opacity,
968
+ name: friendlyName
969
+ };
970
+ });
971
+ }
972
+ if (this.customLayerRegistry) {
973
+ const customLayerIds = this.customLayerRegistry.getAllLayerIds();
974
+ customLayerIds.forEach((layerId) => {
975
+ if (this.state.layerStates[layerId]) return;
976
+ const customState = this.customLayerRegistry.getLayerState(layerId);
977
+ if (customState) {
978
+ this.state.layerStates[layerId] = {
979
+ visible: customState.visible,
980
+ opacity: customState.opacity,
981
+ name: customState.name,
982
+ isCustomLayer: true,
983
+ customLayerType: this.customLayerRegistry.getSymbolType(layerId) || void 0
984
+ };
985
+ }
986
+ });
987
+ }
988
+ this.targetLayers = Object.keys(this.state.layerStates);
989
+ }
990
+ /**
991
+ * Detect which sources are user-added (not from the basemap style)
992
+ * User-added sources are identified by:
993
+ * - Sources that were NOT present when the control was first added
994
+ * - Additionally for sources added later:
995
+ * - GeoJSON sources with inline data objects (not URL strings)
996
+ * - Image, video, canvas sources
997
+ * - Raster, raster-dem, vector sources from non-basemap tile providers
998
+ */
999
+ detectUserAddedSources() {
1000
+ const userAddedSources = /* @__PURE__ */ new Set();
1001
+ const style = this.map.getStyle();
1002
+ if (!style || !style.sources) return userAddedSources;
1003
+ const basemapDomains = /* @__PURE__ */ new Set();
1004
+ const knownBasemapProviders = [
1005
+ "demotiles.maplibre.org",
1006
+ "api.maptiler.com",
1007
+ "tiles.stadiamaps.com",
1008
+ "api.mapbox.com",
1009
+ "basemaps.cartocdn.com",
1010
+ "tiles.mapbox.com",
1011
+ "a.basemaps.cartocdn.com",
1012
+ "b.basemaps.cartocdn.com",
1013
+ "c.basemaps.cartocdn.com",
1014
+ "d.basemaps.cartocdn.com",
1015
+ "tiles.arcgis.com",
1016
+ "server.arcgisonline.com",
1017
+ "services.arcgisonline.com"
1018
+ ];
1019
+ const spriteUrl = style.sprite;
1020
+ if (spriteUrl && typeof spriteUrl === "string") {
1021
+ try {
1022
+ const url = new URL(spriteUrl);
1023
+ basemapDomains.add(url.hostname);
1024
+ } catch {
1025
+ }
1026
+ }
1027
+ if (style.glyphs) {
1028
+ try {
1029
+ const url = new URL(style.glyphs.replace("{fontstack}", "x").replace("{range}", "x"));
1030
+ basemapDomains.add(url.hostname);
1031
+ } catch {
1032
+ }
1033
+ }
1034
+ for (const [sourceId, source] of Object.entries(style.sources)) {
1035
+ if (this.initialSourceIds && this.initialSourceIds.has(sourceId)) {
1036
+ continue;
1037
+ }
1038
+ const src = source;
1039
+ const sourceType = src.type;
1040
+ if (sourceType === "image" || sourceType === "video" || sourceType === "canvas") {
1041
+ userAddedSources.add(sourceId);
1042
+ continue;
1043
+ }
1044
+ if (sourceType === "geojson") {
1045
+ if (src.data && typeof src.data === "object") {
1046
+ userAddedSources.add(sourceId);
1047
+ } else if (src.data && typeof src.data === "string") {
1048
+ try {
1049
+ const url = new URL(src.data);
1050
+ const isBasemap = knownBasemapProviders.some((p) => url.hostname.includes(p)) || basemapDomains.has(url.hostname);
1051
+ if (!isBasemap) {
1052
+ userAddedSources.add(sourceId);
1053
+ }
1054
+ } catch {
1055
+ userAddedSources.add(sourceId);
1056
+ }
1057
+ }
1058
+ continue;
1059
+ }
1060
+ if (sourceType === "raster" || sourceType === "raster-dem" || sourceType === "vector") {
1061
+ const tileUrl = src.url || src.tiles && src.tiles[0] || "";
1062
+ if (tileUrl) {
1063
+ try {
1064
+ const url = new URL(tileUrl.replace(/{[^}]+}/g, "0"));
1065
+ const hostname = url.hostname;
1066
+ const isKnownBasemap = knownBasemapProviders.some(
1067
+ (provider) => hostname === provider || hostname.endsWith("." + provider)
1068
+ );
1069
+ const matchesBasemapDomain = basemapDomains.has(hostname);
1070
+ if (!isKnownBasemap && !matchesBasemapDomain) {
1071
+ userAddedSources.add(sourceId);
1072
+ }
1073
+ } catch {
1074
+ userAddedSources.add(sourceId);
1075
+ }
1076
+ }
1077
+ }
1078
+ }
1079
+ return userAddedSources;
1080
+ }
1081
+ /**
1082
+ * Generate a friendly display name from a layer ID
1083
+ */
1084
+ generateFriendlyName(layerId) {
1085
+ let name = layerId.replace(/^(layer[-_]?|gl[-_]?)/, "");
1086
+ name = name.replace(/[-_]/g, " ");
1087
+ name = name.replace(/\b\w/g, (char) => char.toUpperCase());
1088
+ return name || layerId;
1089
+ }
1090
+ /**
1091
+ * Check if a layer ID belongs to a drawing library (Geoman, Mapbox GL Draw, etc.)
1092
+ * @param layerId The layer ID to check
1093
+ * @returns true if the layer is from a drawing library
1094
+ */
1095
+ isDrawnLayer(layerId) {
1096
+ const drawnLayerPatterns = [
1097
+ // Drawing libraries
1098
+ /^gm[-_\s]/i,
1099
+ // Geoman (gm-main-*, gm_*, Gm Temporary...)
1100
+ /^gl-draw[-_]/i,
1101
+ // Mapbox GL Draw
1102
+ /^mapbox-gl-draw[-_]/i,
1103
+ // Mapbox GL Draw alternative
1104
+ /^terra-draw[-_]/i,
1105
+ // Terra Draw
1106
+ /^maplibre-gl-draw[-_]/i,
1107
+ // MapLibre GL Draw
1108
+ /^draw[-_]layer/i,
1109
+ // Generic draw layers
1110
+ // maplibre-gl-components internal layers
1111
+ /^measure-/i,
1112
+ // MeasureControl (measure-{id}-fill, measure-{id}-line)
1113
+ /^pmtiles-source-/i,
1114
+ // PMTilesLayerControl (managed via adapter)
1115
+ /^stac-search-footprints/i
1116
+ // StacSearchControl footprint layers
1117
+ ];
1118
+ return drawnLayerPatterns.some((pattern) => pattern.test(layerId));
1119
+ }
1120
+ /**
1121
+ * Convert wildcard patterns (e.g., '*-temp-*', 'debug-*') to RegExp objects
1122
+ */
1123
+ wildcardPatternsToRegex(patterns) {
1124
+ return patterns.map((pattern) => {
1125
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
1126
+ const regexStr = escaped.replace(/\*/g, ".*");
1127
+ return new RegExp(`^${regexStr}$`, "i");
1128
+ });
1129
+ }
1130
+ /**
1131
+ * Check if a layer matches any of the user-defined exclusion patterns
1132
+ */
1133
+ isExcludedByPattern(layerId) {
1134
+ return this.excludeLayerPatterns.some((pattern) => pattern.test(layerId));
1135
+ }
1136
+ /**
1137
+ * Create the main container element
1138
+ */
1139
+ createContainer() {
1140
+ const container = document.createElement("div");
1141
+ container.className = "maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-layer-control";
1142
+ return container;
1143
+ }
1144
+ /**
1145
+ * Create the toggle button
1146
+ */
1147
+ createToggleButton() {
1148
+ const button = document.createElement("button");
1149
+ button.type = "button";
1150
+ button.title = "Layer Control";
1151
+ button.setAttribute("aria-label", "Layer Control");
1152
+ const icon = document.createElement("span");
1153
+ icon.className = "layer-control-icon";
1154
+ icon.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 3 3 8.25 12 13.5 21 8.25 12 3"></polygon><polyline points="3 12.75 12 18 21 12.75"></polyline><polyline points="3 17.25 12 22 21 17.25"></polyline></svg>';
1155
+ button.appendChild(icon);
1156
+ return button;
1157
+ }
1158
+ /**
1159
+ * Create the panel element
1160
+ */
1161
+ createPanel() {
1162
+ const panel = document.createElement("div");
1163
+ panel.className = "layer-control-panel";
1164
+ panel.style.width = `${this.state.panelWidth}px`;
1165
+ panel.style.maxHeight = `${this.maxPanelHeight}px`;
1166
+ if (!this.state.collapsed) {
1167
+ panel.classList.add("expanded");
1168
+ }
1169
+ const header = this.createPanelHeader();
1170
+ panel.appendChild(header);
1171
+ const actionButtons = this.createActionButtons();
1172
+ panel.appendChild(actionButtons);
1173
+ return panel;
1174
+ }
1175
+ /**
1176
+ * Detect which corner the control is positioned in
1177
+ * @returns The position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
1178
+ */
1179
+ getControlPosition() {
1180
+ const parent = this.container.parentElement;
1181
+ if (!parent) return "top-right";
1182
+ if (parent.classList.contains("maplibregl-ctrl-top-left")) return "top-left";
1183
+ if (parent.classList.contains("maplibregl-ctrl-top-right")) return "top-right";
1184
+ if (parent.classList.contains("maplibregl-ctrl-bottom-left")) return "bottom-left";
1185
+ if (parent.classList.contains("maplibregl-ctrl-bottom-right")) return "bottom-right";
1186
+ return "top-right";
1187
+ }
1188
+ /**
1189
+ * Update the panel position based on button location and control corner
1190
+ * Positions the panel next to the button, expanding in the appropriate direction
1191
+ */
1192
+ updatePanelPosition() {
1193
+ if (!this.button || !this.panel || !this.mapContainer) return;
1194
+ const buttonRect = this.button.getBoundingClientRect();
1195
+ const mapRect = this.mapContainer.getBoundingClientRect();
1196
+ const position = this.getControlPosition();
1197
+ const buttonTop = buttonRect.top - mapRect.top;
1198
+ const buttonBottom = mapRect.bottom - buttonRect.bottom;
1199
+ const buttonLeft = buttonRect.left - mapRect.left;
1200
+ const buttonRight = mapRect.right - buttonRect.right;
1201
+ const panelGap = 5;
1202
+ this.panel.style.top = "";
1203
+ this.panel.style.bottom = "";
1204
+ this.panel.style.left = "";
1205
+ this.panel.style.right = "";
1206
+ switch (position) {
1207
+ case "top-left":
1208
+ this.panel.style.top = `${buttonTop + buttonRect.height + panelGap}px`;
1209
+ this.panel.style.left = `${buttonLeft}px`;
1210
+ break;
1211
+ case "top-right":
1212
+ this.panel.style.top = `${buttonTop + buttonRect.height + panelGap}px`;
1213
+ this.panel.style.right = `${buttonRight}px`;
1214
+ break;
1215
+ case "bottom-left":
1216
+ this.panel.style.bottom = `${buttonBottom + buttonRect.height + panelGap}px`;
1217
+ this.panel.style.left = `${buttonLeft}px`;
1218
+ break;
1219
+ case "bottom-right":
1220
+ this.panel.style.bottom = `${buttonBottom + buttonRect.height + panelGap}px`;
1221
+ this.panel.style.right = `${buttonRight}px`;
1222
+ break;
1223
+ }
1224
+ }
1225
+ /**
1226
+ * Create action buttons for Show All / Hide All
1227
+ */
1228
+ createActionButtons() {
1229
+ const container = document.createElement("div");
1230
+ container.className = "layer-control-actions";
1231
+ const showAllBtn = document.createElement("button");
1232
+ showAllBtn.type = "button";
1233
+ showAllBtn.className = "layer-control-action-btn";
1234
+ showAllBtn.textContent = "Show All";
1235
+ showAllBtn.title = "Show all layers";
1236
+ showAllBtn.addEventListener("click", () => this.setAllLayersVisibility(true));
1237
+ const hideAllBtn = document.createElement("button");
1238
+ hideAllBtn.type = "button";
1239
+ hideAllBtn.className = "layer-control-action-btn";
1240
+ hideAllBtn.textContent = "Hide All";
1241
+ hideAllBtn.title = "Hide all layers";
1242
+ hideAllBtn.addEventListener("click", () => this.setAllLayersVisibility(false));
1243
+ container.appendChild(showAllBtn);
1244
+ container.appendChild(hideAllBtn);
1245
+ return container;
1246
+ }
1247
+ /**
1248
+ * Set visibility of all layers
1249
+ */
1250
+ setAllLayersVisibility(visible) {
1251
+ Object.keys(this.state.layerStates).forEach((layerId) => {
1252
+ this.toggleLayerVisibility(layerId, visible);
1253
+ const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
1254
+ if (itemEl) {
1255
+ const checkbox = itemEl.querySelector(".layer-control-checkbox");
1256
+ if (checkbox) {
1257
+ checkbox.checked = visible;
1258
+ checkbox.indeterminate = false;
1259
+ }
1260
+ }
1261
+ });
1262
+ }
1263
+ /**
1264
+ * Create the panel header with title and width control
1265
+ */
1266
+ createPanelHeader() {
1267
+ const header = document.createElement("div");
1268
+ header.className = "layer-control-panel-header";
1269
+ const title = document.createElement("span");
1270
+ title.className = "layer-control-panel-title";
1271
+ title.textContent = "Layers";
1272
+ header.appendChild(title);
1273
+ const widthControl = this.createWidthControl();
1274
+ header.appendChild(widthControl);
1275
+ return header;
1276
+ }
1277
+ /**
1278
+ * Create the width control slider
1279
+ */
1280
+ createWidthControl() {
1281
+ const widthControl = document.createElement("label");
1282
+ widthControl.className = "layer-control-width-control";
1283
+ widthControl.title = "Adjust layer panel width";
1284
+ const widthLabel = document.createElement("span");
1285
+ widthLabel.textContent = "Width";
1286
+ widthControl.appendChild(widthLabel);
1287
+ const widthSlider = document.createElement("div");
1288
+ widthSlider.className = "layer-control-width-slider";
1289
+ widthSlider.setAttribute("role", "slider");
1290
+ widthSlider.setAttribute("aria-valuemin", String(this.minPanelWidth));
1291
+ widthSlider.setAttribute("aria-valuemax", String(this.maxPanelWidth));
1292
+ widthSlider.setAttribute("aria-valuenow", String(this.state.panelWidth));
1293
+ widthSlider.setAttribute("aria-valuestep", "10");
1294
+ widthSlider.setAttribute("aria-label", "Layer panel width");
1295
+ widthSlider.tabIndex = 0;
1296
+ const widthTrack = document.createElement("div");
1297
+ widthTrack.className = "layer-control-width-track";
1298
+ const widthThumb = document.createElement("div");
1299
+ widthThumb.className = "layer-control-width-thumb";
1300
+ widthSlider.appendChild(widthTrack);
1301
+ widthSlider.appendChild(widthThumb);
1302
+ this.widthSliderEl = widthSlider;
1303
+ this.widthThumbEl = widthThumb;
1304
+ const widthValue = document.createElement("span");
1305
+ widthValue.className = "layer-control-width-value";
1306
+ this.widthValueEl = widthValue;
1307
+ widthControl.appendChild(widthSlider);
1308
+ widthControl.appendChild(widthValue);
1309
+ this.updateWidthDisplay();
1310
+ this.setupWidthSliderEvents(widthSlider);
1311
+ return widthControl;
1312
+ }
1313
+ /**
1314
+ * Setup event listeners for width slider
1315
+ */
1316
+ setupWidthSliderEvents(widthSlider) {
1317
+ widthSlider.addEventListener("pointerdown", (event) => {
1318
+ event.preventDefault();
1319
+ const rect = widthSlider.getBoundingClientRect();
1320
+ this.widthDragRectWidth = rect.width || 1;
1321
+ this.widthDragStartX = event.clientX;
1322
+ this.widthDragStartWidth = this.state.panelWidth;
1323
+ this.isWidthSliderActive = true;
1324
+ widthSlider.setPointerCapture(event.pointerId);
1325
+ this.updateWidthFromPointer(event, true);
1326
+ });
1327
+ widthSlider.addEventListener("pointermove", (event) => {
1328
+ if (!this.isWidthSliderActive) return;
1329
+ this.updateWidthFromPointer(event);
1330
+ });
1331
+ const endPointerDrag = (event) => {
1332
+ if (!this.isWidthSliderActive) return;
1333
+ if (event.pointerId !== void 0) {
1334
+ try {
1335
+ widthSlider.releasePointerCapture(event.pointerId);
1336
+ } catch (error) {
1337
+ }
1338
+ }
1339
+ this.isWidthSliderActive = false;
1340
+ this.widthDragRectWidth = null;
1341
+ this.widthDragStartX = null;
1342
+ this.widthDragStartWidth = null;
1343
+ this.updateWidthDisplay();
1344
+ };
1345
+ widthSlider.addEventListener("pointerup", endPointerDrag);
1346
+ widthSlider.addEventListener("pointercancel", endPointerDrag);
1347
+ widthSlider.addEventListener("lostpointercapture", endPointerDrag);
1348
+ widthSlider.addEventListener("keydown", (event) => {
1349
+ let handled = true;
1350
+ const step = event.shiftKey ? 20 : 10;
1351
+ switch (event.key) {
1352
+ case "ArrowLeft":
1353
+ case "ArrowDown":
1354
+ this.applyPanelWidth(this.state.panelWidth - step, true);
1355
+ break;
1356
+ case "ArrowRight":
1357
+ case "ArrowUp":
1358
+ this.applyPanelWidth(this.state.panelWidth + step, true);
1359
+ break;
1360
+ case "Home":
1361
+ this.applyPanelWidth(this.minPanelWidth, true);
1362
+ break;
1363
+ case "End":
1364
+ this.applyPanelWidth(this.maxPanelWidth, true);
1365
+ break;
1366
+ case "PageUp":
1367
+ this.applyPanelWidth(this.state.panelWidth + 50, true);
1368
+ break;
1369
+ case "PageDown":
1370
+ this.applyPanelWidth(this.state.panelWidth - 50, true);
1371
+ break;
1372
+ default:
1373
+ handled = false;
1374
+ }
1375
+ if (handled) {
1376
+ event.preventDefault();
1377
+ this.updateWidthDisplay();
1378
+ }
1379
+ });
1380
+ }
1381
+ /**
1382
+ * Update panel width from pointer event
1383
+ */
1384
+ updateWidthFromPointer(event, resetBaseline = false) {
1385
+ if (!this.widthSliderEl) return;
1386
+ const sliderWidth = this.widthDragRectWidth || this.widthSliderEl.getBoundingClientRect().width || 1;
1387
+ const widthRange = this.maxPanelWidth - this.minPanelWidth;
1388
+ let width;
1389
+ if (resetBaseline) {
1390
+ const rect = this.widthSliderEl.getBoundingClientRect();
1391
+ const relative = rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0;
1392
+ const clampedRatio = Math.min(1, Math.max(0, relative));
1393
+ width = this.minPanelWidth + clampedRatio * widthRange;
1394
+ this.widthDragStartWidth = width;
1395
+ this.widthDragStartX = event.clientX;
1396
+ } else {
1397
+ const delta = event.clientX - (this.widthDragStartX || event.clientX);
1398
+ width = (this.widthDragStartWidth || this.state.panelWidth) + delta / sliderWidth * widthRange;
1399
+ }
1400
+ this.applyPanelWidth(width, this.isWidthSliderActive);
1401
+ }
1402
+ /**
1403
+ * Apply panel width (clamped to min/max)
1404
+ */
1405
+ applyPanelWidth(width, immediate = false) {
1406
+ const clamped = Math.round(Math.min(this.maxPanelWidth, Math.max(this.minPanelWidth, width)));
1407
+ const applyWidth = () => {
1408
+ this.state.panelWidth = clamped;
1409
+ const px = `${clamped}px`;
1410
+ this.panel.style.width = px;
1411
+ this.updateWidthDisplay();
1412
+ };
1413
+ if (immediate) {
1414
+ applyWidth();
1415
+ return;
1416
+ }
1417
+ if (this.widthFrame) {
1418
+ cancelAnimationFrame(this.widthFrame);
1419
+ }
1420
+ this.widthFrame = requestAnimationFrame(() => {
1421
+ applyWidth();
1422
+ this.widthFrame = null;
1423
+ });
1424
+ }
1425
+ /**
1426
+ * Update width display (value label and thumb position)
1427
+ */
1428
+ updateWidthDisplay() {
1429
+ if (this.widthValueEl) {
1430
+ this.widthValueEl.textContent = `${this.state.panelWidth}px`;
1431
+ }
1432
+ if (this.widthSliderEl) {
1433
+ this.widthSliderEl.setAttribute("aria-valuenow", String(this.state.panelWidth));
1434
+ const ratio = (this.state.panelWidth - this.minPanelWidth) / (this.maxPanelWidth - this.minPanelWidth || 1);
1435
+ if (this.widthThumbEl) {
1436
+ const sliderWidth = this.widthSliderEl.clientWidth;
1437
+ if (sliderWidth === 0) {
1438
+ requestAnimationFrame(() => this.updateWidthDisplay());
1439
+ return;
1440
+ }
1441
+ const thumbWidth = this.widthThumbEl.offsetWidth || 14;
1442
+ const padding = 16;
1443
+ const available = Math.max(0, sliderWidth - padding - thumbWidth);
1444
+ const clampedRatio = Math.min(1, Math.max(0, ratio));
1445
+ const leftPx = 8 + available * clampedRatio;
1446
+ this.widthThumbEl.style.left = `${leftPx}px`;
1447
+ }
1448
+ }
1449
+ }
1450
+ /**
1451
+ * Setup main event listeners
1452
+ */
1453
+ setupEventListeners() {
1454
+ this.button.addEventListener("click", () => this.toggle());
1455
+ document.addEventListener("click", (e) => {
1456
+ const target = e.target;
1457
+ if (this.contextMenuEl && this.state.contextMenu.visible) {
1458
+ if (!this.contextMenuEl.contains(target)) {
1459
+ this.hideContextMenu();
1460
+ }
1461
+ }
1462
+ if (!this.container.contains(target) && !this.panel.contains(target)) {
1463
+ this.collapse();
1464
+ }
1465
+ });
1466
+ this.resizeHandler = () => {
1467
+ if (!this.state.collapsed) {
1468
+ this.updatePanelPosition();
1469
+ }
1470
+ };
1471
+ window.addEventListener("resize", this.resizeHandler);
1472
+ this.mapResizeHandler = () => {
1473
+ if (!this.state.collapsed) {
1474
+ this.updatePanelPosition();
1475
+ }
1476
+ };
1477
+ this.map.on("resize", this.mapResizeHandler);
1478
+ this.setupLayerChangeListeners();
1479
+ }
1480
+ /**
1481
+ * Setup listeners for map layer changes
1482
+ */
1483
+ setupLayerChangeListeners() {
1484
+ this.map.on("styledata", () => {
1485
+ setTimeout(() => {
1486
+ this.updateLayerStatesFromMap();
1487
+ this.checkForNewLayers();
1488
+ }, 100);
1489
+ });
1490
+ this.map.on("data", (e) => {
1491
+ if (e.sourceDataType === "content") {
1492
+ setTimeout(() => {
1493
+ this.updateLayerStatesFromMap();
1494
+ this.checkForNewLayers();
1495
+ }, 100);
1496
+ }
1497
+ });
1498
+ this.map.on("sourcedata", (e) => {
1499
+ if (e.sourceDataType === "metadata") {
1500
+ setTimeout(() => {
1501
+ this.checkForNewLayers();
1502
+ }, 150);
1503
+ }
1504
+ });
1505
+ if (this.customLayerRegistry) {
1506
+ this.customLayerUnsubscribe = this.customLayerRegistry.onChange((event, layerId) => {
1507
+ if (event === "add" && layerId) {
1508
+ this.removedCustomLayerIds.delete(layerId);
1509
+ }
1510
+ setTimeout(() => this.checkForNewLayers(), 100);
1511
+ });
1512
+ }
1513
+ }
1514
+ /**
1515
+ * Toggle panel expanded/collapsed state
1516
+ */
1517
+ toggle() {
1518
+ if (this.state.collapsed) {
1519
+ this.expand();
1520
+ } else {
1521
+ this.collapse();
1522
+ }
1523
+ }
1524
+ /**
1525
+ * Expand the panel
1526
+ */
1527
+ expand() {
1528
+ this.state.collapsed = false;
1529
+ this.panel.classList.add("expanded");
1530
+ this.updatePanelPosition();
1531
+ }
1532
+ /**
1533
+ * Collapse the panel
1534
+ */
1535
+ collapse() {
1536
+ this.state.collapsed = true;
1537
+ this.panel.classList.remove("expanded");
1538
+ }
1539
+ /**
1540
+ * Build layer items (called initially and when layers change)
1541
+ */
1542
+ buildLayerItems() {
1543
+ const existingItems = this.panel.querySelectorAll(".layer-control-item");
1544
+ existingItems.forEach((item) => item.remove());
1545
+ this.styleEditors.clear();
1546
+ Object.entries(this.state.layerStates).forEach(([layerId, state]) => {
1547
+ if (this.targetLayers.length === 0 || this.targetLayers.includes(layerId)) {
1548
+ this.addLayerItem(layerId, state);
1549
+ }
1550
+ });
1551
+ }
1552
+ /**
1553
+ * Add a single layer item to the panel
1554
+ */
1555
+ addLayerItem(layerId, state) {
1556
+ const item = document.createElement("div");
1557
+ item.className = "layer-control-item";
1558
+ item.setAttribute("data-layer-id", layerId);
1559
+ const row = document.createElement("div");
1560
+ row.className = "layer-control-row";
1561
+ if (this.enableDragAndDrop) {
1562
+ if (layerId === "Background") {
1563
+ const disabledHandle = this.createDisabledDragHandle();
1564
+ row.appendChild(disabledHandle);
1565
+ } else {
1566
+ const dragHandle = this.createDragHandle(layerId);
1567
+ row.appendChild(dragHandle);
1568
+ }
1569
+ }
1570
+ const checkbox = document.createElement("input");
1571
+ checkbox.type = "checkbox";
1572
+ checkbox.className = "layer-control-checkbox";
1573
+ checkbox.checked = state.visible;
1574
+ checkbox.addEventListener("change", () => {
1575
+ this.toggleLayerVisibility(layerId, checkbox.checked);
1576
+ });
1577
+ const displayName = this.state.customLayerNames.get(layerId) || state.name || layerId;
1578
+ const name = document.createElement("span");
1579
+ name.className = "layer-control-name";
1580
+ name.textContent = displayName;
1581
+ name.title = displayName;
1582
+ row.appendChild(checkbox);
1583
+ if (this.showLayerSymbol) {
1584
+ if (layerId === "Background") {
1585
+ const symbol = this.createBackgroundGroupSymbol();
1586
+ row.appendChild(symbol);
1587
+ } else {
1588
+ const symbol = this.createLayerSymbol(layerId);
1589
+ if (symbol) {
1590
+ row.appendChild(symbol);
1591
+ }
1592
+ }
1593
+ }
1594
+ row.appendChild(name);
1595
+ if (this.showOpacitySlider) {
1596
+ const opacity = document.createElement("input");
1597
+ opacity.type = "range";
1598
+ opacity.className = "layer-control-opacity";
1599
+ opacity.min = "0";
1600
+ opacity.max = "1";
1601
+ opacity.step = "0.01";
1602
+ opacity.value = String(state.opacity);
1603
+ opacity.title = `Opacity: ${Math.round(state.opacity * 100)}%`;
1604
+ opacity.addEventListener("mousedown", () => {
1605
+ this.state.userInteractingWithSlider = true;
1606
+ });
1607
+ opacity.addEventListener("mouseup", () => {
1608
+ this.state.userInteractingWithSlider = false;
1609
+ });
1610
+ opacity.addEventListener("input", () => {
1611
+ this.changeLayerOpacity(layerId, parseFloat(opacity.value));
1612
+ opacity.title = `Opacity: ${Math.round(parseFloat(opacity.value) * 100)}%`;
1613
+ });
1614
+ row.appendChild(opacity);
1615
+ }
1616
+ if (this.showStyleEditor) {
1617
+ if (layerId === "Background") {
1618
+ const legendButton = this.createBackgroundLegendButton();
1619
+ row.appendChild(legendButton);
1620
+ } else {
1621
+ const styleButton = this.createStyleButton(layerId);
1622
+ if (styleButton) {
1623
+ row.appendChild(styleButton);
1624
+ }
1625
+ }
1626
+ }
1627
+ item.appendChild(row);
1628
+ if (this.enableContextMenu && layerId !== "Background") {
1629
+ row.addEventListener("contextmenu", (e) => {
1630
+ e.preventDefault();
1631
+ e.stopPropagation();
1632
+ this.showContextMenu(layerId, e.clientX, e.clientY);
1633
+ });
1634
+ }
1635
+ this.panel.appendChild(item);
1636
+ }
1637
+ /**
1638
+ * Create a symbol element for a layer
1639
+ * @param layerId The layer ID
1640
+ * @returns The symbol HTML element, or null if layer not found
1641
+ */
1642
+ createLayerSymbol(layerId) {
1643
+ const layerState = this.state.layerStates[layerId];
1644
+ if (layerState == null ? void 0 : layerState.isCustomLayer) {
1645
+ const symbolType = layerState.customLayerType || "custom-raster";
1646
+ const color2 = "#4a90d9";
1647
+ const svgMarkup2 = createLayerSymbolSVG(symbolType, color2);
1648
+ const symbolContainer2 = document.createElement("span");
1649
+ symbolContainer2.className = "layer-control-symbol";
1650
+ symbolContainer2.innerHTML = svgMarkup2;
1651
+ symbolContainer2.title = `Layer type: ${symbolType}`;
1652
+ return symbolContainer2;
1653
+ }
1654
+ const layer = this.map.getLayer(layerId);
1655
+ if (!layer) return null;
1656
+ const layerType = layer.type;
1657
+ const color = getLayerColor(this.map, layerId, layerType);
1658
+ const svgMarkup = createLayerSymbolSVG(layerType, color);
1659
+ const symbolContainer = document.createElement("span");
1660
+ symbolContainer.className = "layer-control-symbol";
1661
+ symbolContainer.innerHTML = svgMarkup;
1662
+ symbolContainer.title = `Layer type: ${layerType}`;
1663
+ return symbolContainer;
1664
+ }
1665
+ /**
1666
+ * Create a symbol element for a background layer
1667
+ * @param layer The layer specification
1668
+ * @returns The symbol HTML element
1669
+ */
1670
+ createBackgroundLayerSymbol(layer) {
1671
+ const color = getLayerColorFromSpec(layer);
1672
+ const svgMarkup = createLayerSymbolSVG(layer.type, color, { size: 14 });
1673
+ const symbolContainer = document.createElement("span");
1674
+ symbolContainer.className = "background-legend-layer-symbol";
1675
+ symbolContainer.innerHTML = svgMarkup;
1676
+ symbolContainer.title = `Layer type: ${layer.type}`;
1677
+ return symbolContainer;
1678
+ }
1679
+ /**
1680
+ * Create a symbol element for the Background layer group
1681
+ * Shows a stacked layers icon to represent multiple background layers
1682
+ * @returns The symbol HTML element
1683
+ */
1684
+ createBackgroundGroupSymbol() {
1685
+ const svgMarkup = createBackgroundGroupSymbolSVG(16);
1686
+ const symbolContainer = document.createElement("span");
1687
+ symbolContainer.className = "layer-control-symbol";
1688
+ symbolContainer.innerHTML = svgMarkup;
1689
+ symbolContainer.title = "Background layers";
1690
+ return symbolContainer;
1691
+ }
1692
+ /**
1693
+ * Toggle layer visibility
1694
+ */
1695
+ toggleLayerVisibility(layerId, visible) {
1696
+ var _a;
1697
+ if (layerId === "Background") {
1698
+ this.toggleBackgroundVisibility(visible);
1699
+ return;
1700
+ }
1701
+ this.state.isStyleOperationInProgress = true;
1702
+ if (this.state.layerStates[layerId]) {
1703
+ this.state.layerStates[layerId].visible = visible;
1704
+ }
1705
+ if ((_a = this.customLayerRegistry) == null ? void 0 : _a.setVisibility(layerId, visible)) {
1706
+ setTimeout(() => {
1707
+ this.state.isStyleOperationInProgress = false;
1708
+ }, 200);
1709
+ return;
1710
+ }
1711
+ this.map.setLayoutProperty(layerId, "visibility", visible ? "visible" : "none");
1712
+ setTimeout(() => {
1713
+ this.state.isStyleOperationInProgress = false;
1714
+ }, 200);
1715
+ }
1716
+ /**
1717
+ * Change layer opacity
1718
+ */
1719
+ changeLayerOpacity(layerId, opacity) {
1720
+ var _a;
1721
+ if (layerId === "Background") {
1722
+ this.changeBackgroundOpacity(opacity);
1723
+ return;
1724
+ }
1725
+ this.state.isStyleOperationInProgress = true;
1726
+ if (this.state.layerStates[layerId]) {
1727
+ this.state.layerStates[layerId].opacity = opacity;
1728
+ }
1729
+ if ((_a = this.customLayerRegistry) == null ? void 0 : _a.setOpacity(layerId, opacity)) {
1730
+ setTimeout(() => {
1731
+ this.state.isStyleOperationInProgress = false;
1732
+ }, 200);
1733
+ return;
1734
+ }
1735
+ const layerType = getLayerType(this.map, layerId);
1736
+ if (layerType) {
1737
+ setLayerOpacity(this.map, layerId, layerType, opacity);
1738
+ }
1739
+ setTimeout(() => {
1740
+ this.state.isStyleOperationInProgress = false;
1741
+ }, 200);
1742
+ }
1743
+ /**
1744
+ * Check if a layer is a user-added layer (vs basemap layer)
1745
+ * Used primarily for the background legend to determine which layers are background
1746
+ */
1747
+ isUserAddedLayer(layerId) {
1748
+ if (this.state.layerStates[layerId] !== void 0 && layerId !== "Background") {
1749
+ return true;
1750
+ }
1751
+ if (this.basemapLayerIds !== null && this.basemapLayerIds.size > 0) {
1752
+ return !this.basemapLayerIds.has(layerId);
1753
+ }
1754
+ if (this.initialLayerIds !== null && !this.initialLayerIds.has(layerId)) {
1755
+ return true;
1756
+ }
1757
+ const layer = this.map.getLayer(layerId);
1758
+ if (layer) {
1759
+ const sourceId = layer.source;
1760
+ if (sourceId) {
1761
+ const userAddedSourceIds = this.detectUserAddedSources();
1762
+ return userAddedSourceIds.has(sourceId);
1763
+ }
1764
+ }
1765
+ return false;
1766
+ }
1767
+ /**
1768
+ * Toggle visibility for all background layers (basemap layers)
1769
+ */
1770
+ toggleBackgroundVisibility(visible) {
1771
+ if (this.state.layerStates["Background"]) {
1772
+ this.state.layerStates["Background"].visible = visible;
1773
+ }
1774
+ const styleLayers = this.map.getStyle().layers || [];
1775
+ styleLayers.forEach((layer) => {
1776
+ if (!this.isUserAddedLayer(layer.id)) {
1777
+ this.state.backgroundLayerVisibility.set(layer.id, visible);
1778
+ this.map.setLayoutProperty(layer.id, "visibility", visible ? "visible" : "none");
1779
+ }
1780
+ });
1781
+ if (this.state.backgroundLegendOpen) {
1782
+ const legendPanel = this.panel.querySelector(".layer-control-background-legend");
1783
+ if (legendPanel) {
1784
+ const checkboxes = legendPanel.querySelectorAll(".background-legend-checkbox");
1785
+ checkboxes.forEach((checkbox) => {
1786
+ checkbox.checked = visible;
1787
+ });
1788
+ }
1789
+ }
1790
+ }
1791
+ /**
1792
+ * Change opacity for all background layers (basemap layers)
1793
+ */
1794
+ changeBackgroundOpacity(opacity) {
1795
+ if (this.state.layerStates["Background"]) {
1796
+ this.state.layerStates["Background"].opacity = opacity;
1797
+ }
1798
+ const styleLayers = this.map.getStyle().layers || [];
1799
+ styleLayers.forEach((styleLayer) => {
1800
+ if (!this.isUserAddedLayer(styleLayer.id)) {
1801
+ const layerType = getLayerType(this.map, styleLayer.id);
1802
+ if (layerType) {
1803
+ setLayerOpacity(this.map, styleLayer.id, layerType, opacity);
1804
+ }
1805
+ }
1806
+ });
1807
+ }
1808
+ // ===== Background Legend Methods =====
1809
+ /**
1810
+ * Create legend button for Background layer
1811
+ */
1812
+ createBackgroundLegendButton() {
1813
+ const button = document.createElement("button");
1814
+ button.className = "layer-control-style-button layer-control-background-legend-button";
1815
+ button.innerHTML = "&#9881;";
1816
+ button.title = "Show background layer details";
1817
+ button.setAttribute("aria-label", "Show background layer visibility controls");
1818
+ button.setAttribute("aria-expanded", String(this.state.backgroundLegendOpen));
1819
+ button.addEventListener("click", (e) => {
1820
+ e.stopPropagation();
1821
+ this.toggleBackgroundLegend();
1822
+ });
1823
+ return button;
1824
+ }
1825
+ /**
1826
+ * Toggle background legend panel visibility
1827
+ */
1828
+ toggleBackgroundLegend() {
1829
+ if (this.state.backgroundLegendOpen) {
1830
+ this.closeBackgroundLegend();
1831
+ } else {
1832
+ this.openBackgroundLegend();
1833
+ }
1834
+ }
1835
+ /**
1836
+ * Open background legend panel
1837
+ */
1838
+ openBackgroundLegend() {
1839
+ if (this.state.activeStyleEditor) {
1840
+ this.closeStyleEditor(this.state.activeStyleEditor);
1841
+ }
1842
+ const itemEl = this.panel.querySelector('[data-layer-id="Background"]');
1843
+ if (!itemEl) return;
1844
+ let legendPanel = itemEl.querySelector(".layer-control-background-legend");
1845
+ if (legendPanel) {
1846
+ const layerList = legendPanel.querySelector(".background-legend-layer-list");
1847
+ if (layerList) {
1848
+ this.populateBackgroundLayerList(layerList);
1849
+ }
1850
+ } else {
1851
+ legendPanel = this.createBackgroundLegendPanel();
1852
+ itemEl.appendChild(legendPanel);
1853
+ }
1854
+ this.state.backgroundLegendOpen = true;
1855
+ const button = itemEl.querySelector(".layer-control-background-legend-button");
1856
+ if (button) {
1857
+ button.setAttribute("aria-expanded", "true");
1858
+ button.classList.add("active");
1859
+ }
1860
+ setTimeout(() => {
1861
+ legendPanel == null ? void 0 : legendPanel.scrollIntoView({ behavior: "smooth", block: "nearest" });
1862
+ }, 50);
1863
+ }
1864
+ /**
1865
+ * Close background legend panel
1866
+ */
1867
+ closeBackgroundLegend() {
1868
+ const itemEl = this.panel.querySelector('[data-layer-id="Background"]');
1869
+ if (!itemEl) return;
1870
+ const legendPanel = itemEl.querySelector(".layer-control-background-legend");
1871
+ if (legendPanel) {
1872
+ legendPanel.remove();
1873
+ }
1874
+ this.state.backgroundLegendOpen = false;
1875
+ const button = itemEl.querySelector(".layer-control-background-legend-button");
1876
+ if (button) {
1877
+ button.setAttribute("aria-expanded", "false");
1878
+ button.classList.remove("active");
1879
+ }
1880
+ }
1881
+ /**
1882
+ * Create the background legend panel with individual layer controls
1883
+ */
1884
+ createBackgroundLegendPanel() {
1885
+ const panel = document.createElement("div");
1886
+ panel.className = "layer-control-background-legend";
1887
+ const header = document.createElement("div");
1888
+ header.className = "background-legend-header";
1889
+ const title = document.createElement("span");
1890
+ title.className = "background-legend-title";
1891
+ title.textContent = "Background Layers";
1892
+ const closeBtn = document.createElement("button");
1893
+ closeBtn.className = "background-legend-close";
1894
+ closeBtn.innerHTML = "&times;";
1895
+ closeBtn.title = "Close";
1896
+ closeBtn.addEventListener("click", (e) => {
1897
+ e.stopPropagation();
1898
+ this.closeBackgroundLegend();
1899
+ });
1900
+ header.appendChild(title);
1901
+ header.appendChild(closeBtn);
1902
+ const actionsRow = document.createElement("div");
1903
+ actionsRow.className = "background-legend-actions";
1904
+ const showAllBtn = document.createElement("button");
1905
+ showAllBtn.className = "background-legend-action-btn";
1906
+ showAllBtn.textContent = "Show All";
1907
+ showAllBtn.addEventListener("click", () => this.setAllBackgroundLayersVisibility(true));
1908
+ const hideAllBtn = document.createElement("button");
1909
+ hideAllBtn.className = "background-legend-action-btn";
1910
+ hideAllBtn.textContent = "Hide All";
1911
+ hideAllBtn.addEventListener("click", () => this.setAllBackgroundLayersVisibility(false));
1912
+ actionsRow.appendChild(showAllBtn);
1913
+ actionsRow.appendChild(hideAllBtn);
1914
+ const filterRow = document.createElement("div");
1915
+ filterRow.className = "background-legend-filter";
1916
+ const filterCheckbox = document.createElement("input");
1917
+ filterCheckbox.type = "checkbox";
1918
+ filterCheckbox.className = "background-legend-filter-checkbox";
1919
+ filterCheckbox.id = "background-legend-only-rendered";
1920
+ filterCheckbox.checked = this.state.onlyRenderedFilter;
1921
+ filterCheckbox.addEventListener("change", () => {
1922
+ this.state.onlyRenderedFilter = filterCheckbox.checked;
1923
+ const layerList2 = panel.querySelector(".background-legend-layer-list");
1924
+ if (layerList2) {
1925
+ this.populateBackgroundLayerList(layerList2);
1926
+ }
1927
+ });
1928
+ const filterLabel = document.createElement("label");
1929
+ filterLabel.className = "background-legend-filter-label";
1930
+ filterLabel.htmlFor = "background-legend-only-rendered";
1931
+ filterLabel.textContent = "Only rendered";
1932
+ filterRow.appendChild(filterCheckbox);
1933
+ filterRow.appendChild(filterLabel);
1934
+ const layerList = document.createElement("div");
1935
+ layerList.className = "background-legend-layer-list";
1936
+ this.populateBackgroundLayerList(layerList);
1937
+ panel.appendChild(header);
1938
+ panel.appendChild(actionsRow);
1939
+ panel.appendChild(filterRow);
1940
+ panel.appendChild(layerList);
1941
+ return panel;
1942
+ }
1943
+ /**
1944
+ * Check if a layer is currently rendered in the map viewport
1945
+ */
1946
+ isLayerRendered(layerId) {
1947
+ try {
1948
+ const layer = this.map.getLayer(layerId);
1949
+ if (!layer) return false;
1950
+ const visibility = this.map.getLayoutProperty(layerId, "visibility");
1951
+ if (visibility === "none") return false;
1952
+ if (layer.type === "raster" || layer.type === "hillshade") {
1953
+ return true;
1954
+ }
1955
+ if (layer.type === "background") {
1956
+ return true;
1957
+ }
1958
+ const features = this.map.queryRenderedFeatures({ layers: [layerId] });
1959
+ return features.length > 0;
1960
+ } catch (error) {
1961
+ return true;
1962
+ }
1963
+ }
1964
+ /**
1965
+ * Populate the background layer list with individual layers
1966
+ */
1967
+ populateBackgroundLayerList(container) {
1968
+ container.innerHTML = "";
1969
+ const styleLayers = this.map.getStyle().layers || [];
1970
+ styleLayers.forEach((layer) => {
1971
+ if (!this.isUserAddedLayer(layer.id)) {
1972
+ if (this.excludeDrawnLayers && this.isDrawnLayer(layer.id)) {
1973
+ return;
1974
+ }
1975
+ if (this.isExcludedByPattern(layer.id)) {
1976
+ return;
1977
+ }
1978
+ if (this.state.onlyRenderedFilter && !this.isLayerRendered(layer.id)) {
1979
+ return;
1980
+ }
1981
+ const layerRow = document.createElement("div");
1982
+ layerRow.className = "background-legend-layer-row";
1983
+ layerRow.setAttribute("data-background-layer-id", layer.id);
1984
+ const checkbox = document.createElement("input");
1985
+ checkbox.type = "checkbox";
1986
+ checkbox.className = "background-legend-checkbox";
1987
+ const visibility = this.map.getLayoutProperty(layer.id, "visibility");
1988
+ const isVisible = visibility !== "none";
1989
+ checkbox.checked = isVisible;
1990
+ this.state.backgroundLayerVisibility.set(layer.id, isVisible);
1991
+ checkbox.addEventListener("change", () => {
1992
+ this.toggleIndividualBackgroundLayer(layer.id, checkbox.checked);
1993
+ });
1994
+ const name = document.createElement("span");
1995
+ name.className = "background-legend-layer-name";
1996
+ name.textContent = this.generateFriendlyName(layer.id);
1997
+ name.title = layer.id;
1998
+ const typeIndicator = document.createElement("span");
1999
+ typeIndicator.className = "background-legend-layer-type";
2000
+ typeIndicator.textContent = layer.type;
2001
+ layerRow.appendChild(checkbox);
2002
+ if (this.showLayerSymbol) {
2003
+ const symbol = this.createBackgroundLayerSymbol(layer);
2004
+ if (symbol) {
2005
+ layerRow.appendChild(symbol);
2006
+ }
2007
+ }
2008
+ layerRow.appendChild(name);
2009
+ layerRow.appendChild(typeIndicator);
2010
+ container.appendChild(layerRow);
2011
+ }
2012
+ });
2013
+ if (container.children.length === 0) {
2014
+ const emptyMsg = document.createElement("p");
2015
+ emptyMsg.className = "background-legend-empty";
2016
+ emptyMsg.textContent = this.state.onlyRenderedFilter ? "No rendered layers in current view." : "No background layers found.";
2017
+ container.appendChild(emptyMsg);
2018
+ }
2019
+ }
2020
+ /**
2021
+ * Toggle visibility of an individual background layer
2022
+ */
2023
+ toggleIndividualBackgroundLayer(layerId, visible) {
2024
+ this.state.backgroundLayerVisibility.set(layerId, visible);
2025
+ this.map.setLayoutProperty(layerId, "visibility", visible ? "visible" : "none");
2026
+ this.updateBackgroundCheckboxState();
2027
+ }
2028
+ /**
2029
+ * Set visibility for all background layers
2030
+ */
2031
+ setAllBackgroundLayersVisibility(visible) {
2032
+ const styleLayers = this.map.getStyle().layers || [];
2033
+ styleLayers.forEach((layer) => {
2034
+ if (!this.isUserAddedLayer(layer.id)) {
2035
+ this.state.backgroundLayerVisibility.set(layer.id, visible);
2036
+ this.map.setLayoutProperty(layer.id, "visibility", visible ? "visible" : "none");
2037
+ }
2038
+ });
2039
+ const legendPanel = this.panel.querySelector(".layer-control-background-legend");
2040
+ if (legendPanel) {
2041
+ const checkboxes = legendPanel.querySelectorAll(".background-legend-checkbox");
2042
+ checkboxes.forEach((checkbox) => {
2043
+ checkbox.checked = visible;
2044
+ });
2045
+ }
2046
+ this.updateBackgroundCheckboxState();
2047
+ }
2048
+ /**
2049
+ * Update the main Background checkbox based on individual layer states
2050
+ */
2051
+ updateBackgroundCheckboxState() {
2052
+ const styleLayers = this.map.getStyle().layers || [];
2053
+ let anyVisible = false;
2054
+ let allVisible = true;
2055
+ styleLayers.forEach((layer) => {
2056
+ if (!this.isUserAddedLayer(layer.id)) {
2057
+ const visible = this.state.backgroundLayerVisibility.get(layer.id);
2058
+ if (visible === true) anyVisible = true;
2059
+ if (visible === false) allVisible = false;
2060
+ }
2061
+ });
2062
+ const backgroundItem = this.panel.querySelector('[data-layer-id="Background"]');
2063
+ if (backgroundItem) {
2064
+ const checkbox = backgroundItem.querySelector(".layer-control-checkbox");
2065
+ if (checkbox) {
2066
+ checkbox.checked = anyVisible;
2067
+ checkbox.indeterminate = anyVisible && !allVisible;
2068
+ }
2069
+ }
2070
+ if (this.state.layerStates["Background"]) {
2071
+ this.state.layerStates["Background"].visible = anyVisible;
2072
+ }
2073
+ }
2074
+ /**
2075
+ * Create style button for a layer
2076
+ */
2077
+ createStyleButton(layerId) {
2078
+ if (layerId === "Background") {
2079
+ return null;
2080
+ }
2081
+ const layerState = this.state.layerStates[layerId];
2082
+ const isCustomLayer = (layerState == null ? void 0 : layerState.isCustomLayer) === true;
2083
+ const button = document.createElement("button");
2084
+ button.className = "layer-control-style-button";
2085
+ button.innerHTML = "&#9881;";
2086
+ if (isCustomLayer) {
2087
+ button.title = "Layer info (style editing not available)";
2088
+ button.setAttribute("aria-label", `Layer info for ${layerId}`);
2089
+ } else {
2090
+ button.title = "Edit layer style";
2091
+ button.setAttribute("aria-label", `Edit style for ${layerId}`);
2092
+ }
2093
+ button.addEventListener("click", (e) => {
2094
+ e.stopPropagation();
2095
+ this.toggleStyleEditor(layerId);
2096
+ });
2097
+ return button;
2098
+ }
2099
+ /**
2100
+ * Toggle style editor for a layer
2101
+ */
2102
+ toggleStyleEditor(layerId) {
2103
+ if (this.state.activeStyleEditor === layerId) {
2104
+ this.closeStyleEditor(layerId);
2105
+ return;
2106
+ }
2107
+ if (this.state.activeStyleEditor) {
2108
+ this.closeStyleEditor(this.state.activeStyleEditor);
2109
+ }
2110
+ this.openStyleEditor(layerId);
2111
+ }
2112
+ /**
2113
+ * Open style editor for a layer
2114
+ */
2115
+ openStyleEditor(layerId) {
2116
+ var _a;
2117
+ const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
2118
+ if (!itemEl) return;
2119
+ const layerState = this.state.layerStates[layerId];
2120
+ if (layerState == null ? void 0 : layerState.isCustomLayer) {
2121
+ const nativeLayerIds = (_a = this.customLayerRegistry) == null ? void 0 : _a.getNativeLayerIds(layerId);
2122
+ if (nativeLayerIds && nativeLayerIds.length > 0) {
2123
+ for (const nativeId of nativeLayerIds) {
2124
+ if (!this.state.originalStyles.has(nativeId)) {
2125
+ const nativeLayer = this.map.getLayer(nativeId);
2126
+ if (nativeLayer) {
2127
+ cacheOriginalLayerStyle(this.map, nativeId, this.state.originalStyles);
2128
+ }
2129
+ }
2130
+ }
2131
+ const editor3 = this.createNativeSubLayerStyleEditor(layerId, nativeLayerIds);
2132
+ if (editor3) {
2133
+ itemEl.appendChild(editor3);
2134
+ this.styleEditors.set(layerId, editor3);
2135
+ this.state.activeStyleEditor = layerId;
2136
+ setTimeout(() => {
2137
+ editor3.scrollIntoView({ behavior: "smooth", block: "nearest" });
2138
+ }, 50);
2139
+ return;
2140
+ }
2141
+ }
2142
+ const editor2 = this.createCustomLayerInfoPanel(layerId);
2143
+ itemEl.appendChild(editor2);
2144
+ this.styleEditors.set(layerId, editor2);
2145
+ this.state.activeStyleEditor = layerId;
2146
+ setTimeout(() => {
2147
+ editor2.scrollIntoView({ behavior: "smooth", block: "nearest" });
2148
+ }, 50);
2149
+ return;
2150
+ }
2151
+ if (!this.state.originalStyles.has(layerId)) {
2152
+ const layer = this.map.getLayer(layerId);
2153
+ if (layer) {
2154
+ cacheOriginalLayerStyle(this.map, layerId, this.state.originalStyles);
2155
+ }
2156
+ }
2157
+ const editor = this.createStyleEditor(layerId);
2158
+ if (!editor) return;
2159
+ itemEl.appendChild(editor);
2160
+ this.styleEditors.set(layerId, editor);
2161
+ this.state.activeStyleEditor = layerId;
2162
+ setTimeout(() => {
2163
+ editor.scrollIntoView({ behavior: "smooth", block: "nearest" });
2164
+ }, 50);
2165
+ }
2166
+ /**
2167
+ * Create info panel for custom layers (style editing not supported)
2168
+ */
2169
+ createCustomLayerInfoPanel(layerId) {
2170
+ const editor = document.createElement("div");
2171
+ editor.className = "layer-control-style-editor layer-control-custom-info";
2172
+ const header = document.createElement("div");
2173
+ header.className = "style-editor-header";
2174
+ const title = document.createElement("span");
2175
+ title.className = "style-editor-title";
2176
+ title.textContent = "Layer Info";
2177
+ const closeBtn = document.createElement("button");
2178
+ closeBtn.className = "style-editor-close";
2179
+ closeBtn.innerHTML = "&times;";
2180
+ closeBtn.title = "Close";
2181
+ closeBtn.addEventListener("click", (e) => {
2182
+ e.stopPropagation();
2183
+ this.closeStyleEditor(layerId);
2184
+ });
2185
+ header.appendChild(title);
2186
+ header.appendChild(closeBtn);
2187
+ const content = document.createElement("div");
2188
+ content.className = "style-editor-controls";
2189
+ const infoText = document.createElement("p");
2190
+ infoText.className = "layer-control-custom-info-text";
2191
+ infoText.textContent = `This is a custom layer. Style editing is not available for this layer type. Use the visibility toggle and opacity slider to control the layer.`;
2192
+ content.appendChild(infoText);
2193
+ const actions = document.createElement("div");
2194
+ actions.className = "style-editor-actions";
2195
+ const removeBtn = document.createElement("button");
2196
+ removeBtn.className = "style-editor-button style-editor-button-remove";
2197
+ removeBtn.textContent = "Remove";
2198
+ removeBtn.title = "Remove layer from map";
2199
+ removeBtn.addEventListener("click", (e) => {
2200
+ e.stopPropagation();
2201
+ if (confirm("Are you sure you want to remove this layer?")) {
2202
+ this.closeStyleEditor(layerId);
2203
+ this.removeLayer(layerId);
2204
+ }
2205
+ });
2206
+ const closeActionBtn = document.createElement("button");
2207
+ closeActionBtn.className = "style-editor-button style-editor-button-close";
2208
+ closeActionBtn.textContent = "Close";
2209
+ closeActionBtn.addEventListener("click", (e) => {
2210
+ e.stopPropagation();
2211
+ this.closeStyleEditor(layerId);
2212
+ });
2213
+ actions.appendChild(removeBtn);
2214
+ actions.appendChild(closeActionBtn);
2215
+ editor.appendChild(header);
2216
+ editor.appendChild(content);
2217
+ editor.appendChild(actions);
2218
+ return editor;
2219
+ }
2220
+ /**
2221
+ * Create a combined style editor for custom layers with native MapLibre sublayers.
2222
+ * Groups controls by sublayer type (fill, line, circle, etc.).
2223
+ */
2224
+ createNativeSubLayerStyleEditor(layerId, nativeLayerIds) {
2225
+ const layersByType = /* @__PURE__ */ new Map();
2226
+ for (const nativeId of nativeLayerIds) {
2227
+ const layer = this.map.getLayer(nativeId);
2228
+ if (layer) {
2229
+ const type = layer.type;
2230
+ if (!layersByType.has(type)) {
2231
+ layersByType.set(type, []);
2232
+ }
2233
+ layersByType.get(type).push(nativeId);
2234
+ }
2235
+ }
2236
+ if (layersByType.size === 0) return null;
2237
+ const editor = document.createElement("div");
2238
+ editor.className = "layer-control-style-editor";
2239
+ const header = document.createElement("div");
2240
+ header.className = "style-editor-header";
2241
+ const title = document.createElement("span");
2242
+ title.className = "style-editor-title";
2243
+ title.textContent = "Edit Style";
2244
+ const closeBtn = document.createElement("button");
2245
+ closeBtn.className = "style-editor-close";
2246
+ closeBtn.innerHTML = "&times;";
2247
+ closeBtn.title = "Close";
2248
+ closeBtn.addEventListener("click", (e) => {
2249
+ e.stopPropagation();
2250
+ this.closeStyleEditor(layerId);
2251
+ });
2252
+ header.appendChild(title);
2253
+ header.appendChild(closeBtn);
2254
+ editor.appendChild(header);
2255
+ const controls = document.createElement("div");
2256
+ controls.className = "style-editor-controls";
2257
+ const typeLabels = {
2258
+ fill: "Fill",
2259
+ line: "Line",
2260
+ circle: "Circle",
2261
+ symbol: "Symbol",
2262
+ raster: "Raster"
2263
+ };
2264
+ for (const [type, ids] of layersByType) {
2265
+ if (layersByType.size > 1) {
2266
+ const sectionHeader = document.createElement("div");
2267
+ sectionHeader.className = "style-editor-section-header";
2268
+ sectionHeader.textContent = typeLabels[type] || type;
2269
+ controls.appendChild(sectionHeader);
2270
+ }
2271
+ const primaryId = ids[0];
2272
+ this.addStyleControlsForNativeGroup(controls, ids, primaryId, type);
2273
+ }
2274
+ const actions = document.createElement("div");
2275
+ actions.className = "style-editor-actions";
2276
+ const resetBtn = document.createElement("button");
2277
+ resetBtn.className = "style-editor-button style-editor-button-reset";
2278
+ resetBtn.textContent = "Reset";
2279
+ resetBtn.addEventListener("click", (e) => {
2280
+ e.stopPropagation();
2281
+ for (const nativeId of nativeLayerIds) {
2282
+ restoreOriginalStyle(this.map, nativeId, this.state.originalStyles);
2283
+ }
2284
+ this.closeStyleEditor(layerId);
2285
+ this.openStyleEditor(layerId);
2286
+ });
2287
+ const removeBtn = document.createElement("button");
2288
+ removeBtn.className = "style-editor-button style-editor-button-remove";
2289
+ removeBtn.textContent = "Remove";
2290
+ removeBtn.title = "Remove layer from map";
2291
+ removeBtn.addEventListener("click", (e) => {
2292
+ e.stopPropagation();
2293
+ if (confirm("Are you sure you want to remove this layer?")) {
2294
+ this.closeStyleEditor(layerId);
2295
+ this.removeLayer(layerId);
2296
+ }
2297
+ });
2298
+ const closeActionBtn = document.createElement("button");
2299
+ closeActionBtn.className = "style-editor-button style-editor-button-close";
2300
+ closeActionBtn.textContent = "Close";
2301
+ closeActionBtn.addEventListener("click", (e) => {
2302
+ e.stopPropagation();
2303
+ this.closeStyleEditor(layerId);
2304
+ });
2305
+ actions.appendChild(resetBtn);
2306
+ actions.appendChild(removeBtn);
2307
+ actions.appendChild(closeActionBtn);
2308
+ editor.appendChild(controls);
2309
+ editor.appendChild(actions);
2310
+ return editor;
2311
+ }
2312
+ /**
2313
+ * Add style controls for a group of native layers of the same type.
2314
+ * Changes to any control are applied to all layers in the group.
2315
+ */
2316
+ addStyleControlsForNativeGroup(container, layerIds, primaryLayerId, layerType) {
2317
+ this.nativeLayerGroups.set(primaryLayerId, layerIds);
2318
+ this.addStyleControlsForLayerType(container, primaryLayerId, layerType);
2319
+ }
2320
+ /**
2321
+ * Close style editor for a layer
2322
+ */
2323
+ closeStyleEditor(layerId) {
2324
+ const editor = this.styleEditors.get(layerId);
2325
+ if (editor) {
2326
+ editor.remove();
2327
+ this.styleEditors.delete(layerId);
2328
+ }
2329
+ this.nativeLayerGroups.clear();
2330
+ if (this.state.activeStyleEditor === layerId) {
2331
+ this.state.activeStyleEditor = null;
2332
+ }
2333
+ }
2334
+ /**
2335
+ * Create style editor UI
2336
+ */
2337
+ createStyleEditor(layerId) {
2338
+ const layer = this.map.getLayer(layerId);
2339
+ if (!layer) return null;
2340
+ const editor = document.createElement("div");
2341
+ editor.className = "layer-control-style-editor";
2342
+ const header = document.createElement("div");
2343
+ header.className = "style-editor-header";
2344
+ const title = document.createElement("span");
2345
+ title.className = "style-editor-title";
2346
+ title.textContent = "Edit Style";
2347
+ const closeBtn = document.createElement("button");
2348
+ closeBtn.className = "style-editor-close";
2349
+ closeBtn.innerHTML = "&times;";
2350
+ closeBtn.title = "Close";
2351
+ closeBtn.addEventListener("click", (e) => {
2352
+ e.stopPropagation();
2353
+ this.closeStyleEditor(layerId);
2354
+ });
2355
+ header.appendChild(title);
2356
+ header.appendChild(closeBtn);
2357
+ const controls = document.createElement("div");
2358
+ controls.className = "style-editor-controls";
2359
+ const layerType = layer.type;
2360
+ this.addStyleControlsForLayerType(controls, layerId, layerType);
2361
+ const actions = document.createElement("div");
2362
+ actions.className = "style-editor-actions";
2363
+ const resetBtn = document.createElement("button");
2364
+ resetBtn.className = "style-editor-button style-editor-button-reset";
2365
+ resetBtn.textContent = "Reset";
2366
+ resetBtn.addEventListener("click", (e) => {
2367
+ e.stopPropagation();
2368
+ this.resetLayerStyle(layerId);
2369
+ });
2370
+ const removeBtn = document.createElement("button");
2371
+ removeBtn.className = "style-editor-button style-editor-button-remove";
2372
+ removeBtn.textContent = "Remove";
2373
+ removeBtn.title = "Remove layer from map";
2374
+ removeBtn.addEventListener("click", (e) => {
2375
+ e.stopPropagation();
2376
+ if (confirm("Are you sure you want to remove this layer?")) {
2377
+ this.closeStyleEditor(layerId);
2378
+ this.removeLayer(layerId);
2379
+ }
2380
+ });
2381
+ const closeActionBtn = document.createElement("button");
2382
+ closeActionBtn.className = "style-editor-button style-editor-button-close";
2383
+ closeActionBtn.textContent = "Close";
2384
+ closeActionBtn.addEventListener("click", (e) => {
2385
+ e.stopPropagation();
2386
+ this.closeStyleEditor(layerId);
2387
+ });
2388
+ actions.appendChild(resetBtn);
2389
+ actions.appendChild(removeBtn);
2390
+ actions.appendChild(closeActionBtn);
2391
+ editor.appendChild(header);
2392
+ editor.appendChild(controls);
2393
+ editor.appendChild(actions);
2394
+ return editor;
2395
+ }
2396
+ /**
2397
+ * Add style controls based on layer type
2398
+ */
2399
+ addStyleControlsForLayerType(container, layerId, layerType) {
2400
+ switch (layerType) {
2401
+ case "fill":
2402
+ this.addFillControls(container, layerId);
2403
+ break;
2404
+ case "line":
2405
+ this.addLineControls(container, layerId);
2406
+ break;
2407
+ case "circle":
2408
+ this.addCircleControls(container, layerId);
2409
+ break;
2410
+ case "raster":
2411
+ this.addRasterControls(container, layerId);
2412
+ break;
2413
+ case "symbol":
2414
+ this.addSymbolControls(container, layerId);
2415
+ break;
2416
+ default:
2417
+ container.textContent = `Style controls for ${layerType} layers not yet implemented.`;
2418
+ }
2419
+ }
2420
+ /**
2421
+ * Add controls for fill layers
2422
+ */
2423
+ addFillControls(container, layerId) {
2424
+ var _a;
2425
+ const style = this.map.getStyle();
2426
+ const layer = (_a = style.layers) == null ? void 0 : _a.find((l) => l.id === layerId);
2427
+ let fillColor = void 0;
2428
+ if (layer && "paint" in layer && layer.paint && "fill-color" in layer.paint) {
2429
+ fillColor = layer.paint["fill-color"];
2430
+ }
2431
+ if (!fillColor) {
2432
+ fillColor = this.map.getPaintProperty(layerId, "fill-color");
2433
+ }
2434
+ this.createColorControl(container, layerId, "fill-color", "Fill Color", normalizeColor(fillColor || "#088"));
2435
+ const fillOpacity = this.map.getPaintProperty(layerId, "fill-opacity");
2436
+ if (fillOpacity !== void 0 && typeof fillOpacity === "number") {
2437
+ this.createSliderControl(container, layerId, "fill-opacity", "Fill Opacity", fillOpacity, 0, 1, 0.05);
2438
+ }
2439
+ const outlineColor = this.map.getPaintProperty(layerId, "fill-outline-color");
2440
+ if (outlineColor !== void 0) {
2441
+ this.createColorControl(container, layerId, "fill-outline-color", "Outline Color", normalizeColor(outlineColor));
2442
+ }
2443
+ }
2444
+ /**
2445
+ * Add controls for line layers
2446
+ */
2447
+ addLineControls(container, layerId) {
2448
+ var _a;
2449
+ const style = this.map.getStyle();
2450
+ const layer = (_a = style.layers) == null ? void 0 : _a.find((l) => l.id === layerId);
2451
+ let lineColor = void 0;
2452
+ if (layer && "paint" in layer && layer.paint && "line-color" in layer.paint) {
2453
+ lineColor = layer.paint["line-color"];
2454
+ }
2455
+ if (!lineColor) {
2456
+ lineColor = this.map.getPaintProperty(layerId, "line-color");
2457
+ }
2458
+ this.createColorControl(container, layerId, "line-color", "Line Color", normalizeColor(lineColor || "#000"));
2459
+ const lineWidth = this.map.getPaintProperty(layerId, "line-width");
2460
+ this.createSliderControl(container, layerId, "line-width", "Line Width", typeof lineWidth === "number" ? lineWidth : 1, 0, 20, 0.5);
2461
+ const lineOpacity = this.map.getPaintProperty(layerId, "line-opacity");
2462
+ if (lineOpacity !== void 0 && typeof lineOpacity === "number") {
2463
+ this.createSliderControl(container, layerId, "line-opacity", "Line Opacity", lineOpacity, 0, 1, 0.05);
2464
+ }
2465
+ const lineBlur = this.map.getPaintProperty(layerId, "line-blur");
2466
+ if (lineBlur !== void 0 && typeof lineBlur === "number") {
2467
+ this.createSliderControl(container, layerId, "line-blur", "Line Blur", lineBlur, 0, 5, 0.1);
2468
+ }
2469
+ }
2470
+ /**
2471
+ * Add controls for circle layers
2472
+ */
2473
+ addCircleControls(container, layerId) {
2474
+ var _a;
2475
+ const style = this.map.getStyle();
2476
+ const layer = (_a = style.layers) == null ? void 0 : _a.find((l) => l.id === layerId);
2477
+ let circleColor = void 0;
2478
+ if (layer && "paint" in layer && layer.paint && "circle-color" in layer.paint) {
2479
+ circleColor = layer.paint["circle-color"];
2480
+ }
2481
+ if (!circleColor) {
2482
+ circleColor = this.map.getPaintProperty(layerId, "circle-color");
2483
+ }
2484
+ this.createColorControl(container, layerId, "circle-color", "Circle Color", normalizeColor(circleColor || "#000"));
2485
+ const circleRadius = this.map.getPaintProperty(layerId, "circle-radius");
2486
+ this.createSliderControl(container, layerId, "circle-radius", "Radius", typeof circleRadius === "number" ? circleRadius : 5, 0, 40, 0.5);
2487
+ const circleOpacity = this.map.getPaintProperty(layerId, "circle-opacity");
2488
+ if (circleOpacity !== void 0 && typeof circleOpacity === "number") {
2489
+ this.createSliderControl(container, layerId, "circle-opacity", "Opacity", circleOpacity, 0, 1, 0.05);
2490
+ }
2491
+ const strokeColor = this.map.getPaintProperty(layerId, "circle-stroke-color");
2492
+ if (strokeColor !== void 0) {
2493
+ this.createColorControl(container, layerId, "circle-stroke-color", "Stroke Color", normalizeColor(strokeColor));
2494
+ }
2495
+ const strokeWidth = this.map.getPaintProperty(layerId, "circle-stroke-width");
2496
+ if (strokeWidth !== void 0 && typeof strokeWidth === "number") {
2497
+ this.createSliderControl(container, layerId, "circle-stroke-width", "Stroke Width", strokeWidth, 0, 10, 0.1);
2498
+ }
2499
+ }
2500
+ /**
2501
+ * Add controls for raster layers
2502
+ */
2503
+ addRasterControls(container, layerId) {
2504
+ const rasterOpacity = this.map.getPaintProperty(layerId, "raster-opacity");
2505
+ this.createSliderControl(container, layerId, "raster-opacity", "Opacity", typeof rasterOpacity === "number" ? rasterOpacity : 1, 0, 1, 0.05);
2506
+ const brightnessMin = this.map.getPaintProperty(layerId, "raster-brightness-min");
2507
+ this.createSliderControl(container, layerId, "raster-brightness-min", "Brightness Min", typeof brightnessMin === "number" ? brightnessMin : 0, -1, 1, 0.05);
2508
+ const brightnessMax = this.map.getPaintProperty(layerId, "raster-brightness-max");
2509
+ this.createSliderControl(container, layerId, "raster-brightness-max", "Brightness Max", typeof brightnessMax === "number" ? brightnessMax : 1, -1, 1, 0.05);
2510
+ const saturation = this.map.getPaintProperty(layerId, "raster-saturation");
2511
+ this.createSliderControl(container, layerId, "raster-saturation", "Saturation", typeof saturation === "number" ? saturation : 0, -1, 1, 0.05);
2512
+ const contrast = this.map.getPaintProperty(layerId, "raster-contrast");
2513
+ this.createSliderControl(container, layerId, "raster-contrast", "Contrast", typeof contrast === "number" ? contrast : 0, -1, 1, 0.05);
2514
+ const hueRotate = this.map.getPaintProperty(layerId, "raster-hue-rotate");
2515
+ this.createSliderControl(container, layerId, "raster-hue-rotate", "Hue Rotate", typeof hueRotate === "number" ? hueRotate : 0, 0, 350, 5);
2516
+ }
2517
+ /**
2518
+ * Add controls for symbol layers
2519
+ */
2520
+ addSymbolControls(container, layerId) {
2521
+ const textColor = this.map.getPaintProperty(layerId, "text-color");
2522
+ if (textColor !== void 0) {
2523
+ this.createColorControl(container, layerId, "text-color", "Text Color", normalizeColor(textColor));
2524
+ }
2525
+ const textOpacity = this.map.getPaintProperty(layerId, "text-opacity");
2526
+ if (textOpacity !== void 0 && typeof textOpacity === "number") {
2527
+ this.createSliderControl(container, layerId, "text-opacity", "Text Opacity", textOpacity, 0, 1, 0.05);
2528
+ }
2529
+ const iconOpacity = this.map.getPaintProperty(layerId, "icon-opacity");
2530
+ if (iconOpacity !== void 0 && typeof iconOpacity === "number") {
2531
+ this.createSliderControl(container, layerId, "icon-opacity", "Icon Opacity", iconOpacity, 0, 1, 0.05);
2532
+ }
2533
+ }
2534
+ /**
2535
+ * Create a color control
2536
+ */
2537
+ createColorControl(container, layerId, property, label, initialValue) {
2538
+ const controlGroup = document.createElement("div");
2539
+ controlGroup.className = "style-control-group";
2540
+ const labelEl = document.createElement("label");
2541
+ labelEl.className = "style-control-label";
2542
+ labelEl.textContent = label;
2543
+ const inputWrapper = document.createElement("div");
2544
+ inputWrapper.className = "style-control-color-group";
2545
+ const colorInput = document.createElement("input");
2546
+ colorInput.type = "color";
2547
+ colorInput.className = "style-control-color-picker";
2548
+ colorInput.value = initialValue;
2549
+ colorInput.dataset.property = property;
2550
+ const hexDisplay = document.createElement("input");
2551
+ hexDisplay.type = "text";
2552
+ hexDisplay.className = "style-control-color-value";
2553
+ hexDisplay.value = initialValue;
2554
+ hexDisplay.readOnly = true;
2555
+ colorInput.addEventListener("input", () => {
2556
+ const color = colorInput.value;
2557
+ hexDisplay.value = color;
2558
+ const targetIds = this.nativeLayerGroups.get(layerId) || [layerId];
2559
+ for (const id of targetIds) {
2560
+ this.map.setPaintProperty(id, property, color);
2561
+ }
2562
+ });
2563
+ inputWrapper.appendChild(colorInput);
2564
+ inputWrapper.appendChild(hexDisplay);
2565
+ controlGroup.appendChild(labelEl);
2566
+ controlGroup.appendChild(inputWrapper);
2567
+ container.appendChild(controlGroup);
2568
+ }
2569
+ /**
2570
+ * Create a slider control
2571
+ */
2572
+ createSliderControl(container, layerId, property, label, initialValue, min, max, step) {
2573
+ const controlGroup = document.createElement("div");
2574
+ controlGroup.className = "style-control-group";
2575
+ const labelEl = document.createElement("label");
2576
+ labelEl.className = "style-control-label";
2577
+ labelEl.textContent = label;
2578
+ const inputWrapper = document.createElement("div");
2579
+ inputWrapper.className = "style-control-input-wrapper";
2580
+ const slider = document.createElement("input");
2581
+ slider.type = "range";
2582
+ slider.className = "style-control-slider";
2583
+ slider.min = String(min);
2584
+ slider.max = String(max);
2585
+ slider.step = String(step);
2586
+ slider.value = String(initialValue);
2587
+ slider.dataset.property = property;
2588
+ const valueDisplay = document.createElement("span");
2589
+ valueDisplay.className = "style-control-value";
2590
+ valueDisplay.textContent = formatNumericValue(initialValue, step);
2591
+ slider.addEventListener("input", () => {
2592
+ const value = parseFloat(slider.value);
2593
+ valueDisplay.textContent = formatNumericValue(value, step);
2594
+ const targetIds = this.nativeLayerGroups.get(layerId) || [layerId];
2595
+ for (const id of targetIds) {
2596
+ this.map.setPaintProperty(id, property, value);
2597
+ }
2598
+ });
2599
+ inputWrapper.appendChild(slider);
2600
+ inputWrapper.appendChild(valueDisplay);
2601
+ controlGroup.appendChild(labelEl);
2602
+ controlGroup.appendChild(inputWrapper);
2603
+ container.appendChild(controlGroup);
2604
+ }
2605
+ /**
2606
+ * Reset layer style to original
2607
+ */
2608
+ resetLayerStyle(layerId) {
2609
+ const originalStyle = this.state.originalStyles.get(layerId);
2610
+ if (!originalStyle) return;
2611
+ restoreOriginalStyle(this.map, layerId, this.state.originalStyles);
2612
+ const editor = this.styleEditors.get(layerId);
2613
+ if (editor) {
2614
+ const sliders = editor.querySelectorAll(".style-control-slider");
2615
+ sliders.forEach((slider) => {
2616
+ var _a;
2617
+ const property = slider.dataset.property;
2618
+ if (property) {
2619
+ const value = this.map.getPaintProperty(layerId, property);
2620
+ if (value !== void 0 && typeof value === "number") {
2621
+ slider.value = String(value);
2622
+ const valueDisplay = (_a = slider.parentElement) == null ? void 0 : _a.querySelector(".style-control-value");
2623
+ if (valueDisplay) {
2624
+ const step = parseFloat(slider.step);
2625
+ valueDisplay.textContent = formatNumericValue(value, step);
2626
+ }
2627
+ }
2628
+ }
2629
+ });
2630
+ const colorPickers = editor.querySelectorAll(".style-control-color-picker");
2631
+ colorPickers.forEach((picker) => {
2632
+ var _a;
2633
+ const property = picker.dataset.property;
2634
+ if (property) {
2635
+ const value = this.map.getPaintProperty(layerId, property);
2636
+ if (value !== void 0) {
2637
+ const hexColor = normalizeColor(value);
2638
+ picker.value = hexColor;
2639
+ const hexDisplay = (_a = picker.parentElement) == null ? void 0 : _a.querySelector(".style-control-color-value");
2640
+ if (hexDisplay) {
2641
+ hexDisplay.textContent = hexColor;
2642
+ }
2643
+ }
2644
+ }
2645
+ });
2646
+ }
2647
+ }
2648
+ /**
2649
+ * Update layer states from map (sync UI with map)
2650
+ */
2651
+ updateLayerStatesFromMap() {
2652
+ if (this.state.userInteractingWithSlider) {
2653
+ return;
2654
+ }
2655
+ Object.keys(this.state.layerStates).forEach((layerId) => {
2656
+ var _a;
2657
+ try {
2658
+ if ((_a = this.state.layerStates[layerId]) == null ? void 0 : _a.isCustomLayer) {
2659
+ return;
2660
+ }
2661
+ if (layerId === "Background") {
2662
+ return;
2663
+ }
2664
+ const layer = this.map.getLayer(layerId);
2665
+ if (!layer) return;
2666
+ const visibility = this.map.getLayoutProperty(layerId, "visibility");
2667
+ const isVisible = visibility !== "none";
2668
+ const layerType = layer.type;
2669
+ const opacity = getLayerOpacity(this.map, layerId, layerType);
2670
+ if (this.state.layerStates[layerId]) {
2671
+ this.state.layerStates[layerId].visible = isVisible;
2672
+ this.state.layerStates[layerId].opacity = opacity;
2673
+ }
2674
+ this.updateUIForLayer(layerId, isVisible, opacity);
2675
+ } catch (error) {
2676
+ console.warn(`Failed to update state for layer ${layerId}:`, error);
2677
+ }
2678
+ });
2679
+ }
2680
+ /**
2681
+ * Update UI elements for a specific layer
2682
+ */
2683
+ updateUIForLayer(layerId, visible, opacity) {
2684
+ const layerItems = this.panel.querySelectorAll(".layer-control-item");
2685
+ layerItems.forEach((item) => {
2686
+ if (item.dataset.layerId === layerId) {
2687
+ const checkbox = item.querySelector(".layer-control-checkbox");
2688
+ const opacitySlider = item.querySelector(".layer-control-opacity");
2689
+ if (checkbox) {
2690
+ checkbox.checked = visible;
2691
+ }
2692
+ if (opacitySlider) {
2693
+ opacitySlider.value = String(opacity);
2694
+ opacitySlider.title = `Opacity: ${Math.round(opacity * 100)}%`;
2695
+ }
2696
+ }
2697
+ });
2698
+ }
2699
+ /**
2700
+ * Check for new layers and add them to the control, remove deleted layers
2701
+ */
2702
+ checkForNewLayers() {
2703
+ if (this.state.isStyleOperationInProgress) {
2704
+ return;
2705
+ }
2706
+ try {
2707
+ const style = this.map.getStyle();
2708
+ if (!style || !style.layers) {
2709
+ return;
2710
+ }
2711
+ const currentMapLayerIds = new Set(style.layers.map((layer) => layer.id));
2712
+ const isAutoDetectMode = this.targetLayers.length === 0 || this.targetLayers.length === 1 && this.targetLayers[0] === "Background" || this.targetLayers.every((id) => id === "Background" || this.state.layerStates[id]);
2713
+ const newLayers = [];
2714
+ const useBasemapStyleDetection = this.basemapLayerIds !== null && this.basemapLayerIds.size > 0;
2715
+ const useInitialLayerDetection = !useBasemapStyleDetection && this.initialLayerIds !== null && this.initialLayerIds.size > 0;
2716
+ currentMapLayerIds.forEach((layerId) => {
2717
+ if (layerId !== "Background" && !this.state.layerStates[layerId]) {
2718
+ const layer = this.map.getLayer(layerId);
2719
+ if (layer) {
2720
+ if (this.initialLayerIds !== null && this.initialLayerIds.has(layerId)) {
2721
+ return;
2722
+ }
2723
+ if (isAutoDetectMode) {
2724
+ if (this.excludeDrawnLayers && this.isDrawnLayer(layerId)) {
2725
+ return;
2726
+ }
2727
+ if (this.isExcludedByPattern(layerId)) {
2728
+ return;
2729
+ }
2730
+ if (!this.isUserAddedLayer(layerId)) {
2731
+ return;
2732
+ }
2733
+ if (useBasemapStyleDetection) {
2734
+ if (this.basemapLayerIds.has(layerId)) {
2735
+ return;
2736
+ }
2737
+ } else if (useInitialLayerDetection) {
2738
+ if (this.initialLayerIds.has(layerId)) {
2739
+ const userAddedSourceIds = this.detectUserAddedSources();
2740
+ const sourceId = layer.source;
2741
+ if (!sourceId || !userAddedSourceIds.has(sourceId)) {
2742
+ return;
2743
+ }
2744
+ }
2745
+ } else {
2746
+ const userAddedSourceIds = this.detectUserAddedSources();
2747
+ const sourceId = layer.source;
2748
+ if (!sourceId || !userAddedSourceIds.has(sourceId)) {
2749
+ return;
2750
+ }
2751
+ }
2752
+ }
2753
+ newLayers.push(layerId);
2754
+ }
2755
+ }
2756
+ });
2757
+ const removedLayers = [];
2758
+ Object.keys(this.state.layerStates).forEach((layerId) => {
2759
+ const state = this.state.layerStates[layerId];
2760
+ if (layerId !== "Background" && !state.isCustomLayer && !currentMapLayerIds.has(layerId)) {
2761
+ removedLayers.push(layerId);
2762
+ }
2763
+ });
2764
+ if (removedLayers.length > 0) {
2765
+ removedLayers.forEach((layerId) => {
2766
+ delete this.state.layerStates[layerId];
2767
+ const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
2768
+ if (itemEl) {
2769
+ itemEl.remove();
2770
+ }
2771
+ if (this.state.activeStyleEditor === layerId) {
2772
+ this.state.activeStyleEditor = null;
2773
+ }
2774
+ this.styleEditors.delete(layerId);
2775
+ });
2776
+ }
2777
+ if (newLayers.length > 0) {
2778
+ newLayers.forEach((layerId) => {
2779
+ const layer = this.map.getLayer(layerId);
2780
+ if (!layer) return;
2781
+ const layerType = layer.type;
2782
+ const opacity = getLayerOpacity(this.map, layerId, layerType);
2783
+ const visibility = this.map.getLayoutProperty(layerId, "visibility");
2784
+ const isVisible = visibility !== "none";
2785
+ this.state.layerStates[layerId] = {
2786
+ visible: isVisible,
2787
+ opacity,
2788
+ name: this.generateFriendlyName(layerId)
2789
+ };
2790
+ this.addLayerItem(layerId, this.state.layerStates[layerId]);
2791
+ });
2792
+ }
2793
+ if (this.customLayerRegistry) {
2794
+ const customLayerIds = this.customLayerRegistry.getAllLayerIds();
2795
+ customLayerIds.forEach((layerId) => {
2796
+ if (!this.state.layerStates[layerId] && !this.removedCustomLayerIds.has(layerId)) {
2797
+ const customState = this.customLayerRegistry.getLayerState(layerId);
2798
+ if (customState) {
2799
+ this.state.layerStates[layerId] = {
2800
+ visible: customState.visible,
2801
+ opacity: customState.opacity,
2802
+ name: customState.name,
2803
+ isCustomLayer: true,
2804
+ customLayerType: this.customLayerRegistry.getSymbolType(layerId) || void 0
2805
+ };
2806
+ this.addLayerItem(layerId, this.state.layerStates[layerId]);
2807
+ }
2808
+ }
2809
+ });
2810
+ Object.keys(this.state.layerStates).forEach((layerId) => {
2811
+ const state = this.state.layerStates[layerId];
2812
+ if (state.isCustomLayer && !customLayerIds.includes(layerId)) {
2813
+ delete this.state.layerStates[layerId];
2814
+ const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
2815
+ if (itemEl) {
2816
+ itemEl.remove();
2817
+ }
2818
+ if (this.state.activeStyleEditor === layerId) {
2819
+ this.state.activeStyleEditor = null;
2820
+ }
2821
+ this.styleEditors.delete(layerId);
2822
+ }
2823
+ });
2824
+ }
2825
+ } catch (error) {
2826
+ console.warn("Failed to check for new layers:", error);
2827
+ }
2828
+ }
2829
+ /**
2830
+ * Register a custom layer adapter dynamically.
2831
+ * This allows adding adapters after the LayerControl has been initialized.
2832
+ * @param adapter The custom layer adapter to register
2833
+ */
2834
+ registerCustomAdapter(adapter) {
2835
+ if (!this.customLayerRegistry) {
2836
+ this.customLayerRegistry = new CustomLayerRegistry();
2837
+ this.customLayerUnsubscribe = this.customLayerRegistry.onChange((event, layerId) => {
2838
+ if (event === "add" && layerId) {
2839
+ this.removedCustomLayerIds.delete(layerId);
2840
+ }
2841
+ this.checkForNewLayers();
2842
+ });
2843
+ }
2844
+ this.customLayerRegistry.register(adapter);
2845
+ if (this.panel) {
2846
+ this.checkForNewLayers();
2847
+ }
2848
+ }
2849
+ // ===== Context Menu Methods =====
2850
+ /**
2851
+ * Create context menu element
2852
+ */
2853
+ createContextMenu() {
2854
+ const menu = document.createElement("div");
2855
+ menu.className = "layer-control-context-menu";
2856
+ menu.style.display = "none";
2857
+ const renameItem = this.createContextMenuItem("Rename", "✏️", () => {
2858
+ if (this.state.contextMenu.targetLayerId) {
2859
+ this.startRenaming(this.state.contextMenu.targetLayerId);
2860
+ }
2861
+ this.hideContextMenu();
2862
+ });
2863
+ const zoomItem = this.createContextMenuItem("Zoom to Layer", "🔍", () => {
2864
+ if (this.state.contextMenu.targetLayerId) {
2865
+ this.zoomToLayer(this.state.contextMenu.targetLayerId);
2866
+ }
2867
+ this.hideContextMenu();
2868
+ });
2869
+ const sep1 = document.createElement("div");
2870
+ sep1.className = "context-menu-separator";
2871
+ const moveUpItem = this.createContextMenuItem("Move Up", "↑", () => {
2872
+ if (this.state.contextMenu.targetLayerId) {
2873
+ this.moveLayerUp(this.state.contextMenu.targetLayerId);
2874
+ }
2875
+ this.hideContextMenu();
2876
+ });
2877
+ const moveTopItem = this.createContextMenuItem("Move to Top", "⤒", () => {
2878
+ if (this.state.contextMenu.targetLayerId) {
2879
+ this.moveLayerToTop(this.state.contextMenu.targetLayerId);
2880
+ }
2881
+ this.hideContextMenu();
2882
+ });
2883
+ const moveDownItem = this.createContextMenuItem("Move Down", "↓", () => {
2884
+ if (this.state.contextMenu.targetLayerId) {
2885
+ this.moveLayerDown(this.state.contextMenu.targetLayerId);
2886
+ }
2887
+ this.hideContextMenu();
2888
+ });
2889
+ const moveBottomItem = this.createContextMenuItem("Move to Bottom", "⤓", () => {
2890
+ if (this.state.contextMenu.targetLayerId) {
2891
+ this.moveLayerToBottom(this.state.contextMenu.targetLayerId);
2892
+ }
2893
+ this.hideContextMenu();
2894
+ });
2895
+ const sep2 = document.createElement("div");
2896
+ sep2.className = "context-menu-separator";
2897
+ const removeItem = this.createContextMenuItem("Remove Layer", "🗑️", () => {
2898
+ if (this.state.contextMenu.targetLayerId) {
2899
+ this.removeLayer(this.state.contextMenu.targetLayerId);
2900
+ }
2901
+ this.hideContextMenu();
2902
+ }, true);
2903
+ menu.appendChild(renameItem);
2904
+ menu.appendChild(zoomItem);
2905
+ menu.appendChild(sep1);
2906
+ menu.appendChild(moveUpItem);
2907
+ menu.appendChild(moveTopItem);
2908
+ menu.appendChild(moveDownItem);
2909
+ menu.appendChild(moveBottomItem);
2910
+ menu.appendChild(sep2);
2911
+ menu.appendChild(removeItem);
2912
+ return menu;
2913
+ }
2914
+ /**
2915
+ * Create a context menu item
2916
+ */
2917
+ createContextMenuItem(label, icon, onClick, isDanger = false) {
2918
+ const item = document.createElement("div");
2919
+ item.className = "context-menu-item" + (isDanger ? " context-menu-item-danger" : "");
2920
+ const iconEl = document.createElement("span");
2921
+ iconEl.className = "context-menu-item-icon";
2922
+ iconEl.textContent = icon;
2923
+ const labelEl = document.createElement("span");
2924
+ labelEl.className = "context-menu-item-label";
2925
+ labelEl.textContent = label;
2926
+ item.appendChild(iconEl);
2927
+ item.appendChild(labelEl);
2928
+ item.addEventListener("click", (e) => {
2929
+ e.stopPropagation();
2930
+ onClick();
2931
+ });
2932
+ return item;
2933
+ }
2934
+ /**
2935
+ * Show context menu at position
2936
+ */
2937
+ showContextMenu(layerId, x, y) {
2938
+ if (!this.contextMenuEl) return;
2939
+ this.state.contextMenu = {
2940
+ visible: true,
2941
+ targetLayerId: layerId,
2942
+ x,
2943
+ y
2944
+ };
2945
+ const mapRect = this.mapContainer.getBoundingClientRect();
2946
+ let menuX = x - mapRect.left;
2947
+ let menuY = y - mapRect.top;
2948
+ this.contextMenuEl.style.display = "block";
2949
+ const menuRect = this.contextMenuEl.getBoundingClientRect();
2950
+ if (menuX + menuRect.width > mapRect.width) {
2951
+ menuX = mapRect.width - menuRect.width - 5;
2952
+ }
2953
+ if (menuY + menuRect.height > mapRect.height) {
2954
+ menuY = mapRect.height - menuRect.height - 5;
2955
+ }
2956
+ this.contextMenuEl.style.left = `${menuX}px`;
2957
+ this.contextMenuEl.style.top = `${menuY}px`;
2958
+ }
2959
+ /**
2960
+ * Hide context menu
2961
+ */
2962
+ hideContextMenu() {
2963
+ if (!this.contextMenuEl) return;
2964
+ this.state.contextMenu = {
2965
+ visible: false,
2966
+ targetLayerId: null,
2967
+ x: 0,
2968
+ y: 0
2969
+ };
2970
+ this.contextMenuEl.style.display = "none";
2971
+ }
2972
+ /**
2973
+ * Start renaming a layer
2974
+ */
2975
+ startRenaming(layerId) {
2976
+ var _a;
2977
+ const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
2978
+ if (!itemEl) return;
2979
+ const nameEl = itemEl.querySelector(".layer-control-name");
2980
+ if (!nameEl) return;
2981
+ this.state.renamingLayerId = layerId;
2982
+ const currentName = this.state.customLayerNames.get(layerId) || ((_a = this.state.layerStates[layerId]) == null ? void 0 : _a.name) || layerId;
2983
+ const input = document.createElement("input");
2984
+ input.type = "text";
2985
+ input.className = "layer-control-name-input";
2986
+ input.value = currentName;
2987
+ const finishRename = () => {
2988
+ var _a2;
2989
+ const newName = input.value.trim() || currentName;
2990
+ const oldName = currentName;
2991
+ if (newName !== oldName) {
2992
+ this.state.customLayerNames.set(layerId, newName);
2993
+ if (this.state.layerStates[layerId]) {
2994
+ this.state.layerStates[layerId].name = newName;
2995
+ }
2996
+ (_a2 = this.onLayerRename) == null ? void 0 : _a2.call(this, layerId, oldName, newName);
2997
+ }
2998
+ const newNameEl = document.createElement("span");
2999
+ newNameEl.className = "layer-control-name";
3000
+ newNameEl.textContent = newName;
3001
+ newNameEl.title = newName;
3002
+ input.replaceWith(newNameEl);
3003
+ this.state.renamingLayerId = null;
3004
+ };
3005
+ input.addEventListener("blur", finishRename);
3006
+ input.addEventListener("keydown", (e) => {
3007
+ if (e.key === "Enter") {
3008
+ e.preventDefault();
3009
+ finishRename();
3010
+ } else if (e.key === "Escape") {
3011
+ e.preventDefault();
3012
+ const newNameEl = document.createElement("span");
3013
+ newNameEl.className = "layer-control-name";
3014
+ newNameEl.textContent = currentName;
3015
+ newNameEl.title = currentName;
3016
+ input.replaceWith(newNameEl);
3017
+ this.state.renamingLayerId = null;
3018
+ }
3019
+ });
3020
+ nameEl.replaceWith(input);
3021
+ input.focus();
3022
+ input.select();
3023
+ }
3024
+ /**
3025
+ * Zoom to a layer's bounds
3026
+ */
3027
+ zoomToLayer(layerId) {
3028
+ const layerState = this.state.layerStates[layerId];
3029
+ if ((layerState == null ? void 0 : layerState.isCustomLayer) && this.customLayerRegistry) {
3030
+ const bounds = this.customLayerRegistry.getBounds(layerId);
3031
+ if (bounds) {
3032
+ this.map.fitBounds(bounds, { padding: 50 });
3033
+ return;
3034
+ }
3035
+ }
3036
+ const layer = this.map.getLayer(layerId);
3037
+ if (!layer) return;
3038
+ try {
3039
+ const sourceId = layer.source;
3040
+ let features = sourceId ? this.map.querySourceFeatures(sourceId) : [];
3041
+ if (features.length === 0) {
3042
+ features = this.map.queryRenderedFeatures({ layers: [layerId] });
3043
+ }
3044
+ if (features.length === 0) return;
3045
+ let minLng = Infinity, minLat = Infinity, maxLng = -Infinity, maxLat = -Infinity;
3046
+ features.forEach((feature) => {
3047
+ if (!feature.geometry) return;
3048
+ const processCoords = (coords) => {
3049
+ if (typeof coords[0] === "number") {
3050
+ const [lng, lat] = coords;
3051
+ minLng = Math.min(minLng, lng);
3052
+ minLat = Math.min(minLat, lat);
3053
+ maxLng = Math.max(maxLng, lng);
3054
+ maxLat = Math.max(maxLat, lat);
3055
+ } else {
3056
+ coords.forEach(processCoords);
3057
+ }
3058
+ };
3059
+ if (feature.geometry.type === "Point") {
3060
+ processCoords(feature.geometry.coordinates);
3061
+ } else if (feature.geometry.type === "LineString" || feature.geometry.type === "MultiPoint") {
3062
+ feature.geometry.coordinates.forEach(processCoords);
3063
+ } else if (feature.geometry.type === "Polygon" || feature.geometry.type === "MultiLineString") {
3064
+ feature.geometry.coordinates.forEach((ring) => ring.forEach(processCoords));
3065
+ } else if (feature.geometry.type === "MultiPolygon") {
3066
+ feature.geometry.coordinates.forEach(
3067
+ (polygon) => polygon.forEach((ring) => ring.forEach(processCoords))
3068
+ );
3069
+ }
3070
+ });
3071
+ if (minLng !== Infinity && minLat !== Infinity && maxLng !== -Infinity && maxLat !== -Infinity) {
3072
+ this.map.fitBounds([[minLng, minLat], [maxLng, maxLat]], { padding: 50 });
3073
+ }
3074
+ } catch (error) {
3075
+ console.warn(`Failed to zoom to layer ${layerId}:`, error);
3076
+ }
3077
+ }
3078
+ /**
3079
+ * Get user layer IDs in current map order (top to bottom in UI = high z-index to low)
3080
+ * Includes both MapLibre layers and custom layers managed by adapters.
3081
+ */
3082
+ getUserLayerIdsInMapOrder() {
3083
+ const style = this.map.getStyle();
3084
+ if (!(style == null ? void 0 : style.layers)) return [];
3085
+ const mapLayerIds = style.layers.map((l) => l.id);
3086
+ const userLayerIds = Object.keys(this.state.layerStates).filter((id) => id !== "Background");
3087
+ const mapLibreLayers = userLayerIds.filter((id) => mapLayerIds.includes(id));
3088
+ const customLayers = userLayerIds.filter(
3089
+ (id) => {
3090
+ var _a;
3091
+ return ((_a = this.state.layerStates[id]) == null ? void 0 : _a.isCustomLayer) && !mapLayerIds.includes(id);
3092
+ }
3093
+ );
3094
+ const sortedMapLibreLayers = mapLibreLayers.sort((a, b) => mapLayerIds.indexOf(b) - mapLayerIds.indexOf(a));
3095
+ return [...customLayers, ...sortedMapLibreLayers];
3096
+ }
3097
+ /**
3098
+ * Check if a layer is a MapLibre layer (not a custom layer)
3099
+ */
3100
+ isMapLibreLayer(layerId) {
3101
+ return this.map.getLayer(layerId) !== void 0;
3102
+ }
3103
+ /**
3104
+ * Find the next MapLibre layer ID in a direction from a given index
3105
+ * @param layerIds Array of layer IDs
3106
+ * @param startIndex Index to start searching from
3107
+ * @param direction 1 for forward (toward bottom), -1 for backward (toward top)
3108
+ * @returns MapLibre layer ID or undefined if not found
3109
+ */
3110
+ findNextMapLibreLayer(layerIds, startIndex, direction) {
3111
+ for (let i = startIndex; direction === 1 ? i < layerIds.length : i >= 0; i += direction) {
3112
+ if (this.isMapLibreLayer(layerIds[i])) {
3113
+ return layerIds[i];
3114
+ }
3115
+ }
3116
+ return void 0;
3117
+ }
3118
+ /**
3119
+ * Move a layer up in UI (higher rendering order = move to higher z-index)
3120
+ */
3121
+ moveLayerUp(layerId) {
3122
+ var _a, _b;
3123
+ const layerIds = this.getUserLayerIdsInMapOrder();
3124
+ const index = layerIds.indexOf(layerId);
3125
+ if (index <= 0) return;
3126
+ const isCustom = (_a = this.state.layerStates[layerId]) == null ? void 0 : _a.isCustomLayer;
3127
+ if (!isCustom && this.isMapLibreLayer(layerId)) {
3128
+ try {
3129
+ const targetBeforeId = index >= 2 ? this.findNextMapLibreLayer(layerIds, index - 2, -1) : void 0;
3130
+ this.map.moveLayer(layerId, targetBeforeId);
3131
+ } catch (e) {
3132
+ }
3133
+ }
3134
+ const newLayerStates = {};
3135
+ const orderedIds = [...layerIds];
3136
+ [orderedIds[index], orderedIds[index - 1]] = [orderedIds[index - 1], orderedIds[index]];
3137
+ if (this.state.layerStates["Background"]) {
3138
+ newLayerStates["Background"] = this.state.layerStates["Background"];
3139
+ }
3140
+ orderedIds.forEach((id) => {
3141
+ if (this.state.layerStates[id]) {
3142
+ newLayerStates[id] = this.state.layerStates[id];
3143
+ }
3144
+ });
3145
+ this.state.layerStates = newLayerStates;
3146
+ this.buildLayerItems();
3147
+ (_b = this.onLayerReorder) == null ? void 0 : _b.call(this, this.getUserLayerIdsInMapOrder());
3148
+ }
3149
+ /**
3150
+ * Move a layer to the top (highest rendering order)
3151
+ */
3152
+ moveLayerToTop(layerId) {
3153
+ var _a, _b;
3154
+ const layerIds = this.getUserLayerIdsInMapOrder();
3155
+ const index = layerIds.indexOf(layerId);
3156
+ if (index <= 0) return;
3157
+ const isCustom = (_a = this.state.layerStates[layerId]) == null ? void 0 : _a.isCustomLayer;
3158
+ if (!isCustom && this.isMapLibreLayer(layerId)) {
3159
+ try {
3160
+ this.map.moveLayer(layerId);
3161
+ } catch (e) {
3162
+ }
3163
+ }
3164
+ const newLayerStates = {};
3165
+ const orderedIds = [...layerIds];
3166
+ orderedIds.splice(index, 1);
3167
+ orderedIds.unshift(layerId);
3168
+ if (this.state.layerStates["Background"]) {
3169
+ newLayerStates["Background"] = this.state.layerStates["Background"];
3170
+ }
3171
+ orderedIds.forEach((id) => {
3172
+ if (this.state.layerStates[id]) {
3173
+ newLayerStates[id] = this.state.layerStates[id];
3174
+ }
3175
+ });
3176
+ this.state.layerStates = newLayerStates;
3177
+ this.buildLayerItems();
3178
+ (_b = this.onLayerReorder) == null ? void 0 : _b.call(this, this.getUserLayerIdsInMapOrder());
3179
+ }
3180
+ /**
3181
+ * Move a layer down in UI (lower rendering order = move to lower z-index)
3182
+ */
3183
+ moveLayerDown(layerId) {
3184
+ var _a, _b;
3185
+ const layerIds = this.getUserLayerIdsInMapOrder();
3186
+ const index = layerIds.indexOf(layerId);
3187
+ if (index < 0 || index >= layerIds.length - 1) return;
3188
+ const isCustom = (_a = this.state.layerStates[layerId]) == null ? void 0 : _a.isCustomLayer;
3189
+ if (!isCustom && this.isMapLibreLayer(layerId)) {
3190
+ const belowLayerId = this.findNextMapLibreLayer(layerIds, index + 1, 1);
3191
+ if (belowLayerId) {
3192
+ try {
3193
+ this.map.moveLayer(layerId, belowLayerId);
3194
+ } catch (e) {
3195
+ }
3196
+ }
3197
+ }
3198
+ const newLayerStates = {};
3199
+ const orderedIds = [...layerIds];
3200
+ [orderedIds[index], orderedIds[index + 1]] = [orderedIds[index + 1], orderedIds[index]];
3201
+ if (this.state.layerStates["Background"]) {
3202
+ newLayerStates["Background"] = this.state.layerStates["Background"];
3203
+ }
3204
+ orderedIds.forEach((id) => {
3205
+ if (this.state.layerStates[id]) {
3206
+ newLayerStates[id] = this.state.layerStates[id];
3207
+ }
3208
+ });
3209
+ this.state.layerStates = newLayerStates;
3210
+ this.buildLayerItems();
3211
+ (_b = this.onLayerReorder) == null ? void 0 : _b.call(this, this.getUserLayerIdsInMapOrder());
3212
+ }
3213
+ /**
3214
+ * Move a layer to the bottom (lowest rendering order among user layers)
3215
+ */
3216
+ moveLayerToBottom(layerId) {
3217
+ var _a, _b;
3218
+ const layerIds = this.getUserLayerIdsInMapOrder();
3219
+ if (layerIds.length <= 1) return;
3220
+ const index = layerIds.indexOf(layerId);
3221
+ if (index < 0 || index === layerIds.length - 1) return;
3222
+ const isCustom = (_a = this.state.layerStates[layerId]) == null ? void 0 : _a.isCustomLayer;
3223
+ if (!isCustom && this.isMapLibreLayer(layerId)) {
3224
+ const bottomLayerId = this.findNextMapLibreLayer(layerIds, layerIds.length - 1, -1);
3225
+ if (bottomLayerId && bottomLayerId !== layerId) {
3226
+ try {
3227
+ this.map.moveLayer(layerId, bottomLayerId);
3228
+ } catch (e) {
3229
+ }
3230
+ }
3231
+ }
3232
+ const newLayerStates = {};
3233
+ const orderedIds = [...layerIds];
3234
+ orderedIds.splice(index, 1);
3235
+ orderedIds.push(layerId);
3236
+ if (this.state.layerStates["Background"]) {
3237
+ newLayerStates["Background"] = this.state.layerStates["Background"];
3238
+ }
3239
+ orderedIds.forEach((id) => {
3240
+ if (this.state.layerStates[id]) {
3241
+ newLayerStates[id] = this.state.layerStates[id];
3242
+ }
3243
+ });
3244
+ this.state.layerStates = newLayerStates;
3245
+ this.buildLayerItems();
3246
+ (_b = this.onLayerReorder) == null ? void 0 : _b.call(this, this.getUserLayerIdsInMapOrder());
3247
+ }
3248
+ /**
3249
+ * Remove a layer from the map
3250
+ */
3251
+ removeLayer(layerId) {
3252
+ var _a, _b;
3253
+ this.state.isStyleOperationInProgress = true;
3254
+ const layerState = this.state.layerStates[layerId];
3255
+ if ((layerState == null ? void 0 : layerState.isCustomLayer) && this.customLayerRegistry) {
3256
+ this.removedCustomLayerIds.add(layerId);
3257
+ this.customLayerRegistry.setVisibility(layerId, false);
3258
+ this.customLayerRegistry.removeLayer(layerId);
3259
+ }
3260
+ try {
3261
+ const layer = this.map.getLayer(layerId);
3262
+ if (layer) {
3263
+ const sourceId = layer.source;
3264
+ this.map.removeLayer(layerId);
3265
+ if (sourceId) {
3266
+ const style = this.map.getStyle();
3267
+ const sourceStillUsed = (_a = style == null ? void 0 : style.layers) == null ? void 0 : _a.some((l) => l.source === sourceId);
3268
+ if (!sourceStillUsed) {
3269
+ try {
3270
+ this.map.removeSource(sourceId);
3271
+ } catch (e) {
3272
+ }
3273
+ }
3274
+ }
3275
+ }
3276
+ } catch (error) {
3277
+ console.warn(`Failed to remove layer ${layerId}:`, error);
3278
+ }
3279
+ delete this.state.layerStates[layerId];
3280
+ this.state.customLayerNames.delete(layerId);
3281
+ const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
3282
+ if (itemEl) {
3283
+ itemEl.remove();
3284
+ }
3285
+ (_b = this.onLayerRemove) == null ? void 0 : _b.call(this, layerId);
3286
+ setTimeout(() => {
3287
+ this.state.isStyleOperationInProgress = false;
3288
+ }, 200);
3289
+ }
3290
+ // ===== Drag and Drop Methods =====
3291
+ /**
3292
+ * Create drag handle element
3293
+ */
3294
+ createDragHandle(layerId) {
3295
+ const handle = document.createElement("div");
3296
+ handle.className = "layer-control-drag-handle";
3297
+ handle.innerHTML = `<svg viewBox="0 0 16 16" fill="currentColor">
3298
+ <circle cx="5" cy="3" r="1.5"/>
3299
+ <circle cx="11" cy="3" r="1.5"/>
3300
+ <circle cx="5" cy="8" r="1.5"/>
3301
+ <circle cx="11" cy="8" r="1.5"/>
3302
+ <circle cx="5" cy="13" r="1.5"/>
3303
+ <circle cx="11" cy="13" r="1.5"/>
3304
+ </svg>`;
3305
+ handle.title = "Drag to reorder";
3306
+ handle.addEventListener("pointerdown", (e) => {
3307
+ e.preventDefault();
3308
+ e.stopPropagation();
3309
+ this.startDrag(layerId, e);
3310
+ });
3311
+ return handle;
3312
+ }
3313
+ /**
3314
+ * Create a disabled drag handle for alignment (used for Background layer)
3315
+ */
3316
+ createDisabledDragHandle() {
3317
+ const handle = document.createElement("div");
3318
+ handle.className = "layer-control-drag-handle layer-control-drag-handle-disabled";
3319
+ handle.innerHTML = `<svg viewBox="0 0 16 16" fill="currentColor">
3320
+ <circle cx="5" cy="3" r="1.5"/>
3321
+ <circle cx="11" cy="3" r="1.5"/>
3322
+ <circle cx="5" cy="8" r="1.5"/>
3323
+ <circle cx="11" cy="8" r="1.5"/>
3324
+ <circle cx="5" cy="13" r="1.5"/>
3325
+ <circle cx="11" cy="13" r="1.5"/>
3326
+ </svg>`;
3327
+ return handle;
3328
+ }
3329
+ /**
3330
+ * Start dragging a layer
3331
+ */
3332
+ startDrag(layerId, e) {
3333
+ var _a;
3334
+ const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
3335
+ if (!itemEl) return;
3336
+ const rect = itemEl.getBoundingClientRect();
3337
+ this.state.drag = {
3338
+ active: true,
3339
+ layerId,
3340
+ startY: e.clientY,
3341
+ currentY: e.clientY,
3342
+ placeholder: null,
3343
+ draggedElement: null
3344
+ };
3345
+ this.panel.classList.add("dragging-active");
3346
+ itemEl.classList.add("dragging");
3347
+ const placeholder = document.createElement("div");
3348
+ placeholder.className = "layer-control-drop-placeholder";
3349
+ placeholder.style.height = `${rect.height}px`;
3350
+ (_a = itemEl.parentNode) == null ? void 0 : _a.insertBefore(placeholder, itemEl);
3351
+ this.state.drag.placeholder = placeholder;
3352
+ const clone = itemEl.cloneNode(true);
3353
+ clone.classList.remove("dragging");
3354
+ clone.className = "layer-control-item layer-control-item-dragging";
3355
+ clone.style.width = `${rect.width}px`;
3356
+ clone.style.left = `${rect.left}px`;
3357
+ clone.style.top = `${rect.top}px`;
3358
+ document.body.appendChild(clone);
3359
+ this.state.drag.draggedElement = clone;
3360
+ const onMove = (moveE) => this.onDragMove(moveE);
3361
+ const onEnd = (endE) => {
3362
+ document.removeEventListener("pointermove", onMove);
3363
+ document.removeEventListener("pointerup", onEnd);
3364
+ document.removeEventListener("pointercancel", onEnd);
3365
+ this.endDrag(endE);
3366
+ };
3367
+ document.addEventListener("pointermove", onMove);
3368
+ document.addEventListener("pointerup", onEnd);
3369
+ document.addEventListener("pointercancel", onEnd);
3370
+ }
3371
+ /**
3372
+ * Handle drag move
3373
+ */
3374
+ onDragMove(e) {
3375
+ var _a, _b;
3376
+ if (!this.state.drag.active || !this.state.drag.draggedElement) return;
3377
+ const placeholder = this.state.drag.placeholder;
3378
+ if (!placeholder) return;
3379
+ const deltaY = e.clientY - this.state.drag.startY;
3380
+ const clone = this.state.drag.draggedElement;
3381
+ const currentTop = parseFloat(clone.style.top) || 0;
3382
+ clone.style.top = `${currentTop + deltaY}px`;
3383
+ this.state.drag.startY = e.clientY;
3384
+ this.state.drag.currentY = e.clientY;
3385
+ const items = Array.from(this.panel.querySelectorAll(".layer-control-item:not(.dragging)")).filter((item) => item.dataset.layerId !== "Background");
3386
+ for (const item of items) {
3387
+ const itemRect = item.getBoundingClientRect();
3388
+ if (e.clientY >= itemRect.top && e.clientY <= itemRect.bottom) {
3389
+ const itemMiddle = itemRect.top + itemRect.height / 2;
3390
+ if (e.clientY < itemMiddle) {
3391
+ if (placeholder.nextSibling !== item) {
3392
+ (_a = item.parentNode) == null ? void 0 : _a.insertBefore(placeholder, item);
3393
+ }
3394
+ } else {
3395
+ if (placeholder.previousSibling !== item) {
3396
+ (_b = item.parentNode) == null ? void 0 : _b.insertBefore(placeholder, item.nextSibling);
3397
+ }
3398
+ }
3399
+ break;
3400
+ }
3401
+ }
3402
+ }
3403
+ /**
3404
+ * End dragging
3405
+ */
3406
+ endDrag(_e) {
3407
+ var _a;
3408
+ if (!this.state.drag.active) return;
3409
+ const layerId = this.state.drag.layerId;
3410
+ const placeholder = this.state.drag.placeholder;
3411
+ const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
3412
+ if (itemEl && placeholder) {
3413
+ (_a = placeholder.parentNode) == null ? void 0 : _a.insertBefore(itemEl, placeholder);
3414
+ itemEl.classList.remove("dragging");
3415
+ }
3416
+ this.cleanupDragState();
3417
+ this.applyUIOrderToMap();
3418
+ }
3419
+ /**
3420
+ * Clean up drag state
3421
+ */
3422
+ cleanupDragState() {
3423
+ if (this.state.drag.draggedElement) {
3424
+ this.state.drag.draggedElement.remove();
3425
+ }
3426
+ if (this.state.drag.placeholder) {
3427
+ this.state.drag.placeholder.remove();
3428
+ }
3429
+ this.panel.classList.remove("dragging-active");
3430
+ this.panel.querySelectorAll(".layer-control-item.dragging").forEach((el) => {
3431
+ el.classList.remove("dragging");
3432
+ });
3433
+ this.state.drag = {
3434
+ active: false,
3435
+ layerId: null,
3436
+ startY: 0,
3437
+ currentY: 0,
3438
+ placeholder: null,
3439
+ draggedElement: null
3440
+ };
3441
+ }
3442
+ /**
3443
+ * Apply UI order to map layers
3444
+ */
3445
+ applyUIOrderToMap() {
3446
+ var _a, _b;
3447
+ this.state.isStyleOperationInProgress = true;
3448
+ const items = this.panel.querySelectorAll(".layer-control-item");
3449
+ const uiLayerIds = [];
3450
+ items.forEach((item) => {
3451
+ const layerId = item.dataset.layerId;
3452
+ if (layerId && layerId !== "Background") {
3453
+ uiLayerIds.push(layerId);
3454
+ }
3455
+ });
3456
+ const reversedIds = [...uiLayerIds].reverse();
3457
+ const style = this.map.getStyle();
3458
+ const mapLibreLayerIds = new Set(((_a = style == null ? void 0 : style.layers) == null ? void 0 : _a.map((l) => l.id)) || []);
3459
+ for (let i = 0; i < reversedIds.length; i++) {
3460
+ const layerId = reversedIds[i];
3461
+ if (!mapLibreLayerIds.has(layerId)) {
3462
+ continue;
3463
+ }
3464
+ let beforeId = void 0;
3465
+ for (let j = i - 1; j >= 0; j--) {
3466
+ if (mapLibreLayerIds.has(reversedIds[j])) {
3467
+ beforeId = reversedIds[j];
3468
+ break;
3469
+ }
3470
+ }
3471
+ try {
3472
+ this.map.moveLayer(layerId, beforeId);
3473
+ } catch (e) {
3474
+ }
3475
+ }
3476
+ (_b = this.onLayerReorder) == null ? void 0 : _b.call(this, uiLayerIds);
3477
+ setTimeout(() => {
3478
+ this.state.isStyleOperationInProgress = false;
3479
+ }, 200);
3480
+ }
3481
+ }
3482
+ export {
3483
+ CustomLayerRegistry,
3484
+ LayerControl,
3485
+ clamp,
3486
+ createBackgroundGroupSymbolSVG,
3487
+ createLayerSymbolSVG,
3488
+ darkenColor,
3489
+ formatNumericValue,
3490
+ getLayerColor,
3491
+ getLayerColorFromSpec,
3492
+ getLayerOpacity,
3493
+ getLayerType,
3494
+ isStyleableLayerType,
3495
+ normalizeColor,
3496
+ rgbToHex,
3497
+ setLayerOpacity
3498
+ };
3499
+ //# sourceMappingURL=index.mjs.map