maplibre-gl-layer-control 0.8.2 → 0.9.0
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 +635 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +635 -8
- package/dist/index.mjs.map +1 -1
- package/dist/maplibre-gl-layer-control.css +168 -2
- package/dist/types/index.d.ts +118 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -120,6 +120,31 @@ class CustomLayerRegistry {
|
|
|
120
120
|
}
|
|
121
121
|
return null;
|
|
122
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
|
+
* Remove a custom layer through its adapter.
|
|
137
|
+
* @param layerId The layer ID to remove
|
|
138
|
+
* @returns true if the operation was handled by an adapter
|
|
139
|
+
*/
|
|
140
|
+
removeLayer(layerId) {
|
|
141
|
+
const adapter = this.getAdapterForLayer(layerId);
|
|
142
|
+
if (adapter && adapter.removeLayer) {
|
|
143
|
+
adapter.removeLayer(layerId);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
123
148
|
/**
|
|
124
149
|
* Subscribe to layer changes across all adapters.
|
|
125
150
|
* @param callback Function called when layers are added or removed
|
|
@@ -670,6 +695,13 @@ class LayerControl {
|
|
|
670
695
|
__publicField(this, "widthDragStartX", null);
|
|
671
696
|
__publicField(this, "widthDragStartWidth", null);
|
|
672
697
|
__publicField(this, "widthFrame", null);
|
|
698
|
+
// Context menu and drag-drop
|
|
699
|
+
__publicField(this, "contextMenuEl", null);
|
|
700
|
+
__publicField(this, "enableContextMenu");
|
|
701
|
+
__publicField(this, "enableDragAndDrop");
|
|
702
|
+
__publicField(this, "onLayerRename");
|
|
703
|
+
__publicField(this, "onLayerReorder");
|
|
704
|
+
__publicField(this, "onLayerRemove");
|
|
673
705
|
this.minPanelWidth = options.panelMinWidth || 240;
|
|
674
706
|
this.maxPanelWidth = options.panelMaxWidth || 420;
|
|
675
707
|
this.maxPanelHeight = options.panelMaxHeight || 600;
|
|
@@ -678,16 +710,37 @@ class LayerControl {
|
|
|
678
710
|
this.showLayerSymbol = options.showLayerSymbol !== false;
|
|
679
711
|
this.excludeDrawnLayers = options.excludeDrawnLayers !== false;
|
|
680
712
|
this.excludeLayerPatterns = this.wildcardPatternsToRegex(options.excludeLayers || []);
|
|
713
|
+
this.enableContextMenu = options.enableContextMenu !== false;
|
|
714
|
+
this.enableDragAndDrop = options.enableDragAndDrop !== false;
|
|
715
|
+
this.onLayerRename = options.onLayerRename;
|
|
716
|
+
this.onLayerReorder = options.onLayerReorder;
|
|
717
|
+
this.onLayerRemove = options.onLayerRemove;
|
|
681
718
|
this.state = {
|
|
682
719
|
collapsed: options.collapsed !== false,
|
|
683
|
-
panelWidth: options.panelWidth ||
|
|
720
|
+
panelWidth: options.panelWidth || 350,
|
|
684
721
|
activeStyleEditor: null,
|
|
685
722
|
layerStates: options.layerStates || {},
|
|
686
723
|
originalStyles: /* @__PURE__ */ new Map(),
|
|
687
724
|
userInteractingWithSlider: false,
|
|
688
725
|
backgroundLegendOpen: false,
|
|
689
726
|
backgroundLayerVisibility: /* @__PURE__ */ new Map(),
|
|
690
|
-
onlyRenderedFilter: false
|
|
727
|
+
onlyRenderedFilter: false,
|
|
728
|
+
contextMenu: {
|
|
729
|
+
visible: false,
|
|
730
|
+
targetLayerId: null,
|
|
731
|
+
x: 0,
|
|
732
|
+
y: 0
|
|
733
|
+
},
|
|
734
|
+
renamingLayerId: null,
|
|
735
|
+
customLayerNames: /* @__PURE__ */ new Map(),
|
|
736
|
+
drag: {
|
|
737
|
+
active: false,
|
|
738
|
+
layerId: null,
|
|
739
|
+
startY: 0,
|
|
740
|
+
currentY: 0,
|
|
741
|
+
placeholder: null,
|
|
742
|
+
draggedElement: null
|
|
743
|
+
}
|
|
691
744
|
};
|
|
692
745
|
this.targetLayers = options.layers || Object.keys(this.state.layerStates);
|
|
693
746
|
this.styleEditors = /* @__PURE__ */ new Map();
|
|
@@ -721,6 +774,10 @@ class LayerControl {
|
|
|
721
774
|
this.panel = this.createPanel();
|
|
722
775
|
this.container.appendChild(this.button);
|
|
723
776
|
this.mapContainer.appendChild(this.panel);
|
|
777
|
+
if (this.enableContextMenu) {
|
|
778
|
+
this.contextMenuEl = this.createContextMenu();
|
|
779
|
+
this.mapContainer.appendChild(this.contextMenuEl);
|
|
780
|
+
}
|
|
724
781
|
this.updateWidthDisplay();
|
|
725
782
|
this.setupEventListeners();
|
|
726
783
|
if (this.basemapStyleUrl && !this.basemapLayerIds) {
|
|
@@ -777,7 +834,7 @@ class LayerControl {
|
|
|
777
834
|
* Called when the control is removed from the map
|
|
778
835
|
*/
|
|
779
836
|
onRemove() {
|
|
780
|
-
var _a, _b;
|
|
837
|
+
var _a, _b, _c;
|
|
781
838
|
if (this.customLayerUnsubscribe) {
|
|
782
839
|
this.customLayerUnsubscribe();
|
|
783
840
|
this.customLayerUnsubscribe = null;
|
|
@@ -794,8 +851,13 @@ class LayerControl {
|
|
|
794
851
|
this.map.off("resize", this.mapResizeHandler);
|
|
795
852
|
this.mapResizeHandler = null;
|
|
796
853
|
}
|
|
797
|
-
(
|
|
798
|
-
|
|
854
|
+
if (this.contextMenuEl) {
|
|
855
|
+
(_a = this.contextMenuEl.parentNode) == null ? void 0 : _a.removeChild(this.contextMenuEl);
|
|
856
|
+
this.contextMenuEl = null;
|
|
857
|
+
}
|
|
858
|
+
this.cleanupDragState();
|
|
859
|
+
(_b = this.panel.parentNode) == null ? void 0 : _b.removeChild(this.panel);
|
|
860
|
+
(_c = this.container.parentNode) == null ? void 0 : _c.removeChild(this.container);
|
|
799
861
|
}
|
|
800
862
|
/**
|
|
801
863
|
* Auto-detect layers from the map and populate layerStates
|
|
@@ -1371,6 +1433,11 @@ class LayerControl {
|
|
|
1371
1433
|
this.button.addEventListener("click", () => this.toggle());
|
|
1372
1434
|
document.addEventListener("click", (e) => {
|
|
1373
1435
|
const target = e.target;
|
|
1436
|
+
if (this.contextMenuEl && this.state.contextMenu.visible) {
|
|
1437
|
+
if (!this.contextMenuEl.contains(target)) {
|
|
1438
|
+
this.hideContextMenu();
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1374
1441
|
if (!this.container.contains(target) && !this.panel.contains(target)) {
|
|
1375
1442
|
this.collapse();
|
|
1376
1443
|
}
|
|
@@ -1467,6 +1534,15 @@ class LayerControl {
|
|
|
1467
1534
|
item.setAttribute("data-layer-id", layerId);
|
|
1468
1535
|
const row = document.createElement("div");
|
|
1469
1536
|
row.className = "layer-control-row";
|
|
1537
|
+
if (this.enableDragAndDrop) {
|
|
1538
|
+
if (layerId === "Background") {
|
|
1539
|
+
const disabledHandle = this.createDisabledDragHandle();
|
|
1540
|
+
row.appendChild(disabledHandle);
|
|
1541
|
+
} else {
|
|
1542
|
+
const dragHandle = this.createDragHandle(layerId);
|
|
1543
|
+
row.appendChild(dragHandle);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1470
1546
|
const checkbox = document.createElement("input");
|
|
1471
1547
|
checkbox.type = "checkbox";
|
|
1472
1548
|
checkbox.className = "layer-control-checkbox";
|
|
@@ -1474,10 +1550,11 @@ class LayerControl {
|
|
|
1474
1550
|
checkbox.addEventListener("change", () => {
|
|
1475
1551
|
this.toggleLayerVisibility(layerId, checkbox.checked);
|
|
1476
1552
|
});
|
|
1553
|
+
const displayName = this.state.customLayerNames.get(layerId) || state.name || layerId;
|
|
1477
1554
|
const name = document.createElement("span");
|
|
1478
1555
|
name.className = "layer-control-name";
|
|
1479
|
-
name.textContent =
|
|
1480
|
-
name.title =
|
|
1556
|
+
name.textContent = displayName;
|
|
1557
|
+
name.title = displayName;
|
|
1481
1558
|
row.appendChild(checkbox);
|
|
1482
1559
|
if (this.showLayerSymbol) {
|
|
1483
1560
|
if (layerId === "Background") {
|
|
@@ -1524,6 +1601,13 @@ class LayerControl {
|
|
|
1524
1601
|
}
|
|
1525
1602
|
}
|
|
1526
1603
|
item.appendChild(row);
|
|
1604
|
+
if (this.enableContextMenu && layerId !== "Background") {
|
|
1605
|
+
row.addEventListener("contextmenu", (e) => {
|
|
1606
|
+
e.preventDefault();
|
|
1607
|
+
e.stopPropagation();
|
|
1608
|
+
this.showContextMenu(layerId, e.clientX, e.clientY);
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1527
1611
|
this.panel.appendChild(item);
|
|
1528
1612
|
}
|
|
1529
1613
|
/**
|
|
@@ -2243,7 +2327,7 @@ class LayerControl {
|
|
|
2243
2327
|
const contrast = this.map.getPaintProperty(layerId, "raster-contrast");
|
|
2244
2328
|
this.createSliderControl(container, layerId, "raster-contrast", "Contrast", typeof contrast === "number" ? contrast : 0, -1, 1, 0.05);
|
|
2245
2329
|
const hueRotate = this.map.getPaintProperty(layerId, "raster-hue-rotate");
|
|
2246
|
-
this.createSliderControl(container, layerId, "raster-hue-rotate", "Hue Rotate", typeof hueRotate === "number" ? hueRotate : 0, 0,
|
|
2330
|
+
this.createSliderControl(container, layerId, "raster-hue-rotate", "Hue Rotate", typeof hueRotate === "number" ? hueRotate : 0, 0, 350, 5);
|
|
2247
2331
|
}
|
|
2248
2332
|
/**
|
|
2249
2333
|
* Add controls for symbol layers
|
|
@@ -2439,6 +2523,9 @@ class LayerControl {
|
|
|
2439
2523
|
if (layerId !== "Background" && !this.state.layerStates[layerId]) {
|
|
2440
2524
|
const layer = this.map.getLayer(layerId);
|
|
2441
2525
|
if (layer) {
|
|
2526
|
+
if (this.initialLayerIds !== null && this.initialLayerIds.has(layerId)) {
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2442
2529
|
if (isAutoDetectMode) {
|
|
2443
2530
|
if (this.excludeDrawnLayers && this.isDrawnLayer(layerId)) {
|
|
2444
2531
|
return;
|
|
@@ -2446,6 +2533,9 @@ class LayerControl {
|
|
|
2446
2533
|
if (this.isExcludedByPattern(layerId)) {
|
|
2447
2534
|
return;
|
|
2448
2535
|
}
|
|
2536
|
+
if (!this.isUserAddedLayer(layerId)) {
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2449
2539
|
if (useBasemapStyleDetection) {
|
|
2450
2540
|
if (this.basemapLayerIds.has(layerId)) {
|
|
2451
2541
|
return;
|
|
@@ -2559,6 +2649,543 @@ class LayerControl {
|
|
|
2559
2649
|
this.checkForNewLayers();
|
|
2560
2650
|
}
|
|
2561
2651
|
}
|
|
2652
|
+
// ===== Context Menu Methods =====
|
|
2653
|
+
/**
|
|
2654
|
+
* Create context menu element
|
|
2655
|
+
*/
|
|
2656
|
+
createContextMenu() {
|
|
2657
|
+
const menu = document.createElement("div");
|
|
2658
|
+
menu.className = "layer-control-context-menu";
|
|
2659
|
+
menu.style.display = "none";
|
|
2660
|
+
const renameItem = this.createContextMenuItem("Rename", "✏️", () => {
|
|
2661
|
+
if (this.state.contextMenu.targetLayerId) {
|
|
2662
|
+
this.startRenaming(this.state.contextMenu.targetLayerId);
|
|
2663
|
+
}
|
|
2664
|
+
this.hideContextMenu();
|
|
2665
|
+
});
|
|
2666
|
+
const zoomItem = this.createContextMenuItem("Zoom to Layer", "🔍", () => {
|
|
2667
|
+
if (this.state.contextMenu.targetLayerId) {
|
|
2668
|
+
this.zoomToLayer(this.state.contextMenu.targetLayerId);
|
|
2669
|
+
}
|
|
2670
|
+
this.hideContextMenu();
|
|
2671
|
+
});
|
|
2672
|
+
const sep1 = document.createElement("div");
|
|
2673
|
+
sep1.className = "context-menu-separator";
|
|
2674
|
+
const moveUpItem = this.createContextMenuItem("Move Up", "↑", () => {
|
|
2675
|
+
if (this.state.contextMenu.targetLayerId) {
|
|
2676
|
+
this.moveLayerUp(this.state.contextMenu.targetLayerId);
|
|
2677
|
+
}
|
|
2678
|
+
this.hideContextMenu();
|
|
2679
|
+
});
|
|
2680
|
+
const moveTopItem = this.createContextMenuItem("Move to Top", "⤒", () => {
|
|
2681
|
+
if (this.state.contextMenu.targetLayerId) {
|
|
2682
|
+
this.moveLayerToTop(this.state.contextMenu.targetLayerId);
|
|
2683
|
+
}
|
|
2684
|
+
this.hideContextMenu();
|
|
2685
|
+
});
|
|
2686
|
+
const moveDownItem = this.createContextMenuItem("Move Down", "↓", () => {
|
|
2687
|
+
if (this.state.contextMenu.targetLayerId) {
|
|
2688
|
+
this.moveLayerDown(this.state.contextMenu.targetLayerId);
|
|
2689
|
+
}
|
|
2690
|
+
this.hideContextMenu();
|
|
2691
|
+
});
|
|
2692
|
+
const moveBottomItem = this.createContextMenuItem("Move to Bottom", "⤓", () => {
|
|
2693
|
+
if (this.state.contextMenu.targetLayerId) {
|
|
2694
|
+
this.moveLayerToBottom(this.state.contextMenu.targetLayerId);
|
|
2695
|
+
}
|
|
2696
|
+
this.hideContextMenu();
|
|
2697
|
+
});
|
|
2698
|
+
const sep2 = document.createElement("div");
|
|
2699
|
+
sep2.className = "context-menu-separator";
|
|
2700
|
+
const removeItem = this.createContextMenuItem("Remove Layer", "🗑️", () => {
|
|
2701
|
+
if (this.state.contextMenu.targetLayerId) {
|
|
2702
|
+
this.removeLayer(this.state.contextMenu.targetLayerId);
|
|
2703
|
+
}
|
|
2704
|
+
this.hideContextMenu();
|
|
2705
|
+
}, true);
|
|
2706
|
+
menu.appendChild(renameItem);
|
|
2707
|
+
menu.appendChild(zoomItem);
|
|
2708
|
+
menu.appendChild(sep1);
|
|
2709
|
+
menu.appendChild(moveUpItem);
|
|
2710
|
+
menu.appendChild(moveTopItem);
|
|
2711
|
+
menu.appendChild(moveDownItem);
|
|
2712
|
+
menu.appendChild(moveBottomItem);
|
|
2713
|
+
menu.appendChild(sep2);
|
|
2714
|
+
menu.appendChild(removeItem);
|
|
2715
|
+
return menu;
|
|
2716
|
+
}
|
|
2717
|
+
/**
|
|
2718
|
+
* Create a context menu item
|
|
2719
|
+
*/
|
|
2720
|
+
createContextMenuItem(label, icon, onClick, isDanger = false) {
|
|
2721
|
+
const item = document.createElement("div");
|
|
2722
|
+
item.className = "context-menu-item" + (isDanger ? " context-menu-item-danger" : "");
|
|
2723
|
+
const iconEl = document.createElement("span");
|
|
2724
|
+
iconEl.className = "context-menu-item-icon";
|
|
2725
|
+
iconEl.textContent = icon;
|
|
2726
|
+
const labelEl = document.createElement("span");
|
|
2727
|
+
labelEl.className = "context-menu-item-label";
|
|
2728
|
+
labelEl.textContent = label;
|
|
2729
|
+
item.appendChild(iconEl);
|
|
2730
|
+
item.appendChild(labelEl);
|
|
2731
|
+
item.addEventListener("click", (e) => {
|
|
2732
|
+
e.stopPropagation();
|
|
2733
|
+
onClick();
|
|
2734
|
+
});
|
|
2735
|
+
return item;
|
|
2736
|
+
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Show context menu at position
|
|
2739
|
+
*/
|
|
2740
|
+
showContextMenu(layerId, x, y) {
|
|
2741
|
+
if (!this.contextMenuEl) return;
|
|
2742
|
+
this.state.contextMenu = {
|
|
2743
|
+
visible: true,
|
|
2744
|
+
targetLayerId: layerId,
|
|
2745
|
+
x,
|
|
2746
|
+
y
|
|
2747
|
+
};
|
|
2748
|
+
const mapRect = this.mapContainer.getBoundingClientRect();
|
|
2749
|
+
let menuX = x - mapRect.left;
|
|
2750
|
+
let menuY = y - mapRect.top;
|
|
2751
|
+
this.contextMenuEl.style.display = "block";
|
|
2752
|
+
const menuRect = this.contextMenuEl.getBoundingClientRect();
|
|
2753
|
+
if (menuX + menuRect.width > mapRect.width) {
|
|
2754
|
+
menuX = mapRect.width - menuRect.width - 5;
|
|
2755
|
+
}
|
|
2756
|
+
if (menuY + menuRect.height > mapRect.height) {
|
|
2757
|
+
menuY = mapRect.height - menuRect.height - 5;
|
|
2758
|
+
}
|
|
2759
|
+
this.contextMenuEl.style.left = `${menuX}px`;
|
|
2760
|
+
this.contextMenuEl.style.top = `${menuY}px`;
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2763
|
+
* Hide context menu
|
|
2764
|
+
*/
|
|
2765
|
+
hideContextMenu() {
|
|
2766
|
+
if (!this.contextMenuEl) return;
|
|
2767
|
+
this.state.contextMenu = {
|
|
2768
|
+
visible: false,
|
|
2769
|
+
targetLayerId: null,
|
|
2770
|
+
x: 0,
|
|
2771
|
+
y: 0
|
|
2772
|
+
};
|
|
2773
|
+
this.contextMenuEl.style.display = "none";
|
|
2774
|
+
}
|
|
2775
|
+
/**
|
|
2776
|
+
* Start renaming a layer
|
|
2777
|
+
*/
|
|
2778
|
+
startRenaming(layerId) {
|
|
2779
|
+
var _a;
|
|
2780
|
+
const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
|
|
2781
|
+
if (!itemEl) return;
|
|
2782
|
+
const nameEl = itemEl.querySelector(".layer-control-name");
|
|
2783
|
+
if (!nameEl) return;
|
|
2784
|
+
this.state.renamingLayerId = layerId;
|
|
2785
|
+
const currentName = this.state.customLayerNames.get(layerId) || ((_a = this.state.layerStates[layerId]) == null ? void 0 : _a.name) || layerId;
|
|
2786
|
+
const input = document.createElement("input");
|
|
2787
|
+
input.type = "text";
|
|
2788
|
+
input.className = "layer-control-name-input";
|
|
2789
|
+
input.value = currentName;
|
|
2790
|
+
const finishRename = () => {
|
|
2791
|
+
var _a2;
|
|
2792
|
+
const newName = input.value.trim() || currentName;
|
|
2793
|
+
const oldName = currentName;
|
|
2794
|
+
if (newName !== oldName) {
|
|
2795
|
+
this.state.customLayerNames.set(layerId, newName);
|
|
2796
|
+
if (this.state.layerStates[layerId]) {
|
|
2797
|
+
this.state.layerStates[layerId].name = newName;
|
|
2798
|
+
}
|
|
2799
|
+
(_a2 = this.onLayerRename) == null ? void 0 : _a2.call(this, layerId, oldName, newName);
|
|
2800
|
+
}
|
|
2801
|
+
const newNameEl = document.createElement("span");
|
|
2802
|
+
newNameEl.className = "layer-control-name";
|
|
2803
|
+
newNameEl.textContent = newName;
|
|
2804
|
+
newNameEl.title = newName;
|
|
2805
|
+
input.replaceWith(newNameEl);
|
|
2806
|
+
this.state.renamingLayerId = null;
|
|
2807
|
+
};
|
|
2808
|
+
input.addEventListener("blur", finishRename);
|
|
2809
|
+
input.addEventListener("keydown", (e) => {
|
|
2810
|
+
if (e.key === "Enter") {
|
|
2811
|
+
e.preventDefault();
|
|
2812
|
+
finishRename();
|
|
2813
|
+
} else if (e.key === "Escape") {
|
|
2814
|
+
e.preventDefault();
|
|
2815
|
+
const newNameEl = document.createElement("span");
|
|
2816
|
+
newNameEl.className = "layer-control-name";
|
|
2817
|
+
newNameEl.textContent = currentName;
|
|
2818
|
+
newNameEl.title = currentName;
|
|
2819
|
+
input.replaceWith(newNameEl);
|
|
2820
|
+
this.state.renamingLayerId = null;
|
|
2821
|
+
}
|
|
2822
|
+
});
|
|
2823
|
+
nameEl.replaceWith(input);
|
|
2824
|
+
input.focus();
|
|
2825
|
+
input.select();
|
|
2826
|
+
}
|
|
2827
|
+
/**
|
|
2828
|
+
* Zoom to a layer's bounds
|
|
2829
|
+
*/
|
|
2830
|
+
zoomToLayer(layerId) {
|
|
2831
|
+
const layerState = this.state.layerStates[layerId];
|
|
2832
|
+
if ((layerState == null ? void 0 : layerState.isCustomLayer) && this.customLayerRegistry) {
|
|
2833
|
+
const bounds = this.customLayerRegistry.getBounds(layerId);
|
|
2834
|
+
if (bounds) {
|
|
2835
|
+
this.map.fitBounds(bounds, { padding: 50 });
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
const layer = this.map.getLayer(layerId);
|
|
2840
|
+
if (!layer) return;
|
|
2841
|
+
try {
|
|
2842
|
+
const sourceId = layer.source;
|
|
2843
|
+
let features = sourceId ? this.map.querySourceFeatures(sourceId) : [];
|
|
2844
|
+
if (features.length === 0) {
|
|
2845
|
+
features = this.map.queryRenderedFeatures({ layers: [layerId] });
|
|
2846
|
+
}
|
|
2847
|
+
if (features.length === 0) return;
|
|
2848
|
+
let minLng = Infinity, minLat = Infinity, maxLng = -Infinity, maxLat = -Infinity;
|
|
2849
|
+
features.forEach((feature) => {
|
|
2850
|
+
if (!feature.geometry) return;
|
|
2851
|
+
const processCoords = (coords) => {
|
|
2852
|
+
if (typeof coords[0] === "number") {
|
|
2853
|
+
const [lng, lat] = coords;
|
|
2854
|
+
minLng = Math.min(minLng, lng);
|
|
2855
|
+
minLat = Math.min(minLat, lat);
|
|
2856
|
+
maxLng = Math.max(maxLng, lng);
|
|
2857
|
+
maxLat = Math.max(maxLat, lat);
|
|
2858
|
+
} else {
|
|
2859
|
+
coords.forEach(processCoords);
|
|
2860
|
+
}
|
|
2861
|
+
};
|
|
2862
|
+
if (feature.geometry.type === "Point") {
|
|
2863
|
+
processCoords(feature.geometry.coordinates);
|
|
2864
|
+
} else if (feature.geometry.type === "LineString" || feature.geometry.type === "MultiPoint") {
|
|
2865
|
+
feature.geometry.coordinates.forEach(processCoords);
|
|
2866
|
+
} else if (feature.geometry.type === "Polygon" || feature.geometry.type === "MultiLineString") {
|
|
2867
|
+
feature.geometry.coordinates.forEach((ring) => ring.forEach(processCoords));
|
|
2868
|
+
} else if (feature.geometry.type === "MultiPolygon") {
|
|
2869
|
+
feature.geometry.coordinates.forEach(
|
|
2870
|
+
(polygon) => polygon.forEach((ring) => ring.forEach(processCoords))
|
|
2871
|
+
);
|
|
2872
|
+
}
|
|
2873
|
+
});
|
|
2874
|
+
if (minLng !== Infinity && minLat !== Infinity && maxLng !== -Infinity && maxLat !== -Infinity) {
|
|
2875
|
+
this.map.fitBounds([[minLng, minLat], [maxLng, maxLat]], { padding: 50 });
|
|
2876
|
+
}
|
|
2877
|
+
} catch (error) {
|
|
2878
|
+
console.warn(`Failed to zoom to layer ${layerId}:`, error);
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Get user layer IDs in current map order (top to bottom in UI = high z-index to low)
|
|
2883
|
+
*/
|
|
2884
|
+
getUserLayerIdsInMapOrder() {
|
|
2885
|
+
const style = this.map.getStyle();
|
|
2886
|
+
if (!(style == null ? void 0 : style.layers)) return [];
|
|
2887
|
+
const mapLayerIds = style.layers.map((l) => l.id);
|
|
2888
|
+
const userLayerIds = Object.keys(this.state.layerStates).filter((id) => id !== "Background");
|
|
2889
|
+
return userLayerIds.filter((id) => mapLayerIds.includes(id)).sort((a, b) => mapLayerIds.indexOf(b) - mapLayerIds.indexOf(a));
|
|
2890
|
+
}
|
|
2891
|
+
/**
|
|
2892
|
+
* Move a layer up in UI (higher rendering order = move to higher z-index)
|
|
2893
|
+
*/
|
|
2894
|
+
moveLayerUp(layerId) {
|
|
2895
|
+
var _a;
|
|
2896
|
+
const layerIds = this.getUserLayerIdsInMapOrder();
|
|
2897
|
+
const index = layerIds.indexOf(layerId);
|
|
2898
|
+
if (index <= 0) return;
|
|
2899
|
+
try {
|
|
2900
|
+
if (index === 1) {
|
|
2901
|
+
this.map.moveLayer(layerId);
|
|
2902
|
+
} else {
|
|
2903
|
+
const targetBeforeId = layerIds[index - 2];
|
|
2904
|
+
this.map.moveLayer(layerId, targetBeforeId);
|
|
2905
|
+
}
|
|
2906
|
+
} catch (e) {
|
|
2907
|
+
}
|
|
2908
|
+
this.rebuildLayerItems();
|
|
2909
|
+
(_a = this.onLayerReorder) == null ? void 0 : _a.call(this, this.getUserLayerIdsInMapOrder());
|
|
2910
|
+
}
|
|
2911
|
+
/**
|
|
2912
|
+
* Move a layer to the top (highest rendering order)
|
|
2913
|
+
*/
|
|
2914
|
+
moveLayerToTop(layerId) {
|
|
2915
|
+
var _a;
|
|
2916
|
+
try {
|
|
2917
|
+
this.map.moveLayer(layerId);
|
|
2918
|
+
} catch (e) {
|
|
2919
|
+
}
|
|
2920
|
+
this.rebuildLayerItems();
|
|
2921
|
+
(_a = this.onLayerReorder) == null ? void 0 : _a.call(this, this.getUserLayerIdsInMapOrder());
|
|
2922
|
+
}
|
|
2923
|
+
/**
|
|
2924
|
+
* Move a layer down in UI (lower rendering order = move to lower z-index)
|
|
2925
|
+
*/
|
|
2926
|
+
moveLayerDown(layerId) {
|
|
2927
|
+
var _a;
|
|
2928
|
+
const layerIds = this.getUserLayerIdsInMapOrder();
|
|
2929
|
+
const index = layerIds.indexOf(layerId);
|
|
2930
|
+
if (index < 0 || index >= layerIds.length - 1) return;
|
|
2931
|
+
const belowLayerId = layerIds[index + 1];
|
|
2932
|
+
try {
|
|
2933
|
+
this.map.moveLayer(layerId, belowLayerId);
|
|
2934
|
+
} catch (e) {
|
|
2935
|
+
}
|
|
2936
|
+
this.rebuildLayerItems();
|
|
2937
|
+
(_a = this.onLayerReorder) == null ? void 0 : _a.call(this, this.getUserLayerIdsInMapOrder());
|
|
2938
|
+
}
|
|
2939
|
+
/**
|
|
2940
|
+
* Move a layer to the bottom (lowest rendering order among user layers)
|
|
2941
|
+
*/
|
|
2942
|
+
moveLayerToBottom(layerId) {
|
|
2943
|
+
var _a;
|
|
2944
|
+
const layerIds = this.getUserLayerIdsInMapOrder();
|
|
2945
|
+
if (layerIds.length <= 1) return;
|
|
2946
|
+
const index = layerIds.indexOf(layerId);
|
|
2947
|
+
if (index < 0 || index === layerIds.length - 1) return;
|
|
2948
|
+
const bottomLayerId = layerIds[layerIds.length - 1];
|
|
2949
|
+
try {
|
|
2950
|
+
this.map.moveLayer(layerId, bottomLayerId);
|
|
2951
|
+
} catch (e) {
|
|
2952
|
+
}
|
|
2953
|
+
this.rebuildLayerItems();
|
|
2954
|
+
(_a = this.onLayerReorder) == null ? void 0 : _a.call(this, this.getUserLayerIdsInMapOrder());
|
|
2955
|
+
}
|
|
2956
|
+
/**
|
|
2957
|
+
* Remove a layer from the map
|
|
2958
|
+
*/
|
|
2959
|
+
removeLayer(layerId) {
|
|
2960
|
+
var _a, _b;
|
|
2961
|
+
const layerState = this.state.layerStates[layerId];
|
|
2962
|
+
if ((layerState == null ? void 0 : layerState.isCustomLayer) && this.customLayerRegistry) {
|
|
2963
|
+
this.customLayerRegistry.removeLayer(layerId);
|
|
2964
|
+
} else {
|
|
2965
|
+
try {
|
|
2966
|
+
const layer = this.map.getLayer(layerId);
|
|
2967
|
+
if (layer) {
|
|
2968
|
+
const sourceId = layer.source;
|
|
2969
|
+
this.map.removeLayer(layerId);
|
|
2970
|
+
if (sourceId) {
|
|
2971
|
+
const style = this.map.getStyle();
|
|
2972
|
+
const sourceStillUsed = (_a = style == null ? void 0 : style.layers) == null ? void 0 : _a.some((l) => l.source === sourceId);
|
|
2973
|
+
if (!sourceStillUsed) {
|
|
2974
|
+
try {
|
|
2975
|
+
this.map.removeSource(sourceId);
|
|
2976
|
+
} catch (e) {
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
} catch (error) {
|
|
2982
|
+
console.warn(`Failed to remove layer ${layerId}:`, error);
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
delete this.state.layerStates[layerId];
|
|
2986
|
+
this.state.customLayerNames.delete(layerId);
|
|
2987
|
+
const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
|
|
2988
|
+
if (itemEl) {
|
|
2989
|
+
itemEl.remove();
|
|
2990
|
+
}
|
|
2991
|
+
(_b = this.onLayerRemove) == null ? void 0 : _b.call(this, layerId);
|
|
2992
|
+
}
|
|
2993
|
+
/**
|
|
2994
|
+
* Rebuild layer items to reflect current order
|
|
2995
|
+
*/
|
|
2996
|
+
rebuildLayerItems() {
|
|
2997
|
+
const userLayerIds = this.getUserLayerIdsInMapOrder();
|
|
2998
|
+
const newLayerStates = {};
|
|
2999
|
+
if (this.state.layerStates["Background"]) {
|
|
3000
|
+
newLayerStates["Background"] = this.state.layerStates["Background"];
|
|
3001
|
+
}
|
|
3002
|
+
userLayerIds.forEach((id) => {
|
|
3003
|
+
if (this.state.layerStates[id]) {
|
|
3004
|
+
newLayerStates[id] = this.state.layerStates[id];
|
|
3005
|
+
}
|
|
3006
|
+
});
|
|
3007
|
+
this.state.layerStates = newLayerStates;
|
|
3008
|
+
this.buildLayerItems();
|
|
3009
|
+
}
|
|
3010
|
+
// ===== Drag and Drop Methods =====
|
|
3011
|
+
/**
|
|
3012
|
+
* Create drag handle element
|
|
3013
|
+
*/
|
|
3014
|
+
createDragHandle(layerId) {
|
|
3015
|
+
const handle = document.createElement("div");
|
|
3016
|
+
handle.className = "layer-control-drag-handle";
|
|
3017
|
+
handle.innerHTML = `<svg viewBox="0 0 16 16" fill="currentColor">
|
|
3018
|
+
<circle cx="5" cy="3" r="1.5"/>
|
|
3019
|
+
<circle cx="11" cy="3" r="1.5"/>
|
|
3020
|
+
<circle cx="5" cy="8" r="1.5"/>
|
|
3021
|
+
<circle cx="11" cy="8" r="1.5"/>
|
|
3022
|
+
<circle cx="5" cy="13" r="1.5"/>
|
|
3023
|
+
<circle cx="11" cy="13" r="1.5"/>
|
|
3024
|
+
</svg>`;
|
|
3025
|
+
handle.title = "Drag to reorder";
|
|
3026
|
+
handle.addEventListener("pointerdown", (e) => {
|
|
3027
|
+
e.preventDefault();
|
|
3028
|
+
e.stopPropagation();
|
|
3029
|
+
this.startDrag(layerId, e);
|
|
3030
|
+
});
|
|
3031
|
+
return handle;
|
|
3032
|
+
}
|
|
3033
|
+
/**
|
|
3034
|
+
* Create a disabled drag handle for alignment (used for Background layer)
|
|
3035
|
+
*/
|
|
3036
|
+
createDisabledDragHandle() {
|
|
3037
|
+
const handle = document.createElement("div");
|
|
3038
|
+
handle.className = "layer-control-drag-handle layer-control-drag-handle-disabled";
|
|
3039
|
+
handle.innerHTML = `<svg viewBox="0 0 16 16" fill="currentColor">
|
|
3040
|
+
<circle cx="5" cy="3" r="1.5"/>
|
|
3041
|
+
<circle cx="11" cy="3" r="1.5"/>
|
|
3042
|
+
<circle cx="5" cy="8" r="1.5"/>
|
|
3043
|
+
<circle cx="11" cy="8" r="1.5"/>
|
|
3044
|
+
<circle cx="5" cy="13" r="1.5"/>
|
|
3045
|
+
<circle cx="11" cy="13" r="1.5"/>
|
|
3046
|
+
</svg>`;
|
|
3047
|
+
return handle;
|
|
3048
|
+
}
|
|
3049
|
+
/**
|
|
3050
|
+
* Start dragging a layer
|
|
3051
|
+
*/
|
|
3052
|
+
startDrag(layerId, e) {
|
|
3053
|
+
var _a;
|
|
3054
|
+
const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
|
|
3055
|
+
if (!itemEl) return;
|
|
3056
|
+
const rect = itemEl.getBoundingClientRect();
|
|
3057
|
+
this.state.drag = {
|
|
3058
|
+
active: true,
|
|
3059
|
+
layerId,
|
|
3060
|
+
startY: e.clientY,
|
|
3061
|
+
currentY: e.clientY,
|
|
3062
|
+
placeholder: null,
|
|
3063
|
+
draggedElement: null
|
|
3064
|
+
};
|
|
3065
|
+
this.panel.classList.add("dragging-active");
|
|
3066
|
+
itemEl.classList.add("dragging");
|
|
3067
|
+
const placeholder = document.createElement("div");
|
|
3068
|
+
placeholder.className = "layer-control-drop-placeholder";
|
|
3069
|
+
placeholder.style.height = `${rect.height}px`;
|
|
3070
|
+
(_a = itemEl.parentNode) == null ? void 0 : _a.insertBefore(placeholder, itemEl);
|
|
3071
|
+
this.state.drag.placeholder = placeholder;
|
|
3072
|
+
const clone = itemEl.cloneNode(true);
|
|
3073
|
+
clone.classList.remove("dragging");
|
|
3074
|
+
clone.className = "layer-control-item layer-control-item-dragging";
|
|
3075
|
+
clone.style.width = `${rect.width}px`;
|
|
3076
|
+
clone.style.left = `${rect.left}px`;
|
|
3077
|
+
clone.style.top = `${rect.top}px`;
|
|
3078
|
+
document.body.appendChild(clone);
|
|
3079
|
+
this.state.drag.draggedElement = clone;
|
|
3080
|
+
const onMove = (moveE) => this.onDragMove(moveE);
|
|
3081
|
+
const onEnd = (endE) => {
|
|
3082
|
+
document.removeEventListener("pointermove", onMove);
|
|
3083
|
+
document.removeEventListener("pointerup", onEnd);
|
|
3084
|
+
document.removeEventListener("pointercancel", onEnd);
|
|
3085
|
+
this.endDrag(endE);
|
|
3086
|
+
};
|
|
3087
|
+
document.addEventListener("pointermove", onMove);
|
|
3088
|
+
document.addEventListener("pointerup", onEnd);
|
|
3089
|
+
document.addEventListener("pointercancel", onEnd);
|
|
3090
|
+
}
|
|
3091
|
+
/**
|
|
3092
|
+
* Handle drag move
|
|
3093
|
+
*/
|
|
3094
|
+
onDragMove(e) {
|
|
3095
|
+
var _a, _b;
|
|
3096
|
+
if (!this.state.drag.active || !this.state.drag.draggedElement) return;
|
|
3097
|
+
const placeholder = this.state.drag.placeholder;
|
|
3098
|
+
if (!placeholder) return;
|
|
3099
|
+
const deltaY = e.clientY - this.state.drag.startY;
|
|
3100
|
+
const clone = this.state.drag.draggedElement;
|
|
3101
|
+
const currentTop = parseFloat(clone.style.top) || 0;
|
|
3102
|
+
clone.style.top = `${currentTop + deltaY}px`;
|
|
3103
|
+
this.state.drag.startY = e.clientY;
|
|
3104
|
+
this.state.drag.currentY = e.clientY;
|
|
3105
|
+
const items = Array.from(this.panel.querySelectorAll(".layer-control-item:not(.dragging)")).filter((item) => item.dataset.layerId !== "Background");
|
|
3106
|
+
for (const item of items) {
|
|
3107
|
+
const itemRect = item.getBoundingClientRect();
|
|
3108
|
+
if (e.clientY >= itemRect.top && e.clientY <= itemRect.bottom) {
|
|
3109
|
+
const itemMiddle = itemRect.top + itemRect.height / 2;
|
|
3110
|
+
if (e.clientY < itemMiddle) {
|
|
3111
|
+
if (placeholder.nextSibling !== item) {
|
|
3112
|
+
(_a = item.parentNode) == null ? void 0 : _a.insertBefore(placeholder, item);
|
|
3113
|
+
}
|
|
3114
|
+
} else {
|
|
3115
|
+
if (placeholder.previousSibling !== item) {
|
|
3116
|
+
(_b = item.parentNode) == null ? void 0 : _b.insertBefore(placeholder, item.nextSibling);
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
break;
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
/**
|
|
3124
|
+
* End dragging
|
|
3125
|
+
*/
|
|
3126
|
+
endDrag(_e) {
|
|
3127
|
+
var _a;
|
|
3128
|
+
if (!this.state.drag.active) return;
|
|
3129
|
+
const layerId = this.state.drag.layerId;
|
|
3130
|
+
const placeholder = this.state.drag.placeholder;
|
|
3131
|
+
const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
|
|
3132
|
+
if (itemEl && placeholder) {
|
|
3133
|
+
(_a = placeholder.parentNode) == null ? void 0 : _a.insertBefore(itemEl, placeholder);
|
|
3134
|
+
itemEl.classList.remove("dragging");
|
|
3135
|
+
}
|
|
3136
|
+
this.cleanupDragState();
|
|
3137
|
+
this.applyUIOrderToMap();
|
|
3138
|
+
}
|
|
3139
|
+
/**
|
|
3140
|
+
* Clean up drag state
|
|
3141
|
+
*/
|
|
3142
|
+
cleanupDragState() {
|
|
3143
|
+
if (this.state.drag.draggedElement) {
|
|
3144
|
+
this.state.drag.draggedElement.remove();
|
|
3145
|
+
}
|
|
3146
|
+
if (this.state.drag.placeholder) {
|
|
3147
|
+
this.state.drag.placeholder.remove();
|
|
3148
|
+
}
|
|
3149
|
+
this.panel.classList.remove("dragging-active");
|
|
3150
|
+
this.panel.querySelectorAll(".layer-control-item.dragging").forEach((el) => {
|
|
3151
|
+
el.classList.remove("dragging");
|
|
3152
|
+
});
|
|
3153
|
+
this.state.drag = {
|
|
3154
|
+
active: false,
|
|
3155
|
+
layerId: null,
|
|
3156
|
+
startY: 0,
|
|
3157
|
+
currentY: 0,
|
|
3158
|
+
placeholder: null,
|
|
3159
|
+
draggedElement: null
|
|
3160
|
+
};
|
|
3161
|
+
}
|
|
3162
|
+
/**
|
|
3163
|
+
* Apply UI order to map layers
|
|
3164
|
+
*/
|
|
3165
|
+
applyUIOrderToMap() {
|
|
3166
|
+
var _a;
|
|
3167
|
+
const items = this.panel.querySelectorAll(".layer-control-item");
|
|
3168
|
+
const uiLayerIds = [];
|
|
3169
|
+
items.forEach((item) => {
|
|
3170
|
+
const layerId = item.dataset.layerId;
|
|
3171
|
+
if (layerId && layerId !== "Background") {
|
|
3172
|
+
uiLayerIds.push(layerId);
|
|
3173
|
+
}
|
|
3174
|
+
});
|
|
3175
|
+
const reversedIds = [...uiLayerIds].reverse();
|
|
3176
|
+
for (let i = 0; i < reversedIds.length; i++) {
|
|
3177
|
+
const layerId = reversedIds[i];
|
|
3178
|
+
const beforeId = i > 0 ? reversedIds[i - 1] : void 0;
|
|
3179
|
+
try {
|
|
3180
|
+
const layer = this.map.getLayer(layerId);
|
|
3181
|
+
if (layer) {
|
|
3182
|
+
this.map.moveLayer(layerId, beforeId);
|
|
3183
|
+
}
|
|
3184
|
+
} catch (e) {
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
(_a = this.onLayerReorder) == null ? void 0 : _a.call(this, uiLayerIds);
|
|
3188
|
+
}
|
|
2562
3189
|
}
|
|
2563
3190
|
exports.CustomLayerRegistry = CustomLayerRegistry;
|
|
2564
3191
|
exports.LayerControl = LayerControl;
|