maplibre-gl-layer-control 0.2.0 → 0.4.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 +6 -0
- package/dist/index.cjs +189 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +189 -36
- package/dist/index.mjs.map +1 -1
- package/dist/maplibre-gl-layer-control.css +43 -6
- package/dist/types/index.d.ts +394 -0
- package/package.json +4 -1
package/dist/index.mjs
CHANGED
|
@@ -179,6 +179,8 @@ class LayerControl {
|
|
|
179
179
|
// Panel width management
|
|
180
180
|
__publicField(this, "minPanelWidth");
|
|
181
181
|
__publicField(this, "maxPanelWidth");
|
|
182
|
+
__publicField(this, "showStyleEditor");
|
|
183
|
+
__publicField(this, "showOpacitySlider");
|
|
182
184
|
__publicField(this, "widthSliderEl", null);
|
|
183
185
|
__publicField(this, "widthThumbEl", null);
|
|
184
186
|
__publicField(this, "widthValueEl", null);
|
|
@@ -189,6 +191,8 @@ class LayerControl {
|
|
|
189
191
|
__publicField(this, "widthFrame", null);
|
|
190
192
|
this.minPanelWidth = options.panelMinWidth || 240;
|
|
191
193
|
this.maxPanelWidth = options.panelMaxWidth || 420;
|
|
194
|
+
this.showStyleEditor = options.showStyleEditor !== false;
|
|
195
|
+
this.showOpacitySlider = options.showOpacitySlider !== false;
|
|
192
196
|
this.state = {
|
|
193
197
|
collapsed: options.collapsed !== false,
|
|
194
198
|
panelWidth: options.panelWidth || 320,
|
|
@@ -238,7 +242,27 @@ class LayerControl {
|
|
|
238
242
|
}
|
|
239
243
|
const allLayerIds = style.layers.map((layer) => layer.id);
|
|
240
244
|
if (this.targetLayers.length === 0) {
|
|
245
|
+
const originalSourceIds = this.getOriginalStyleSourceIds();
|
|
246
|
+
const userAddedLayers = [];
|
|
247
|
+
const backgroundLayerIds = [];
|
|
241
248
|
allLayerIds.forEach((layerId) => {
|
|
249
|
+
const layer = this.map.getLayer(layerId);
|
|
250
|
+
if (!layer) return;
|
|
251
|
+
const sourceId = layer.source;
|
|
252
|
+
if (!sourceId || originalSourceIds.has(sourceId)) {
|
|
253
|
+
backgroundLayerIds.push(layerId);
|
|
254
|
+
} else {
|
|
255
|
+
userAddedLayers.push(layerId);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
if (backgroundLayerIds.length > 0) {
|
|
259
|
+
this.state.layerStates["Background"] = {
|
|
260
|
+
visible: true,
|
|
261
|
+
opacity: 1,
|
|
262
|
+
name: "Background"
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
userAddedLayers.forEach((layerId) => {
|
|
242
266
|
const layer = this.map.getLayer(layerId);
|
|
243
267
|
if (!layer) return;
|
|
244
268
|
const visibility = this.map.getLayoutProperty(layerId, "visibility");
|
|
@@ -286,6 +310,60 @@ class LayerControl {
|
|
|
286
310
|
}
|
|
287
311
|
this.targetLayers = Object.keys(this.state.layerStates);
|
|
288
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Get the source IDs that were part of the original style (from the style URL)
|
|
315
|
+
* Sources added via map.addSource() are considered user-added
|
|
316
|
+
*/
|
|
317
|
+
getOriginalStyleSourceIds() {
|
|
318
|
+
const originalSourceIds = /* @__PURE__ */ new Set();
|
|
319
|
+
const style = this.map.getStyle();
|
|
320
|
+
if (!style || !style.sources) return originalSourceIds;
|
|
321
|
+
const spriteUrl = style.sprite;
|
|
322
|
+
const glyphsUrl = style.glyphs;
|
|
323
|
+
let styleBaseDomain = "";
|
|
324
|
+
if (spriteUrl) {
|
|
325
|
+
try {
|
|
326
|
+
const url = new URL(typeof spriteUrl === "string" ? spriteUrl : "");
|
|
327
|
+
styleBaseDomain = url.hostname;
|
|
328
|
+
} catch {
|
|
329
|
+
}
|
|
330
|
+
} else if (glyphsUrl) {
|
|
331
|
+
try {
|
|
332
|
+
const url = new URL(glyphsUrl.replace("{fontstack}", "test").replace("{range}", "test"));
|
|
333
|
+
styleBaseDomain = url.hostname;
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
Object.entries(style.sources).forEach(([sourceId, source]) => {
|
|
338
|
+
const src = source;
|
|
339
|
+
let sourceUrl = src.url || src.tiles && src.tiles[0] || "";
|
|
340
|
+
if (sourceUrl) {
|
|
341
|
+
try {
|
|
342
|
+
const url = new URL(sourceUrl);
|
|
343
|
+
if (styleBaseDomain && url.hostname === styleBaseDomain) {
|
|
344
|
+
originalSourceIds.add(sourceId);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const basemapDomains = [
|
|
348
|
+
"demotiles.maplibre.org",
|
|
349
|
+
"api.maptiler.com",
|
|
350
|
+
"tiles.stadiamaps.com",
|
|
351
|
+
"api.mapbox.com",
|
|
352
|
+
"basemaps.cartocdn.com"
|
|
353
|
+
];
|
|
354
|
+
if (basemapDomains.some((domain) => url.hostname.includes(domain))) {
|
|
355
|
+
originalSourceIds.add(sourceId);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
} catch {
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (!src.data && !sourceUrl && src.type !== "geojson") {
|
|
362
|
+
originalSourceIds.add(sourceId);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
return originalSourceIds;
|
|
366
|
+
}
|
|
289
367
|
/**
|
|
290
368
|
* Generate a friendly display name from a layer ID
|
|
291
369
|
*/
|
|
@@ -329,8 +407,53 @@ class LayerControl {
|
|
|
329
407
|
}
|
|
330
408
|
const header = this.createPanelHeader();
|
|
331
409
|
panel.appendChild(header);
|
|
410
|
+
const actionButtons = this.createActionButtons();
|
|
411
|
+
panel.appendChild(actionButtons);
|
|
332
412
|
return panel;
|
|
333
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Create action buttons for Show All / Hide All
|
|
416
|
+
*/
|
|
417
|
+
createActionButtons() {
|
|
418
|
+
const container = document.createElement("div");
|
|
419
|
+
container.className = "layer-control-actions";
|
|
420
|
+
const showAllBtn = document.createElement("button");
|
|
421
|
+
showAllBtn.type = "button";
|
|
422
|
+
showAllBtn.className = "layer-control-action-btn";
|
|
423
|
+
showAllBtn.textContent = "Show All";
|
|
424
|
+
showAllBtn.title = "Show all layers";
|
|
425
|
+
showAllBtn.addEventListener("click", () => this.setAllLayersVisibility(true));
|
|
426
|
+
const hideAllBtn = document.createElement("button");
|
|
427
|
+
hideAllBtn.type = "button";
|
|
428
|
+
hideAllBtn.className = "layer-control-action-btn";
|
|
429
|
+
hideAllBtn.textContent = "Hide All";
|
|
430
|
+
hideAllBtn.title = "Hide all layers";
|
|
431
|
+
hideAllBtn.addEventListener("click", () => this.setAllLayersVisibility(false));
|
|
432
|
+
container.appendChild(showAllBtn);
|
|
433
|
+
container.appendChild(hideAllBtn);
|
|
434
|
+
return container;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Set visibility of all layers
|
|
438
|
+
*/
|
|
439
|
+
setAllLayersVisibility(visible) {
|
|
440
|
+
Object.keys(this.state.layerStates).forEach((layerId) => {
|
|
441
|
+
if (layerId === "Background") {
|
|
442
|
+
this.toggleBackgroundVisibility(visible);
|
|
443
|
+
} else {
|
|
444
|
+
this.state.layerStates[layerId].visible = visible;
|
|
445
|
+
this.map.setLayoutProperty(layerId, "visibility", visible ? "visible" : "none");
|
|
446
|
+
}
|
|
447
|
+
const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
|
|
448
|
+
if (itemEl) {
|
|
449
|
+
const checkbox = itemEl.querySelector(".layer-control-checkbox");
|
|
450
|
+
if (checkbox) {
|
|
451
|
+
checkbox.checked = visible;
|
|
452
|
+
checkbox.indeterminate = false;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
334
457
|
/**
|
|
335
458
|
* Create the panel header with title and width control
|
|
336
459
|
*/
|
|
@@ -504,7 +627,11 @@ class LayerControl {
|
|
|
504
627
|
this.widthSliderEl.setAttribute("aria-valuenow", String(this.state.panelWidth));
|
|
505
628
|
const ratio = (this.state.panelWidth - this.minPanelWidth) / (this.maxPanelWidth - this.minPanelWidth || 1);
|
|
506
629
|
if (this.widthThumbEl) {
|
|
507
|
-
const sliderWidth = this.widthSliderEl.clientWidth
|
|
630
|
+
const sliderWidth = this.widthSliderEl.clientWidth;
|
|
631
|
+
if (sliderWidth === 0) {
|
|
632
|
+
requestAnimationFrame(() => this.updateWidthDisplay());
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
508
635
|
const thumbWidth = this.widthThumbEl.offsetWidth || 14;
|
|
509
636
|
const padding = 16;
|
|
510
637
|
const available = Math.max(0, sliderWidth - padding - thumbWidth);
|
|
@@ -609,34 +736,38 @@ class LayerControl {
|
|
|
609
736
|
name.className = "layer-control-name";
|
|
610
737
|
name.textContent = state.name || layerId;
|
|
611
738
|
name.title = state.name || layerId;
|
|
612
|
-
const opacity = document.createElement("input");
|
|
613
|
-
opacity.type = "range";
|
|
614
|
-
opacity.className = "layer-control-opacity";
|
|
615
|
-
opacity.min = "0";
|
|
616
|
-
opacity.max = "1";
|
|
617
|
-
opacity.step = "0.01";
|
|
618
|
-
opacity.value = String(state.opacity);
|
|
619
|
-
opacity.title = `Opacity: ${Math.round(state.opacity * 100)}%`;
|
|
620
|
-
opacity.addEventListener("mousedown", () => {
|
|
621
|
-
this.state.userInteractingWithSlider = true;
|
|
622
|
-
});
|
|
623
|
-
opacity.addEventListener("mouseup", () => {
|
|
624
|
-
this.state.userInteractingWithSlider = false;
|
|
625
|
-
});
|
|
626
|
-
opacity.addEventListener("input", () => {
|
|
627
|
-
this.changeLayerOpacity(layerId, parseFloat(opacity.value));
|
|
628
|
-
opacity.title = `Opacity: ${Math.round(parseFloat(opacity.value) * 100)}%`;
|
|
629
|
-
});
|
|
630
739
|
row.appendChild(checkbox);
|
|
631
740
|
row.appendChild(name);
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
741
|
+
if (this.showOpacitySlider) {
|
|
742
|
+
const opacity = document.createElement("input");
|
|
743
|
+
opacity.type = "range";
|
|
744
|
+
opacity.className = "layer-control-opacity";
|
|
745
|
+
opacity.min = "0";
|
|
746
|
+
opacity.max = "1";
|
|
747
|
+
opacity.step = "0.01";
|
|
748
|
+
opacity.value = String(state.opacity);
|
|
749
|
+
opacity.title = `Opacity: ${Math.round(state.opacity * 100)}%`;
|
|
750
|
+
opacity.addEventListener("mousedown", () => {
|
|
751
|
+
this.state.userInteractingWithSlider = true;
|
|
752
|
+
});
|
|
753
|
+
opacity.addEventListener("mouseup", () => {
|
|
754
|
+
this.state.userInteractingWithSlider = false;
|
|
755
|
+
});
|
|
756
|
+
opacity.addEventListener("input", () => {
|
|
757
|
+
this.changeLayerOpacity(layerId, parseFloat(opacity.value));
|
|
758
|
+
opacity.title = `Opacity: ${Math.round(parseFloat(opacity.value) * 100)}%`;
|
|
759
|
+
});
|
|
760
|
+
row.appendChild(opacity);
|
|
761
|
+
}
|
|
762
|
+
if (this.showStyleEditor) {
|
|
763
|
+
if (layerId === "Background") {
|
|
764
|
+
const legendButton = this.createBackgroundLegendButton();
|
|
765
|
+
row.appendChild(legendButton);
|
|
766
|
+
} else {
|
|
767
|
+
const styleButton = this.createStyleButton(layerId);
|
|
768
|
+
if (styleButton) {
|
|
769
|
+
row.appendChild(styleButton);
|
|
770
|
+
}
|
|
640
771
|
}
|
|
641
772
|
}
|
|
642
773
|
item.appendChild(row);
|
|
@@ -1378,7 +1509,7 @@ class LayerControl {
|
|
|
1378
1509
|
});
|
|
1379
1510
|
}
|
|
1380
1511
|
/**
|
|
1381
|
-
* Check for new layers and add them to the control
|
|
1512
|
+
* Check for new layers and add them to the control, remove deleted layers
|
|
1382
1513
|
*/
|
|
1383
1514
|
checkForNewLayers() {
|
|
1384
1515
|
try {
|
|
@@ -1386,21 +1517,43 @@ class LayerControl {
|
|
|
1386
1517
|
if (!style || !style.layers) {
|
|
1387
1518
|
return;
|
|
1388
1519
|
}
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
}
|
|
1393
|
-
return true;
|
|
1394
|
-
});
|
|
1520
|
+
const currentMapLayerIds = new Set(style.layers.map((layer) => layer.id));
|
|
1521
|
+
const originalSourceIds = this.getOriginalStyleSourceIds();
|
|
1522
|
+
const isAutoDetectMode = this.targetLayers.length === 0 || this.targetLayers.length === 1 && this.targetLayers[0] === "Background" || this.targetLayers.every((id) => id === "Background" || this.state.layerStates[id]);
|
|
1395
1523
|
const newLayers = [];
|
|
1396
|
-
|
|
1524
|
+
currentMapLayerIds.forEach((layerId) => {
|
|
1397
1525
|
if (layerId !== "Background" && !this.state.layerStates[layerId]) {
|
|
1398
1526
|
const layer = this.map.getLayer(layerId);
|
|
1399
1527
|
if (layer) {
|
|
1528
|
+
if (isAutoDetectMode) {
|
|
1529
|
+
const sourceId = layer.source;
|
|
1530
|
+
if (!sourceId || originalSourceIds.has(sourceId)) {
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1400
1534
|
newLayers.push(layerId);
|
|
1401
1535
|
}
|
|
1402
1536
|
}
|
|
1403
1537
|
});
|
|
1538
|
+
const removedLayers = [];
|
|
1539
|
+
Object.keys(this.state.layerStates).forEach((layerId) => {
|
|
1540
|
+
if (layerId !== "Background" && !currentMapLayerIds.has(layerId)) {
|
|
1541
|
+
removedLayers.push(layerId);
|
|
1542
|
+
}
|
|
1543
|
+
});
|
|
1544
|
+
if (removedLayers.length > 0) {
|
|
1545
|
+
removedLayers.forEach((layerId) => {
|
|
1546
|
+
delete this.state.layerStates[layerId];
|
|
1547
|
+
const itemEl = this.panel.querySelector(`[data-layer-id="${layerId}"]`);
|
|
1548
|
+
if (itemEl) {
|
|
1549
|
+
itemEl.remove();
|
|
1550
|
+
}
|
|
1551
|
+
if (this.state.activeStyleEditor === layerId) {
|
|
1552
|
+
this.state.activeStyleEditor = null;
|
|
1553
|
+
}
|
|
1554
|
+
this.styleEditors.delete(layerId);
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1404
1557
|
if (newLayers.length > 0) {
|
|
1405
1558
|
newLayers.forEach((layerId) => {
|
|
1406
1559
|
const layer = this.map.getLayer(layerId);
|
|
@@ -1412,7 +1565,7 @@ class LayerControl {
|
|
|
1412
1565
|
this.state.layerStates[layerId] = {
|
|
1413
1566
|
visible: isVisible,
|
|
1414
1567
|
opacity,
|
|
1415
|
-
name:
|
|
1568
|
+
name: this.generateFriendlyName(layerId)
|
|
1416
1569
|
};
|
|
1417
1570
|
this.addLayerItem(layerId, this.state.layerStates[layerId]);
|
|
1418
1571
|
});
|