maplibre-gl-layer-control 0.7.2 → 0.8.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 +111 -3
- package/dist/index.cjs +104 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +104 -13
- package/dist/index.mjs.map +1 -1
- package/dist/types/index.d.ts +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,7 +73,17 @@ map.on('load', () => {
|
|
|
73
73
|
panelMaxWidth: 450
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
// Option 2:
|
|
76
|
+
// Option 2: Auto-detect with basemapStyleUrl (recommended for reliable basemap detection)
|
|
77
|
+
// - Fetches the basemap style to identify basemap layers
|
|
78
|
+
// - All basemap layers are grouped under "Background"
|
|
79
|
+
// - User-added layers are shown individually
|
|
80
|
+
// const BASEMAP_STYLE = 'https://demotiles.maplibre.org/style.json';
|
|
81
|
+
// const layerControl = new LayerControl({
|
|
82
|
+
// collapsed: false,
|
|
83
|
+
// basemapStyleUrl: BASEMAP_STYLE
|
|
84
|
+
// });
|
|
85
|
+
|
|
86
|
+
// Option 3: Show ALL layers individually (no layers parameter)
|
|
77
87
|
// - Auto-detects ALL layers from the map
|
|
78
88
|
// - Generates friendly names from layer IDs (e.g., 'countries-layer' → 'Countries Layer')
|
|
79
89
|
// const layerControl = new LayerControl({
|
|
@@ -83,7 +93,7 @@ map.on('load', () => {
|
|
|
83
93
|
// panelMaxWidth: 450
|
|
84
94
|
// });
|
|
85
95
|
|
|
86
|
-
// Option
|
|
96
|
+
// Option 4: Manually specify layer states (for full control over names)
|
|
87
97
|
// const layerControl = new LayerControl({
|
|
88
98
|
// collapsed: false,
|
|
89
99
|
// layerStates: {
|
|
@@ -161,6 +171,7 @@ function MapComponent() {
|
|
|
161
171
|
| `showLayerSymbol` | `boolean` | `true` | Show layer type symbols (colored icons) next to layer names |
|
|
162
172
|
| `excludeDrawnLayers` | `boolean` | `true` | Exclude layers from drawing libraries (Geoman, Mapbox GL Draw, etc.) |
|
|
163
173
|
| `customLayerAdapters` | `CustomLayerAdapter[]` | `undefined` | Adapters for non-MapLibre layers (deck.gl, Zarr, etc.) |
|
|
174
|
+
| `basemapStyleUrl` | `string` | `undefined` | URL of basemap style JSON for reliable layer detection (see below) |
|
|
164
175
|
|
|
165
176
|
### LayerState
|
|
166
177
|
|
|
@@ -172,14 +183,76 @@ interface LayerState {
|
|
|
172
183
|
}
|
|
173
184
|
```
|
|
174
185
|
|
|
186
|
+
### Basemap Style URL Detection
|
|
187
|
+
|
|
188
|
+
When using auto-detection (without specifying `layers`), the control needs to distinguish between basemap layers and user-added layers. By default, it uses heuristics based on source detection, which may not always be reliable.
|
|
189
|
+
|
|
190
|
+
For **reliable detection**, provide the `basemapStyleUrl` option with the same URL used for the map's style:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
const BASEMAP_STYLE_URL = 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json';
|
|
194
|
+
|
|
195
|
+
const map = new maplibregl.Map({
|
|
196
|
+
container: 'map',
|
|
197
|
+
style: BASEMAP_STYLE_URL,
|
|
198
|
+
center: [0, 0],
|
|
199
|
+
zoom: 2
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
map.on('load', () => {
|
|
203
|
+
// Add your custom layers
|
|
204
|
+
map.addLayer({
|
|
205
|
+
id: 'my-custom-layer',
|
|
206
|
+
type: 'fill',
|
|
207
|
+
source: 'my-source',
|
|
208
|
+
paint: { 'fill-color': '#088' }
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Create layer control with basemapStyleUrl for reliable detection
|
|
212
|
+
const layerControl = new LayerControl({
|
|
213
|
+
collapsed: false,
|
|
214
|
+
basemapStyleUrl: BASEMAP_STYLE_URL // All layers from this URL go to "Background"
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
map.addControl(layerControl, 'top-right');
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
When `basemapStyleUrl` is provided:
|
|
222
|
+
- The control fetches the style JSON and extracts all layer IDs
|
|
223
|
+
- Layers that exist in the basemap style are grouped under "Background"
|
|
224
|
+
- All other layers (user-added) are shown individually in the control
|
|
225
|
+
- New layers added later are automatically detected as user layers
|
|
226
|
+
|
|
227
|
+
### Automatic Detection Without basemapStyleUrl
|
|
228
|
+
|
|
229
|
+
Even without `basemapStyleUrl`, the control uses source-based heuristics to detect user-added layers. Custom MapLibre layers (using `map.addLayer()`) are automatically detected whether they are added **before** or **after** the layer control - no custom adapter is needed for standard MapLibre layer types!
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
map.on('load', () => {
|
|
233
|
+
// Add custom layers BEFORE the control - they will be detected
|
|
234
|
+
map.addSource('my-source', { type: 'geojson', data: myGeoJson });
|
|
235
|
+
map.addLayer({ id: 'my-layer', type: 'fill', source: 'my-source', ... });
|
|
236
|
+
|
|
237
|
+
// Add the control - it detects existing custom layers
|
|
238
|
+
const layerControl = new LayerControl({ collapsed: false });
|
|
239
|
+
map.addControl(layerControl, 'top-right');
|
|
240
|
+
|
|
241
|
+
// Add more layers AFTER the control - they will also be detected automatically
|
|
242
|
+
map.addLayer({ id: 'another-layer', type: 'circle', source: 'another-source', ... });
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
175
246
|
## Examples
|
|
176
247
|
|
|
177
248
|
See the [examples](./examples) folder for complete working examples:
|
|
178
249
|
|
|
179
250
|
- **[basic](./examples/basic)** - Simple vanilla JavaScript example
|
|
180
|
-
- **[full-demo](./examples/full-demo)** - Full demo with multiple layer types
|
|
251
|
+
- **[full-demo](./examples/full-demo)** - Full demo with multiple layer types and `basemapStyleUrl` for reliable basemap detection
|
|
252
|
+
- **[dynamic-layers](./examples/dynamic-layers)** - Auto-detect layers added before or after control
|
|
181
253
|
- **[background-legend](./examples/background-legend)** - Background layer visibility control
|
|
182
254
|
- **[react](./examples/react)** - React integration example
|
|
255
|
+
- **[cdn](./examples/cdn)** - Browser-only example using CDN (no build step required)
|
|
183
256
|
|
|
184
257
|
### Layer Symbols
|
|
185
258
|
|
|
@@ -386,6 +459,41 @@ npm test
|
|
|
386
459
|
npm run build
|
|
387
460
|
```
|
|
388
461
|
|
|
462
|
+
## Docker
|
|
463
|
+
|
|
464
|
+
The examples can be run using Docker. The image is automatically built and published to GitHub Container Registry.
|
|
465
|
+
|
|
466
|
+
### Pull and Run
|
|
467
|
+
|
|
468
|
+
```bash
|
|
469
|
+
# Pull the latest image
|
|
470
|
+
docker pull ghcr.io/opengeos/maplibre-gl-layer-control:latest
|
|
471
|
+
|
|
472
|
+
# Run the container
|
|
473
|
+
docker run -p 8080:80 ghcr.io/opengeos/maplibre-gl-layer-control:latest
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Then open http://localhost:8080/maplibre-gl-layer-control/ in your browser to view the examples.
|
|
477
|
+
|
|
478
|
+
### Build Locally
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
# Build the image
|
|
482
|
+
docker build -t maplibre-gl-layer-control .
|
|
483
|
+
|
|
484
|
+
# Run the container
|
|
485
|
+
docker run -p 8080:80 maplibre-gl-layer-control
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Available Tags
|
|
489
|
+
|
|
490
|
+
| Tag | Description |
|
|
491
|
+
|-----|-------------|
|
|
492
|
+
| `latest` | Latest release |
|
|
493
|
+
| `x.y.z` | Specific version (e.g., `1.0.0`) |
|
|
494
|
+
| `x.y` | Minor version (e.g., `1.0`) |
|
|
495
|
+
|
|
496
|
+
|
|
389
497
|
## License
|
|
390
498
|
|
|
391
499
|
MIT © Qiusheng Wu
|
package/dist/index.cjs
CHANGED
|
@@ -643,6 +643,7 @@ class LayerControl {
|
|
|
643
643
|
__publicField(this, "targetLayers");
|
|
644
644
|
__publicField(this, "styleEditors");
|
|
645
645
|
__publicField(this, "initialSourceIds", null);
|
|
646
|
+
__publicField(this, "initialLayerIds", null);
|
|
646
647
|
// Panel width management
|
|
647
648
|
__publicField(this, "minPanelWidth");
|
|
648
649
|
__publicField(this, "maxPanelWidth");
|
|
@@ -653,6 +654,8 @@ class LayerControl {
|
|
|
653
654
|
__publicField(this, "excludeDrawnLayers");
|
|
654
655
|
__publicField(this, "customLayerRegistry", null);
|
|
655
656
|
__publicField(this, "customLayerUnsubscribe", null);
|
|
657
|
+
__publicField(this, "basemapStyleUrl", null);
|
|
658
|
+
__publicField(this, "basemapLayerIds", null);
|
|
656
659
|
__publicField(this, "widthSliderEl", null);
|
|
657
660
|
__publicField(this, "widthThumbEl", null);
|
|
658
661
|
__publicField(this, "widthValueEl", null);
|
|
@@ -687,6 +690,7 @@ class LayerControl {
|
|
|
687
690
|
this.customLayerRegistry.register(adapter);
|
|
688
691
|
});
|
|
689
692
|
}
|
|
693
|
+
this.basemapStyleUrl = options.basemapStyleUrl || null;
|
|
690
694
|
}
|
|
691
695
|
/**
|
|
692
696
|
* Called when the control is added to the map
|
|
@@ -700,8 +704,10 @@ class LayerControl {
|
|
|
700
704
|
} else {
|
|
701
705
|
this.initialSourceIds = /* @__PURE__ */ new Set();
|
|
702
706
|
}
|
|
703
|
-
if (
|
|
704
|
-
this.
|
|
707
|
+
if (style && style.layers) {
|
|
708
|
+
this.initialLayerIds = new Set(style.layers.map((layer) => layer.id));
|
|
709
|
+
} else {
|
|
710
|
+
this.initialLayerIds = /* @__PURE__ */ new Set();
|
|
705
711
|
}
|
|
706
712
|
this.container = this.createContainer();
|
|
707
713
|
this.button = this.createToggleButton();
|
|
@@ -710,7 +716,25 @@ class LayerControl {
|
|
|
710
716
|
this.mapContainer.appendChild(this.panel);
|
|
711
717
|
this.updateWidthDisplay();
|
|
712
718
|
this.setupEventListeners();
|
|
713
|
-
this.
|
|
719
|
+
if (this.basemapStyleUrl && !this.basemapLayerIds) {
|
|
720
|
+
this.fetchBasemapStyle().then(() => {
|
|
721
|
+
if (Object.keys(this.state.layerStates).length === 0) {
|
|
722
|
+
this.autoDetectLayers();
|
|
723
|
+
}
|
|
724
|
+
this.buildLayerItems();
|
|
725
|
+
}).catch((error) => {
|
|
726
|
+
console.warn("Failed to fetch basemap style, falling back to heuristic detection:", error);
|
|
727
|
+
if (Object.keys(this.state.layerStates).length === 0) {
|
|
728
|
+
this.autoDetectLayers();
|
|
729
|
+
}
|
|
730
|
+
this.buildLayerItems();
|
|
731
|
+
});
|
|
732
|
+
} else {
|
|
733
|
+
if (Object.keys(this.state.layerStates).length === 0) {
|
|
734
|
+
this.autoDetectLayers();
|
|
735
|
+
}
|
|
736
|
+
this.buildLayerItems();
|
|
737
|
+
}
|
|
714
738
|
if (!this.state.collapsed) {
|
|
715
739
|
requestAnimationFrame(() => {
|
|
716
740
|
this.updatePanelPosition();
|
|
@@ -718,6 +742,30 @@ class LayerControl {
|
|
|
718
742
|
}
|
|
719
743
|
return this.container;
|
|
720
744
|
}
|
|
745
|
+
/**
|
|
746
|
+
* Fetch the basemap style JSON and extract layer IDs.
|
|
747
|
+
* This provides reliable distinction between basemap and user-added layers.
|
|
748
|
+
*/
|
|
749
|
+
async fetchBasemapStyle() {
|
|
750
|
+
if (!this.basemapStyleUrl) return;
|
|
751
|
+
try {
|
|
752
|
+
const response = await fetch(this.basemapStyleUrl);
|
|
753
|
+
if (!response.ok) {
|
|
754
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
755
|
+
}
|
|
756
|
+
const styleJson = await response.json();
|
|
757
|
+
if (styleJson && Array.isArray(styleJson.layers)) {
|
|
758
|
+
this.basemapLayerIds = new Set(
|
|
759
|
+
styleJson.layers.map((layer) => layer.id)
|
|
760
|
+
);
|
|
761
|
+
} else {
|
|
762
|
+
this.basemapLayerIds = /* @__PURE__ */ new Set();
|
|
763
|
+
}
|
|
764
|
+
} catch (error) {
|
|
765
|
+
console.warn("Failed to fetch basemap style from URL:", this.basemapStyleUrl, error);
|
|
766
|
+
throw error;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
721
769
|
/**
|
|
722
770
|
* Called when the control is removed from the map
|
|
723
771
|
*/
|
|
@@ -754,7 +802,8 @@ class LayerControl {
|
|
|
754
802
|
if (this.targetLayers.length === 0) {
|
|
755
803
|
const userAddedLayers = [];
|
|
756
804
|
const backgroundLayerIds = [];
|
|
757
|
-
const
|
|
805
|
+
const useBasemapStyleDetection = this.basemapLayerIds !== null && this.basemapLayerIds.size > 0;
|
|
806
|
+
const userAddedSourceIds = useBasemapStyleDetection ? /* @__PURE__ */ new Set() : this.detectUserAddedSources();
|
|
758
807
|
allLayerIds.forEach((layerId) => {
|
|
759
808
|
const layer = this.map.getLayer(layerId);
|
|
760
809
|
if (!layer) return;
|
|
@@ -762,11 +811,19 @@ class LayerControl {
|
|
|
762
811
|
backgroundLayerIds.push(layerId);
|
|
763
812
|
return;
|
|
764
813
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
814
|
+
if (useBasemapStyleDetection) {
|
|
815
|
+
if (this.basemapLayerIds.has(layerId)) {
|
|
816
|
+
backgroundLayerIds.push(layerId);
|
|
817
|
+
} else {
|
|
818
|
+
userAddedLayers.push(layerId);
|
|
819
|
+
}
|
|
768
820
|
} else {
|
|
769
|
-
|
|
821
|
+
const sourceId = layer.source;
|
|
822
|
+
if (sourceId && userAddedSourceIds.has(sourceId)) {
|
|
823
|
+
userAddedLayers.push(layerId);
|
|
824
|
+
} else {
|
|
825
|
+
backgroundLayerIds.push(layerId);
|
|
826
|
+
}
|
|
770
827
|
}
|
|
771
828
|
});
|
|
772
829
|
if (backgroundLayerIds.length > 0) {
|
|
@@ -1532,9 +1589,27 @@ class LayerControl {
|
|
|
1532
1589
|
}
|
|
1533
1590
|
/**
|
|
1534
1591
|
* Check if a layer is a user-added layer (vs basemap layer)
|
|
1592
|
+
* Used primarily for the background legend to determine which layers are background
|
|
1535
1593
|
*/
|
|
1536
1594
|
isUserAddedLayer(layerId) {
|
|
1537
|
-
|
|
1595
|
+
if (this.state.layerStates[layerId] !== void 0 && layerId !== "Background") {
|
|
1596
|
+
return true;
|
|
1597
|
+
}
|
|
1598
|
+
if (this.basemapLayerIds !== null && this.basemapLayerIds.size > 0) {
|
|
1599
|
+
return !this.basemapLayerIds.has(layerId);
|
|
1600
|
+
}
|
|
1601
|
+
if (this.initialLayerIds !== null && !this.initialLayerIds.has(layerId)) {
|
|
1602
|
+
return true;
|
|
1603
|
+
}
|
|
1604
|
+
const layer = this.map.getLayer(layerId);
|
|
1605
|
+
if (layer) {
|
|
1606
|
+
const sourceId = layer.source;
|
|
1607
|
+
if (sourceId) {
|
|
1608
|
+
const userAddedSourceIds = this.detectUserAddedSources();
|
|
1609
|
+
return userAddedSourceIds.has(sourceId);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
return false;
|
|
1538
1613
|
}
|
|
1539
1614
|
/**
|
|
1540
1615
|
* Toggle visibility for all background layers (basemap layers)
|
|
@@ -2324,7 +2399,8 @@ class LayerControl {
|
|
|
2324
2399
|
const currentMapLayerIds = new Set(style.layers.map((layer) => layer.id));
|
|
2325
2400
|
const isAutoDetectMode = this.targetLayers.length === 0 || this.targetLayers.length === 1 && this.targetLayers[0] === "Background" || this.targetLayers.every((id) => id === "Background" || this.state.layerStates[id]);
|
|
2326
2401
|
const newLayers = [];
|
|
2327
|
-
const
|
|
2402
|
+
const useBasemapStyleDetection = this.basemapLayerIds !== null && this.basemapLayerIds.size > 0;
|
|
2403
|
+
const useInitialLayerDetection = !useBasemapStyleDetection && this.initialLayerIds !== null && this.initialLayerIds.size > 0;
|
|
2328
2404
|
currentMapLayerIds.forEach((layerId) => {
|
|
2329
2405
|
if (layerId !== "Background" && !this.state.layerStates[layerId]) {
|
|
2330
2406
|
const layer = this.map.getLayer(layerId);
|
|
@@ -2333,9 +2409,24 @@ class LayerControl {
|
|
|
2333
2409
|
if (this.excludeDrawnLayers && this.isDrawnLayer(layerId)) {
|
|
2334
2410
|
return;
|
|
2335
2411
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2412
|
+
if (useBasemapStyleDetection) {
|
|
2413
|
+
if (this.basemapLayerIds.has(layerId)) {
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
} else if (useInitialLayerDetection) {
|
|
2417
|
+
if (this.initialLayerIds.has(layerId)) {
|
|
2418
|
+
const userAddedSourceIds = this.detectUserAddedSources();
|
|
2419
|
+
const sourceId = layer.source;
|
|
2420
|
+
if (!sourceId || !userAddedSourceIds.has(sourceId)) {
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
} else {
|
|
2425
|
+
const userAddedSourceIds = this.detectUserAddedSources();
|
|
2426
|
+
const sourceId = layer.source;
|
|
2427
|
+
if (!sourceId || !userAddedSourceIds.has(sourceId)) {
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2339
2430
|
}
|
|
2340
2431
|
}
|
|
2341
2432
|
newLayers.push(layerId);
|