maplibre-gl-layer-control 0.16.0 → 0.17.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 CHANGED
@@ -14,7 +14,7 @@ A comprehensive layer control for MapLibre GL with advanced styling capabilities
14
14
  - ✅ **Layer visibility toggle** - Checkbox control for each layer
15
15
  - ✅ **Layer opacity control** - Smooth opacity slider with type-aware property mapping; **double-click the slider to type an exact percentage (0-100%)**
16
16
  - ✅ **Layer symbols** - Visual type indicators (colored shapes) next to layer names, auto-detected from layer paint properties
17
- - ✅ **Resizable panel** - **Drag either edge of the panel to resize it**, plus a width slider and keyboard support
17
+ - ✅ **Resizable panel** - **Drag either edge of the panel to resize it**; double-click an edge to reset to the default width. The panel also grows to fill the available height so long layer lists only scroll once they exceed the map.
18
18
  - ✅ **Advanced style editor** - Per-layer-type styling controls:
19
19
  - **Fill layers**: color, opacity, outline-color
20
20
  - **Line layers**: color, width, opacity, blur
@@ -73,7 +73,7 @@ map.on('load', () => {
73
73
  layers: ['my-layer'], // LayerControl auto-detects opacity, visibility, and generates friendly names
74
74
  panelWidth: 340,
75
75
  panelMinWidth: 240,
76
- panelMaxWidth: 450
76
+ panelMaxWidth: 960
77
77
  });
78
78
 
79
79
  // Option 2: Auto-detect with basemapStyleUrl (recommended for reliable basemap detection)
@@ -93,7 +93,7 @@ map.on('load', () => {
93
93
  // collapsed: false,
94
94
  // panelWidth: 340,
95
95
  // panelMinWidth: 240,
96
- // panelMaxWidth: 450
96
+ // panelMaxWidth: 960
97
97
  // });
98
98
 
99
99
  // Option 4: Manually specify layer states (for full control over names)
@@ -167,8 +167,8 @@ function MapComponent() {
167
167
  | `layerStates` | `Record<string, LayerState>` | `undefined` | Manual layer state configuration |
168
168
  | `panelWidth` | `number` | `320` | Initial panel width in pixels |
169
169
  | `panelMinWidth` | `number` | `240` | Minimum panel width |
170
- | `panelMaxWidth` | `number` | `420` | Maximum panel width |
171
- | `panelMaxHeight` | `number` | `600` | Maximum panel height (scrollable when exceeded) |
170
+ | `panelMaxWidth` | `number` | `960` | Maximum panel width |
171
+ | `panelMaxHeight` | `number` | `undefined` | Maximum panel height in pixels. Omit to fill the available vertical space (scrollable only when the layer list is taller than the map) |
172
172
  | `showStyleEditor` | `boolean` | `true` | Show gear icon for style editor |
173
173
  | `showOpacitySlider` | `boolean` | `true` | Show opacity slider for layers |
174
174
  | `showLayerSymbol` | `boolean` | `true` | Show layer type symbols (colored icons) next to layer names |
package/dist/index.cjs CHANGED
@@ -691,6 +691,7 @@ class LayerControl {
691
691
  __publicField(this, "minPanelWidth");
692
692
  __publicField(this, "maxPanelWidth");
693
693
  __publicField(this, "initialPanelWidth");
694
+ /** Explicit max panel height; null means fill available vertical space */
694
695
  __publicField(this, "maxPanelHeight");
695
696
  __publicField(this, "showStyleEditor");
696
697
  __publicField(this, "showOpacitySlider");
@@ -703,13 +704,6 @@ class LayerControl {
703
704
  __publicField(this, "nativeLayerGroups", /* @__PURE__ */ new Map());
704
705
  __publicField(this, "basemapStyleUrl", null);
705
706
  __publicField(this, "basemapLayerIds", null);
706
- __publicField(this, "widthSliderEl", null);
707
- __publicField(this, "widthThumbEl", null);
708
- __publicField(this, "widthValueEl", null);
709
- __publicField(this, "isWidthSliderActive", false);
710
- __publicField(this, "widthDragRectWidth", null);
711
- __publicField(this, "widthDragStartX", null);
712
- __publicField(this, "widthDragStartWidth", null);
713
707
  __publicField(this, "widthFrame", null);
714
708
  // Exact-opacity input popup
715
709
  __publicField(this, "opacityInputEl", null);
@@ -735,9 +729,9 @@ class LayerControl {
735
729
  __publicField(this, "backgroundPresetStorageKey");
736
730
  __publicField(this, "onBackgroundPresetsChange");
737
731
  this.minPanelWidth = options.panelMinWidth || 240;
738
- this.maxPanelWidth = options.panelMaxWidth || 420;
732
+ this.maxPanelWidth = options.panelMaxWidth || 960;
739
733
  this.initialPanelWidth = options.panelWidth || 350;
740
- this.maxPanelHeight = options.panelMaxHeight || 600;
734
+ this.maxPanelHeight = options.panelMaxHeight ?? null;
741
735
  this.showStyleEditor = options.showStyleEditor !== false;
742
736
  this.showOpacitySlider = options.showOpacitySlider !== false;
743
737
  this.showLayerSymbol = options.showLayerSymbol !== false;
@@ -818,7 +812,6 @@ class LayerControl {
818
812
  this.contextMenuEl = this.createContextMenu();
819
813
  this.mapContainer.appendChild(this.contextMenuEl);
820
814
  }
821
- this.updateWidthDisplay();
822
815
  this.setupEventListeners();
823
816
  if (this.basemapStyleUrl && !this.basemapLayerIds) {
824
817
  this.fetchBasemapStyle().then(() => {
@@ -1218,7 +1211,9 @@ class LayerControl {
1218
1211
  const panel = document.createElement("div");
1219
1212
  panel.className = "layer-control-panel";
1220
1213
  panel.style.width = `${this.state.panelWidth}px`;
1221
- panel.style.maxHeight = `${this.maxPanelHeight}px`;
1214
+ if (this.maxPanelHeight != null) {
1215
+ panel.style.maxHeight = `${this.maxPanelHeight}px`;
1216
+ }
1222
1217
  if (!this.state.collapsed) {
1223
1218
  panel.classList.add("expanded");
1224
1219
  }
@@ -1385,6 +1380,15 @@ class LayerControl {
1385
1380
  this.panel.style.right = `${buttonRight}px`;
1386
1381
  break;
1387
1382
  }
1383
+ const edgeMargin = 8;
1384
+ const isTopAnchored = position === "top-left" || position === "top-right";
1385
+ const occupiedFromEdge = isTopAnchored ? buttonTop + buttonRect.height + panelGap : buttonBottom + buttonRect.height + panelGap;
1386
+ const availableHeight = Math.max(
1387
+ 120,
1388
+ Math.round(mapRect.height - occupiedFromEdge - edgeMargin)
1389
+ );
1390
+ const maxHeight = this.maxPanelHeight != null ? Math.min(this.maxPanelHeight, availableHeight) : availableHeight;
1391
+ this.panel.style.maxHeight = `${maxHeight}px`;
1388
1392
  }
1389
1393
  /**
1390
1394
  * Create action buttons for Show All / Hide All
@@ -1433,7 +1437,8 @@ class LayerControl {
1433
1437
  });
1434
1438
  }
1435
1439
  /**
1436
- * Create the panel header with title and width control
1440
+ * Create the panel header with the title. Panel width is adjusted by
1441
+ * dragging either edge of the panel (see createResizeHandle).
1437
1442
  */
1438
1443
  createPanelHeader() {
1439
1444
  const header = document.createElement("div");
@@ -1442,135 +1447,8 @@ class LayerControl {
1442
1447
  title.className = "layer-control-panel-title";
1443
1448
  title.textContent = "Layers";
1444
1449
  header.appendChild(title);
1445
- const widthControl = this.createWidthControl();
1446
- header.appendChild(widthControl);
1447
1450
  return header;
1448
1451
  }
1449
- /**
1450
- * Create the width control slider
1451
- */
1452
- createWidthControl() {
1453
- const widthControl = document.createElement("label");
1454
- widthControl.className = "layer-control-width-control";
1455
- widthControl.title = "Adjust layer panel width";
1456
- const widthLabel = document.createElement("span");
1457
- widthLabel.textContent = "Width";
1458
- widthControl.appendChild(widthLabel);
1459
- const widthSlider = document.createElement("div");
1460
- widthSlider.className = "layer-control-width-slider";
1461
- widthSlider.setAttribute("role", "slider");
1462
- widthSlider.setAttribute("aria-valuemin", String(this.minPanelWidth));
1463
- widthSlider.setAttribute("aria-valuemax", String(this.maxPanelWidth));
1464
- widthSlider.setAttribute("aria-valuenow", String(this.state.panelWidth));
1465
- widthSlider.setAttribute("aria-valuestep", "10");
1466
- widthSlider.setAttribute("aria-label", "Layer panel width");
1467
- widthSlider.tabIndex = 0;
1468
- const widthTrack = document.createElement("div");
1469
- widthTrack.className = "layer-control-width-track";
1470
- const widthThumb = document.createElement("div");
1471
- widthThumb.className = "layer-control-width-thumb";
1472
- widthSlider.appendChild(widthTrack);
1473
- widthSlider.appendChild(widthThumb);
1474
- this.widthSliderEl = widthSlider;
1475
- this.widthThumbEl = widthThumb;
1476
- const widthValue = document.createElement("span");
1477
- widthValue.className = "layer-control-width-value";
1478
- this.widthValueEl = widthValue;
1479
- widthControl.appendChild(widthSlider);
1480
- widthControl.appendChild(widthValue);
1481
- this.updateWidthDisplay();
1482
- this.setupWidthSliderEvents(widthSlider);
1483
- return widthControl;
1484
- }
1485
- /**
1486
- * Setup event listeners for width slider
1487
- */
1488
- setupWidthSliderEvents(widthSlider) {
1489
- widthSlider.addEventListener("pointerdown", (event) => {
1490
- event.preventDefault();
1491
- const rect = widthSlider.getBoundingClientRect();
1492
- this.widthDragRectWidth = rect.width || 1;
1493
- this.widthDragStartX = event.clientX;
1494
- this.widthDragStartWidth = this.state.panelWidth;
1495
- this.isWidthSliderActive = true;
1496
- widthSlider.setPointerCapture(event.pointerId);
1497
- this.updateWidthFromPointer(event, true);
1498
- });
1499
- widthSlider.addEventListener("pointermove", (event) => {
1500
- if (!this.isWidthSliderActive) return;
1501
- this.updateWidthFromPointer(event);
1502
- });
1503
- const endPointerDrag = (event) => {
1504
- if (!this.isWidthSliderActive) return;
1505
- if (event.pointerId !== void 0) {
1506
- try {
1507
- widthSlider.releasePointerCapture(event.pointerId);
1508
- } catch (error) {
1509
- }
1510
- }
1511
- this.isWidthSliderActive = false;
1512
- this.widthDragRectWidth = null;
1513
- this.widthDragStartX = null;
1514
- this.widthDragStartWidth = null;
1515
- this.updateWidthDisplay();
1516
- };
1517
- widthSlider.addEventListener("pointerup", endPointerDrag);
1518
- widthSlider.addEventListener("pointercancel", endPointerDrag);
1519
- widthSlider.addEventListener("lostpointercapture", endPointerDrag);
1520
- widthSlider.addEventListener("keydown", (event) => {
1521
- let handled = true;
1522
- const step = event.shiftKey ? 20 : 10;
1523
- switch (event.key) {
1524
- case "ArrowLeft":
1525
- case "ArrowDown":
1526
- this.applyPanelWidth(this.state.panelWidth - step, true);
1527
- break;
1528
- case "ArrowRight":
1529
- case "ArrowUp":
1530
- this.applyPanelWidth(this.state.panelWidth + step, true);
1531
- break;
1532
- case "Home":
1533
- this.applyPanelWidth(this.minPanelWidth, true);
1534
- break;
1535
- case "End":
1536
- this.applyPanelWidth(this.maxPanelWidth, true);
1537
- break;
1538
- case "PageUp":
1539
- this.applyPanelWidth(this.state.panelWidth + 50, true);
1540
- break;
1541
- case "PageDown":
1542
- this.applyPanelWidth(this.state.panelWidth - 50, true);
1543
- break;
1544
- default:
1545
- handled = false;
1546
- }
1547
- if (handled) {
1548
- event.preventDefault();
1549
- this.updateWidthDisplay();
1550
- }
1551
- });
1552
- }
1553
- /**
1554
- * Update panel width from pointer event
1555
- */
1556
- updateWidthFromPointer(event, resetBaseline = false) {
1557
- if (!this.widthSliderEl) return;
1558
- const sliderWidth = this.widthDragRectWidth || this.widthSliderEl.getBoundingClientRect().width || 1;
1559
- const widthRange = this.maxPanelWidth - this.minPanelWidth;
1560
- let width;
1561
- if (resetBaseline) {
1562
- const rect = this.widthSliderEl.getBoundingClientRect();
1563
- const relative = rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0;
1564
- const clampedRatio = Math.min(1, Math.max(0, relative));
1565
- width = this.minPanelWidth + clampedRatio * widthRange;
1566
- this.widthDragStartWidth = width;
1567
- this.widthDragStartX = event.clientX;
1568
- } else {
1569
- const delta = event.clientX - (this.widthDragStartX || event.clientX);
1570
- width = (this.widthDragStartWidth || this.state.panelWidth) + delta / sliderWidth * widthRange;
1571
- }
1572
- this.applyPanelWidth(width, this.isWidthSliderActive);
1573
- }
1574
1452
  /**
1575
1453
  * Apply panel width (clamped to min/max)
1576
1454
  */
@@ -1582,7 +1460,6 @@ class LayerControl {
1582
1460
  this.state.panelWidth = clamped;
1583
1461
  const px = `${clamped}px`;
1584
1462
  this.panel.style.width = px;
1585
- this.updateWidthDisplay();
1586
1463
  };
1587
1464
  if (immediate) {
1588
1465
  applyWidth();
@@ -1596,34 +1473,6 @@ class LayerControl {
1596
1473
  this.widthFrame = null;
1597
1474
  });
1598
1475
  }
1599
- /**
1600
- * Update width display (value label and thumb position)
1601
- */
1602
- updateWidthDisplay() {
1603
- if (this.widthValueEl) {
1604
- this.widthValueEl.textContent = `${this.state.panelWidth}px`;
1605
- }
1606
- if (this.widthSliderEl) {
1607
- this.widthSliderEl.setAttribute(
1608
- "aria-valuenow",
1609
- String(this.state.panelWidth)
1610
- );
1611
- const ratio = (this.state.panelWidth - this.minPanelWidth) / (this.maxPanelWidth - this.minPanelWidth || 1);
1612
- if (this.widthThumbEl) {
1613
- const sliderWidth = this.widthSliderEl.clientWidth;
1614
- if (sliderWidth === 0) {
1615
- requestAnimationFrame(() => this.updateWidthDisplay());
1616
- return;
1617
- }
1618
- const thumbWidth = this.widthThumbEl.offsetWidth || 14;
1619
- const padding = 16;
1620
- const available = Math.max(0, sliderWidth - padding - thumbWidth);
1621
- const clampedRatio = Math.min(1, Math.max(0, ratio));
1622
- const leftPx = 8 + available * clampedRatio;
1623
- this.widthThumbEl.style.left = `${leftPx}px`;
1624
- }
1625
- }
1626
- }
1627
1476
  /**
1628
1477
  * Setup main event listeners
1629
1478
  */
@@ -1722,7 +1571,21 @@ class LayerControl {
1722
1571
  const existingItems = this.panel.querySelectorAll(".layer-control-item");
1723
1572
  existingItems.forEach((item) => item.remove());
1724
1573
  this.styleEditors.clear();
1725
- Object.entries(this.state.layerStates).forEach(([layerId, state]) => {
1574
+ const orderedLayerIds = this.getUserLayerIdsInMapOrder();
1575
+ const captured = new Set(orderedLayerIds);
1576
+ for (const layerId of Object.keys(this.state.layerStates)) {
1577
+ if (layerId !== "Background" && !captured.has(layerId)) {
1578
+ orderedLayerIds.push(layerId);
1579
+ }
1580
+ }
1581
+ if (this.state.layerStates["Background"]) {
1582
+ orderedLayerIds.push("Background");
1583
+ }
1584
+ orderedLayerIds.forEach((layerId) => {
1585
+ const state = this.state.layerStates[layerId];
1586
+ if (!state) {
1587
+ return;
1588
+ }
1726
1589
  if (this.targetLayers.length === 0 || this.targetLayers.includes(layerId)) {
1727
1590
  this.addLayerItem(layerId, state);
1728
1591
  }
@@ -2503,7 +2366,8 @@ class LayerControl {
2503
2366
  const styleLayers = this.map.getStyle().layers || [];
2504
2367
  return styleLayers.filter((layer) => {
2505
2368
  if (this.isUserAddedLayer(layer.id)) return false;
2506
- if (this.excludeDrawnLayers && this.isDrawnLayer(layer.id)) return false;
2369
+ if (this.excludeDrawnLayers && this.isDrawnLayer(layer.id))
2370
+ return false;
2507
2371
  if (this.isExcludedByPattern(layer.id)) return false;
2508
2372
  return true;
2509
2373
  }).map((layer) => layer.id);
@@ -4320,18 +4184,17 @@ class LayerControl {
4320
4184
  uiLayerIds.push(layerId);
4321
4185
  }
4322
4186
  });
4323
- const reversedIds = [...uiLayerIds].reverse();
4324
4187
  const style = this.map.getStyle();
4325
4188
  const mapLibreLayerIds = new Set(((_a = style == null ? void 0 : style.layers) == null ? void 0 : _a.map((l) => l.id)) || []);
4326
- for (let i = 0; i < reversedIds.length; i++) {
4327
- const layerId = reversedIds[i];
4189
+ for (let i = 0; i < uiLayerIds.length; i++) {
4190
+ const layerId = uiLayerIds[i];
4328
4191
  if (!mapLibreLayerIds.has(layerId)) {
4329
4192
  continue;
4330
4193
  }
4331
4194
  let beforeId = void 0;
4332
4195
  for (let j = i - 1; j >= 0; j--) {
4333
- if (mapLibreLayerIds.has(reversedIds[j])) {
4334
- beforeId = reversedIds[j];
4196
+ if (mapLibreLayerIds.has(uiLayerIds[j])) {
4197
+ beforeId = uiLayerIds[j];
4335
4198
  break;
4336
4199
  }
4337
4200
  }