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