maplibre-gl-layer-control 0.1.0 → 0.2.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 +14 -0
- package/dist/index.cjs +272 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +272 -2
- package/dist/index.mjs.map +1 -1
- package/dist/maplibre-gl-layer-control.css +183 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ A comprehensive layer control for MapLibre GL with advanced styling capabilities
|
|
|
16
16
|
- **Raster layers**: opacity, brightness, saturation, contrast, hue-rotate
|
|
17
17
|
- ✅ **Dynamic layer detection** - Automatically detect and manage new layers
|
|
18
18
|
- ✅ **Background layer grouping** - Control all basemap layers as one group
|
|
19
|
+
- ✅ **Background layer legend** - Gear icon to toggle individual background layer visibility
|
|
19
20
|
- ✅ **Accessibility** - Full ARIA support and keyboard navigation
|
|
20
21
|
- ✅ **TypeScript** - Full type safety and IntelliSense support
|
|
21
22
|
- ✅ **React integration** - Optional React components and hooks
|
|
@@ -165,8 +166,21 @@ See the [examples](./examples) folder for complete working examples:
|
|
|
165
166
|
|
|
166
167
|
- **[basic](./examples/basic)** - Simple vanilla JavaScript example
|
|
167
168
|
- **[full-demo](./examples/full-demo)** - Full demo with multiple layer types
|
|
169
|
+
- **[background-legend](./examples/background-legend)** - Background layer visibility control
|
|
168
170
|
- **[react](./examples/react)** - React integration example
|
|
169
171
|
|
|
172
|
+
### Background Layer Legend
|
|
173
|
+
|
|
174
|
+
When using the `layers` option to specify specific layers, all other layers are grouped under a "Background" entry. The Background layer includes a **gear icon** that opens a detailed legend panel showing:
|
|
175
|
+
|
|
176
|
+
- Individual visibility toggles for each background layer
|
|
177
|
+
- Layer type indicators (fill, line, symbol, etc.)
|
|
178
|
+
- Quick "Show All" / "Hide All" buttons
|
|
179
|
+
- **"Only rendered" filter** - Shows only layers that are currently rendered in the map viewport
|
|
180
|
+
- Indeterminate checkbox state when some layers are hidden
|
|
181
|
+
|
|
182
|
+
This allows fine-grained control over which basemap layers are visible while maintaining a simplified layer control interface.
|
|
183
|
+
|
|
170
184
|
## Development
|
|
171
185
|
|
|
172
186
|
```bash
|
package/dist/index.cjs
CHANGED
|
@@ -197,7 +197,10 @@ class LayerControl {
|
|
|
197
197
|
activeStyleEditor: null,
|
|
198
198
|
layerStates: options.layerStates || {},
|
|
199
199
|
originalStyles: /* @__PURE__ */ new Map(),
|
|
200
|
-
userInteractingWithSlider: false
|
|
200
|
+
userInteractingWithSlider: false,
|
|
201
|
+
backgroundLegendOpen: false,
|
|
202
|
+
backgroundLayerVisibility: /* @__PURE__ */ new Map(),
|
|
203
|
+
onlyRenderedFilter: false
|
|
201
204
|
};
|
|
202
205
|
this.targetLayers = options.layers || Object.keys(this.state.layerStates);
|
|
203
206
|
this.styleEditors = /* @__PURE__ */ new Map();
|
|
@@ -629,7 +632,10 @@ class LayerControl {
|
|
|
629
632
|
row.appendChild(checkbox);
|
|
630
633
|
row.appendChild(name);
|
|
631
634
|
row.appendChild(opacity);
|
|
632
|
-
if (layerId
|
|
635
|
+
if (layerId === "Background") {
|
|
636
|
+
const legendButton = this.createBackgroundLegendButton();
|
|
637
|
+
row.appendChild(legendButton);
|
|
638
|
+
} else {
|
|
633
639
|
const styleButton = this.createStyleButton(layerId);
|
|
634
640
|
if (styleButton) {
|
|
635
641
|
row.appendChild(styleButton);
|
|
@@ -683,9 +689,19 @@ class LayerControl {
|
|
|
683
689
|
const styleLayers = this.map.getStyle().layers || [];
|
|
684
690
|
styleLayers.forEach((layer) => {
|
|
685
691
|
if (!this.isUserAddedLayer(layer.id)) {
|
|
692
|
+
this.state.backgroundLayerVisibility.set(layer.id, visible);
|
|
686
693
|
this.map.setLayoutProperty(layer.id, "visibility", visible ? "visible" : "none");
|
|
687
694
|
}
|
|
688
695
|
});
|
|
696
|
+
if (this.state.backgroundLegendOpen) {
|
|
697
|
+
const legendPanel = this.panel.querySelector(".layer-control-background-legend");
|
|
698
|
+
if (legendPanel) {
|
|
699
|
+
const checkboxes = legendPanel.querySelectorAll(".background-legend-checkbox");
|
|
700
|
+
checkboxes.forEach((checkbox) => {
|
|
701
|
+
checkbox.checked = visible;
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
}
|
|
689
705
|
}
|
|
690
706
|
/**
|
|
691
707
|
* Change opacity for all background layers (basemap layers)
|
|
@@ -704,6 +720,260 @@ class LayerControl {
|
|
|
704
720
|
}
|
|
705
721
|
});
|
|
706
722
|
}
|
|
723
|
+
// ===== Background Legend Methods =====
|
|
724
|
+
/**
|
|
725
|
+
* Create legend button for Background layer
|
|
726
|
+
*/
|
|
727
|
+
createBackgroundLegendButton() {
|
|
728
|
+
const button = document.createElement("button");
|
|
729
|
+
button.className = "layer-control-style-button layer-control-background-legend-button";
|
|
730
|
+
button.innerHTML = "⚙";
|
|
731
|
+
button.title = "Show background layer details";
|
|
732
|
+
button.setAttribute("aria-label", "Show background layer visibility controls");
|
|
733
|
+
button.setAttribute("aria-expanded", String(this.state.backgroundLegendOpen));
|
|
734
|
+
button.addEventListener("click", (e) => {
|
|
735
|
+
e.stopPropagation();
|
|
736
|
+
this.toggleBackgroundLegend();
|
|
737
|
+
});
|
|
738
|
+
return button;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Toggle background legend panel visibility
|
|
742
|
+
*/
|
|
743
|
+
toggleBackgroundLegend() {
|
|
744
|
+
if (this.state.backgroundLegendOpen) {
|
|
745
|
+
this.closeBackgroundLegend();
|
|
746
|
+
} else {
|
|
747
|
+
this.openBackgroundLegend();
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Open background legend panel
|
|
752
|
+
*/
|
|
753
|
+
openBackgroundLegend() {
|
|
754
|
+
if (this.state.activeStyleEditor) {
|
|
755
|
+
this.closeStyleEditor(this.state.activeStyleEditor);
|
|
756
|
+
}
|
|
757
|
+
const itemEl = this.panel.querySelector('[data-layer-id="Background"]');
|
|
758
|
+
if (!itemEl) return;
|
|
759
|
+
let legendPanel = itemEl.querySelector(".layer-control-background-legend");
|
|
760
|
+
if (legendPanel) {
|
|
761
|
+
const layerList = legendPanel.querySelector(".background-legend-layer-list");
|
|
762
|
+
if (layerList) {
|
|
763
|
+
this.populateBackgroundLayerList(layerList);
|
|
764
|
+
}
|
|
765
|
+
} else {
|
|
766
|
+
legendPanel = this.createBackgroundLegendPanel();
|
|
767
|
+
itemEl.appendChild(legendPanel);
|
|
768
|
+
}
|
|
769
|
+
this.state.backgroundLegendOpen = true;
|
|
770
|
+
const button = itemEl.querySelector(".layer-control-background-legend-button");
|
|
771
|
+
if (button) {
|
|
772
|
+
button.setAttribute("aria-expanded", "true");
|
|
773
|
+
button.classList.add("active");
|
|
774
|
+
}
|
|
775
|
+
setTimeout(() => {
|
|
776
|
+
legendPanel == null ? void 0 : legendPanel.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
777
|
+
}, 50);
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Close background legend panel
|
|
781
|
+
*/
|
|
782
|
+
closeBackgroundLegend() {
|
|
783
|
+
const itemEl = this.panel.querySelector('[data-layer-id="Background"]');
|
|
784
|
+
if (!itemEl) return;
|
|
785
|
+
const legendPanel = itemEl.querySelector(".layer-control-background-legend");
|
|
786
|
+
if (legendPanel) {
|
|
787
|
+
legendPanel.remove();
|
|
788
|
+
}
|
|
789
|
+
this.state.backgroundLegendOpen = false;
|
|
790
|
+
const button = itemEl.querySelector(".layer-control-background-legend-button");
|
|
791
|
+
if (button) {
|
|
792
|
+
button.setAttribute("aria-expanded", "false");
|
|
793
|
+
button.classList.remove("active");
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Create the background legend panel with individual layer controls
|
|
798
|
+
*/
|
|
799
|
+
createBackgroundLegendPanel() {
|
|
800
|
+
const panel = document.createElement("div");
|
|
801
|
+
panel.className = "layer-control-background-legend";
|
|
802
|
+
const header = document.createElement("div");
|
|
803
|
+
header.className = "background-legend-header";
|
|
804
|
+
const title = document.createElement("span");
|
|
805
|
+
title.className = "background-legend-title";
|
|
806
|
+
title.textContent = "Background Layers";
|
|
807
|
+
const closeBtn = document.createElement("button");
|
|
808
|
+
closeBtn.className = "background-legend-close";
|
|
809
|
+
closeBtn.innerHTML = "×";
|
|
810
|
+
closeBtn.title = "Close";
|
|
811
|
+
closeBtn.addEventListener("click", (e) => {
|
|
812
|
+
e.stopPropagation();
|
|
813
|
+
this.closeBackgroundLegend();
|
|
814
|
+
});
|
|
815
|
+
header.appendChild(title);
|
|
816
|
+
header.appendChild(closeBtn);
|
|
817
|
+
const actionsRow = document.createElement("div");
|
|
818
|
+
actionsRow.className = "background-legend-actions";
|
|
819
|
+
const showAllBtn = document.createElement("button");
|
|
820
|
+
showAllBtn.className = "background-legend-action-btn";
|
|
821
|
+
showAllBtn.textContent = "Show All";
|
|
822
|
+
showAllBtn.addEventListener("click", () => this.setAllBackgroundLayersVisibility(true));
|
|
823
|
+
const hideAllBtn = document.createElement("button");
|
|
824
|
+
hideAllBtn.className = "background-legend-action-btn";
|
|
825
|
+
hideAllBtn.textContent = "Hide All";
|
|
826
|
+
hideAllBtn.addEventListener("click", () => this.setAllBackgroundLayersVisibility(false));
|
|
827
|
+
actionsRow.appendChild(showAllBtn);
|
|
828
|
+
actionsRow.appendChild(hideAllBtn);
|
|
829
|
+
const filterRow = document.createElement("div");
|
|
830
|
+
filterRow.className = "background-legend-filter";
|
|
831
|
+
const filterCheckbox = document.createElement("input");
|
|
832
|
+
filterCheckbox.type = "checkbox";
|
|
833
|
+
filterCheckbox.className = "background-legend-filter-checkbox";
|
|
834
|
+
filterCheckbox.id = "background-legend-only-rendered";
|
|
835
|
+
filterCheckbox.checked = this.state.onlyRenderedFilter;
|
|
836
|
+
filterCheckbox.addEventListener("change", () => {
|
|
837
|
+
this.state.onlyRenderedFilter = filterCheckbox.checked;
|
|
838
|
+
const layerList2 = panel.querySelector(".background-legend-layer-list");
|
|
839
|
+
if (layerList2) {
|
|
840
|
+
this.populateBackgroundLayerList(layerList2);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
const filterLabel = document.createElement("label");
|
|
844
|
+
filterLabel.className = "background-legend-filter-label";
|
|
845
|
+
filterLabel.htmlFor = "background-legend-only-rendered";
|
|
846
|
+
filterLabel.textContent = "Only rendered";
|
|
847
|
+
filterRow.appendChild(filterCheckbox);
|
|
848
|
+
filterRow.appendChild(filterLabel);
|
|
849
|
+
const layerList = document.createElement("div");
|
|
850
|
+
layerList.className = "background-legend-layer-list";
|
|
851
|
+
this.populateBackgroundLayerList(layerList);
|
|
852
|
+
panel.appendChild(header);
|
|
853
|
+
panel.appendChild(actionsRow);
|
|
854
|
+
panel.appendChild(filterRow);
|
|
855
|
+
panel.appendChild(layerList);
|
|
856
|
+
return panel;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Check if a layer is currently rendered in the map viewport
|
|
860
|
+
*/
|
|
861
|
+
isLayerRendered(layerId) {
|
|
862
|
+
try {
|
|
863
|
+
const layer = this.map.getLayer(layerId);
|
|
864
|
+
if (!layer) return false;
|
|
865
|
+
const visibility = this.map.getLayoutProperty(layerId, "visibility");
|
|
866
|
+
if (visibility === "none") return false;
|
|
867
|
+
if (layer.type === "raster" || layer.type === "hillshade") {
|
|
868
|
+
return true;
|
|
869
|
+
}
|
|
870
|
+
if (layer.type === "background") {
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
const features = this.map.queryRenderedFeatures({ layers: [layerId] });
|
|
874
|
+
return features.length > 0;
|
|
875
|
+
} catch (error) {
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Populate the background layer list with individual layers
|
|
881
|
+
*/
|
|
882
|
+
populateBackgroundLayerList(container) {
|
|
883
|
+
container.innerHTML = "";
|
|
884
|
+
const styleLayers = this.map.getStyle().layers || [];
|
|
885
|
+
styleLayers.forEach((layer) => {
|
|
886
|
+
if (!this.isUserAddedLayer(layer.id)) {
|
|
887
|
+
if (this.state.onlyRenderedFilter && !this.isLayerRendered(layer.id)) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
const layerRow = document.createElement("div");
|
|
891
|
+
layerRow.className = "background-legend-layer-row";
|
|
892
|
+
layerRow.setAttribute("data-background-layer-id", layer.id);
|
|
893
|
+
const checkbox = document.createElement("input");
|
|
894
|
+
checkbox.type = "checkbox";
|
|
895
|
+
checkbox.className = "background-legend-checkbox";
|
|
896
|
+
const visibility = this.map.getLayoutProperty(layer.id, "visibility");
|
|
897
|
+
const isVisible = visibility !== "none";
|
|
898
|
+
checkbox.checked = isVisible;
|
|
899
|
+
this.state.backgroundLayerVisibility.set(layer.id, isVisible);
|
|
900
|
+
checkbox.addEventListener("change", () => {
|
|
901
|
+
this.toggleIndividualBackgroundLayer(layer.id, checkbox.checked);
|
|
902
|
+
});
|
|
903
|
+
const name = document.createElement("span");
|
|
904
|
+
name.className = "background-legend-layer-name";
|
|
905
|
+
name.textContent = this.generateFriendlyName(layer.id);
|
|
906
|
+
name.title = layer.id;
|
|
907
|
+
const typeIndicator = document.createElement("span");
|
|
908
|
+
typeIndicator.className = "background-legend-layer-type";
|
|
909
|
+
typeIndicator.textContent = layer.type;
|
|
910
|
+
layerRow.appendChild(checkbox);
|
|
911
|
+
layerRow.appendChild(name);
|
|
912
|
+
layerRow.appendChild(typeIndicator);
|
|
913
|
+
container.appendChild(layerRow);
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
if (container.children.length === 0) {
|
|
917
|
+
const emptyMsg = document.createElement("p");
|
|
918
|
+
emptyMsg.className = "background-legend-empty";
|
|
919
|
+
emptyMsg.textContent = this.state.onlyRenderedFilter ? "No rendered layers in current view." : "No background layers found.";
|
|
920
|
+
container.appendChild(emptyMsg);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Toggle visibility of an individual background layer
|
|
925
|
+
*/
|
|
926
|
+
toggleIndividualBackgroundLayer(layerId, visible) {
|
|
927
|
+
this.state.backgroundLayerVisibility.set(layerId, visible);
|
|
928
|
+
this.map.setLayoutProperty(layerId, "visibility", visible ? "visible" : "none");
|
|
929
|
+
this.updateBackgroundCheckboxState();
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Set visibility for all background layers
|
|
933
|
+
*/
|
|
934
|
+
setAllBackgroundLayersVisibility(visible) {
|
|
935
|
+
const styleLayers = this.map.getStyle().layers || [];
|
|
936
|
+
styleLayers.forEach((layer) => {
|
|
937
|
+
if (!this.isUserAddedLayer(layer.id)) {
|
|
938
|
+
this.state.backgroundLayerVisibility.set(layer.id, visible);
|
|
939
|
+
this.map.setLayoutProperty(layer.id, "visibility", visible ? "visible" : "none");
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
const legendPanel = this.panel.querySelector(".layer-control-background-legend");
|
|
943
|
+
if (legendPanel) {
|
|
944
|
+
const checkboxes = legendPanel.querySelectorAll(".background-legend-checkbox");
|
|
945
|
+
checkboxes.forEach((checkbox) => {
|
|
946
|
+
checkbox.checked = visible;
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
this.updateBackgroundCheckboxState();
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Update the main Background checkbox based on individual layer states
|
|
953
|
+
*/
|
|
954
|
+
updateBackgroundCheckboxState() {
|
|
955
|
+
const styleLayers = this.map.getStyle().layers || [];
|
|
956
|
+
let anyVisible = false;
|
|
957
|
+
let allVisible = true;
|
|
958
|
+
styleLayers.forEach((layer) => {
|
|
959
|
+
if (!this.isUserAddedLayer(layer.id)) {
|
|
960
|
+
const visible = this.state.backgroundLayerVisibility.get(layer.id);
|
|
961
|
+
if (visible === true) anyVisible = true;
|
|
962
|
+
if (visible === false) allVisible = false;
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
const backgroundItem = this.panel.querySelector('[data-layer-id="Background"]');
|
|
966
|
+
if (backgroundItem) {
|
|
967
|
+
const checkbox = backgroundItem.querySelector(".layer-control-checkbox");
|
|
968
|
+
if (checkbox) {
|
|
969
|
+
checkbox.checked = anyVisible;
|
|
970
|
+
checkbox.indeterminate = anyVisible && !allVisible;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (this.state.layerStates["Background"]) {
|
|
974
|
+
this.state.layerStates["Background"].visible = anyVisible;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
707
977
|
/**
|
|
708
978
|
* Create style button for a layer
|
|
709
979
|
*/
|