maplibre-gl-basemap-control 0.3.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 CHANGED
@@ -190,14 +190,50 @@ https://api.mapbox.com/styles/v1/mapbox/{styleId}?access_token={api-key}
190
190
  | `providers` | `BasemapProvider[]` | `[]` | Custom provider labels |
191
191
  | `includeDefaultBasemaps` | `boolean` | `true` | Include the built-in public catalog |
192
192
  | `defaultBasemapId` | `string` | `undefined` | Basemap to apply after the control is added |
193
+ | `allowMultiple` | `boolean` | `false` | Stack raster basemaps instead of replacing the active one |
194
+ | `showMultipleToggle` | `boolean` | `true` | Show the in-panel toggle that switches between adding and replacing |
195
+ | `resizable` | `boolean` | `true` | Allow resizing the panel by dragging its bottom-left or bottom-right corner |
196
+
197
+ ### Multiple Basemaps
198
+
199
+ By default, selecting a basemap replaces the active one. Set `allowMultiple: true`
200
+ to stack raster basemaps instead: each click adds the selected raster basemap as
201
+ an additional overlay, and clicking an already-active raster basemap removes it.
202
+ This lets a project hold several raster basemaps at once and switch between them by
203
+ toggling the entries in the panel.
204
+
205
+ ```typescript
206
+ const control = new BasemapControl({
207
+ allowMultiple: true,
208
+ });
209
+ ```
210
+
211
+ The panel also shows an **Add basemaps** toggle so users can switch between adding
212
+ and replacing at runtime. Hide it with `showMultipleToggle: false`.
213
+
214
+ ### Resizable Panel
215
+
216
+ The panel can be resized by dragging either of its bottom corners (bottom-left or
217
+ bottom-right). The resized width and height are reflected in `state.panelWidth` and
218
+ `state.panelHeight`. Set `resizable: false` to disable the handles.
219
+
220
+ Style basemaps cannot stack because they replace the entire map style, so selecting
221
+ a style basemap always replaces the active basemaps (and clears any stacked raster
222
+ overlays). Use the `before_id` input to control where each raster basemap is
223
+ inserted relative to existing layers.
193
224
 
194
225
  ### Methods
195
226
 
196
227
  - `setBasemap(id)` - Apply a basemap and remove the previous plugin-managed basemap
228
+ - `addBasemap(id)` - Add a raster basemap as an additional overlay (style basemaps replace instead)
229
+ - `removeBasemap(id)` - Remove a previously added managed raster basemap
230
+ - `toggleBasemap(id)` - Add the raster basemap if inactive, otherwise remove it
231
+ - `isBasemapActive(id)` - Whether the basemap is currently active
197
232
  - `setMapTilerApiKey(apiKey)` - Set or update the MapTiler API key used by MapTiler styles
198
233
  - `setAmazonCredentials(apiKey, awsRegion)` - Set or update Amazon Location credentials
199
234
  - `setMapboxAccessToken(accessToken)` - Set or update the Mapbox access token
200
- - `getActiveBasemap()` - Return the current basemap definition
235
+ - `getActiveBasemap()` - Return the most recently selected basemap definition
236
+ - `getActiveBasemaps()` - Return all currently active basemap definitions
201
237
  - `getBasemaps()` - Return the catalog
202
238
  - `setBasemaps(basemaps)` - Replace the catalog
203
239
  - `toggle()`, `expand()`, `collapse()` - Control panel visibility
@@ -211,7 +247,8 @@ before that layer.
211
247
 
212
248
  ### Events
213
249
 
214
- - `basemapchange`
250
+ - `basemapchange` (includes a `mode` of `'replace'` or `'add'`)
251
+ - `basemapremove`
215
252
  - `error`
216
253
  - `collapse`
217
254
  - `expand`
@@ -1101,7 +1101,10 @@ const DEFAULT_OPTIONS = {
1101
1101
  title: "Basemaps",
1102
1102
  panelWidth: 340,
1103
1103
  className: "",
1104
- includeDefaultBasemaps: true
1104
+ includeDefaultBasemaps: true,
1105
+ allowMultiple: false,
1106
+ showMultipleToggle: true,
1107
+ resizable: true
1105
1108
  };
1106
1109
  const CONTROL_SOURCE_PREFIX = "maplibre-basemap-control-source";
1107
1110
  const CONTROL_LAYER_PREFIX = "";
@@ -1110,6 +1113,9 @@ const MAPTILER_API_KEY_EXAMPLE = "YOUR_MAPTILER_API_KEY";
1110
1113
  const MAPTILER_API_KEY_QUERY = "?key";
1111
1114
  const MAPTILER_API_KEY_EMPTY_QUERY = "?key=";
1112
1115
  const AWS_REGION_PLACEHOLDER = "{aws-region}";
1116
+ const MIN_PANEL_WIDTH = 240;
1117
+ const MIN_PANEL_HEIGHT = 200;
1118
+ const PANEL_VIEWPORT_MARGIN = 12;
1113
1119
  class BasemapControl {
1114
1120
  constructor(options) {
1115
1121
  __publicField(this, "_map");
@@ -1122,8 +1128,10 @@ class BasemapControl {
1122
1128
  __publicField(this, "_basemaps");
1123
1129
  __publicField(this, "_providers");
1124
1130
  __publicField(this, "_eventHandlers", new globalThis.Map());
1125
- __publicField(this, "_managedSourceIds", []);
1126
- __publicField(this, "_managedLayerIds", []);
1131
+ // Active managed raster basemaps keyed by basemap id, in insertion order. In
1132
+ // single mode this holds at most one entry; in multiple mode it tracks every
1133
+ // stacked raster overlay so each can be added or removed independently.
1134
+ __publicField(this, "_managedRasters", new globalThis.Map());
1127
1135
  __publicField(this, "_mapTilerApiKey", "");
1128
1136
  __publicField(this, "_amazonApiKey", "");
1129
1137
  __publicField(this, "_awsRegion", "");
@@ -1131,6 +1139,33 @@ class BasemapControl {
1131
1139
  __publicField(this, "_providerSettingsCollapsed", true);
1132
1140
  __publicField(this, "_resizeHandler", null);
1133
1141
  __publicField(this, "_mapResizeHandler", null);
1142
+ __publicField(this, "_resizeAnchor", null);
1143
+ __publicField(this, "_resizeHandleEl", null);
1144
+ __publicField(this, "_onResizeMove", (event) => {
1145
+ if (!this._panel || !this._resizeAnchor) return;
1146
+ const { x, y, isLeft, isTop } = this._resizeAnchor;
1147
+ const bounds = this._getResizeBounds();
1148
+ const rawWidth = isLeft ? event.clientX - x : x - event.clientX;
1149
+ const rawHeight = isTop ? event.clientY - y : y - event.clientY;
1150
+ const width = this._clampSize(rawWidth, MIN_PANEL_WIDTH, bounds.maxWidth);
1151
+ const height = this._clampSize(rawHeight, MIN_PANEL_HEIGHT, bounds.maxHeight);
1152
+ this._panel.style.width = `${width}px`;
1153
+ this._panel.style.height = `${height}px`;
1154
+ this._panel.classList.add("is-resized");
1155
+ this._state = { ...this._state, panelWidth: width, panelHeight: height };
1156
+ this._updatePanelPosition();
1157
+ });
1158
+ __publicField(this, "_onResizeEnd", (event) => {
1159
+ var _a, _b;
1160
+ window.removeEventListener("pointermove", this._onResizeMove);
1161
+ window.removeEventListener("pointerup", this._onResizeEnd);
1162
+ window.removeEventListener("pointercancel", this._onResizeEnd);
1163
+ (_b = (_a = this._resizeHandleEl) == null ? void 0 : _a.releasePointerCapture) == null ? void 0 : _b.call(_a, event.pointerId);
1164
+ this._resizeHandleEl = null;
1165
+ if (!this._resizeAnchor) return;
1166
+ this._resizeAnchor = null;
1167
+ this._emit({ type: "statechange", state: this.getState() });
1168
+ });
1134
1169
  this._options = { ...DEFAULT_OPTIONS, ...options };
1135
1170
  this._mapTilerApiKey = (options == null ? void 0 : options.mapTilerApiKey) ?? "";
1136
1171
  this._amazonApiKey = (options == null ? void 0 : options.amazonApiKey) ?? "";
@@ -1149,6 +1184,8 @@ class BasemapControl {
1149
1184
  collapsed: this._options.collapsed,
1150
1185
  panelWidth: this._options.panelWidth,
1151
1186
  activeBasemapId: this._options.defaultBasemapId,
1187
+ activeBasemapIds: this._options.defaultBasemapId ? [this._options.defaultBasemapId] : [],
1188
+ allowMultiple: this._options.allowMultiple,
1152
1189
  query: "",
1153
1190
  providerFilter: "",
1154
1191
  categoryFilter: "",
@@ -1169,7 +1206,8 @@ class BasemapControl {
1169
1206
  }
1170
1207
  this._renderContent();
1171
1208
  if (this._state.activeBasemapId) {
1172
- this._selectBasemap(this._state.activeBasemapId);
1209
+ this.setBasemap(this._state.activeBasemapId).catch(() => {
1210
+ });
1173
1211
  }
1174
1212
  return this._container;
1175
1213
  }
@@ -1183,6 +1221,11 @@ class BasemapControl {
1183
1221
  this._map.off("resize", this._mapResizeHandler);
1184
1222
  this._mapResizeHandler = null;
1185
1223
  }
1224
+ window.removeEventListener("pointermove", this._onResizeMove);
1225
+ window.removeEventListener("pointerup", this._onResizeEnd);
1226
+ window.removeEventListener("pointercancel", this._onResizeEnd);
1227
+ this._resizeAnchor = null;
1228
+ this._resizeHandleEl = null;
1186
1229
  (_b = (_a = this._panel) == null ? void 0 : _a.parentNode) == null ? void 0 : _b.removeChild(this._panel);
1187
1230
  (_d = (_c = this._container) == null ? void 0 : _c.parentNode) == null ? void 0 : _d.removeChild(this._container);
1188
1231
  this._map = void 0;
@@ -1197,9 +1240,24 @@ class BasemapControl {
1197
1240
  }
1198
1241
  setState(newState) {
1199
1242
  this._state = { ...this._state, ...newState };
1243
+ this._syncActiveBasemapState(newState);
1200
1244
  this._renderContent();
1201
1245
  this._emit({ type: "statechange", state: this.getState() });
1202
1246
  }
1247
+ // Keeps `activeBasemapId` and `activeBasemapIds` consistent when a caller
1248
+ // updates only one of them through setState (the React wrapper and external
1249
+ // consumers commonly set just `activeBasemapId`).
1250
+ _syncActiveBasemapState(applied) {
1251
+ const hasId = "activeBasemapId" in applied;
1252
+ const hasIds = "activeBasemapIds" in applied;
1253
+ if (hasId && !hasIds) {
1254
+ const id = this._state.activeBasemapId;
1255
+ this._state = { ...this._state, activeBasemapIds: id ? [id] : [] };
1256
+ } else if (hasIds && !hasId) {
1257
+ const ids = this._state.activeBasemapIds;
1258
+ this._state = { ...this._state, activeBasemapId: ids[ids.length - 1] };
1259
+ }
1260
+ }
1203
1261
  getBasemaps() {
1204
1262
  return [...this._basemaps];
1205
1263
  }
@@ -1210,9 +1268,14 @@ class BasemapControl {
1210
1268
  this._options.providers,
1211
1269
  this._options.includeDefaultBasemaps
1212
1270
  );
1213
- if (this._state.activeBasemapId && !this._basemaps.some((basemap) => basemap.id === this._state.activeBasemapId)) {
1214
- this._state = { ...this._state, activeBasemapId: void 0 };
1215
- }
1271
+ const activeBasemapIds = this._state.activeBasemapIds.filter(
1272
+ (id) => this._basemaps.some((basemap) => basemap.id === id)
1273
+ );
1274
+ this._state = {
1275
+ ...this._state,
1276
+ activeBasemapIds,
1277
+ activeBasemapId: this._state.activeBasemapId && activeBasemapIds.includes(this._state.activeBasemapId) ? this._state.activeBasemapId : activeBasemapIds[activeBasemapIds.length - 1]
1278
+ };
1216
1279
  this._renderContent();
1217
1280
  this._emit({ type: "statechange", state: this.getState() });
1218
1281
  }
@@ -1238,7 +1301,54 @@ class BasemapControl {
1238
1301
  getActiveBasemap() {
1239
1302
  return this._basemaps.find((basemap) => basemap.id === this._state.activeBasemapId);
1240
1303
  }
1304
+ getActiveBasemaps() {
1305
+ return this._state.activeBasemapIds.map((id) => this._basemaps.find((basemap) => basemap.id === id)).filter((basemap) => Boolean(basemap));
1306
+ }
1307
+ isBasemapActive(id) {
1308
+ return this._state.activeBasemapIds.includes(id);
1309
+ }
1310
+ // Apply a basemap, replacing any previously active basemaps.
1241
1311
  async setBasemap(id) {
1312
+ return this._applyBasemapChange(id, "replace");
1313
+ }
1314
+ // Add a raster basemap as an additional overlay without removing the active
1315
+ // ones. Style basemaps cannot stack, so they always replace the map style.
1316
+ async addBasemap(id) {
1317
+ return this._applyBasemapChange(id, "add");
1318
+ }
1319
+ // Remove a previously added managed raster basemap from the map.
1320
+ async removeBasemap(id) {
1321
+ const basemap = this._basemaps.find((candidate) => candidate.id === id);
1322
+ if (!basemap) {
1323
+ const error = new Error(`Basemap "${id}" was not found.`);
1324
+ this._handleError(error);
1325
+ throw error;
1326
+ }
1327
+ if (!this._map) {
1328
+ const error = new Error("BasemapControl must be added to a map before removing a basemap.");
1329
+ this._handleError(error, basemap);
1330
+ throw error;
1331
+ }
1332
+ const managedRaster = this._removeManagedRaster(id);
1333
+ const activeBasemapIds = this._state.activeBasemapIds.filter((value) => value !== id);
1334
+ this._state = {
1335
+ ...this._state,
1336
+ activeBasemapIds,
1337
+ activeBasemapId: activeBasemapIds[activeBasemapIds.length - 1],
1338
+ error: void 0
1339
+ };
1340
+ this._renderContent(true);
1341
+ this._emit({ type: "basemapremove", state: this.getState(), basemap, managedRaster });
1342
+ this._emit({ type: "statechange", state: this.getState() });
1343
+ }
1344
+ // Add the basemap if it is not active, otherwise remove it.
1345
+ async toggleBasemap(id) {
1346
+ if (this.isBasemapActive(id)) {
1347
+ return this.removeBasemap(id);
1348
+ }
1349
+ return this.addBasemap(id);
1350
+ }
1351
+ async _applyBasemapChange(id, mode) {
1242
1352
  const basemap = this._basemaps.find((candidate) => candidate.id === id);
1243
1353
  if (!basemap) {
1244
1354
  const error = new Error(`Basemap "${id}" was not found.`);
@@ -1250,14 +1360,20 @@ class BasemapControl {
1250
1360
  this._handleError(error, basemap);
1251
1361
  throw error;
1252
1362
  }
1363
+ const isRaster = basemap.source.type === "raster";
1364
+ const effectiveMode = isRaster ? mode : "replace";
1253
1365
  this._state = { ...this._state, loading: true, error: void 0 };
1254
1366
  this._renderContent(true);
1255
1367
  this._emit({ type: "statechange", state: this.getState() });
1256
1368
  try {
1257
1369
  let managedRaster;
1258
- if (basemap.source.type === "raster") {
1370
+ if (isRaster) {
1259
1371
  await this._waitForStyleReady();
1260
- this._removeManagedBasemap();
1372
+ if (effectiveMode === "replace") {
1373
+ this._removeManagedBasemap();
1374
+ } else {
1375
+ this._removeManagedRaster(basemap.id);
1376
+ }
1261
1377
  managedRaster = this._addRasterBasemap(basemap);
1262
1378
  } else {
1263
1379
  const styleUrl = this._resolveStyleUrl(basemap);
@@ -1270,9 +1386,11 @@ class BasemapControl {
1270
1386
  }
1271
1387
  }
1272
1388
  this._applyBasemapView(basemap);
1389
+ const activeBasemapIds = effectiveMode === "add" ? [...this._state.activeBasemapIds.filter((value) => value !== basemap.id), basemap.id] : [basemap.id];
1273
1390
  this._state = {
1274
1391
  ...this._state,
1275
1392
  activeBasemapId: basemap.id,
1393
+ activeBasemapIds,
1276
1394
  loading: false,
1277
1395
  error: void 0
1278
1396
  };
@@ -1281,7 +1399,8 @@ class BasemapControl {
1281
1399
  type: "basemapchange",
1282
1400
  state: this.getState(),
1283
1401
  basemap,
1284
- managedRaster
1402
+ managedRaster,
1403
+ mode: effectiveMode
1285
1404
  });
1286
1405
  this._emit({ type: "statechange", state: this.getState() });
1287
1406
  } catch (cause) {
@@ -1292,10 +1411,18 @@ class BasemapControl {
1292
1411
  throw error;
1293
1412
  }
1294
1413
  }
1295
- // setBasemap rethrows after emitting the error event so callers that await
1296
- // it can react. Internal fire-and-forget callers use this instead to avoid
1414
+ // The public mutators rethrow after emitting the error event so callers that
1415
+ // await them can react. The panel's click handler uses this instead to avoid
1297
1416
  // unhandled promise rejections; the failure is still reported via `error`.
1417
+ // In multiple mode a raster basemap click toggles the overlay on or off.
1298
1418
  _selectBasemap(id) {
1419
+ const basemap = this._basemaps.find((candidate) => candidate.id === id);
1420
+ const isRaster = (basemap == null ? void 0 : basemap.source.type) === "raster";
1421
+ if (this._state.allowMultiple && isRaster) {
1422
+ this.toggleBasemap(id).catch(() => {
1423
+ });
1424
+ return;
1425
+ }
1299
1426
  this.setBasemap(id).catch(() => {
1300
1427
  });
1301
1428
  }
@@ -1367,7 +1494,11 @@ class BasemapControl {
1367
1494
  _createPanel() {
1368
1495
  const panel = document.createElement("div");
1369
1496
  panel.className = "basemap-control-panel";
1370
- panel.style.width = `${this._options.panelWidth}px`;
1497
+ panel.style.width = `${this._state.panelWidth}px`;
1498
+ if (this._state.panelHeight !== void 0) {
1499
+ panel.style.height = `${this._state.panelHeight}px`;
1500
+ panel.classList.add("is-resized");
1501
+ }
1371
1502
  const header = document.createElement("div");
1372
1503
  header.className = "basemap-control-header";
1373
1504
  const title = document.createElement("span");
@@ -1385,8 +1516,19 @@ class BasemapControl {
1385
1516
  this._content.className = "basemap-control-content";
1386
1517
  panel.appendChild(header);
1387
1518
  panel.appendChild(this._content);
1519
+ if (this._options.resizable) {
1520
+ panel.appendChild(this._createResizeHandle("bottom-left"));
1521
+ panel.appendChild(this._createResizeHandle("bottom-right"));
1522
+ }
1388
1523
  return panel;
1389
1524
  }
1525
+ _createResizeHandle(corner) {
1526
+ const handle = document.createElement("div");
1527
+ handle.className = `basemap-control-resize-handle basemap-control-resize-${corner}`;
1528
+ handle.setAttribute("aria-hidden", "true");
1529
+ handle.addEventListener("pointerdown", (event) => this._startResize(event));
1530
+ return handle;
1531
+ }
1390
1532
  _renderContent(preserveResultsScroll = false) {
1391
1533
  var _a;
1392
1534
  if (!this._content) return;
@@ -1395,6 +1537,7 @@ class BasemapControl {
1395
1537
  const previousResultsScrollTop = preserveResultsScroll ? (_a = this._content.querySelector(".basemap-control-results")) == null ? void 0 : _a.scrollTop : void 0;
1396
1538
  this._content.replaceChildren(
1397
1539
  this._createSearchRow(),
1540
+ ...this._options.showMultipleToggle ? [this._createMultipleToggleRow()] : [],
1398
1541
  ...this._hasProviderSettings() ? [this._createProviderSettingsSection()] : [],
1399
1542
  this._createFilterRow(this._providers, categories),
1400
1543
  this._createStatus(results.length),
@@ -1430,6 +1573,25 @@ class BasemapControl {
1430
1573
  row.appendChild(this._createBeforeIdInput());
1431
1574
  return row;
1432
1575
  }
1576
+ _createMultipleToggleRow() {
1577
+ const label = document.createElement("label");
1578
+ label.className = "basemap-control-multiple-toggle";
1579
+ const checkbox = document.createElement("input");
1580
+ checkbox.type = "checkbox";
1581
+ checkbox.className = "basemap-control-multiple-checkbox";
1582
+ checkbox.checked = this._state.allowMultiple;
1583
+ checkbox.setAttribute("aria-label", "Add basemaps instead of replacing");
1584
+ checkbox.addEventListener("change", () => {
1585
+ this._state = { ...this._state, allowMultiple: checkbox.checked };
1586
+ this._emit({ type: "statechange", state: this.getState() });
1587
+ });
1588
+ const text = document.createElement("span");
1589
+ text.className = "basemap-control-multiple-toggle-text";
1590
+ text.textContent = "Add basemaps (stack instead of replace)";
1591
+ label.appendChild(checkbox);
1592
+ label.appendChild(text);
1593
+ return label;
1594
+ }
1433
1595
  _createSearchInput() {
1434
1596
  const wrapper = document.createElement("div");
1435
1597
  wrapper.className = "basemap-control-search";
@@ -1638,7 +1800,7 @@ class BasemapControl {
1638
1800
  row.type = "button";
1639
1801
  row.className = "basemap-control-result";
1640
1802
  row.dataset.basemapId = basemap.id;
1641
- if (basemap.id === this._state.activeBasemapId) {
1803
+ if (this._state.activeBasemapIds.includes(basemap.id)) {
1642
1804
  row.classList.add("is-active");
1643
1805
  row.setAttribute("aria-current", "true");
1644
1806
  }
@@ -1827,13 +1989,9 @@ class BasemapControl {
1827
1989
  };
1828
1990
  this._map.addSource(sourceId, source);
1829
1991
  this._map.addLayer(layer, beforeId);
1830
- this._managedSourceIds = [sourceId];
1831
- this._managedLayerIds = [layerId];
1832
- return {
1833
- sourceId,
1834
- layerId,
1835
- beforeId
1836
- };
1992
+ const managed = { sourceId, layerId, beforeId };
1993
+ this._managedRasters.set(basemap.id, managed);
1994
+ return managed;
1837
1995
  }
1838
1996
  _waitForStyleReady() {
1839
1997
  if (!this._map) return Promise.resolve();
@@ -1842,32 +2000,43 @@ class BasemapControl {
1842
2000
  }
1843
2001
  return new Promise((resolve) => {
1844
2002
  var _a, _b;
2003
+ let settled = false;
2004
+ let timer;
1845
2005
  const done = () => {
1846
2006
  var _a2, _b2;
2007
+ if (settled) return;
2008
+ settled = true;
2009
+ if (timer !== void 0) clearTimeout(timer);
1847
2010
  (_a2 = this._map) == null ? void 0 : _a2.off("load", done);
1848
2011
  (_b2 = this._map) == null ? void 0 : _b2.off("style.load", done);
1849
2012
  resolve();
1850
2013
  };
2014
+ timer = setTimeout(done, 1500);
1851
2015
  (_a = this._map) == null ? void 0 : _a.once("load", done);
1852
2016
  (_b = this._map) == null ? void 0 : _b.once("style.load", done);
1853
2017
  });
1854
2018
  }
2019
+ // Remove every managed raster basemap from the map.
1855
2020
  _removeManagedBasemap() {
1856
2021
  if (!this._map) return;
1857
- [...this._managedLayerIds].reverse().forEach((layerId) => {
1858
- var _a;
1859
- if ((_a = this._map) == null ? void 0 : _a.getLayer(layerId)) {
1860
- this._map.removeLayer(layerId);
1861
- }
1862
- });
1863
- [...this._managedSourceIds].reverse().forEach((sourceId) => {
1864
- var _a;
1865
- if ((_a = this._map) == null ? void 0 : _a.getSource(sourceId)) {
1866
- this._map.removeSource(sourceId);
1867
- }
1868
- });
1869
- this._managedLayerIds = [];
1870
- this._managedSourceIds = [];
2022
+ for (const id of [...this._managedRasters.keys()]) {
2023
+ this._removeManagedRaster(id);
2024
+ }
2025
+ }
2026
+ // Remove a single managed raster basemap by its basemap id, returning the
2027
+ // managed source/layer record that was removed (if any).
2028
+ _removeManagedRaster(id) {
2029
+ var _a, _b;
2030
+ const managed = this._managedRasters.get(id);
2031
+ if (!managed) return void 0;
2032
+ if ((_a = this._map) == null ? void 0 : _a.getLayer(managed.layerId)) {
2033
+ this._map.removeLayer(managed.layerId);
2034
+ }
2035
+ if ((_b = this._map) == null ? void 0 : _b.getSource(managed.sourceId)) {
2036
+ this._map.removeSource(managed.sourceId);
2037
+ }
2038
+ this._managedRasters.delete(id);
2039
+ return managed;
1871
2040
  }
1872
2041
  _applyBasemapView(basemap) {
1873
2042
  if (!this._map || !basemap.view) return;
@@ -1939,6 +2108,44 @@ class BasemapControl {
1939
2108
  break;
1940
2109
  }
1941
2110
  }
2111
+ // Begin a panel resize from a bottom corner. The panel stays pinned to its
2112
+ // toggle button, so resizing is measured from the button-anchored corner:
2113
+ // dragging either bottom handle grows or shrinks the panel away from it.
2114
+ _startResize(event) {
2115
+ var _a;
2116
+ if (!this._panel) return;
2117
+ event.preventDefault();
2118
+ event.stopPropagation();
2119
+ const position = this._getControlPosition();
2120
+ const isLeft = position === "top-left" || position === "bottom-left";
2121
+ const isTop = position === "top-left" || position === "top-right";
2122
+ const rect = this._panel.getBoundingClientRect();
2123
+ this._resizeAnchor = {
2124
+ x: isLeft ? rect.left : rect.right,
2125
+ y: isTop ? rect.top : rect.bottom,
2126
+ isLeft,
2127
+ isTop
2128
+ };
2129
+ const handle = event.currentTarget;
2130
+ this._resizeHandleEl = handle;
2131
+ (_a = handle.setPointerCapture) == null ? void 0 : _a.call(handle, event.pointerId);
2132
+ window.addEventListener("pointermove", this._onResizeMove);
2133
+ window.addEventListener("pointerup", this._onResizeEnd);
2134
+ window.addEventListener("pointercancel", this._onResizeEnd);
2135
+ }
2136
+ _getResizeBounds() {
2137
+ var _a;
2138
+ const mapRect = (_a = this._mapContainer) == null ? void 0 : _a.getBoundingClientRect();
2139
+ const availableWidth = ((mapRect == null ? void 0 : mapRect.width) ?? window.innerWidth) - PANEL_VIEWPORT_MARGIN * 2;
2140
+ const availableHeight = ((mapRect == null ? void 0 : mapRect.height) ?? window.innerHeight) - PANEL_VIEWPORT_MARGIN * 2;
2141
+ return {
2142
+ maxWidth: Math.max(MIN_PANEL_WIDTH, availableWidth),
2143
+ maxHeight: Math.max(MIN_PANEL_HEIGHT, availableHeight)
2144
+ };
2145
+ }
2146
+ _clampSize(value, min, max) {
2147
+ return Math.round(Math.min(Math.max(value, min), max));
2148
+ }
1942
2149
  }
1943
2150
  export {
1944
2151
  BasemapControl as B,
@@ -1950,4 +2157,4 @@ export {
1950
2157
  getBasemapCategories as g,
1951
2158
  resolveBasemapProviders as r
1952
2159
  };
1953
- //# sourceMappingURL=BasemapControl-DD97GXiq.js.map
2160
+ //# sourceMappingURL=BasemapControl-DCusTnvr.js.map