maplibre-gl-layer-control 0.14.0 → 0.15.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/README.md +2 -2
- package/dist/index.cjs +679 -111
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +679 -111
- package/dist/index.mjs.map +1 -1
- package/dist/maplibre-gl-layer-control.css +111 -51
- package/dist/types/index.d.ts +72 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -686,9 +686,11 @@ class LayerControl {
|
|
|
686
686
|
__publicField(this, "styleEditors");
|
|
687
687
|
__publicField(this, "initialSourceIds", null);
|
|
688
688
|
__publicField(this, "initialLayerIds", null);
|
|
689
|
+
__publicField(this, "initialLayerStates");
|
|
689
690
|
// Panel width management
|
|
690
691
|
__publicField(this, "minPanelWidth");
|
|
691
692
|
__publicField(this, "maxPanelWidth");
|
|
693
|
+
__publicField(this, "initialPanelWidth");
|
|
692
694
|
__publicField(this, "maxPanelHeight");
|
|
693
695
|
__publicField(this, "showStyleEditor");
|
|
694
696
|
__publicField(this, "showOpacitySlider");
|
|
@@ -709,6 +711,18 @@ class LayerControl {
|
|
|
709
711
|
__publicField(this, "widthDragStartX", null);
|
|
710
712
|
__publicField(this, "widthDragStartWidth", null);
|
|
711
713
|
__publicField(this, "widthFrame", null);
|
|
714
|
+
// Exact-opacity input popup
|
|
715
|
+
__publicField(this, "opacityInputEl", null);
|
|
716
|
+
// Panel edge-drag resizing (handles on both the left and right edges)
|
|
717
|
+
__publicField(this, "resizeHandleEls", []);
|
|
718
|
+
/** Which edge is anchored to the control corner (the other edge is "free") */
|
|
719
|
+
__publicField(this, "panelAnchorSide", "right");
|
|
720
|
+
__publicField(this, "isPanelResizing", false);
|
|
721
|
+
/** The physical edge being dragged in the current resize gesture */
|
|
722
|
+
__publicField(this, "panelResizeEdge", "left");
|
|
723
|
+
__publicField(this, "panelResizeStartX", null);
|
|
724
|
+
__publicField(this, "panelResizeStartWidth", null);
|
|
725
|
+
__publicField(this, "panelResizeStartOffset", 0);
|
|
712
726
|
// Context menu and drag-drop
|
|
713
727
|
__publicField(this, "contextMenuEl", null);
|
|
714
728
|
__publicField(this, "enableContextMenu");
|
|
@@ -718,22 +732,26 @@ class LayerControl {
|
|
|
718
732
|
__publicField(this, "onLayerRemove");
|
|
719
733
|
this.minPanelWidth = options.panelMinWidth || 240;
|
|
720
734
|
this.maxPanelWidth = options.panelMaxWidth || 420;
|
|
735
|
+
this.initialPanelWidth = options.panelWidth || 350;
|
|
721
736
|
this.maxPanelHeight = options.panelMaxHeight || 600;
|
|
722
737
|
this.showStyleEditor = options.showStyleEditor !== false;
|
|
723
738
|
this.showOpacitySlider = options.showOpacitySlider !== false;
|
|
724
739
|
this.showLayerSymbol = options.showLayerSymbol !== false;
|
|
725
740
|
this.excludeDrawnLayers = options.excludeDrawnLayers !== false;
|
|
726
|
-
this.excludeLayerPatterns = this.wildcardPatternsToRegex(
|
|
741
|
+
this.excludeLayerPatterns = this.wildcardPatternsToRegex(
|
|
742
|
+
options.excludeLayers || []
|
|
743
|
+
);
|
|
727
744
|
this.enableContextMenu = options.enableContextMenu !== false;
|
|
728
745
|
this.enableDragAndDrop = options.enableDragAndDrop !== false;
|
|
729
746
|
this.onLayerRename = options.onLayerRename;
|
|
730
747
|
this.onLayerReorder = options.onLayerReorder;
|
|
731
748
|
this.onLayerRemove = options.onLayerRemove;
|
|
749
|
+
this.initialLayerStates = options.layerStates || {};
|
|
732
750
|
this.state = {
|
|
733
751
|
collapsed: options.collapsed !== false,
|
|
734
752
|
panelWidth: options.panelWidth || 350,
|
|
735
753
|
activeStyleEditor: null,
|
|
736
|
-
layerStates:
|
|
754
|
+
layerStates: {},
|
|
737
755
|
originalStyles: /* @__PURE__ */ new Map(),
|
|
738
756
|
userInteractingWithSlider: false,
|
|
739
757
|
backgroundLegendOpen: false,
|
|
@@ -757,7 +775,7 @@ class LayerControl {
|
|
|
757
775
|
},
|
|
758
776
|
isStyleOperationInProgress: false
|
|
759
777
|
};
|
|
760
|
-
this.targetLayers = options.layers || Object.keys(this.
|
|
778
|
+
this.targetLayers = options.layers || Object.keys(this.initialLayerStates);
|
|
761
779
|
this.styleEditors = /* @__PURE__ */ new Map();
|
|
762
780
|
if (options.customLayerAdapters && options.customLayerAdapters.length > 0) {
|
|
763
781
|
this.customLayerRegistry = new CustomLayerRegistry();
|
|
@@ -802,7 +820,10 @@ class LayerControl {
|
|
|
802
820
|
}
|
|
803
821
|
this.buildLayerItems();
|
|
804
822
|
}).catch((error) => {
|
|
805
|
-
console.warn(
|
|
823
|
+
console.warn(
|
|
824
|
+
"Failed to fetch basemap style, falling back to heuristic detection:",
|
|
825
|
+
error
|
|
826
|
+
);
|
|
806
827
|
if (Object.keys(this.state.layerStates).length === 0) {
|
|
807
828
|
this.autoDetectLayers();
|
|
808
829
|
}
|
|
@@ -841,7 +862,11 @@ class LayerControl {
|
|
|
841
862
|
this.basemapLayerIds = /* @__PURE__ */ new Set();
|
|
842
863
|
}
|
|
843
864
|
} catch (error) {
|
|
844
|
-
console.warn(
|
|
865
|
+
console.warn(
|
|
866
|
+
"Failed to fetch basemap style from URL:",
|
|
867
|
+
this.basemapStyleUrl,
|
|
868
|
+
error
|
|
869
|
+
);
|
|
845
870
|
throw error;
|
|
846
871
|
}
|
|
847
872
|
}
|
|
@@ -870,6 +895,7 @@ class LayerControl {
|
|
|
870
895
|
(_a = this.contextMenuEl.parentNode) == null ? void 0 : _a.removeChild(this.contextMenuEl);
|
|
871
896
|
this.contextMenuEl = null;
|
|
872
897
|
}
|
|
898
|
+
this.hideOpacityInput();
|
|
873
899
|
this.cleanupDragState();
|
|
874
900
|
(_b = this.panel.parentNode) == null ? void 0 : _b.removeChild(this.panel);
|
|
875
901
|
(_c = this.container.parentNode) == null ? void 0 : _c.removeChild(this.container);
|
|
@@ -929,11 +955,11 @@ class LayerControl {
|
|
|
929
955
|
const layerType = layer.type;
|
|
930
956
|
const opacity = getLayerOpacity(this.map, layerId, layerType);
|
|
931
957
|
const friendlyName = this.generateFriendlyName(layerId);
|
|
932
|
-
this.state.layerStates[layerId] = {
|
|
958
|
+
this.state.layerStates[layerId] = this.mergeWithUserState(layerId, {
|
|
933
959
|
visible: isVisible,
|
|
934
960
|
opacity,
|
|
935
961
|
name: friendlyName
|
|
936
|
-
};
|
|
962
|
+
});
|
|
937
963
|
});
|
|
938
964
|
} else {
|
|
939
965
|
const userLayers = [];
|
|
@@ -964,11 +990,11 @@ class LayerControl {
|
|
|
964
990
|
const layerType = layer.type;
|
|
965
991
|
const opacity = getLayerOpacity(this.map, layerId, layerType);
|
|
966
992
|
const friendlyName = this.generateFriendlyName(layerId);
|
|
967
|
-
this.state.layerStates[layerId] = {
|
|
993
|
+
this.state.layerStates[layerId] = this.mergeWithUserState(layerId, {
|
|
968
994
|
visible: isVisible,
|
|
969
995
|
opacity,
|
|
970
996
|
name: friendlyName
|
|
971
|
-
};
|
|
997
|
+
});
|
|
972
998
|
});
|
|
973
999
|
}
|
|
974
1000
|
if (this.customLayerRegistry) {
|
|
@@ -1028,7 +1054,9 @@ class LayerControl {
|
|
|
1028
1054
|
}
|
|
1029
1055
|
if (style.glyphs) {
|
|
1030
1056
|
try {
|
|
1031
|
-
const url = new URL(
|
|
1057
|
+
const url = new URL(
|
|
1058
|
+
style.glyphs.replace("{fontstack}", "x").replace("{range}", "x")
|
|
1059
|
+
);
|
|
1032
1060
|
basemapDomains.add(url.hostname);
|
|
1033
1061
|
} catch {
|
|
1034
1062
|
}
|
|
@@ -1089,6 +1117,25 @@ class LayerControl {
|
|
|
1089
1117
|
name = name.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
1090
1118
|
return name || layerId;
|
|
1091
1119
|
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Merge auto-detected layer state with user-provided initial state.
|
|
1122
|
+
* User-provided values take precedence over detected values.
|
|
1123
|
+
*/
|
|
1124
|
+
mergeWithUserState(layerId, detected) {
|
|
1125
|
+
const userState = this.initialLayerStates[layerId];
|
|
1126
|
+
if (!userState) return detected;
|
|
1127
|
+
return {
|
|
1128
|
+
visible: userState.visible ?? detected.visible,
|
|
1129
|
+
opacity: userState.opacity ?? detected.opacity,
|
|
1130
|
+
name: userState.name ?? detected.name,
|
|
1131
|
+
...userState.isCustomLayer !== void 0 && {
|
|
1132
|
+
isCustomLayer: userState.isCustomLayer
|
|
1133
|
+
},
|
|
1134
|
+
...userState.customLayerType !== void 0 && {
|
|
1135
|
+
customLayerType: userState.customLayerType
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1092
1139
|
/**
|
|
1093
1140
|
* Check if a layer ID belongs to a drawing library (Geoman, Mapbox GL Draw, etc.)
|
|
1094
1141
|
* @param layerId The layer ID to check
|
|
@@ -1172,8 +1219,111 @@ class LayerControl {
|
|
|
1172
1219
|
panel.appendChild(header);
|
|
1173
1220
|
const actionButtons = this.createActionButtons();
|
|
1174
1221
|
panel.appendChild(actionButtons);
|
|
1222
|
+
panel.appendChild(this.createResizeHandle("left"));
|
|
1223
|
+
panel.appendChild(this.createResizeHandle("right"));
|
|
1175
1224
|
return panel;
|
|
1176
1225
|
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Create a draggable edge handle for resizing the panel width. A handle is
|
|
1228
|
+
* added on each side so the panel can be resized by dragging either edge,
|
|
1229
|
+
* like a conventional resizable window.
|
|
1230
|
+
*
|
|
1231
|
+
* @param side Which physical edge of the panel the handle sits on
|
|
1232
|
+
*/
|
|
1233
|
+
createResizeHandle(side) {
|
|
1234
|
+
const handle = document.createElement("div");
|
|
1235
|
+
handle.className = `layer-control-resize-handle layer-control-resize-handle-${side}`;
|
|
1236
|
+
handle.title = "Drag to resize panel";
|
|
1237
|
+
handle.setAttribute("role", "separator");
|
|
1238
|
+
handle.setAttribute("aria-orientation", "vertical");
|
|
1239
|
+
this.resizeHandleEls.push(handle);
|
|
1240
|
+
this.setupResizeHandleEvents(handle, side);
|
|
1241
|
+
return handle;
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Wire pointer events for an edge resize handle.
|
|
1245
|
+
*
|
|
1246
|
+
* @param handle The handle element
|
|
1247
|
+
* @param side Which physical edge of the panel the handle sits on
|
|
1248
|
+
*/
|
|
1249
|
+
setupResizeHandleEvents(handle, side) {
|
|
1250
|
+
handle.addEventListener("pointerdown", (event) => {
|
|
1251
|
+
event.preventDefault();
|
|
1252
|
+
event.stopPropagation();
|
|
1253
|
+
this.isPanelResizing = true;
|
|
1254
|
+
this.panelResizeEdge = side;
|
|
1255
|
+
this.panelResizeStartX = event.clientX;
|
|
1256
|
+
this.panelResizeStartWidth = this.state.panelWidth;
|
|
1257
|
+
this.panelResizeStartOffset = parseFloat(this.panel.style[this.panelAnchorSide]) || 0;
|
|
1258
|
+
handle.setPointerCapture(event.pointerId);
|
|
1259
|
+
this.panel.classList.add("resizing-active");
|
|
1260
|
+
});
|
|
1261
|
+
handle.addEventListener("pointermove", (event) => {
|
|
1262
|
+
if (!this.isPanelResizing) return;
|
|
1263
|
+
this.resizePanelFromPointer(event.clientX);
|
|
1264
|
+
});
|
|
1265
|
+
const endResize = (event) => {
|
|
1266
|
+
if (!this.isPanelResizing) return;
|
|
1267
|
+
if (event.pointerId !== void 0) {
|
|
1268
|
+
try {
|
|
1269
|
+
handle.releasePointerCapture(event.pointerId);
|
|
1270
|
+
} catch {
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
this.isPanelResizing = false;
|
|
1274
|
+
this.panelResizeStartX = null;
|
|
1275
|
+
this.panelResizeStartWidth = null;
|
|
1276
|
+
this.panel.classList.remove("resizing-active");
|
|
1277
|
+
};
|
|
1278
|
+
handle.addEventListener("pointerup", endResize);
|
|
1279
|
+
handle.addEventListener("pointercancel", endResize);
|
|
1280
|
+
handle.addEventListener("lostpointercapture", endResize);
|
|
1281
|
+
handle.addEventListener("dblclick", (event) => {
|
|
1282
|
+
event.preventDefault();
|
|
1283
|
+
event.stopPropagation();
|
|
1284
|
+
this.applyPanelWidth(this.initialPanelWidth, true);
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Compute and apply a new panel width from the pointer position during an
|
|
1289
|
+
* edge drag. Dragging the free edge changes the width while the anchored
|
|
1290
|
+
* edge stays put; dragging the anchored edge shifts the anchor so the free
|
|
1291
|
+
* edge stays put — both feel like resizing a normal window.
|
|
1292
|
+
*/
|
|
1293
|
+
resizePanelFromPointer(clientX) {
|
|
1294
|
+
const dx = clientX - (this.panelResizeStartX ?? clientX);
|
|
1295
|
+
const startWidth = this.panelResizeStartWidth ?? this.state.panelWidth;
|
|
1296
|
+
const desiredWidth = this.panelResizeEdge === "left" ? startWidth - dx : startWidth + dx;
|
|
1297
|
+
const clamped = Math.round(
|
|
1298
|
+
Math.min(this.maxPanelWidth, Math.max(this.minPanelWidth, desiredWidth))
|
|
1299
|
+
);
|
|
1300
|
+
if (this.panelResizeEdge === this.panelAnchorSide) {
|
|
1301
|
+
let appliedDelta = clamped - startWidth;
|
|
1302
|
+
let newOffset = this.panelResizeStartOffset - appliedDelta;
|
|
1303
|
+
if (newOffset < 0) {
|
|
1304
|
+
newOffset = 0;
|
|
1305
|
+
appliedDelta = this.panelResizeStartOffset;
|
|
1306
|
+
}
|
|
1307
|
+
const finalWidth = Math.round(
|
|
1308
|
+
Math.min(
|
|
1309
|
+
this.maxPanelWidth,
|
|
1310
|
+
Math.max(this.minPanelWidth, startWidth + appliedDelta)
|
|
1311
|
+
)
|
|
1312
|
+
);
|
|
1313
|
+
this.applyPanelWidth(finalWidth, true);
|
|
1314
|
+
this.panel.style[this.panelAnchorSide] = `${newOffset}px`;
|
|
1315
|
+
} else {
|
|
1316
|
+
this.applyPanelWidth(clamped, true);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Record which panel edge is anchored to the control corner, based on the
|
|
1321
|
+
* corner the control occupies. Right corners anchor the right edge (panel
|
|
1322
|
+
* grows leftward); left corners anchor the left edge (panel grows rightward).
|
|
1323
|
+
*/
|
|
1324
|
+
updatePanelAnchorSide(position) {
|
|
1325
|
+
this.panelAnchorSide = position === "top-right" || position === "bottom-right" ? "right" : "left";
|
|
1326
|
+
}
|
|
1177
1327
|
/**
|
|
1178
1328
|
* Detect which corner the control is positioned in
|
|
1179
1329
|
* @returns The position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
|
@@ -1181,10 +1331,14 @@ class LayerControl {
|
|
|
1181
1331
|
getControlPosition() {
|
|
1182
1332
|
const parent = this.container.parentElement;
|
|
1183
1333
|
if (!parent) return "top-right";
|
|
1184
|
-
if (parent.classList.contains("maplibregl-ctrl-top-left"))
|
|
1185
|
-
|
|
1186
|
-
if (parent.classList.contains("maplibregl-ctrl-
|
|
1187
|
-
|
|
1334
|
+
if (parent.classList.contains("maplibregl-ctrl-top-left"))
|
|
1335
|
+
return "top-left";
|
|
1336
|
+
if (parent.classList.contains("maplibregl-ctrl-top-right"))
|
|
1337
|
+
return "top-right";
|
|
1338
|
+
if (parent.classList.contains("maplibregl-ctrl-bottom-left"))
|
|
1339
|
+
return "bottom-left";
|
|
1340
|
+
if (parent.classList.contains("maplibregl-ctrl-bottom-right"))
|
|
1341
|
+
return "bottom-right";
|
|
1188
1342
|
return "top-right";
|
|
1189
1343
|
}
|
|
1190
1344
|
/**
|
|
@@ -1196,6 +1350,7 @@ class LayerControl {
|
|
|
1196
1350
|
const buttonRect = this.button.getBoundingClientRect();
|
|
1197
1351
|
const mapRect = this.mapContainer.getBoundingClientRect();
|
|
1198
1352
|
const position = this.getControlPosition();
|
|
1353
|
+
this.updatePanelAnchorSide(position);
|
|
1199
1354
|
const buttonTop = buttonRect.top - mapRect.top;
|
|
1200
1355
|
const buttonBottom = mapRect.bottom - buttonRect.bottom;
|
|
1201
1356
|
const buttonLeft = buttonRect.left - mapRect.left;
|
|
@@ -1235,13 +1390,19 @@ class LayerControl {
|
|
|
1235
1390
|
showAllBtn.className = "layer-control-action-btn";
|
|
1236
1391
|
showAllBtn.textContent = "Show All";
|
|
1237
1392
|
showAllBtn.title = "Show all layers";
|
|
1238
|
-
showAllBtn.addEventListener(
|
|
1393
|
+
showAllBtn.addEventListener(
|
|
1394
|
+
"click",
|
|
1395
|
+
() => this.setAllLayersVisibility(true)
|
|
1396
|
+
);
|
|
1239
1397
|
const hideAllBtn = document.createElement("button");
|
|
1240
1398
|
hideAllBtn.type = "button";
|
|
1241
1399
|
hideAllBtn.className = "layer-control-action-btn";
|
|
1242
1400
|
hideAllBtn.textContent = "Hide All";
|
|
1243
1401
|
hideAllBtn.title = "Hide all layers";
|
|
1244
|
-
hideAllBtn.addEventListener(
|
|
1402
|
+
hideAllBtn.addEventListener(
|
|
1403
|
+
"click",
|
|
1404
|
+
() => this.setAllLayersVisibility(false)
|
|
1405
|
+
);
|
|
1245
1406
|
container.appendChild(showAllBtn);
|
|
1246
1407
|
container.appendChild(hideAllBtn);
|
|
1247
1408
|
return container;
|
|
@@ -1254,7 +1415,9 @@ class LayerControl {
|
|
|
1254
1415
|
this.toggleLayerVisibility(layerId, visible);
|
|
1255
1416
|
const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
|
|
1256
1417
|
if (itemEl) {
|
|
1257
|
-
const checkbox = itemEl.querySelector(
|
|
1418
|
+
const checkbox = itemEl.querySelector(
|
|
1419
|
+
".layer-control-checkbox"
|
|
1420
|
+
);
|
|
1258
1421
|
if (checkbox) {
|
|
1259
1422
|
checkbox.checked = visible;
|
|
1260
1423
|
checkbox.indeterminate = false;
|
|
@@ -1405,7 +1568,9 @@ class LayerControl {
|
|
|
1405
1568
|
* Apply panel width (clamped to min/max)
|
|
1406
1569
|
*/
|
|
1407
1570
|
applyPanelWidth(width, immediate = false) {
|
|
1408
|
-
const clamped = Math.round(
|
|
1571
|
+
const clamped = Math.round(
|
|
1572
|
+
Math.min(this.maxPanelWidth, Math.max(this.minPanelWidth, width))
|
|
1573
|
+
);
|
|
1409
1574
|
const applyWidth = () => {
|
|
1410
1575
|
this.state.panelWidth = clamped;
|
|
1411
1576
|
const px = `${clamped}px`;
|
|
@@ -1432,7 +1597,10 @@ class LayerControl {
|
|
|
1432
1597
|
this.widthValueEl.textContent = `${this.state.panelWidth}px`;
|
|
1433
1598
|
}
|
|
1434
1599
|
if (this.widthSliderEl) {
|
|
1435
|
-
this.widthSliderEl.setAttribute(
|
|
1600
|
+
this.widthSliderEl.setAttribute(
|
|
1601
|
+
"aria-valuenow",
|
|
1602
|
+
String(this.state.panelWidth)
|
|
1603
|
+
);
|
|
1436
1604
|
const ratio = (this.state.panelWidth - this.minPanelWidth) / (this.maxPanelWidth - this.minPanelWidth || 1);
|
|
1437
1605
|
if (this.widthThumbEl) {
|
|
1438
1606
|
const sliderWidth = this.widthSliderEl.clientWidth;
|
|
@@ -1505,12 +1673,14 @@ class LayerControl {
|
|
|
1505
1673
|
}
|
|
1506
1674
|
});
|
|
1507
1675
|
if (this.customLayerRegistry) {
|
|
1508
|
-
this.customLayerUnsubscribe = this.customLayerRegistry.onChange(
|
|
1509
|
-
|
|
1510
|
-
|
|
1676
|
+
this.customLayerUnsubscribe = this.customLayerRegistry.onChange(
|
|
1677
|
+
(event, layerId) => {
|
|
1678
|
+
if (event === "add" && layerId) {
|
|
1679
|
+
this.removedCustomLayerIds.delete(layerId);
|
|
1680
|
+
}
|
|
1681
|
+
setTimeout(() => this.checkForNewLayers(), 100);
|
|
1511
1682
|
}
|
|
1512
|
-
|
|
1513
|
-
});
|
|
1683
|
+
);
|
|
1514
1684
|
}
|
|
1515
1685
|
}
|
|
1516
1686
|
/**
|
|
@@ -1613,6 +1783,10 @@ class LayerControl {
|
|
|
1613
1783
|
this.changeLayerOpacity(layerId, parseFloat(opacity.value));
|
|
1614
1784
|
opacity.title = `Opacity: ${Math.round(parseFloat(opacity.value) * 100)}%`;
|
|
1615
1785
|
});
|
|
1786
|
+
opacity.addEventListener("dblclick", (event) => {
|
|
1787
|
+
event.preventDefault();
|
|
1788
|
+
this.showOpacityInput(layerId, opacity);
|
|
1789
|
+
});
|
|
1616
1790
|
row.appendChild(opacity);
|
|
1617
1791
|
}
|
|
1618
1792
|
if (this.showStyleEditor) {
|
|
@@ -1710,11 +1884,103 @@ class LayerControl {
|
|
|
1710
1884
|
}, 200);
|
|
1711
1885
|
return;
|
|
1712
1886
|
}
|
|
1713
|
-
this.map.setLayoutProperty(
|
|
1887
|
+
this.map.setLayoutProperty(
|
|
1888
|
+
layerId,
|
|
1889
|
+
"visibility",
|
|
1890
|
+
visible ? "visible" : "none"
|
|
1891
|
+
);
|
|
1714
1892
|
setTimeout(() => {
|
|
1715
1893
|
this.state.isStyleOperationInProgress = false;
|
|
1716
1894
|
}, 200);
|
|
1717
1895
|
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Show a small popup that lets the user type an exact opacity percentage
|
|
1898
|
+
* (0-100) for a layer. Triggered by double-clicking the opacity slider.
|
|
1899
|
+
*
|
|
1900
|
+
* @param layerId The layer ID whose opacity is being edited
|
|
1901
|
+
* @param slider The opacity range input associated with the layer row
|
|
1902
|
+
*/
|
|
1903
|
+
showOpacityInput(layerId, slider) {
|
|
1904
|
+
this.hideOpacityInput();
|
|
1905
|
+
const popup = document.createElement("div");
|
|
1906
|
+
popup.className = "layer-control-opacity-input";
|
|
1907
|
+
const label = document.createElement("span");
|
|
1908
|
+
label.className = "layer-control-opacity-input-label";
|
|
1909
|
+
label.textContent = "Opacity";
|
|
1910
|
+
const field = document.createElement("div");
|
|
1911
|
+
field.className = "layer-control-opacity-input-field";
|
|
1912
|
+
const input = document.createElement("input");
|
|
1913
|
+
input.type = "number";
|
|
1914
|
+
input.min = "0";
|
|
1915
|
+
input.max = "100";
|
|
1916
|
+
input.step = "1";
|
|
1917
|
+
input.value = String(Math.round(parseFloat(slider.value) * 100));
|
|
1918
|
+
const suffix = document.createElement("span");
|
|
1919
|
+
suffix.className = "layer-control-opacity-input-suffix";
|
|
1920
|
+
suffix.textContent = "%";
|
|
1921
|
+
field.appendChild(input);
|
|
1922
|
+
field.appendChild(suffix);
|
|
1923
|
+
popup.appendChild(label);
|
|
1924
|
+
popup.appendChild(field);
|
|
1925
|
+
popup.addEventListener("click", (event) => event.stopPropagation());
|
|
1926
|
+
popup.addEventListener("pointerdown", (event) => event.stopPropagation());
|
|
1927
|
+
let settled = false;
|
|
1928
|
+
const apply = () => {
|
|
1929
|
+
if (settled) return;
|
|
1930
|
+
settled = true;
|
|
1931
|
+
const pct = clamp(Math.round(parseFloat(input.value)), 0, 100);
|
|
1932
|
+
if (!Number.isNaN(pct)) {
|
|
1933
|
+
const opacity = pct / 100;
|
|
1934
|
+
slider.value = String(opacity);
|
|
1935
|
+
slider.title = `Opacity: ${pct}%`;
|
|
1936
|
+
this.changeLayerOpacity(layerId, opacity);
|
|
1937
|
+
}
|
|
1938
|
+
this.hideOpacityInput();
|
|
1939
|
+
};
|
|
1940
|
+
const cancel = () => {
|
|
1941
|
+
if (settled) return;
|
|
1942
|
+
settled = true;
|
|
1943
|
+
this.hideOpacityInput();
|
|
1944
|
+
};
|
|
1945
|
+
input.addEventListener("keydown", (event) => {
|
|
1946
|
+
if (event.key === "Enter") {
|
|
1947
|
+
event.preventDefault();
|
|
1948
|
+
apply();
|
|
1949
|
+
slider.focus();
|
|
1950
|
+
} else if (event.key === "Escape") {
|
|
1951
|
+
event.preventDefault();
|
|
1952
|
+
cancel();
|
|
1953
|
+
slider.focus();
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
input.addEventListener("blur", apply);
|
|
1957
|
+
this.mapContainer.appendChild(popup);
|
|
1958
|
+
this.opacityInputEl = popup;
|
|
1959
|
+
const sliderRect = slider.getBoundingClientRect();
|
|
1960
|
+
const mapRect = this.mapContainer.getBoundingClientRect();
|
|
1961
|
+
const popupRect = popup.getBoundingClientRect();
|
|
1962
|
+
let left = sliderRect.left - mapRect.left;
|
|
1963
|
+
let top = sliderRect.bottom - mapRect.top + 4;
|
|
1964
|
+
if (left + popupRect.width > mapRect.width) {
|
|
1965
|
+
left = Math.max(0, mapRect.width - popupRect.width - 5);
|
|
1966
|
+
}
|
|
1967
|
+
if (top + popupRect.height > mapRect.height) {
|
|
1968
|
+
top = sliderRect.top - mapRect.top - popupRect.height - 4;
|
|
1969
|
+
}
|
|
1970
|
+
popup.style.left = `${Math.max(0, left)}px`;
|
|
1971
|
+
popup.style.top = `${Math.max(0, top)}px`;
|
|
1972
|
+
input.focus();
|
|
1973
|
+
input.select();
|
|
1974
|
+
}
|
|
1975
|
+
/**
|
|
1976
|
+
* Remove the exact-opacity input popup if it is open.
|
|
1977
|
+
*/
|
|
1978
|
+
hideOpacityInput() {
|
|
1979
|
+
if (this.opacityInputEl) {
|
|
1980
|
+
this.opacityInputEl.remove();
|
|
1981
|
+
this.opacityInputEl = null;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1718
1984
|
/**
|
|
1719
1985
|
* Change layer opacity
|
|
1720
1986
|
*/
|
|
@@ -1777,13 +2043,21 @@ class LayerControl {
|
|
|
1777
2043
|
styleLayers.forEach((layer) => {
|
|
1778
2044
|
if (!this.isUserAddedLayer(layer.id)) {
|
|
1779
2045
|
this.state.backgroundLayerVisibility.set(layer.id, visible);
|
|
1780
|
-
this.map.setLayoutProperty(
|
|
2046
|
+
this.map.setLayoutProperty(
|
|
2047
|
+
layer.id,
|
|
2048
|
+
"visibility",
|
|
2049
|
+
visible ? "visible" : "none"
|
|
2050
|
+
);
|
|
1781
2051
|
}
|
|
1782
2052
|
});
|
|
1783
2053
|
if (this.state.backgroundLegendOpen) {
|
|
1784
|
-
const legendPanel = this.panel.querySelector(
|
|
2054
|
+
const legendPanel = this.panel.querySelector(
|
|
2055
|
+
".layer-control-background-legend"
|
|
2056
|
+
);
|
|
1785
2057
|
if (legendPanel) {
|
|
1786
|
-
const checkboxes = legendPanel.querySelectorAll(
|
|
2058
|
+
const checkboxes = legendPanel.querySelectorAll(
|
|
2059
|
+
".background-legend-checkbox"
|
|
2060
|
+
);
|
|
1787
2061
|
checkboxes.forEach((checkbox) => {
|
|
1788
2062
|
checkbox.checked = visible;
|
|
1789
2063
|
});
|
|
@@ -1816,8 +2090,14 @@ class LayerControl {
|
|
|
1816
2090
|
button.className = "layer-control-style-button layer-control-background-legend-button";
|
|
1817
2091
|
button.innerHTML = "⚙";
|
|
1818
2092
|
button.title = "Show background layer details";
|
|
1819
|
-
button.setAttribute(
|
|
1820
|
-
|
|
2093
|
+
button.setAttribute(
|
|
2094
|
+
"aria-label",
|
|
2095
|
+
"Show background layer visibility controls"
|
|
2096
|
+
);
|
|
2097
|
+
button.setAttribute(
|
|
2098
|
+
"aria-expanded",
|
|
2099
|
+
String(this.state.backgroundLegendOpen)
|
|
2100
|
+
);
|
|
1821
2101
|
button.addEventListener("click", (e) => {
|
|
1822
2102
|
e.stopPropagation();
|
|
1823
2103
|
this.toggleBackgroundLegend();
|
|
@@ -1845,7 +2125,9 @@ class LayerControl {
|
|
|
1845
2125
|
if (!itemEl) return;
|
|
1846
2126
|
let legendPanel = itemEl.querySelector(".layer-control-background-legend");
|
|
1847
2127
|
if (legendPanel) {
|
|
1848
|
-
const layerList = legendPanel.querySelector(
|
|
2128
|
+
const layerList = legendPanel.querySelector(
|
|
2129
|
+
".background-legend-layer-list"
|
|
2130
|
+
);
|
|
1849
2131
|
if (layerList) {
|
|
1850
2132
|
this.populateBackgroundLayerList(layerList);
|
|
1851
2133
|
}
|
|
@@ -1854,7 +2136,9 @@ class LayerControl {
|
|
|
1854
2136
|
itemEl.appendChild(legendPanel);
|
|
1855
2137
|
}
|
|
1856
2138
|
this.state.backgroundLegendOpen = true;
|
|
1857
|
-
const button = itemEl.querySelector(
|
|
2139
|
+
const button = itemEl.querySelector(
|
|
2140
|
+
".layer-control-background-legend-button"
|
|
2141
|
+
);
|
|
1858
2142
|
if (button) {
|
|
1859
2143
|
button.setAttribute("aria-expanded", "true");
|
|
1860
2144
|
button.classList.add("active");
|
|
@@ -1869,12 +2153,16 @@ class LayerControl {
|
|
|
1869
2153
|
closeBackgroundLegend() {
|
|
1870
2154
|
const itemEl = this.panel.querySelector('[data-layer-id="Background"]');
|
|
1871
2155
|
if (!itemEl) return;
|
|
1872
|
-
const legendPanel = itemEl.querySelector(
|
|
2156
|
+
const legendPanel = itemEl.querySelector(
|
|
2157
|
+
".layer-control-background-legend"
|
|
2158
|
+
);
|
|
1873
2159
|
if (legendPanel) {
|
|
1874
2160
|
legendPanel.remove();
|
|
1875
2161
|
}
|
|
1876
2162
|
this.state.backgroundLegendOpen = false;
|
|
1877
|
-
const button = itemEl.querySelector(
|
|
2163
|
+
const button = itemEl.querySelector(
|
|
2164
|
+
".layer-control-background-legend-button"
|
|
2165
|
+
);
|
|
1878
2166
|
if (button) {
|
|
1879
2167
|
button.setAttribute("aria-expanded", "false");
|
|
1880
2168
|
button.classList.remove("active");
|
|
@@ -1906,11 +2194,17 @@ class LayerControl {
|
|
|
1906
2194
|
const showAllBtn = document.createElement("button");
|
|
1907
2195
|
showAllBtn.className = "background-legend-action-btn";
|
|
1908
2196
|
showAllBtn.textContent = "Show All";
|
|
1909
|
-
showAllBtn.addEventListener(
|
|
2197
|
+
showAllBtn.addEventListener(
|
|
2198
|
+
"click",
|
|
2199
|
+
() => this.setAllBackgroundLayersVisibility(true)
|
|
2200
|
+
);
|
|
1910
2201
|
const hideAllBtn = document.createElement("button");
|
|
1911
2202
|
hideAllBtn.className = "background-legend-action-btn";
|
|
1912
2203
|
hideAllBtn.textContent = "Hide All";
|
|
1913
|
-
hideAllBtn.addEventListener(
|
|
2204
|
+
hideAllBtn.addEventListener(
|
|
2205
|
+
"click",
|
|
2206
|
+
() => this.setAllBackgroundLayersVisibility(false)
|
|
2207
|
+
);
|
|
1914
2208
|
actionsRow.appendChild(showAllBtn);
|
|
1915
2209
|
actionsRow.appendChild(hideAllBtn);
|
|
1916
2210
|
const filterRow = document.createElement("div");
|
|
@@ -2024,7 +2318,11 @@ class LayerControl {
|
|
|
2024
2318
|
*/
|
|
2025
2319
|
toggleIndividualBackgroundLayer(layerId, visible) {
|
|
2026
2320
|
this.state.backgroundLayerVisibility.set(layerId, visible);
|
|
2027
|
-
this.map.setLayoutProperty(
|
|
2321
|
+
this.map.setLayoutProperty(
|
|
2322
|
+
layerId,
|
|
2323
|
+
"visibility",
|
|
2324
|
+
visible ? "visible" : "none"
|
|
2325
|
+
);
|
|
2028
2326
|
this.updateBackgroundCheckboxState();
|
|
2029
2327
|
}
|
|
2030
2328
|
/**
|
|
@@ -2035,12 +2333,20 @@ class LayerControl {
|
|
|
2035
2333
|
styleLayers.forEach((layer) => {
|
|
2036
2334
|
if (!this.isUserAddedLayer(layer.id)) {
|
|
2037
2335
|
this.state.backgroundLayerVisibility.set(layer.id, visible);
|
|
2038
|
-
this.map.setLayoutProperty(
|
|
2336
|
+
this.map.setLayoutProperty(
|
|
2337
|
+
layer.id,
|
|
2338
|
+
"visibility",
|
|
2339
|
+
visible ? "visible" : "none"
|
|
2340
|
+
);
|
|
2039
2341
|
}
|
|
2040
2342
|
});
|
|
2041
|
-
const legendPanel = this.panel.querySelector(
|
|
2343
|
+
const legendPanel = this.panel.querySelector(
|
|
2344
|
+
".layer-control-background-legend"
|
|
2345
|
+
);
|
|
2042
2346
|
if (legendPanel) {
|
|
2043
|
-
const checkboxes = legendPanel.querySelectorAll(
|
|
2347
|
+
const checkboxes = legendPanel.querySelectorAll(
|
|
2348
|
+
".background-legend-checkbox"
|
|
2349
|
+
);
|
|
2044
2350
|
checkboxes.forEach((checkbox) => {
|
|
2045
2351
|
checkbox.checked = visible;
|
|
2046
2352
|
});
|
|
@@ -2061,9 +2367,13 @@ class LayerControl {
|
|
|
2061
2367
|
if (visible === false) allVisible = false;
|
|
2062
2368
|
}
|
|
2063
2369
|
});
|
|
2064
|
-
const backgroundItem = this.panel.querySelector(
|
|
2370
|
+
const backgroundItem = this.panel.querySelector(
|
|
2371
|
+
'[data-layer-id="Background"]'
|
|
2372
|
+
);
|
|
2065
2373
|
if (backgroundItem) {
|
|
2066
|
-
const checkbox = backgroundItem.querySelector(
|
|
2374
|
+
const checkbox = backgroundItem.querySelector(
|
|
2375
|
+
".layer-control-checkbox"
|
|
2376
|
+
);
|
|
2067
2377
|
if (checkbox) {
|
|
2068
2378
|
checkbox.checked = anyVisible;
|
|
2069
2379
|
checkbox.indeterminate = anyVisible && !allVisible;
|
|
@@ -2126,11 +2436,18 @@ class LayerControl {
|
|
|
2126
2436
|
if (!this.state.originalStyles.has(nativeId)) {
|
|
2127
2437
|
const nativeLayer = this.map.getLayer(nativeId);
|
|
2128
2438
|
if (nativeLayer) {
|
|
2129
|
-
cacheOriginalLayerStyle(
|
|
2439
|
+
cacheOriginalLayerStyle(
|
|
2440
|
+
this.map,
|
|
2441
|
+
nativeId,
|
|
2442
|
+
this.state.originalStyles
|
|
2443
|
+
);
|
|
2130
2444
|
}
|
|
2131
2445
|
}
|
|
2132
2446
|
}
|
|
2133
|
-
const editor3 = this.createNativeSubLayerStyleEditor(
|
|
2447
|
+
const editor3 = this.createNativeSubLayerStyleEditor(
|
|
2448
|
+
layerId,
|
|
2449
|
+
nativeLayerIds
|
|
2450
|
+
);
|
|
2134
2451
|
if (editor3) {
|
|
2135
2452
|
itemEl.appendChild(editor3);
|
|
2136
2453
|
this.styleEditors.set(layerId, editor3);
|
|
@@ -2433,14 +2750,38 @@ class LayerControl {
|
|
|
2433
2750
|
if (!fillColor) {
|
|
2434
2751
|
fillColor = this.map.getPaintProperty(layerId, "fill-color");
|
|
2435
2752
|
}
|
|
2436
|
-
this.createColorControl(
|
|
2753
|
+
this.createColorControl(
|
|
2754
|
+
container,
|
|
2755
|
+
layerId,
|
|
2756
|
+
"fill-color",
|
|
2757
|
+
"Fill Color",
|
|
2758
|
+
normalizeColor(fillColor || "#088")
|
|
2759
|
+
);
|
|
2437
2760
|
const fillOpacity = this.map.getPaintProperty(layerId, "fill-opacity");
|
|
2438
2761
|
if (fillOpacity !== void 0 && typeof fillOpacity === "number") {
|
|
2439
|
-
this.createSliderControl(
|
|
2440
|
-
|
|
2441
|
-
|
|
2762
|
+
this.createSliderControl(
|
|
2763
|
+
container,
|
|
2764
|
+
layerId,
|
|
2765
|
+
"fill-opacity",
|
|
2766
|
+
"Fill Opacity",
|
|
2767
|
+
fillOpacity,
|
|
2768
|
+
0,
|
|
2769
|
+
1,
|
|
2770
|
+
0.05
|
|
2771
|
+
);
|
|
2772
|
+
}
|
|
2773
|
+
const outlineColor = this.map.getPaintProperty(
|
|
2774
|
+
layerId,
|
|
2775
|
+
"fill-outline-color"
|
|
2776
|
+
);
|
|
2442
2777
|
if (outlineColor !== void 0) {
|
|
2443
|
-
this.createColorControl(
|
|
2778
|
+
this.createColorControl(
|
|
2779
|
+
container,
|
|
2780
|
+
layerId,
|
|
2781
|
+
"fill-outline-color",
|
|
2782
|
+
"Outline Color",
|
|
2783
|
+
normalizeColor(outlineColor)
|
|
2784
|
+
);
|
|
2444
2785
|
}
|
|
2445
2786
|
}
|
|
2446
2787
|
/**
|
|
@@ -2457,16 +2798,49 @@ class LayerControl {
|
|
|
2457
2798
|
if (!lineColor) {
|
|
2458
2799
|
lineColor = this.map.getPaintProperty(layerId, "line-color");
|
|
2459
2800
|
}
|
|
2460
|
-
this.createColorControl(
|
|
2801
|
+
this.createColorControl(
|
|
2802
|
+
container,
|
|
2803
|
+
layerId,
|
|
2804
|
+
"line-color",
|
|
2805
|
+
"Line Color",
|
|
2806
|
+
normalizeColor(lineColor || "#000")
|
|
2807
|
+
);
|
|
2461
2808
|
const lineWidth = this.map.getPaintProperty(layerId, "line-width");
|
|
2462
|
-
this.createSliderControl(
|
|
2809
|
+
this.createSliderControl(
|
|
2810
|
+
container,
|
|
2811
|
+
layerId,
|
|
2812
|
+
"line-width",
|
|
2813
|
+
"Line Width",
|
|
2814
|
+
typeof lineWidth === "number" ? lineWidth : 1,
|
|
2815
|
+
0,
|
|
2816
|
+
20,
|
|
2817
|
+
0.5
|
|
2818
|
+
);
|
|
2463
2819
|
const lineOpacity = this.map.getPaintProperty(layerId, "line-opacity");
|
|
2464
2820
|
if (lineOpacity !== void 0 && typeof lineOpacity === "number") {
|
|
2465
|
-
this.createSliderControl(
|
|
2821
|
+
this.createSliderControl(
|
|
2822
|
+
container,
|
|
2823
|
+
layerId,
|
|
2824
|
+
"line-opacity",
|
|
2825
|
+
"Line Opacity",
|
|
2826
|
+
lineOpacity,
|
|
2827
|
+
0,
|
|
2828
|
+
1,
|
|
2829
|
+
0.05
|
|
2830
|
+
);
|
|
2466
2831
|
}
|
|
2467
2832
|
const lineBlur = this.map.getPaintProperty(layerId, "line-blur");
|
|
2468
2833
|
if (lineBlur !== void 0 && typeof lineBlur === "number") {
|
|
2469
|
-
this.createSliderControl(
|
|
2834
|
+
this.createSliderControl(
|
|
2835
|
+
container,
|
|
2836
|
+
layerId,
|
|
2837
|
+
"line-blur",
|
|
2838
|
+
"Line Blur",
|
|
2839
|
+
lineBlur,
|
|
2840
|
+
0,
|
|
2841
|
+
5,
|
|
2842
|
+
0.1
|
|
2843
|
+
);
|
|
2470
2844
|
}
|
|
2471
2845
|
}
|
|
2472
2846
|
/**
|
|
@@ -2483,20 +2857,65 @@ class LayerControl {
|
|
|
2483
2857
|
if (!circleColor) {
|
|
2484
2858
|
circleColor = this.map.getPaintProperty(layerId, "circle-color");
|
|
2485
2859
|
}
|
|
2486
|
-
this.createColorControl(
|
|
2860
|
+
this.createColorControl(
|
|
2861
|
+
container,
|
|
2862
|
+
layerId,
|
|
2863
|
+
"circle-color",
|
|
2864
|
+
"Circle Color",
|
|
2865
|
+
normalizeColor(circleColor || "#000")
|
|
2866
|
+
);
|
|
2487
2867
|
const circleRadius = this.map.getPaintProperty(layerId, "circle-radius");
|
|
2488
|
-
this.createSliderControl(
|
|
2868
|
+
this.createSliderControl(
|
|
2869
|
+
container,
|
|
2870
|
+
layerId,
|
|
2871
|
+
"circle-radius",
|
|
2872
|
+
"Radius",
|
|
2873
|
+
typeof circleRadius === "number" ? circleRadius : 5,
|
|
2874
|
+
0,
|
|
2875
|
+
40,
|
|
2876
|
+
0.5
|
|
2877
|
+
);
|
|
2489
2878
|
const circleOpacity = this.map.getPaintProperty(layerId, "circle-opacity");
|
|
2490
2879
|
if (circleOpacity !== void 0 && typeof circleOpacity === "number") {
|
|
2491
|
-
this.createSliderControl(
|
|
2492
|
-
|
|
2493
|
-
|
|
2880
|
+
this.createSliderControl(
|
|
2881
|
+
container,
|
|
2882
|
+
layerId,
|
|
2883
|
+
"circle-opacity",
|
|
2884
|
+
"Opacity",
|
|
2885
|
+
circleOpacity,
|
|
2886
|
+
0,
|
|
2887
|
+
1,
|
|
2888
|
+
0.05
|
|
2889
|
+
);
|
|
2890
|
+
}
|
|
2891
|
+
const strokeColor = this.map.getPaintProperty(
|
|
2892
|
+
layerId,
|
|
2893
|
+
"circle-stroke-color"
|
|
2894
|
+
);
|
|
2494
2895
|
if (strokeColor !== void 0) {
|
|
2495
|
-
this.createColorControl(
|
|
2496
|
-
|
|
2497
|
-
|
|
2896
|
+
this.createColorControl(
|
|
2897
|
+
container,
|
|
2898
|
+
layerId,
|
|
2899
|
+
"circle-stroke-color",
|
|
2900
|
+
"Stroke Color",
|
|
2901
|
+
normalizeColor(strokeColor)
|
|
2902
|
+
);
|
|
2903
|
+
}
|
|
2904
|
+
const strokeWidth = this.map.getPaintProperty(
|
|
2905
|
+
layerId,
|
|
2906
|
+
"circle-stroke-width"
|
|
2907
|
+
);
|
|
2498
2908
|
if (strokeWidth !== void 0 && typeof strokeWidth === "number") {
|
|
2499
|
-
this.createSliderControl(
|
|
2909
|
+
this.createSliderControl(
|
|
2910
|
+
container,
|
|
2911
|
+
layerId,
|
|
2912
|
+
"circle-stroke-width",
|
|
2913
|
+
"Stroke Width",
|
|
2914
|
+
strokeWidth,
|
|
2915
|
+
0,
|
|
2916
|
+
10,
|
|
2917
|
+
0.1
|
|
2918
|
+
);
|
|
2500
2919
|
}
|
|
2501
2920
|
}
|
|
2502
2921
|
/**
|
|
@@ -2504,17 +2923,77 @@ class LayerControl {
|
|
|
2504
2923
|
*/
|
|
2505
2924
|
addRasterControls(container, layerId) {
|
|
2506
2925
|
const rasterOpacity = this.map.getPaintProperty(layerId, "raster-opacity");
|
|
2507
|
-
this.createSliderControl(
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2926
|
+
this.createSliderControl(
|
|
2927
|
+
container,
|
|
2928
|
+
layerId,
|
|
2929
|
+
"raster-opacity",
|
|
2930
|
+
"Opacity",
|
|
2931
|
+
typeof rasterOpacity === "number" ? rasterOpacity : 1,
|
|
2932
|
+
0,
|
|
2933
|
+
1,
|
|
2934
|
+
0.05
|
|
2935
|
+
);
|
|
2936
|
+
const brightnessMin = this.map.getPaintProperty(
|
|
2937
|
+
layerId,
|
|
2938
|
+
"raster-brightness-min"
|
|
2939
|
+
);
|
|
2940
|
+
this.createSliderControl(
|
|
2941
|
+
container,
|
|
2942
|
+
layerId,
|
|
2943
|
+
"raster-brightness-min",
|
|
2944
|
+
"Brightness Min",
|
|
2945
|
+
typeof brightnessMin === "number" ? brightnessMin : 0,
|
|
2946
|
+
-1,
|
|
2947
|
+
1,
|
|
2948
|
+
0.05
|
|
2949
|
+
);
|
|
2950
|
+
const brightnessMax = this.map.getPaintProperty(
|
|
2951
|
+
layerId,
|
|
2952
|
+
"raster-brightness-max"
|
|
2953
|
+
);
|
|
2954
|
+
this.createSliderControl(
|
|
2955
|
+
container,
|
|
2956
|
+
layerId,
|
|
2957
|
+
"raster-brightness-max",
|
|
2958
|
+
"Brightness Max",
|
|
2959
|
+
typeof brightnessMax === "number" ? brightnessMax : 1,
|
|
2960
|
+
-1,
|
|
2961
|
+
1,
|
|
2962
|
+
0.05
|
|
2963
|
+
);
|
|
2512
2964
|
const saturation = this.map.getPaintProperty(layerId, "raster-saturation");
|
|
2513
|
-
this.createSliderControl(
|
|
2965
|
+
this.createSliderControl(
|
|
2966
|
+
container,
|
|
2967
|
+
layerId,
|
|
2968
|
+
"raster-saturation",
|
|
2969
|
+
"Saturation",
|
|
2970
|
+
typeof saturation === "number" ? saturation : 0,
|
|
2971
|
+
-1,
|
|
2972
|
+
1,
|
|
2973
|
+
0.05
|
|
2974
|
+
);
|
|
2514
2975
|
const contrast = this.map.getPaintProperty(layerId, "raster-contrast");
|
|
2515
|
-
this.createSliderControl(
|
|
2976
|
+
this.createSliderControl(
|
|
2977
|
+
container,
|
|
2978
|
+
layerId,
|
|
2979
|
+
"raster-contrast",
|
|
2980
|
+
"Contrast",
|
|
2981
|
+
typeof contrast === "number" ? contrast : 0,
|
|
2982
|
+
-1,
|
|
2983
|
+
1,
|
|
2984
|
+
0.05
|
|
2985
|
+
);
|
|
2516
2986
|
const hueRotate = this.map.getPaintProperty(layerId, "raster-hue-rotate");
|
|
2517
|
-
this.createSliderControl(
|
|
2987
|
+
this.createSliderControl(
|
|
2988
|
+
container,
|
|
2989
|
+
layerId,
|
|
2990
|
+
"raster-hue-rotate",
|
|
2991
|
+
"Hue Rotate",
|
|
2992
|
+
typeof hueRotate === "number" ? hueRotate : 0,
|
|
2993
|
+
0,
|
|
2994
|
+
350,
|
|
2995
|
+
5
|
|
2996
|
+
);
|
|
2518
2997
|
}
|
|
2519
2998
|
/**
|
|
2520
2999
|
* Add controls for symbol layers
|
|
@@ -2522,15 +3001,39 @@ class LayerControl {
|
|
|
2522
3001
|
addSymbolControls(container, layerId) {
|
|
2523
3002
|
const textColor = this.map.getPaintProperty(layerId, "text-color");
|
|
2524
3003
|
if (textColor !== void 0) {
|
|
2525
|
-
this.createColorControl(
|
|
3004
|
+
this.createColorControl(
|
|
3005
|
+
container,
|
|
3006
|
+
layerId,
|
|
3007
|
+
"text-color",
|
|
3008
|
+
"Text Color",
|
|
3009
|
+
normalizeColor(textColor)
|
|
3010
|
+
);
|
|
2526
3011
|
}
|
|
2527
3012
|
const textOpacity = this.map.getPaintProperty(layerId, "text-opacity");
|
|
2528
3013
|
if (textOpacity !== void 0 && typeof textOpacity === "number") {
|
|
2529
|
-
this.createSliderControl(
|
|
3014
|
+
this.createSliderControl(
|
|
3015
|
+
container,
|
|
3016
|
+
layerId,
|
|
3017
|
+
"text-opacity",
|
|
3018
|
+
"Text Opacity",
|
|
3019
|
+
textOpacity,
|
|
3020
|
+
0,
|
|
3021
|
+
1,
|
|
3022
|
+
0.05
|
|
3023
|
+
);
|
|
2530
3024
|
}
|
|
2531
3025
|
const iconOpacity = this.map.getPaintProperty(layerId, "icon-opacity");
|
|
2532
3026
|
if (iconOpacity !== void 0 && typeof iconOpacity === "number") {
|
|
2533
|
-
this.createSliderControl(
|
|
3027
|
+
this.createSliderControl(
|
|
3028
|
+
container,
|
|
3029
|
+
layerId,
|
|
3030
|
+
"icon-opacity",
|
|
3031
|
+
"Icon Opacity",
|
|
3032
|
+
iconOpacity,
|
|
3033
|
+
0,
|
|
3034
|
+
1,
|
|
3035
|
+
0.05
|
|
3036
|
+
);
|
|
2534
3037
|
}
|
|
2535
3038
|
}
|
|
2536
3039
|
/**
|
|
@@ -2613,7 +3116,9 @@ class LayerControl {
|
|
|
2613
3116
|
restoreOriginalStyle(this.map, layerId, this.state.originalStyles);
|
|
2614
3117
|
const editor = this.styleEditors.get(layerId);
|
|
2615
3118
|
if (editor) {
|
|
2616
|
-
const sliders = editor.querySelectorAll(
|
|
3119
|
+
const sliders = editor.querySelectorAll(
|
|
3120
|
+
".style-control-slider"
|
|
3121
|
+
);
|
|
2617
3122
|
sliders.forEach((slider) => {
|
|
2618
3123
|
var _a;
|
|
2619
3124
|
const property = slider.dataset.property;
|
|
@@ -2621,7 +3126,9 @@ class LayerControl {
|
|
|
2621
3126
|
const value = this.map.getPaintProperty(layerId, property);
|
|
2622
3127
|
if (value !== void 0 && typeof value === "number") {
|
|
2623
3128
|
slider.value = String(value);
|
|
2624
|
-
const valueDisplay = (_a = slider.parentElement) == null ? void 0 : _a.querySelector(
|
|
3129
|
+
const valueDisplay = (_a = slider.parentElement) == null ? void 0 : _a.querySelector(
|
|
3130
|
+
".style-control-value"
|
|
3131
|
+
);
|
|
2625
3132
|
if (valueDisplay) {
|
|
2626
3133
|
const step = parseFloat(slider.step);
|
|
2627
3134
|
valueDisplay.textContent = formatNumericValue(value, step);
|
|
@@ -2629,7 +3136,9 @@ class LayerControl {
|
|
|
2629
3136
|
}
|
|
2630
3137
|
}
|
|
2631
3138
|
});
|
|
2632
|
-
const colorPickers = editor.querySelectorAll(
|
|
3139
|
+
const colorPickers = editor.querySelectorAll(
|
|
3140
|
+
".style-control-color-picker"
|
|
3141
|
+
);
|
|
2633
3142
|
colorPickers.forEach((picker) => {
|
|
2634
3143
|
var _a;
|
|
2635
3144
|
const property = picker.dataset.property;
|
|
@@ -2638,7 +3147,9 @@ class LayerControl {
|
|
|
2638
3147
|
if (value !== void 0) {
|
|
2639
3148
|
const hexColor = normalizeColor(value);
|
|
2640
3149
|
picker.value = hexColor;
|
|
2641
|
-
const hexDisplay = (_a = picker.parentElement) == null ? void 0 : _a.querySelector(
|
|
3150
|
+
const hexDisplay = (_a = picker.parentElement) == null ? void 0 : _a.querySelector(
|
|
3151
|
+
".style-control-color-value"
|
|
3152
|
+
);
|
|
2642
3153
|
if (hexDisplay) {
|
|
2643
3154
|
hexDisplay.textContent = hexColor;
|
|
2644
3155
|
}
|
|
@@ -2686,8 +3197,12 @@ class LayerControl {
|
|
|
2686
3197
|
const layerItems = this.panel.querySelectorAll(".layer-control-item");
|
|
2687
3198
|
layerItems.forEach((item) => {
|
|
2688
3199
|
if (item.dataset.layerId === layerId) {
|
|
2689
|
-
const checkbox = item.querySelector(
|
|
2690
|
-
|
|
3200
|
+
const checkbox = item.querySelector(
|
|
3201
|
+
".layer-control-checkbox"
|
|
3202
|
+
);
|
|
3203
|
+
const opacitySlider = item.querySelector(
|
|
3204
|
+
".layer-control-opacity"
|
|
3205
|
+
);
|
|
2691
3206
|
if (checkbox) {
|
|
2692
3207
|
checkbox.checked = visible;
|
|
2693
3208
|
}
|
|
@@ -2711,7 +3226,9 @@ class LayerControl {
|
|
|
2711
3226
|
return;
|
|
2712
3227
|
}
|
|
2713
3228
|
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(
|
|
3229
|
+
const isAutoDetectMode = this.targetLayers.length === 0 || this.targetLayers.length === 1 && this.targetLayers[0] === "Background" || this.targetLayers.every(
|
|
3230
|
+
(id) => id === "Background" || this.state.layerStates[id]
|
|
3231
|
+
);
|
|
2715
3232
|
const newLayers = [];
|
|
2716
3233
|
const useBasemapStyleDetection = this.basemapLayerIds !== null && this.basemapLayerIds.size > 0;
|
|
2717
3234
|
const useInitialLayerDetection = !useBasemapStyleDetection && this.initialLayerIds !== null && this.initialLayerIds.size > 0;
|
|
@@ -2766,7 +3283,9 @@ class LayerControl {
|
|
|
2766
3283
|
if (removedLayers.length > 0) {
|
|
2767
3284
|
removedLayers.forEach((layerId) => {
|
|
2768
3285
|
delete this.state.layerStates[layerId];
|
|
2769
|
-
const itemEl = this.panel.querySelector(
|
|
3286
|
+
const itemEl = this.panel.querySelector(
|
|
3287
|
+
`[data-layer-id="${layerId}"]`
|
|
3288
|
+
);
|
|
2770
3289
|
if (itemEl) {
|
|
2771
3290
|
itemEl.remove();
|
|
2772
3291
|
}
|
|
@@ -2813,7 +3332,9 @@ class LayerControl {
|
|
|
2813
3332
|
const state = this.state.layerStates[layerId];
|
|
2814
3333
|
if (state.isCustomLayer && !customLayerIds.includes(layerId)) {
|
|
2815
3334
|
delete this.state.layerStates[layerId];
|
|
2816
|
-
const itemEl = this.panel.querySelector(
|
|
3335
|
+
const itemEl = this.panel.querySelector(
|
|
3336
|
+
`[data-layer-id="${layerId}"]`
|
|
3337
|
+
);
|
|
2817
3338
|
if (itemEl) {
|
|
2818
3339
|
itemEl.remove();
|
|
2819
3340
|
}
|
|
@@ -2836,12 +3357,14 @@ class LayerControl {
|
|
|
2836
3357
|
registerCustomAdapter(adapter) {
|
|
2837
3358
|
if (!this.customLayerRegistry) {
|
|
2838
3359
|
this.customLayerRegistry = new CustomLayerRegistry();
|
|
2839
|
-
this.customLayerUnsubscribe = this.customLayerRegistry.onChange(
|
|
2840
|
-
|
|
2841
|
-
|
|
3360
|
+
this.customLayerUnsubscribe = this.customLayerRegistry.onChange(
|
|
3361
|
+
(event, layerId) => {
|
|
3362
|
+
if (event === "add" && layerId) {
|
|
3363
|
+
this.removedCustomLayerIds.delete(layerId);
|
|
3364
|
+
}
|
|
3365
|
+
this.checkForNewLayers();
|
|
2842
3366
|
}
|
|
2843
|
-
|
|
2844
|
-
});
|
|
3367
|
+
);
|
|
2845
3368
|
}
|
|
2846
3369
|
this.customLayerRegistry.register(adapter);
|
|
2847
3370
|
if (this.panel) {
|
|
@@ -2888,20 +3411,29 @@ class LayerControl {
|
|
|
2888
3411
|
}
|
|
2889
3412
|
this.hideContextMenu();
|
|
2890
3413
|
});
|
|
2891
|
-
const moveBottomItem = this.createContextMenuItem(
|
|
2892
|
-
|
|
2893
|
-
|
|
3414
|
+
const moveBottomItem = this.createContextMenuItem(
|
|
3415
|
+
"Move to Bottom",
|
|
3416
|
+
"⤓",
|
|
3417
|
+
() => {
|
|
3418
|
+
if (this.state.contextMenu.targetLayerId) {
|
|
3419
|
+
this.moveLayerToBottom(this.state.contextMenu.targetLayerId);
|
|
3420
|
+
}
|
|
3421
|
+
this.hideContextMenu();
|
|
2894
3422
|
}
|
|
2895
|
-
|
|
2896
|
-
});
|
|
3423
|
+
);
|
|
2897
3424
|
const sep2 = document.createElement("div");
|
|
2898
3425
|
sep2.className = "context-menu-separator";
|
|
2899
|
-
const removeItem = this.createContextMenuItem(
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
3426
|
+
const removeItem = this.createContextMenuItem(
|
|
3427
|
+
"Remove Layer",
|
|
3428
|
+
"🗑️",
|
|
3429
|
+
() => {
|
|
3430
|
+
if (this.state.contextMenu.targetLayerId) {
|
|
3431
|
+
this.removeLayer(this.state.contextMenu.targetLayerId);
|
|
3432
|
+
}
|
|
3433
|
+
this.hideContextMenu();
|
|
3434
|
+
},
|
|
3435
|
+
true
|
|
3436
|
+
);
|
|
2905
3437
|
menu.appendChild(renameItem);
|
|
2906
3438
|
menu.appendChild(zoomItem);
|
|
2907
3439
|
menu.appendChild(sep1);
|
|
@@ -3031,7 +3563,9 @@ class LayerControl {
|
|
|
3031
3563
|
if ((layerState == null ? void 0 : layerState.isCustomLayer) && this.customLayerRegistry) {
|
|
3032
3564
|
const bounds = this.customLayerRegistry.getBounds(layerId);
|
|
3033
3565
|
if (bounds) {
|
|
3034
|
-
this.map.fitBounds(bounds, {
|
|
3566
|
+
this.map.fitBounds(bounds, {
|
|
3567
|
+
padding: 50
|
|
3568
|
+
});
|
|
3035
3569
|
return;
|
|
3036
3570
|
}
|
|
3037
3571
|
}
|
|
@@ -3063,7 +3597,9 @@ class LayerControl {
|
|
|
3063
3597
|
} else if (feature.geometry.type === "LineString" || feature.geometry.type === "MultiPoint") {
|
|
3064
3598
|
feature.geometry.coordinates.forEach(processCoords);
|
|
3065
3599
|
} else if (feature.geometry.type === "Polygon" || feature.geometry.type === "MultiLineString") {
|
|
3066
|
-
feature.geometry.coordinates.forEach(
|
|
3600
|
+
feature.geometry.coordinates.forEach(
|
|
3601
|
+
(ring) => ring.forEach(processCoords)
|
|
3602
|
+
);
|
|
3067
3603
|
} else if (feature.geometry.type === "MultiPolygon") {
|
|
3068
3604
|
feature.geometry.coordinates.forEach(
|
|
3069
3605
|
(polygon) => polygon.forEach((ring) => ring.forEach(processCoords))
|
|
@@ -3071,7 +3607,13 @@ class LayerControl {
|
|
|
3071
3607
|
}
|
|
3072
3608
|
});
|
|
3073
3609
|
if (minLng !== Infinity && minLat !== Infinity && maxLng !== -Infinity && maxLat !== -Infinity) {
|
|
3074
|
-
this.map.fitBounds(
|
|
3610
|
+
this.map.fitBounds(
|
|
3611
|
+
[
|
|
3612
|
+
[minLng, minLat],
|
|
3613
|
+
[maxLng, maxLat]
|
|
3614
|
+
],
|
|
3615
|
+
{ padding: 50 }
|
|
3616
|
+
);
|
|
3075
3617
|
}
|
|
3076
3618
|
} catch (error) {
|
|
3077
3619
|
console.warn(`Failed to zoom to layer ${layerId}:`, error);
|
|
@@ -3085,15 +3627,21 @@ class LayerControl {
|
|
|
3085
3627
|
const style = this.map.getStyle();
|
|
3086
3628
|
if (!(style == null ? void 0 : style.layers)) return [];
|
|
3087
3629
|
const mapLayerIds = style.layers.map((l) => l.id);
|
|
3088
|
-
const userLayerIds = Object.keys(this.state.layerStates).filter(
|
|
3089
|
-
|
|
3630
|
+
const userLayerIds = Object.keys(this.state.layerStates).filter(
|
|
3631
|
+
(id) => id !== "Background"
|
|
3632
|
+
);
|
|
3633
|
+
const mapLibreLayers = userLayerIds.filter(
|
|
3634
|
+
(id) => mapLayerIds.includes(id)
|
|
3635
|
+
);
|
|
3090
3636
|
const customLayers = userLayerIds.filter(
|
|
3091
3637
|
(id) => {
|
|
3092
3638
|
var _a;
|
|
3093
3639
|
return ((_a = this.state.layerStates[id]) == null ? void 0 : _a.isCustomLayer) && !mapLayerIds.includes(id);
|
|
3094
3640
|
}
|
|
3095
3641
|
);
|
|
3096
|
-
const sortedMapLibreLayers = mapLibreLayers.sort(
|
|
3642
|
+
const sortedMapLibreLayers = mapLibreLayers.sort(
|
|
3643
|
+
(a, b) => mapLayerIds.indexOf(b) - mapLayerIds.indexOf(a)
|
|
3644
|
+
);
|
|
3097
3645
|
return [...customLayers, ...sortedMapLibreLayers];
|
|
3098
3646
|
}
|
|
3099
3647
|
/**
|
|
@@ -3135,7 +3683,10 @@ class LayerControl {
|
|
|
3135
3683
|
}
|
|
3136
3684
|
const newLayerStates = {};
|
|
3137
3685
|
const orderedIds = [...layerIds];
|
|
3138
|
-
[orderedIds[index], orderedIds[index - 1]] = [
|
|
3686
|
+
[orderedIds[index], orderedIds[index - 1]] = [
|
|
3687
|
+
orderedIds[index - 1],
|
|
3688
|
+
orderedIds[index]
|
|
3689
|
+
];
|
|
3139
3690
|
if (this.state.layerStates["Background"]) {
|
|
3140
3691
|
newLayerStates["Background"] = this.state.layerStates["Background"];
|
|
3141
3692
|
}
|
|
@@ -3199,7 +3750,10 @@ class LayerControl {
|
|
|
3199
3750
|
}
|
|
3200
3751
|
const newLayerStates = {};
|
|
3201
3752
|
const orderedIds = [...layerIds];
|
|
3202
|
-
[orderedIds[index], orderedIds[index + 1]] = [
|
|
3753
|
+
[orderedIds[index], orderedIds[index + 1]] = [
|
|
3754
|
+
orderedIds[index + 1],
|
|
3755
|
+
orderedIds[index]
|
|
3756
|
+
];
|
|
3203
3757
|
if (this.state.layerStates["Background"]) {
|
|
3204
3758
|
newLayerStates["Background"] = this.state.layerStates["Background"];
|
|
3205
3759
|
}
|
|
@@ -3223,7 +3777,11 @@ class LayerControl {
|
|
|
3223
3777
|
if (index < 0 || index === layerIds.length - 1) return;
|
|
3224
3778
|
const isCustom = (_a = this.state.layerStates[layerId]) == null ? void 0 : _a.isCustomLayer;
|
|
3225
3779
|
if (!isCustom && this.isMapLibreLayer(layerId)) {
|
|
3226
|
-
const bottomLayerId = this.findNextMapLibreLayer(
|
|
3780
|
+
const bottomLayerId = this.findNextMapLibreLayer(
|
|
3781
|
+
layerIds,
|
|
3782
|
+
layerIds.length - 1,
|
|
3783
|
+
-1
|
|
3784
|
+
);
|
|
3227
3785
|
if (bottomLayerId && bottomLayerId !== layerId) {
|
|
3228
3786
|
try {
|
|
3229
3787
|
this.map.moveLayer(layerId, bottomLayerId);
|
|
@@ -3311,7 +3869,9 @@ class LayerControl {
|
|
|
3311
3869
|
this.map.removeLayer(layerId);
|
|
3312
3870
|
if (sourceId) {
|
|
3313
3871
|
const style = this.map.getStyle();
|
|
3314
|
-
const sourceStillUsed = (_a = style == null ? void 0 : style.layers) == null ? void 0 : _a.some(
|
|
3872
|
+
const sourceStillUsed = (_a = style == null ? void 0 : style.layers) == null ? void 0 : _a.some(
|
|
3873
|
+
(l) => l.source === sourceId
|
|
3874
|
+
);
|
|
3315
3875
|
if (!sourceStillUsed) {
|
|
3316
3876
|
try {
|
|
3317
3877
|
this.map.removeSource(sourceId);
|
|
@@ -3378,7 +3938,9 @@ class LayerControl {
|
|
|
3378
3938
|
*/
|
|
3379
3939
|
startDrag(layerId, e) {
|
|
3380
3940
|
var _a;
|
|
3381
|
-
const itemEl = this.panel.querySelector(
|
|
3941
|
+
const itemEl = this.panel.querySelector(
|
|
3942
|
+
`[data-layer-id="${layerId}"]`
|
|
3943
|
+
);
|
|
3382
3944
|
if (!itemEl) return;
|
|
3383
3945
|
const rect = itemEl.getBoundingClientRect();
|
|
3384
3946
|
this.state.drag = {
|
|
@@ -3429,7 +3991,11 @@ class LayerControl {
|
|
|
3429
3991
|
clone.style.top = `${currentTop + deltaY}px`;
|
|
3430
3992
|
this.state.drag.startY = e.clientY;
|
|
3431
3993
|
this.state.drag.currentY = e.clientY;
|
|
3432
|
-
const items = Array.from(
|
|
3994
|
+
const items = Array.from(
|
|
3995
|
+
this.panel.querySelectorAll(".layer-control-item:not(.dragging)")
|
|
3996
|
+
).filter(
|
|
3997
|
+
(item) => item.dataset.layerId !== "Background"
|
|
3998
|
+
);
|
|
3433
3999
|
for (const item of items) {
|
|
3434
4000
|
const itemRect = item.getBoundingClientRect();
|
|
3435
4001
|
if (e.clientY >= itemRect.top && e.clientY <= itemRect.bottom) {
|
|
@@ -3455,7 +4021,9 @@ class LayerControl {
|
|
|
3455
4021
|
if (!this.state.drag.active) return;
|
|
3456
4022
|
const layerId = this.state.drag.layerId;
|
|
3457
4023
|
const placeholder = this.state.drag.placeholder;
|
|
3458
|
-
const itemEl = this.panel.querySelector(
|
|
4024
|
+
const itemEl = this.panel.querySelector(
|
|
4025
|
+
`[data-layer-id="${layerId}"]`
|
|
4026
|
+
);
|
|
3459
4027
|
if (itemEl && placeholder) {
|
|
3460
4028
|
(_a = placeholder.parentNode) == null ? void 0 : _a.insertBefore(itemEl, placeholder);
|
|
3461
4029
|
itemEl.classList.remove("dragging");
|