maplibre-gl-layer-control 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -728,6 +728,10 @@ class LayerControl {
728
728
  __publicField(this, "onLayerRename");
729
729
  __publicField(this, "onLayerReorder");
730
730
  __publicField(this, "onLayerRemove");
731
+ // Background-layer visibility presets
732
+ __publicField(this, "enableBackgroundPresets");
733
+ __publicField(this, "backgroundPresetStorageKey");
734
+ __publicField(this, "onBackgroundPresetsChange");
731
735
  this.minPanelWidth = options.panelMinWidth || 240;
732
736
  this.maxPanelWidth = options.panelMaxWidth || 420;
733
737
  this.initialPanelWidth = options.panelWidth || 350;
@@ -744,6 +748,9 @@ class LayerControl {
744
748
  this.onLayerRename = options.onLayerRename;
745
749
  this.onLayerReorder = options.onLayerReorder;
746
750
  this.onLayerRemove = options.onLayerRemove;
751
+ this.enableBackgroundPresets = options.enableBackgroundPresets !== false;
752
+ this.backgroundPresetStorageKey = options.backgroundPresetStorageKey || "maplibre-layer-control:background-presets";
753
+ this.onBackgroundPresetsChange = options.onBackgroundPresetsChange;
747
754
  this.initialLayerStates = options.layerStates || {};
748
755
  this.state = {
749
756
  collapsed: options.collapsed !== false,
@@ -2232,8 +2239,112 @@ class LayerControl {
2232
2239
  panel.appendChild(actionsRow);
2233
2240
  panel.appendChild(filterRow);
2234
2241
  panel.appendChild(layerList);
2242
+ if (this.enableBackgroundPresets) {
2243
+ panel.appendChild(this.createBackgroundPresetsRow());
2244
+ }
2235
2245
  return panel;
2236
2246
  }
2247
+ /**
2248
+ * Build the "Saved configurations" controls: a dropdown of saved presets with
2249
+ * Apply/Delete actions, plus a name field and Save button to capture the
2250
+ * current basemap element visibility as a reusable, persisted preset.
2251
+ */
2252
+ createBackgroundPresetsRow() {
2253
+ const section = document.createElement("div");
2254
+ section.className = "background-legend-presets";
2255
+ const label = document.createElement("div");
2256
+ label.className = "background-legend-presets-label";
2257
+ label.textContent = "Saved configurations";
2258
+ section.appendChild(label);
2259
+ const selectRow = document.createElement("div");
2260
+ selectRow.className = "background-legend-presets-row";
2261
+ const select = document.createElement("select");
2262
+ select.className = "background-legend-presets-select";
2263
+ select.title = "Saved configurations";
2264
+ const applyBtn = document.createElement("button");
2265
+ applyBtn.className = "background-legend-action-btn";
2266
+ applyBtn.textContent = "Apply";
2267
+ applyBtn.title = "Apply the selected configuration";
2268
+ const deleteBtn = document.createElement("button");
2269
+ deleteBtn.className = "background-legend-action-btn";
2270
+ deleteBtn.textContent = "Delete";
2271
+ deleteBtn.title = "Delete the selected configuration";
2272
+ const refreshSelect = (selected) => {
2273
+ const presets = this.getBackgroundPresets();
2274
+ const names = Object.keys(presets).sort(
2275
+ (a, b) => a.localeCompare(b, void 0, { sensitivity: "base" })
2276
+ );
2277
+ select.innerHTML = "";
2278
+ const placeholder = document.createElement("option");
2279
+ placeholder.value = "";
2280
+ placeholder.textContent = names.length ? "Select a configuration…" : "No saved configurations";
2281
+ placeholder.disabled = names.length > 0;
2282
+ select.appendChild(placeholder);
2283
+ names.forEach((name) => {
2284
+ const option = document.createElement("option");
2285
+ option.value = name;
2286
+ option.textContent = name;
2287
+ select.appendChild(option);
2288
+ });
2289
+ select.value = selected && names.includes(selected) ? selected : "";
2290
+ const hasSelection = select.value !== "";
2291
+ applyBtn.disabled = !hasSelection;
2292
+ deleteBtn.disabled = !hasSelection;
2293
+ };
2294
+ select.addEventListener("change", () => {
2295
+ const hasSelection = select.value !== "";
2296
+ applyBtn.disabled = !hasSelection;
2297
+ deleteBtn.disabled = !hasSelection;
2298
+ });
2299
+ applyBtn.addEventListener("click", (e) => {
2300
+ e.stopPropagation();
2301
+ if (select.value) this.applyBackgroundPreset(select.value);
2302
+ });
2303
+ deleteBtn.addEventListener("click", (e) => {
2304
+ e.stopPropagation();
2305
+ if (select.value) {
2306
+ this.deleteBackgroundPreset(select.value);
2307
+ refreshSelect();
2308
+ }
2309
+ });
2310
+ selectRow.appendChild(select);
2311
+ selectRow.appendChild(applyBtn);
2312
+ selectRow.appendChild(deleteBtn);
2313
+ const saveRow = document.createElement("div");
2314
+ saveRow.className = "background-legend-presets-row";
2315
+ const nameInput = document.createElement("input");
2316
+ nameInput.type = "text";
2317
+ nameInput.className = "background-legend-presets-input";
2318
+ nameInput.placeholder = "Configuration name…";
2319
+ nameInput.maxLength = 60;
2320
+ const saveBtn = document.createElement("button");
2321
+ saveBtn.className = "background-legend-action-btn";
2322
+ saveBtn.textContent = "Save";
2323
+ saveBtn.title = "Save the current visibility as a configuration";
2324
+ const commitSave = () => {
2325
+ const name = nameInput.value.trim();
2326
+ if (!name) return;
2327
+ this.saveBackgroundPreset(name);
2328
+ nameInput.value = "";
2329
+ refreshSelect(name);
2330
+ };
2331
+ saveBtn.addEventListener("click", (e) => {
2332
+ e.stopPropagation();
2333
+ commitSave();
2334
+ });
2335
+ nameInput.addEventListener("keydown", (e) => {
2336
+ if (e.key === "Enter") {
2337
+ e.preventDefault();
2338
+ commitSave();
2339
+ }
2340
+ });
2341
+ saveRow.appendChild(nameInput);
2342
+ saveRow.appendChild(saveBtn);
2343
+ section.appendChild(selectRow);
2344
+ section.appendChild(saveRow);
2345
+ refreshSelect();
2346
+ return section;
2347
+ }
2237
2348
  /**
2238
2349
  * Check if a layer is currently rendered in the map viewport
2239
2350
  */
@@ -2381,6 +2492,147 @@ class LayerControl {
2381
2492
  this.state.layerStates["Background"].visible = anyVisible;
2382
2493
  }
2383
2494
  }
2495
+ /**
2496
+ * Return the IDs of the background (basemap) style layers the control can
2497
+ * toggle, honoring the drawn-layer and user-defined exclusion filters but not
2498
+ * the transient "only rendered" view filter.
2499
+ */
2500
+ getControllableBackgroundLayerIds() {
2501
+ const styleLayers = this.map.getStyle().layers || [];
2502
+ return styleLayers.filter((layer) => {
2503
+ if (this.isUserAddedLayer(layer.id)) return false;
2504
+ if (this.excludeDrawnLayers && this.isDrawnLayer(layer.id)) return false;
2505
+ if (this.isExcludedByPattern(layer.id)) return false;
2506
+ return true;
2507
+ }).map((layer) => layer.id);
2508
+ }
2509
+ /**
2510
+ * Get the current visibility of every controllable background (basemap) layer.
2511
+ *
2512
+ * @returns A map of style-layer ID to whether it is currently visible.
2513
+ */
2514
+ getBackgroundLayerVisibility() {
2515
+ const visibility = {};
2516
+ for (const layerId of this.getControllableBackgroundLayerIds()) {
2517
+ const value = this.map.getLayoutProperty(layerId, "visibility");
2518
+ visibility[layerId] = value !== "none";
2519
+ }
2520
+ return visibility;
2521
+ }
2522
+ /**
2523
+ * Apply a saved background-layer visibility configuration to the map. Only
2524
+ * layers that currently exist in the style are affected, so a configuration
2525
+ * captured on one basemap degrades gracefully when applied to another.
2526
+ *
2527
+ * @param visibility - Map of style-layer ID to desired visibility.
2528
+ */
2529
+ applyBackgroundLayerVisibility(visibility) {
2530
+ for (const [layerId, visible] of Object.entries(visibility)) {
2531
+ if (this.isUserAddedLayer(layerId)) continue;
2532
+ if (!this.map.getLayer(layerId)) continue;
2533
+ this.state.backgroundLayerVisibility.set(layerId, visible);
2534
+ this.map.setLayoutProperty(
2535
+ layerId,
2536
+ "visibility",
2537
+ visible ? "visible" : "none"
2538
+ );
2539
+ }
2540
+ const legendPanel = this.panel.querySelector(
2541
+ ".background-legend-layer-list"
2542
+ );
2543
+ if (legendPanel) {
2544
+ this.populateBackgroundLayerList(legendPanel);
2545
+ }
2546
+ this.updateBackgroundCheckboxState();
2547
+ }
2548
+ /**
2549
+ * Read all saved background-layer presets from localStorage. Returns an empty
2550
+ * object when storage is unavailable or the stored value is malformed.
2551
+ */
2552
+ getBackgroundPresets() {
2553
+ try {
2554
+ const raw = typeof localStorage !== "undefined" ? localStorage.getItem(this.backgroundPresetStorageKey) : null;
2555
+ if (!raw) return {};
2556
+ const parsed = JSON.parse(raw);
2557
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2558
+ return {};
2559
+ }
2560
+ const presets = {};
2561
+ for (const [name, value] of Object.entries(parsed)) {
2562
+ if (this.isUnsafePresetKey(name)) continue;
2563
+ if (value && typeof value === "object" && !Array.isArray(value)) {
2564
+ presets[name] = value;
2565
+ }
2566
+ }
2567
+ return presets;
2568
+ } catch {
2569
+ return {};
2570
+ }
2571
+ }
2572
+ /**
2573
+ * Reject preset names that, used as object keys, could pollute the prototype
2574
+ * chain or otherwise alias built-in object members.
2575
+ */
2576
+ isUnsafePresetKey(name) {
2577
+ return name === "__proto__" || name === "constructor" || name === "prototype";
2578
+ }
2579
+ /**
2580
+ * Persist the full preset map to localStorage and notify listeners.
2581
+ */
2582
+ writeBackgroundPresets(presets) {
2583
+ var _a;
2584
+ try {
2585
+ if (typeof localStorage !== "undefined") {
2586
+ localStorage.setItem(
2587
+ this.backgroundPresetStorageKey,
2588
+ JSON.stringify(presets)
2589
+ );
2590
+ }
2591
+ } catch {
2592
+ }
2593
+ (_a = this.onBackgroundPresetsChange) == null ? void 0 : _a.call(this, presets);
2594
+ }
2595
+ /**
2596
+ * Save the current background-layer visibility as a named preset. Reusing an
2597
+ * existing name overwrites that preset.
2598
+ *
2599
+ * @param name - Preset name; whitespace is trimmed. Empty names are ignored.
2600
+ * @returns The updated preset map.
2601
+ */
2602
+ saveBackgroundPreset(name) {
2603
+ const trimmed = name.trim();
2604
+ const presets = this.getBackgroundPresets();
2605
+ if (!trimmed || this.isUnsafePresetKey(trimmed)) return presets;
2606
+ presets[trimmed] = this.getBackgroundLayerVisibility();
2607
+ this.writeBackgroundPresets(presets);
2608
+ return presets;
2609
+ }
2610
+ /**
2611
+ * Apply a previously saved preset to the map.
2612
+ *
2613
+ * @param name - The preset name to apply.
2614
+ * @returns `true` if a preset with that name existed and was applied.
2615
+ */
2616
+ applyBackgroundPreset(name) {
2617
+ const presets = this.getBackgroundPresets();
2618
+ if (!Object.prototype.hasOwnProperty.call(presets, name)) return false;
2619
+ this.applyBackgroundLayerVisibility(presets[name]);
2620
+ return true;
2621
+ }
2622
+ /**
2623
+ * Delete a saved preset.
2624
+ *
2625
+ * @param name - The preset name to remove.
2626
+ * @returns The updated preset map.
2627
+ */
2628
+ deleteBackgroundPreset(name) {
2629
+ const presets = this.getBackgroundPresets();
2630
+ if (Object.prototype.hasOwnProperty.call(presets, name)) {
2631
+ delete presets[name];
2632
+ this.writeBackgroundPresets(presets);
2633
+ }
2634
+ return presets;
2635
+ }
2384
2636
  /**
2385
2637
  * Create style button for a layer
2386
2638
  */