@mapvx/web-js 3.1.0 → 3.2.0-dev.1

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.
Files changed (50) hide show
  1. package/dist/cjs/controllers/markerLayoutController.js +359 -0
  2. package/dist/cjs/controllers/markerLayoutController.js.map +1 -0
  3. package/dist/cjs/controllers/routeController.js +13 -0
  4. package/dist/cjs/controllers/routeController.js.map +1 -1
  5. package/dist/cjs/domain/models/mapConfig.js +14 -1
  6. package/dist/cjs/domain/models/mapConfig.js.map +1 -1
  7. package/dist/cjs/domain/models/marker.js +147 -82
  8. package/dist/cjs/domain/models/marker.js.map +1 -1
  9. package/dist/cjs/index.js +2 -1
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/logger/sentry.js +1 -1
  12. package/dist/cjs/map/map.js +41 -3
  13. package/dist/cjs/map/map.js.map +1 -1
  14. package/dist/cjs/utils/mapvxHostname.js +4 -3
  15. package/dist/cjs/utils/mapvxHostname.js.map +1 -1
  16. package/dist/es/config/sdkConfig.d.ts +1 -1
  17. package/dist/es/controllers/markerLayoutController.d.ts +148 -0
  18. package/dist/es/controllers/markerLayoutController.d.ts.map +1 -0
  19. package/dist/es/controllers/markerLayoutController.js +351 -0
  20. package/dist/es/controllers/markerLayoutController.js.map +1 -0
  21. package/dist/es/controllers/routeController.d.ts +7 -0
  22. package/dist/es/controllers/routeController.d.ts.map +1 -1
  23. package/dist/es/controllers/routeController.js +13 -0
  24. package/dist/es/controllers/routeController.js.map +1 -1
  25. package/dist/es/domain/models/mapConfig.d.ts +96 -0
  26. package/dist/es/domain/models/mapConfig.d.ts.map +1 -1
  27. package/dist/es/domain/models/mapConfig.js +13 -0
  28. package/dist/es/domain/models/mapConfig.js.map +1 -1
  29. package/dist/es/domain/models/marker.d.ts +232 -1
  30. package/dist/es/domain/models/marker.d.ts.map +1 -1
  31. package/dist/es/domain/models/marker.js +146 -82
  32. package/dist/es/domain/models/marker.js.map +1 -1
  33. package/dist/es/index.d.ts +2 -2
  34. package/dist/es/index.d.ts.map +1 -1
  35. package/dist/es/index.js +1 -1
  36. package/dist/es/index.js.map +1 -1
  37. package/dist/es/logger/sentry.js +1 -1
  38. package/dist/es/map/map.d.ts +15 -1
  39. package/dist/es/map/map.d.ts.map +1 -1
  40. package/dist/es/map/map.js +42 -4
  41. package/dist/es/map/map.js.map +1 -1
  42. package/dist/es/utils/mapvxHostname.d.ts +3 -2
  43. package/dist/es/utils/mapvxHostname.d.ts.map +1 -1
  44. package/dist/es/utils/mapvxHostname.js +4 -3
  45. package/dist/es/utils/mapvxHostname.js.map +1 -1
  46. package/dist/umd/index.js +867 -133
  47. package/dist/umd/index.js.map +1 -1
  48. package/dist/umd/styles.css +45 -19
  49. package/dist/umd/styles.css.map +1 -1
  50. package/package.json +1 -1
package/dist/umd/index.js CHANGED
@@ -281,6 +281,7 @@ __webpack_require__.d(__webpack_exports__, {
281
281
  CIRCLE_DEFAULTS: () => (/* reexport */ CIRCLE_DEFAULTS),
282
282
  CacheManager: () => (/* reexport */ CacheManager),
283
283
  DEFAULT_CACHE_CONFIGS: () => (/* reexport */ DEFAULT_CACHE_CONFIGS),
284
+ DEFAULT_MARKER_LAYOUT_CONFIG: () => (/* reexport */ DEFAULT_MARKER_LAYOUT_CONFIG),
284
285
  DEFAULT_MAX_STORAGE_BYTES: () => (/* reexport */ DEFAULT_MAX_STORAGE_BYTES),
285
286
  DEFAULT_TILE_CACHE_CONFIG: () => (/* reexport */ DEFAULT_TILE_CACHE_CONFIG),
286
287
  FetchHttpClient: () => (/* reexport */ FetchHttpClient),
@@ -11239,7 +11240,7 @@ const extractSafariExtensionDetails = (func, filename) => {
11239
11240
  // exists only for the type-checker, so in the ES/CJS builds the identifier is
11240
11241
  // genuinely absent and a direct comparison throws ReferenceError at module load.
11241
11242
  // `typeof` is the only safe way to probe a possibly-undeclared global.
11242
- var sentry_SDK_VERSION = false ? 0 : "3.1.0";
11243
+ var sentry_SDK_VERSION = false ? 0 : "3.2.0-dev.1";
11243
11244
  var DSN = false ? 0 : "https://872f598f020e3cd73a0933b562d24c81@sentry.mapvx.app/5";
11244
11245
 
11245
11246
  /**
@@ -11875,6 +11876,10 @@ function getBoundingBox(points) {
11875
11876
  return [southwest, northeast];
11876
11877
  }
11877
11878
  ;// ./src/domain/models/marker.ts
11879
+ function marker_typeof(o) { "@babel/helpers - typeof"; return marker_typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, marker_typeof(o); }
11880
+ function marker_ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
11881
+ function marker_objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? marker_ownKeys(Object(t), !0).forEach(function (r) { marker_defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : marker_ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
11882
+ function marker_defineProperty(e, r, t) { return (r = marker_toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
11878
11883
  function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
11879
11884
  function _possibleConstructorReturn(t, e) { if (e && ("object" == marker_typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); }
11880
11885
  function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; }
@@ -11882,7 +11887,6 @@ function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.
11882
11887
  function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }
11883
11888
  function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); }
11884
11889
  function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }
11885
- function marker_typeof(o) { "@babel/helpers - typeof"; return marker_typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, marker_typeof(o); }
11886
11890
  function marker_classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
11887
11891
  function marker_defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, marker_toPropertyKey(o.key), o); } }
11888
11892
  function marker_createClass(e, r, t) { return r && marker_defineProperties(e.prototype, r), t && marker_defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
@@ -11917,6 +11921,22 @@ var TextPosition = /*#__PURE__*/function (TextPosition) {
11917
11921
  return TextPosition;
11918
11922
  }({});
11919
11923
 
11924
+ /**
11925
+ * Fallback icon width/height, in pixels, used whenever a marker is created
11926
+ * without explicit `iconProperties`.
11927
+ *
11928
+ * @group Markers
11929
+ */
11930
+ var DEFAULT_ICON_SIZE_PX = 40;
11931
+
11932
+ /**
11933
+ * Fallback maximum label width, in pixels, used whenever neither the marker
11934
+ * nor the map's `markerLayout` configuration specifies one.
11935
+ *
11936
+ * @group Markers
11937
+ */
11938
+ var DEFAULT_MAX_LABEL_WIDTH_PX = 200;
11939
+
11920
11940
  /**
11921
11941
  * Alignment options for marker rotation and pitch behavior.
11922
11942
  * Controls how the marker responds to map rotation and pitch changes.
@@ -11961,6 +11981,7 @@ var TextPosition = /*#__PURE__*/function (TextPosition) {
11961
11981
  *
11962
11982
  * @group Markers
11963
11983
  */
11984
+
11964
11985
  /**
11965
11986
  * Utility class for creating and manipulating marker elements on a map.
11966
11987
  * Provides static methods for marker element creation and styling.
@@ -11990,75 +12011,55 @@ var MarkerUtils = /*#__PURE__*/function () {
11990
12011
  var markerContainer = document.createElement("div");
11991
12012
 
11992
12013
  // Configure specific marker dimensions
11993
- var iconWidth = (_marker$iconPropertie = (_marker$iconPropertie2 = marker.iconProperties) === null || _marker$iconPropertie2 === void 0 ? void 0 : _marker$iconPropertie2.width) !== null && _marker$iconPropertie !== void 0 ? _marker$iconPropertie : 40;
11994
- var iconHeight = (_marker$iconPropertie3 = (_marker$iconPropertie4 = marker.iconProperties) === null || _marker$iconPropertie4 === void 0 ? void 0 : _marker$iconPropertie4.height) !== null && _marker$iconPropertie3 !== void 0 ? _marker$iconPropertie3 : 40;
12014
+ var iconWidth = (_marker$iconPropertie = (_marker$iconPropertie2 = marker.iconProperties) === null || _marker$iconPropertie2 === void 0 ? void 0 : _marker$iconPropertie2.width) !== null && _marker$iconPropertie !== void 0 ? _marker$iconPropertie : DEFAULT_ICON_SIZE_PX;
12015
+ var iconHeight = (_marker$iconPropertie3 = (_marker$iconPropertie4 = marker.iconProperties) === null || _marker$iconPropertie4 === void 0 ? void 0 : _marker$iconPropertie4.height) !== null && _marker$iconPropertie3 !== void 0 ? _marker$iconPropertie3 : DEFAULT_ICON_SIZE_PX;
11995
12016
 
11996
- // Apply base class
12017
+ // Apply base class. The container's own box is always the icon's box (or
12018
+ // the custom `element`'s natural box) — the label is positioned
12019
+ // absolutely (see .text-container in styles.css) so it never affects
12020
+ // this box, which is exactly what MapLibre anchors the marker to.
11997
12021
  markerContainer.className = "mapvx-marker";
11998
-
11999
- // Configure classes and dimensions based on text presence
12000
- if (marker.text) {
12001
- // If text exists, adjust layout according to position
12002
- switch (marker.textPosition) {
12003
- case TextPosition.top:
12004
- case TextPosition.bottom:
12005
- markerContainer.classList.add("text-column");
12006
- markerContainer.style.width = "".concat(Math.max(iconWidth, 200), "px");
12007
- markerContainer.style.height = "auto";
12008
- break;
12009
- case TextPosition.left:
12010
- case TextPosition.right:
12011
- default:
12012
- markerContainer.classList.add("text-row");
12013
- markerContainer.style.width = "auto";
12014
- markerContainer.style.height = "".concat(iconHeight, "px");
12015
- break;
12016
- }
12017
- } else {
12018
- // No text, use fixed icon dimensions as in official example
12019
- markerContainer.classList.add("no-text");
12020
- markerContainer.style.width = "".concat(iconWidth, "px");
12021
- markerContainer.style.height = "".concat(iconHeight, "px");
12022
- }
12023
12022
  markerContainer.addEventListener("click", function () {
12024
12023
  var _marker$onClick;
12025
12024
  (_marker$onClick = marker.onClick) === null || _marker$onClick === void 0 || _marker$onClick.call(marker);
12026
12025
  });
12026
+
12027
+ // Custom element replaces icon entirely; the label (if any) still gets
12028
+ // appended, positioned absolutely relative to it.
12027
12029
  if (marker.element) {
12030
+ // Only force a box when the caller declared the footprint. Otherwise
12031
+ // the container is left to size itself to the custom element, and
12032
+ // automatic label collision avoidance measures that real box from the
12033
+ // DOM instead of assuming the default icon size — see
12034
+ // {@link MarkerAttribute.measureIconFromDom}.
12035
+ if (marker.iconProperties) {
12036
+ markerContainer.style.width = "".concat(iconWidth, "px");
12037
+ markerContainer.style.height = "".concat(iconHeight, "px");
12038
+ }
12028
12039
  markerContainer.appendChild(marker.element);
12029
- return this.setAndCreateText(marker, markerContainer, "".concat(iconHeight, "px"), "".concat(iconWidth, "px"));
12040
+ return this.setAndCreateText(marker, markerContainer);
12030
12041
  }
12031
-
12032
- // Create elements in correct order according to textPosition
12042
+ markerContainer.style.width = "".concat(iconWidth, "px");
12043
+ markerContainer.style.height = "".concat(iconHeight, "px");
12033
12044
  var iconElement = this.createIconElement(marker, iconWidth, iconHeight);
12034
- var textElement = marker.text ? this.createTextContainer(typeof marker.text === "string" ? this.createTextElement(marker.text, marker.textProperties) : marker.text, marker.textPosition) : null;
12035
-
12036
- // Add elements in correct order
12037
- if (marker.textPosition === TextPosition.top || marker.textPosition === TextPosition.left) {
12038
- if (textElement) markerContainer.appendChild(textElement);
12039
- markerContainer.appendChild(iconElement);
12040
- } else {
12041
- markerContainer.appendChild(iconElement);
12042
- if (textElement) markerContainer.appendChild(textElement);
12043
- }
12044
- return markerContainer;
12045
+ markerContainer.appendChild(iconElement);
12046
+ return this.setAndCreateText(marker, markerContainer);
12045
12047
  }
12046
12048
 
12047
12049
  /**
12048
- * Sets up text positioning and styling for the marker.
12050
+ * Appends the marker's text label (if any) to the container, positioned
12051
+ * around whichever element (icon or custom `element`) is already there.
12049
12052
  *
12050
12053
  * @param markerConfig - Configuration object containing text properties
12051
- * @param container - Container element where text will be added
12052
- * @param iconHeight - Height of the icon for positioning calculations
12053
- * @param iconWidth - Width of the icon for positioning calculations
12054
- * @returns Container element with properly positioned text
12054
+ * @param container - Container element to append the label to
12055
+ * @returns The same container, with the label appended when text is present
12055
12056
  */
12056
12057
  }, {
12057
12058
  key: "setAndCreateText",
12058
- value: function setAndCreateText(markerConfig, container, iconHeight, iconWidth) {
12059
+ value: function setAndCreateText(markerConfig, container) {
12059
12060
  if (markerConfig.text) {
12060
12061
  var textElement = typeof markerConfig.text === "string" ? this.createTextElement(markerConfig.text, markerConfig.textProperties) : markerConfig.text;
12061
- var textContainer = this.createTextContainer(textElement, markerConfig.textPosition);
12062
+ var textContainer = this.createTextContainer(textElement, markerConfig.textPosition, markerConfig.maxLabelWidthPx);
12062
12063
  container.appendChild(textContainer);
12063
12064
  }
12064
12065
  return container;
@@ -12094,43 +12095,75 @@ var MarkerUtils = /*#__PURE__*/function () {
12094
12095
  }
12095
12096
 
12096
12097
  /**
12097
- * Creates a text container with proper positioning using CSS classes.
12098
- * Uses natural block/flex layout without absolute positioning.
12098
+ * Creates a text container, absolutely positioned around the marker's icon
12099
+ * via CSS (see `.text-container` in styles.css), so the label never affects
12100
+ * the container's own box.
12099
12101
  *
12100
12102
  * @param textElement - The text element to contain
12101
12103
  * @param textPosition - Desired position relative to the icon
12104
+ * @param maxLabelWidthPx - Maximum label width in pixels before truncation
12102
12105
  * @returns Container with text positioned using CSS classes
12103
12106
  */
12104
12107
  }, {
12105
12108
  key: "createTextContainer",
12106
- value: function createTextContainer(textElement, textPosition) {
12109
+ value: function createTextContainer(textElement, textPosition, maxLabelWidthPx) {
12107
12110
  var textContainer = document.createElement("div");
12108
12111
 
12109
12112
  // Apply base class
12110
12113
  textContainer.className = "text-container";
12111
-
12112
- // Apply position class
12113
- switch (textPosition) {
12114
- case TextPosition.top:
12115
- textContainer.classList.add("top");
12116
- break;
12117
- case TextPosition.bottom:
12118
- textContainer.classList.add("bottom");
12119
- break;
12120
- case TextPosition.left:
12121
- textContainer.classList.add("left");
12122
- break;
12123
- default:
12124
- // right
12125
- textContainer.classList.add("right");
12126
- break;
12127
- }
12114
+ textContainer.style.maxWidth = "".concat(maxLabelWidthPx !== null && maxLabelWidthPx !== void 0 ? maxLabelWidthPx : DEFAULT_MAX_LABEL_WIDTH_PX, "px");
12115
+ this.setTextPositionClass(textContainer, this.resolveTextPosition(textPosition));
12128
12116
  textElement.style.display = "block";
12129
12117
  textElement.style.margin = "0";
12130
12118
  textContainer.appendChild(textElement);
12131
12119
  return textContainer;
12132
12120
  }
12133
12121
 
12122
+ /**
12123
+ * Resolves an optional {@link TextPosition} to its default (`right`), the
12124
+ * same default `createTextContainer` has always applied.
12125
+ */
12126
+ }, {
12127
+ key: "resolveTextPosition",
12128
+ value: function resolveTextPosition(textPosition) {
12129
+ return textPosition !== null && textPosition !== void 0 ? textPosition : TextPosition.right;
12130
+ }
12131
+
12132
+ /**
12133
+ * Swaps a text container's position class, keeping exactly one of
12134
+ * `top|bottom|left|right` applied at a time.
12135
+ */
12136
+ }, {
12137
+ key: "setTextPositionClass",
12138
+ value: function setTextPositionClass(textContainer, position) {
12139
+ textContainer.classList.remove("top", "bottom", "left", "right");
12140
+ textContainer.classList.add(TextPosition[position]);
12141
+ }
12142
+
12143
+ /**
12144
+ * Repositions (or hides) a marker's already-rendered text label at
12145
+ * runtime, without rebuilding any DOM. Used both by automatic collision
12146
+ * avoidance and by anything else that needs to move a label after
12147
+ * creation.
12148
+ *
12149
+ * @param textContainer - The `.text-container` element to reposition
12150
+ * @param position - Side of the icon the label should render on
12151
+ * @param hidden - Whether the label should be hidden (kept measurable via
12152
+ * `visibility`, never removed via `display`, so it can be measured again
12153
+ * to decide whether it fits once something else changes)
12154
+ *
12155
+ * @example
12156
+ * ```typescript
12157
+ * MarkerUtils.applyTextPosition(textEl, TextPosition.left, false);
12158
+ * ```
12159
+ */
12160
+ }, {
12161
+ key: "applyTextPosition",
12162
+ value: function applyTextPosition(textContainer, position, hidden) {
12163
+ this.setTextPositionClass(textContainer, position);
12164
+ textContainer.style.visibility = hidden ? "hidden" : "";
12165
+ }
12166
+
12134
12167
  /**
12135
12168
  * Creates and returns an icon element for the marker, using the image URL
12136
12169
  * specified in the `MarkerConfig` object.
@@ -12150,8 +12183,8 @@ var MarkerUtils = /*#__PURE__*/function () {
12150
12183
  var _marker$iconPropertie5, _marker$iconPropertie6, _marker$iconPropertie7, _marker$iconPropertie8;
12151
12184
  var image = document.createElement("img");
12152
12185
  image.src = typeof marker.icon == "string" ? marker.icon : "";
12153
- image.width = (_marker$iconPropertie5 = (_marker$iconPropertie6 = marker.iconProperties) === null || _marker$iconPropertie6 === void 0 ? void 0 : _marker$iconPropertie6.width) !== null && _marker$iconPropertie5 !== void 0 ? _marker$iconPropertie5 : 40;
12154
- image.height = (_marker$iconPropertie7 = (_marker$iconPropertie8 = marker.iconProperties) === null || _marker$iconPropertie8 === void 0 ? void 0 : _marker$iconPropertie8.height) !== null && _marker$iconPropertie7 !== void 0 ? _marker$iconPropertie7 : 40;
12186
+ image.width = (_marker$iconPropertie5 = (_marker$iconPropertie6 = marker.iconProperties) === null || _marker$iconPropertie6 === void 0 ? void 0 : _marker$iconPropertie6.width) !== null && _marker$iconPropertie5 !== void 0 ? _marker$iconPropertie5 : DEFAULT_ICON_SIZE_PX;
12187
+ image.height = (_marker$iconPropertie7 = (_marker$iconPropertie8 = marker.iconProperties) === null || _marker$iconPropertie8 === void 0 ? void 0 : _marker$iconPropertie8.height) !== null && _marker$iconPropertie7 !== void 0 ? _marker$iconPropertie7 : DEFAULT_ICON_SIZE_PX;
12155
12188
  image.style.objectFit = "contain";
12156
12189
  image.style.filter = "drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25))";
12157
12190
  return image;
@@ -12190,6 +12223,7 @@ var MarkerUtils = /*#__PURE__*/function () {
12190
12223
  }
12191
12224
  }]);
12192
12225
  }();
12226
+
12193
12227
  /**
12194
12228
  * Custom implementation of a MapLibre Marker with additional functionality.
12195
12229
  * Extends the base MapLibre Marker class with MapVX-specific features.
@@ -12220,6 +12254,56 @@ var MarkerAttribute = /*#__PURE__*/function (_Marker) {
12220
12254
  * Used for indoor navigation and floor-specific marker management.
12221
12255
  */
12222
12256
 
12257
+ /**
12258
+ * Resolved icon dimensions (defaults to {@link DEFAULT_ICON_SIZE_PX} square
12259
+ * when unspecified), matching whatever `MarkerUtils.createMarkerElement`
12260
+ * rendered. Used by automatic label collision avoidance to compute the
12261
+ * icon's on-screen rect without a DOM measurement — except when
12262
+ * {@link measureIconFromDom} is set.
12263
+ * @internal
12264
+ */
12265
+
12266
+ /**
12267
+ * The `PositionAnchor` this marker was created with, needed by automatic
12268
+ * label collision avoidance to translate a screen point into the icon's
12269
+ * on-screen rect the same way MapLibre itself does.
12270
+ * @internal
12271
+ */
12272
+
12273
+ /**
12274
+ * The marker's preferred text position, used as the first candidate by
12275
+ * automatic label collision avoidance before it tries the other sides.
12276
+ * @internal
12277
+ */
12278
+
12279
+ /**
12280
+ * Priority used to resolve label collisions; see
12281
+ * {@link MarkerConfig.labelPriority}.
12282
+ * @internal
12283
+ */
12284
+
12285
+ /**
12286
+ * Cached reference to this marker's `.text-container` element, if it has
12287
+ * a text label. Avoids querying the DOM on every layout pass.
12288
+ * @internal
12289
+ */
12290
+
12291
+ /**
12292
+ * True when the marker renders a caller-supplied `element` and no
12293
+ * `iconProperties` declared its footprint. In that case {@link iconSize}
12294
+ * is only a fallback and automatic label collision avoidance measures the
12295
+ * marker element's real box from the DOM instead.
12296
+ * @internal
12297
+ */
12298
+
12299
+ /**
12300
+ * The config this marker currently renders from. {@link updateIcon} merges
12301
+ * its (usually partial) config on top of this one, so an update that omits
12302
+ * `iconProperties`, `textPosition` or `labelPriority` keeps whatever the
12303
+ * marker already had instead of silently snapping back to the SDK defaults
12304
+ * — both in the DOM it rebuilds and in the layout state derived from it.
12305
+ */
12306
+
12223
12307
  /**
12224
12308
  * Constructs a new `MarkerAttribute` object with the specified properties.
12225
12309
  *
@@ -12236,7 +12320,7 @@ var MarkerAttribute = /*#__PURE__*/function (_Marker) {
12236
12320
  * ```
12237
12321
  */
12238
12322
  function MarkerAttribute(markerConfig) {
12239
- var _markerConfig$anchor, _markerConfig$rotatio, _markerConfig$id, _markerConfig$floorId;
12323
+ var _markerConfig$anchor, _markerConfig$rotatio, _markerConfig$id, _markerConfig$floorId, _markerConfig$anchor2;
12240
12324
  var _this;
12241
12325
  marker_classCallCheck(this, MarkerAttribute);
12242
12326
  _this = _callSuper(this, MarkerAttribute, [{
@@ -12254,22 +12338,78 @@ var MarkerAttribute = /*#__PURE__*/function (_Marker) {
12254
12338
  _this.id = (_markerConfig$id = markerConfig.id) !== null && _markerConfig$id !== void 0 ? _markerConfig$id : generateHexadecimalKey();
12255
12339
  _this.coordinate = markerConfig.coordinate;
12256
12340
  _this.floorId = (_markerConfig$floorId = markerConfig.floorId) !== null && _markerConfig$floorId !== void 0 ? _markerConfig$floorId : "";
12341
+ // MapLibre resolves the anchor once, in the `super()` call above, and
12342
+ // offers no way to change it afterwards — so this mirror is set here and
12343
+ // never touched by `updateIcon`.
12344
+ _this.anchorType = (_markerConfig$anchor2 = markerConfig.anchor) !== null && _markerConfig$anchor2 !== void 0 ? _markerConfig$anchor2 : "center";
12345
+ _this.currentConfig = markerConfig;
12346
+ _this.applyLayoutState(markerConfig);
12257
12347
  return _this;
12258
12348
  }
12259
12349
 
12260
12350
  /**
12261
- * Adds the marker attribute to the specified map.
12262
- *
12263
- * @param map - The map instance where the marker attribute will be added
12264
- *
12265
- * @example
12266
- * ```typescript
12267
- * const marker = new MarkerAttribute(markerConfig);
12268
- * marker.addToMap(mapInstance);
12269
- * ```
12351
+ * Derives the state automatic label collision avoidance reads (icon box,
12352
+ * preferred label side, priority, label element) from a fully-resolved
12353
+ * config. It is what assigns the fields declared with `!` above — the
12354
+ * constructor always calls it, but TypeScript can't see through the
12355
+ * indirection. Shared by the constructor and {@link updateIcon} so the two can
12356
+ * never drift apart.
12270
12357
  */
12271
12358
  _inherits(MarkerAttribute, _Marker);
12272
12359
  return marker_createClass(MarkerAttribute, [{
12360
+ key: "applyLayoutState",
12361
+ value: function applyLayoutState(config) {
12362
+ var _config$iconPropertie, _config$iconPropertie2, _config$iconPropertie3, _config$iconPropertie4, _config$labelPriority;
12363
+ this.iconSize = {
12364
+ width: (_config$iconPropertie = (_config$iconPropertie2 = config.iconProperties) === null || _config$iconPropertie2 === void 0 ? void 0 : _config$iconPropertie2.width) !== null && _config$iconPropertie !== void 0 ? _config$iconPropertie : DEFAULT_ICON_SIZE_PX,
12365
+ height: (_config$iconPropertie3 = (_config$iconPropertie4 = config.iconProperties) === null || _config$iconPropertie4 === void 0 ? void 0 : _config$iconPropertie4.height) !== null && _config$iconPropertie3 !== void 0 ? _config$iconPropertie3 : DEFAULT_ICON_SIZE_PX
12366
+ };
12367
+ this.preferredTextPosition = MarkerUtils.resolveTextPosition(config.textPosition);
12368
+ this.labelPriority = (_config$labelPriority = config.labelPriority) !== null && _config$labelPriority !== void 0 ? _config$labelPriority : 0;
12369
+ this.measureIconFromDom = config.element != null && config.iconProperties == null;
12370
+ this.textContainerEl = this.findTextContainerEl();
12371
+ }
12372
+
12373
+ /**
12374
+ * Merges an update on top of the marker's current config, so a partial
12375
+ * update such as `{ id, coordinate, icon }` keeps the marker's existing
12376
+ * icon dimensions, label side and priority.
12377
+ *
12378
+ * `element` is deliberately *not* carried forward: it and `icon` are
12379
+ * mutually exclusive rendering paths, and `updateIcon` has always chosen
12380
+ * the path named by the update itself.
12381
+ */
12382
+ }, {
12383
+ key: "mergeConfig",
12384
+ value: function mergeConfig(update) {
12385
+ return marker_objectSpread(marker_objectSpread(marker_objectSpread({}, this.currentConfig), update), {}, {
12386
+ element: update.element
12387
+ });
12388
+ }
12389
+
12390
+ /**
12391
+ * Finds this marker's `.text-container` child element, if it has one.
12392
+ */
12393
+ }, {
12394
+ key: "findTextContainerEl",
12395
+ value: function findTextContainerEl() {
12396
+ return Array.from(this._element.children).find(function (child) {
12397
+ return child instanceof HTMLElement && child.classList.contains("text-container");
12398
+ });
12399
+ }
12400
+
12401
+ /**
12402
+ * Adds the marker attribute to the specified map.
12403
+ *
12404
+ * @param map - The map instance where the marker attribute will be added
12405
+ *
12406
+ * @example
12407
+ * ```typescript
12408
+ * const marker = new MarkerAttribute(markerConfig);
12409
+ * marker.addToMap(mapInstance);
12410
+ * ```
12411
+ */
12412
+ }, {
12273
12413
  key: "addToMap",
12274
12414
  value: function addToMap(map) {
12275
12415
  if (!map) return;
@@ -12279,6 +12419,11 @@ var MarkerAttribute = /*#__PURE__*/function (_Marker) {
12279
12419
  /**
12280
12420
  * Updates the icon of the marker attribute based on the specified marker configuration.
12281
12421
  *
12422
+ * The config is merged on top of the one the marker currently renders
12423
+ * from, so a partial update keeps every property it omits — passing only
12424
+ * `{ icon }` no longer resets the marker's icon dimensions, preferred
12425
+ * label side or label priority to their defaults.
12426
+ *
12282
12427
  * @param marker - `MarkerConfig` object containing the new marker configuration
12283
12428
  *
12284
12429
  * @example
@@ -12292,12 +12437,13 @@ var MarkerAttribute = /*#__PURE__*/function (_Marker) {
12292
12437
  }, {
12293
12438
  key: "updateIcon",
12294
12439
  value: function updateIcon(marker) {
12295
- var _marker$rotationAlign;
12296
- if (marker.element) {
12440
+ var _config$rotationAlign;
12441
+ var config = this.mergeConfig(marker);
12442
+ if (config.element) {
12297
12443
  this._element.innerHTML = "";
12298
- this._element.appendChild(marker.element);
12444
+ this._element.appendChild(config.element);
12299
12445
  } else {
12300
- var markerElement = MarkerUtils.createMarkerElement(marker);
12446
+ var markerElement = MarkerUtils.createMarkerElement(config);
12301
12447
  var oldMarkerContent = this._element.querySelector("div");
12302
12448
  var newMarkerContent = markerElement.querySelector("div");
12303
12449
  if (newMarkerContent) {
@@ -12305,11 +12451,13 @@ var MarkerAttribute = /*#__PURE__*/function (_Marker) {
12305
12451
  oldMarkerContent === null || oldMarkerContent === void 0 || (_oldMarkerContent$par = oldMarkerContent.parentElement) === null || _oldMarkerContent$par === void 0 || _oldMarkerContent$par.replaceChild(newMarkerContent, oldMarkerContent);
12306
12452
  }
12307
12453
  }
12308
- this.setRotationAlignment((_marker$rotationAlign = marker.rotationAlignment) !== null && _marker$rotationAlign !== void 0 ? _marker$rotationAlign : "viewport");
12454
+ this.setRotationAlignment((_config$rotationAlign = config.rotationAlignment) !== null && _config$rotationAlign !== void 0 ? _config$rotationAlign : "viewport");
12309
12455
  this.setPitchAlignment("viewport");
12310
- if (typeof marker.rotation === "number") {
12311
- this.setRotation(marker.rotation);
12456
+ if (typeof config.rotation === "number") {
12457
+ this.setRotation(config.rotation);
12312
12458
  }
12459
+ this.currentConfig = config;
12460
+ this.applyLayoutState(config);
12313
12461
  }
12314
12462
 
12315
12463
  /**
@@ -12386,6 +12534,485 @@ var MarkerAttribute = /*#__PURE__*/function (_Marker) {
12386
12534
  }
12387
12535
  }]);
12388
12536
  }(maplibre_gl.Marker);
12537
+ ;// ./src/controllers/markerLayoutController.ts
12538
+ function markerLayoutController_typeof(o) { "@babel/helpers - typeof"; return markerLayoutController_typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, markerLayoutController_typeof(o); }
12539
+ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || markerLayoutController_unsupportedIterableToArray(r) || _nonIterableSpread(); }
12540
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
12541
+ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
12542
+ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return markerLayoutController_arrayLikeToArray(r); }
12543
+ function markerLayoutController_createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = markerLayoutController_unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
12544
+ function markerLayoutController_unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return markerLayoutController_arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? markerLayoutController_arrayLikeToArray(r, a) : void 0; } }
12545
+ function markerLayoutController_arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
12546
+ function markerLayoutController_classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
12547
+ function markerLayoutController_defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, markerLayoutController_toPropertyKey(o.key), o); } }
12548
+ function markerLayoutController_createClass(e, r, t) { return r && markerLayoutController_defineProperties(e.prototype, r), t && markerLayoutController_defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
12549
+ function markerLayoutController_defineProperty(e, r, t) { return (r = markerLayoutController_toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
12550
+ function markerLayoutController_toPropertyKey(t) { var i = markerLayoutController_toPrimitive(t, "string"); return "symbol" == markerLayoutController_typeof(i) ? i : i + ""; }
12551
+ function markerLayoutController_toPrimitive(t, r) { if ("object" != markerLayoutController_typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != markerLayoutController_typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
12552
+
12553
+
12554
+ /** Axis-aligned box in screen (CSS pixel) space. */
12555
+
12556
+ /**
12557
+ * Fraction of the icon's own box, along each axis, that MapLibre's Marker
12558
+ * anchor math (`translate(-x%, -y%)`) aligns to the geodetic point. Mirrors
12559
+ * MapLibre's own `anchorTranslate` table so the collision math agrees with
12560
+ * what's actually on screen.
12561
+ */
12562
+ var ANCHOR_FRACTIONS = {
12563
+ center: {
12564
+ fx: 0.5,
12565
+ fy: 0.5
12566
+ },
12567
+ top: {
12568
+ fx: 0.5,
12569
+ fy: 0
12570
+ },
12571
+ bottom: {
12572
+ fx: 0.5,
12573
+ fy: 1
12574
+ },
12575
+ left: {
12576
+ fx: 0,
12577
+ fy: 0.5
12578
+ },
12579
+ right: {
12580
+ fx: 1,
12581
+ fy: 0.5
12582
+ },
12583
+ "top-left": {
12584
+ fx: 0,
12585
+ fy: 0
12586
+ },
12587
+ "top-right": {
12588
+ fx: 1,
12589
+ fy: 0
12590
+ },
12591
+ "bottom-left": {
12592
+ fx: 0,
12593
+ fy: 1
12594
+ },
12595
+ "bottom-right": {
12596
+ fx: 1,
12597
+ fy: 1
12598
+ }
12599
+ };
12600
+
12601
+ /** Candidate order tried when a marker's preferred side is unavailable. */
12602
+ var FALLBACK_ORDER = [TextPosition.right, TextPosition.top, TextPosition.left, TextPosition.bottom];
12603
+
12604
+ /** Builds the icon's on-screen rect from its projected point, size and anchor. */
12605
+ function computeIconRect(screen, iconSize, anchor) {
12606
+ var _ANCHOR_FRACTIONS$anc;
12607
+ var _ref = (_ANCHOR_FRACTIONS$anc = ANCHOR_FRACTIONS[anchor]) !== null && _ANCHOR_FRACTIONS$anc !== void 0 ? _ANCHOR_FRACTIONS$anc : ANCHOR_FRACTIONS.center,
12608
+ fx = _ref.fx,
12609
+ fy = _ref.fy;
12610
+ return {
12611
+ left: screen.x - fx * iconSize.width,
12612
+ top: screen.y - fy * iconSize.height,
12613
+ width: iconSize.width,
12614
+ height: iconSize.height
12615
+ };
12616
+ }
12617
+
12618
+ /**
12619
+ * Builds a candidate label rect for one side of an icon. Mirrors the CSS in
12620
+ * `styles.css` (`.text-container.right/left/top/bottom`) exactly, so a
12621
+ * position the algorithm picks as collision-free renders collision-free.
12622
+ */
12623
+ function computeCandidateRect(iconRect, labelSize, position, gapPx, verticalGapPx) {
12624
+ switch (position) {
12625
+ case TextPosition.left:
12626
+ return {
12627
+ left: iconRect.left - gapPx - labelSize.width,
12628
+ top: iconRect.top + iconRect.height / 2 - labelSize.height / 2,
12629
+ width: labelSize.width,
12630
+ height: labelSize.height
12631
+ };
12632
+ case TextPosition.top:
12633
+ return {
12634
+ left: iconRect.left + iconRect.width / 2 - labelSize.width / 2,
12635
+ top: iconRect.top - verticalGapPx - labelSize.height,
12636
+ width: labelSize.width,
12637
+ height: labelSize.height
12638
+ };
12639
+ case TextPosition.bottom:
12640
+ return {
12641
+ left: iconRect.left + iconRect.width / 2 - labelSize.width / 2,
12642
+ top: iconRect.top + iconRect.height + verticalGapPx,
12643
+ width: labelSize.width,
12644
+ height: labelSize.height
12645
+ };
12646
+ case TextPosition.right:
12647
+ default:
12648
+ return {
12649
+ left: iconRect.left + iconRect.width + gapPx,
12650
+ top: iconRect.top + iconRect.height / 2 - labelSize.height / 2,
12651
+ width: labelSize.width,
12652
+ height: labelSize.height
12653
+ };
12654
+ }
12655
+ }
12656
+
12657
+ /** Expands a rect by `padding` on every side. */
12658
+ function expandRect(rect, padding) {
12659
+ return {
12660
+ left: rect.left - padding,
12661
+ top: rect.top - padding,
12662
+ width: rect.width + 2 * padding,
12663
+ height: rect.height + 2 * padding
12664
+ };
12665
+ }
12666
+
12667
+ /** Standard AABB intersection test. */
12668
+ function rectsIntersect(a, b) {
12669
+ return a.left < b.left + b.width && a.left + a.width > b.left && a.top < b.top + b.height && a.top + a.height > b.top;
12670
+ }
12671
+
12672
+ /**
12673
+ * Automatic collision avoidance for marker text labels: keeps a marker's
12674
+ * icon fixed while moving (or hiding) its label to avoid overlapping
12675
+ * neighboring markers' icons/labels, similar to Google Maps' label
12676
+ * placement. Below `minZoomForLabels` it withholds every label instead, so
12677
+ * a zoomed-out map shows pins only. See {@link MarkerLayoutConfig}.
12678
+ *
12679
+ * Not part of the public SDK surface — owned and driven entirely by
12680
+ * {@link InternalMapVXMap}, mirroring `RouteController`.
12681
+ */
12682
+ var MarkerLayoutController = /*#__PURE__*/function () {
12683
+ function MarkerLayoutController(map, config) {
12684
+ var _this = this;
12685
+ markerLayoutController_classCallCheck(this, MarkerLayoutController);
12686
+ markerLayoutController_defineProperty(this, "labelSizeCache", new WeakMap());
12687
+ markerLayoutController_defineProperty(this, "iconSizeCache", new WeakMap());
12688
+ markerLayoutController_defineProperty(this, "lastAppliedState", new WeakMap());
12689
+ markerLayoutController_defineProperty(this, "attached", false);
12690
+ this.mapvxMap = map;
12691
+ this.config = config;
12692
+ this.boundOnMove = function () {
12693
+ return _this.scheduleRelayout();
12694
+ };
12695
+ }
12696
+ return markerLayoutController_createClass(MarkerLayoutController, [{
12697
+ key: "map",
12698
+ get: function get() {
12699
+ return this.mapvxMap.map;
12700
+ }
12701
+
12702
+ /**
12703
+ * Starts listening for camera movement. Safe to call once the underlying
12704
+ * MapLibre `Map` instance exists; a single `"move"` listener covers pan,
12705
+ * zoom, rotate, pitch and `resize()`.
12706
+ */
12707
+ }, {
12708
+ key: "attach",
12709
+ value: function attach() {
12710
+ if (this.attached) return;
12711
+ this.attached = true;
12712
+ this.map.on("move", this.boundOnMove);
12713
+ this.remeasureWhenFontsLoad();
12714
+ this.scheduleRelayout();
12715
+ }
12716
+
12717
+ /**
12718
+ * Publishes the configured gaps and label width cap as CSS custom
12719
+ * properties, which `styles.css` reads to offset labels from their icon.
12720
+ * Without this the stylesheet would keep its hardcoded defaults while the
12721
+ * collision math used the configured values, so any custom `gapPx` /
12722
+ * `verticalGapPx` would produce placements this controller believes are
12723
+ * collision-free but that visibly overlap on screen.
12724
+ *
12725
+ * Called with the map's own container, so every marker MapLibre mounts
12726
+ * inside it inherits the values.
12727
+ */
12728
+ }, {
12729
+ key: "applyLayoutVariables",
12730
+ value: function applyLayoutVariables(container) {
12731
+ container.style.setProperty("--mapvx-label-gap", "".concat(this.config.gapPx, "px"));
12732
+ container.style.setProperty("--mapvx-label-vertical-gap", "".concat(this.config.verticalGapPx, "px"));
12733
+ container.style.setProperty("--mapvx-label-max-width", "".concat(this.config.maxLabelWidthPx, "px"));
12734
+ }
12735
+
12736
+ /**
12737
+ * A label measured before its web font finishes loading is measured in the
12738
+ * fallback font, and that wrong footprint would otherwise be cached for
12739
+ * the marker's lifetime. Drop every measurement once fonts settle.
12740
+ */
12741
+ }, {
12742
+ key: "remeasureWhenFontsLoad",
12743
+ value: function remeasureWhenFontsLoad() {
12744
+ var _this2 = this;
12745
+ var fonts = document.fonts;
12746
+ if (!fonts) return;
12747
+ fonts.ready.then(function () {
12748
+ if (!_this2.attached) return;
12749
+ _this2.labelSizeCache = new WeakMap();
12750
+ _this2.iconSizeCache = new WeakMap();
12751
+ _this2.scheduleRelayout();
12752
+ })["catch"](function () {
12753
+ // Font loading failing is not a layout error: the measurements
12754
+ // already taken in the fallback font stay in effect.
12755
+ });
12756
+ }
12757
+
12758
+ /** Stops listening and cancels any pending layout pass. */
12759
+ }, {
12760
+ key: "destroy",
12761
+ value: function destroy() {
12762
+ if (this.attached) {
12763
+ this.map.off("move", this.boundOnMove);
12764
+ this.attached = false;
12765
+ }
12766
+ if (this.rafHandle !== undefined) {
12767
+ cancelAnimationFrame(this.rafHandle);
12768
+ this.rafHandle = undefined;
12769
+ }
12770
+ }
12771
+
12772
+ /**
12773
+ * Drops everything cached for a marker, e.g. after its text/content
12774
+ * changed.
12775
+ *
12776
+ * The last applied decision has to go too: rebuilding a marker's DOM
12777
+ * replaces its `.text-container` with a fresh one carrying the preferred
12778
+ * side and no `visibility`, so a decision remembered from the old element
12779
+ * would make {@link applyDecisions} short-circuit and leave the new label
12780
+ * on the wrong side — or visible when it should be hidden.
12781
+ */
12782
+ }, {
12783
+ key: "invalidateMarker",
12784
+ value: function invalidateMarker(marker) {
12785
+ this.labelSizeCache["delete"](marker);
12786
+ this.iconSizeCache["delete"](marker);
12787
+ this.lastAppliedState["delete"](marker);
12788
+ }
12789
+
12790
+ /** Coalesces repeated calls into a single layout pass per animation frame. */
12791
+ }, {
12792
+ key: "scheduleRelayout",
12793
+ value: function scheduleRelayout() {
12794
+ var _this3 = this;
12795
+ if (!this.config.enabled || this.rafHandle !== undefined) return;
12796
+ this.rafHandle = requestAnimationFrame(function () {
12797
+ _this3.rafHandle = undefined;
12798
+ _this3.performLayout();
12799
+ });
12800
+ }
12801
+ }, {
12802
+ key: "performLayout",
12803
+ value: function performLayout() {
12804
+ // Guards against a relayout firing before the underlying MapLibre `map`
12805
+ // exists (e.g. a marker added before the "load" event) — `attach()`
12806
+ // schedules its own initial pass once it's safe to touch `this.map`.
12807
+ if (!this.attached) return;
12808
+ var markers = this.mapvxMap.getMarkers();
12809
+ if (markers.length === 0) return;
12810
+ var container = this.map.getContainer();
12811
+ var viewportWidth = container.clientWidth;
12812
+ var viewportHeight = container.clientHeight;
12813
+ var cullMargin = this.config.maxLabelWidthPx + Math.max(this.config.gapPx, this.config.verticalGapPx);
12814
+
12815
+ // A single `>=` per pass, evaluated here rather than in `attach()` so it
12816
+ // tracks the live camera: the controller already relayouts on every
12817
+ // "move", which fires continuously through a pan/pinch, so labels cross
12818
+ // the threshold mid-gesture instead of snapping at its end.
12819
+ var labelsAllowed = this.map.getZoom() >= this.config.minZoomForLabels;
12820
+ var entries = this.readMarkers(markers, viewportWidth, viewportHeight, cullMargin);
12821
+ var decisions = this.decidePlacements(entries, labelsAllowed);
12822
+ this.applyDecisions(decisions);
12823
+ }
12824
+
12825
+ /** Read phase: project + measure, no writes. Culls markers fully outside the viewport. */
12826
+ }, {
12827
+ key: "readMarkers",
12828
+ value: function readMarkers(markers, viewportWidth, viewportHeight, cullMargin) {
12829
+ var entries = [];
12830
+ var _iterator = markerLayoutController_createForOfIteratorHelper(markers),
12831
+ _step;
12832
+ try {
12833
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
12834
+ var marker = _step.value;
12835
+ if (!marker.getElement().isConnected) continue;
12836
+ var screen = this.map.project(marker.getLngLat());
12837
+ var iconRect = computeIconRect(screen, this.resolveIconSize(marker), marker.anchorType);
12838
+ var outOfViewport = iconRect.left + iconRect.width < -cullMargin || iconRect.left > viewportWidth + cullMargin || iconRect.top + iconRect.height < -cullMargin || iconRect.top > viewportHeight + cullMargin;
12839
+ if (outOfViewport) continue;
12840
+ entries.push({
12841
+ marker: marker,
12842
+ iconRect: iconRect,
12843
+ labelSize: this.resolveLabelSize(marker)
12844
+ });
12845
+ }
12846
+ } catch (err) {
12847
+ _iterator.e(err);
12848
+ } finally {
12849
+ _iterator.f();
12850
+ }
12851
+ return entries;
12852
+ }
12853
+ }, {
12854
+ key: "resolveLabelSize",
12855
+ value: function resolveLabelSize(marker) {
12856
+ var textEl = marker.textContainerEl;
12857
+ if (!textEl) return undefined;
12858
+ return this.measureCached(this.labelSizeCache, marker, textEl);
12859
+ }
12860
+
12861
+ /**
12862
+ * The icon's box. Markers built from a caller-supplied `element` with no
12863
+ * declared `iconProperties` are measured from the DOM, since their real
12864
+ * size is whatever that element renders at; everyone else's box is exactly
12865
+ * what `MarkerUtils.createMarkerElement` pinned it to.
12866
+ */
12867
+ }, {
12868
+ key: "resolveIconSize",
12869
+ value: function resolveIconSize(marker) {
12870
+ var _this$measureCached;
12871
+ if (!marker.measureIconFromDom) return marker.iconSize;
12872
+ return (_this$measureCached = this.measureCached(this.iconSizeCache, marker, marker.getElement())) !== null && _this$measureCached !== void 0 ? _this$measureCached : marker.iconSize;
12873
+ }
12874
+
12875
+ /**
12876
+ * Measures an element once and remembers it. A zero measurement is
12877
+ * returned but never cached: it means the element isn't laid out yet (a
12878
+ * `display: none` ancestor, a first pass before the web font loads), and
12879
+ * caching it would freeze that marker's footprint at nothing for the rest
12880
+ * of its life.
12881
+ */
12882
+ }, {
12883
+ key: "measureCached",
12884
+ value: function measureCached(cache, marker, element) {
12885
+ var cached = cache.get(marker);
12886
+ if (cached) return cached;
12887
+ var measured = {
12888
+ width: element.offsetWidth,
12889
+ height: element.offsetHeight
12890
+ };
12891
+ if (measured.width <= 0 || measured.height <= 0) return undefined;
12892
+ cache.set(marker, measured);
12893
+ return measured;
12894
+ }
12895
+
12896
+ /**
12897
+ * Compute phase: pure, no DOM access. Icons always block; labels negotiate
12898
+ * a side or hide.
12899
+ *
12900
+ * Below `minZoomForLabels` every label is hidden and none is placed, so no
12901
+ * label ever becomes an obstacle for a neighbor at that zoom — a label the
12902
+ * threshold withheld must not displace anything on its way out.
12903
+ */
12904
+ }, {
12905
+ key: "decidePlacements",
12906
+ value: function decidePlacements(entries, labelsAllowed) {
12907
+ var _this4 = this;
12908
+ var placedLabels = [];
12909
+ var withLabel = entries.filter(hasLabelSize);
12910
+ var decisions = new Map();
12911
+ if (!labelsAllowed) {
12912
+ var _iterator2 = markerLayoutController_createForOfIteratorHelper(withLabel),
12913
+ _step2;
12914
+ try {
12915
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
12916
+ var entry = _step2.value;
12917
+ decisions.set(entry.marker, {
12918
+ position: entry.marker.preferredTextPosition,
12919
+ hidden: true
12920
+ });
12921
+ }
12922
+ } catch (err) {
12923
+ _iterator2.e(err);
12924
+ } finally {
12925
+ _iterator2.f();
12926
+ }
12927
+ return decisions;
12928
+ }
12929
+
12930
+ // Stable sort (spec-guaranteed): equal priority keeps insertion order.
12931
+ withLabel.sort(function (a, b) {
12932
+ return b.marker.labelPriority - a.marker.labelPriority;
12933
+ });
12934
+ var _iterator3 = markerLayoutController_createForOfIteratorHelper(withLabel),
12935
+ _step3;
12936
+ try {
12937
+ var _loop = function _loop() {
12938
+ var entry = _step3.value;
12939
+ var order = [entry.marker.preferredTextPosition].concat(_toConsumableArray(FALLBACK_ORDER.filter(function (position) {
12940
+ return position !== entry.marker.preferredTextPosition;
12941
+ })));
12942
+ var fit = order.map(function (position) {
12943
+ return {
12944
+ position: position,
12945
+ rect: computeCandidateRect(entry.iconRect, entry.labelSize, position, _this4.config.gapPx, _this4.config.verticalGapPx)
12946
+ };
12947
+ }).find(function (_ref2) {
12948
+ var rect = _ref2.rect;
12949
+ return !_this4.collides(rect, entry.marker, entries, placedLabels);
12950
+ });
12951
+ if (fit) {
12952
+ placedLabels.push(fit.rect);
12953
+ decisions.set(entry.marker, {
12954
+ position: fit.position,
12955
+ hidden: false
12956
+ });
12957
+ } else {
12958
+ decisions.set(entry.marker, {
12959
+ position: entry.marker.preferredTextPosition,
12960
+ hidden: true
12961
+ });
12962
+ }
12963
+ };
12964
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
12965
+ _loop();
12966
+ }
12967
+ } catch (err) {
12968
+ _iterator3.e(err);
12969
+ } finally {
12970
+ _iterator3.f();
12971
+ }
12972
+ return decisions;
12973
+ }
12974
+
12975
+ /**
12976
+ * Whether a candidate label rect clashes with any other marker's icon or
12977
+ * with a label already placed this pass.
12978
+ *
12979
+ * A label's own icon is deliberately excluded: `gapPx`/`verticalGapPx`
12980
+ * already define the spacing to it, and `paddingPx` is breathing room
12981
+ * against *neighbors*. Counting the own icon here would make top/bottom
12982
+ * impossible for every marker as soon as `paddingPx` exceeded
12983
+ * `verticalGapPx` (4 vs 4 by default) — and at `paddingPx: 10` would hide
12984
+ * every label on the map.
12985
+ */
12986
+ }, {
12987
+ key: "collides",
12988
+ value: function collides(rect, self, entries, placedLabels) {
12989
+ var padded = expandRect(rect, this.config.paddingPx);
12990
+ return entries.some(function (other) {
12991
+ return other.marker !== self && rectsIntersect(padded, other.iconRect);
12992
+ }) || placedLabels.some(function (label) {
12993
+ return rectsIntersect(padded, label);
12994
+ });
12995
+ }
12996
+
12997
+ /** Write phase: only touches the DOM for markers whose decision actually changed. */
12998
+ }, {
12999
+ key: "applyDecisions",
13000
+ value: function applyDecisions(decisions) {
13001
+ var _this5 = this;
13002
+ decisions.forEach(function (decision, marker) {
13003
+ var last = _this5.lastAppliedState.get(marker);
13004
+ if (last && last.position === decision.position && last.hidden === decision.hidden) return;
13005
+ if (marker.textContainerEl) {
13006
+ MarkerUtils.applyTextPosition(marker.textContainerEl, decision.position, decision.hidden);
13007
+ }
13008
+ _this5.lastAppliedState.set(marker, decision);
13009
+ });
13010
+ }
13011
+ }]);
13012
+ }();
13013
+ function hasLabelSize(entry) {
13014
+ return entry.labelSize !== undefined;
13015
+ }
12389
13016
  ;// ./src/domain/models/routeInstance.ts
12390
13017
  /**
12391
13018
  * Represents the conditions for the step animation.
@@ -12615,10 +13242,10 @@ nav_functions_defineProperty(NavFunctions, "EARTH_RADIUS", 6378000);
12615
13242
  function routeController_typeof(o) { "@babel/helpers - typeof"; return routeController_typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, routeController_typeof(o); }
12616
13243
  function routeController_ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
12617
13244
  function routeController_objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? routeController_ownKeys(Object(t), !0).forEach(function (r) { routeController_defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : routeController_ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
12618
- function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || routeController_unsupportedIterableToArray(r) || _nonIterableSpread(); }
12619
- function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
12620
- function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
12621
- function _arrayWithoutHoles(r) { if (Array.isArray(r)) return routeController_arrayLikeToArray(r); }
13245
+ function routeController_toConsumableArray(r) { return routeController_arrayWithoutHoles(r) || routeController_iterableToArray(r) || routeController_unsupportedIterableToArray(r) || routeController_nonIterableSpread(); }
13246
+ function routeController_nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
13247
+ function routeController_iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
13248
+ function routeController_arrayWithoutHoles(r) { if (Array.isArray(r)) return routeController_arrayLikeToArray(r); }
12622
13249
  function routeController_createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = routeController_unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
12623
13250
  function routeController_unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return routeController_arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? routeController_arrayLikeToArray(r, a) : void 0; } }
12624
13251
  function routeController_arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
@@ -13045,7 +13672,7 @@ var RouteController = /*#__PURE__*/function () {
13045
13672
  throw new Error("Error: position array has different length than colors array");
13046
13673
  }
13047
13674
  var stops = createStops(positionsToUse, colors);
13048
- var gradient = ["interpolate", ["linear"], ["line-progress"]].concat(_toConsumableArray(stops));
13675
+ var gradient = ["interpolate", ["linear"], ["line-progress"]].concat(routeController_toConsumableArray(stops));
13049
13676
  return {
13050
13677
  "line-color": null,
13051
13678
  "line-gradient": gradient,
@@ -13156,25 +13783,49 @@ var RouteController = /*#__PURE__*/function () {
13156
13783
  }
13157
13784
 
13158
13785
  /**
13159
- * Update the visibility of route markers for all active routes.
13160
- * @param floorId The floor ID to update the visibility for.
13786
+ * Every active route's marker. Used by `MarkerLayoutController` so marker
13787
+ * labels are never placed on top of a route's own marker.
13788
+ * @internal
13161
13789
  */
13162
13790
  }, {
13163
- key: "updateRouteMarkerVisibility",
13164
- value: function updateRouteMarkerVisibility(floorId) {
13791
+ key: "getRouteMarkers",
13792
+ value: function getRouteMarkers() {
13793
+ var markers = [];
13165
13794
  var _iterator6 = routeController_createForOfIteratorHelper(this.routes.values()),
13166
13795
  _step6;
13167
13796
  try {
13168
13797
  for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
13169
- var _instance$routeMarker, _instance$routeMarker2, _instance$routeMarker3;
13170
13798
  var instance = _step6.value;
13171
- ((_instance$routeMarker = instance.routeMarker) === null || _instance$routeMarker === void 0 ? void 0 : _instance$routeMarker.floorId) === floorId ? (_instance$routeMarker2 = instance.routeMarker) === null || _instance$routeMarker2 === void 0 ? void 0 : _instance$routeMarker2.show(this.map) : (_instance$routeMarker3 = instance.routeMarker) === null || _instance$routeMarker3 === void 0 ? void 0 : _instance$routeMarker3.hide();
13799
+ if (instance.routeMarker) markers.push(instance.routeMarker);
13172
13800
  }
13173
13801
  } catch (err) {
13174
13802
  _iterator6.e(err);
13175
13803
  } finally {
13176
13804
  _iterator6.f();
13177
13805
  }
13806
+ return markers;
13807
+ }
13808
+
13809
+ /**
13810
+ * Update the visibility of route markers for all active routes.
13811
+ * @param floorId The floor ID to update the visibility for.
13812
+ */
13813
+ }, {
13814
+ key: "updateRouteMarkerVisibility",
13815
+ value: function updateRouteMarkerVisibility(floorId) {
13816
+ var _iterator7 = routeController_createForOfIteratorHelper(this.routes.values()),
13817
+ _step7;
13818
+ try {
13819
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
13820
+ var _instance$routeMarker, _instance$routeMarker2, _instance$routeMarker3;
13821
+ var instance = _step7.value;
13822
+ ((_instance$routeMarker = instance.routeMarker) === null || _instance$routeMarker === void 0 ? void 0 : _instance$routeMarker.floorId) === floorId ? (_instance$routeMarker2 = instance.routeMarker) === null || _instance$routeMarker2 === void 0 ? void 0 : _instance$routeMarker2.show(this.map) : (_instance$routeMarker3 = instance.routeMarker) === null || _instance$routeMarker3 === void 0 ? void 0 : _instance$routeMarker3.hide();
13823
+ }
13824
+ } catch (err) {
13825
+ _iterator7.e(err);
13826
+ } finally {
13827
+ _iterator7.f();
13828
+ }
13178
13829
  }
13179
13830
  }, {
13180
13831
  key: "updatePlaceFloorForRouting",
@@ -13293,7 +13944,7 @@ var RouteController = /*#__PURE__*/function () {
13293
13944
  this.removeSourcesAndLayersForRoute(instance);
13294
13945
  this.routes["delete"](instance.id);
13295
13946
  if (this.lastAddedRouteId === instance.id) {
13296
- var remaining = _toConsumableArray(this.routes.keys());
13947
+ var remaining = routeController_toConsumableArray(this.routes.keys());
13297
13948
  this.lastAddedRouteId = remaining.length > 0 ? remaining[remaining.length - 1] : undefined;
13298
13949
  }
13299
13950
  this.syncRouteCollision();
@@ -13305,18 +13956,18 @@ var RouteController = /*#__PURE__*/function () {
13305
13956
  }, {
13306
13957
  key: "removeAllRoutes",
13307
13958
  value: function removeAllRoutes() {
13308
- var _iterator7 = routeController_createForOfIteratorHelper(this.routes.values()),
13309
- _step7;
13959
+ var _iterator8 = routeController_createForOfIteratorHelper(this.routes.values()),
13960
+ _step8;
13310
13961
  try {
13311
- for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
13312
- var instance = _step7.value;
13962
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
13963
+ var instance = _step8.value;
13313
13964
  this.clearRouteInstance(instance);
13314
13965
  this.removeSourcesAndLayersForRoute(instance);
13315
13966
  }
13316
13967
  } catch (err) {
13317
- _iterator7.e(err);
13968
+ _iterator8.e(err);
13318
13969
  } finally {
13319
- _iterator7.f();
13970
+ _iterator8.f();
13320
13971
  }
13321
13972
  this.routes.clear();
13322
13973
  this.lastAddedRouteId = undefined;
@@ -13330,7 +13981,7 @@ var RouteController = /*#__PURE__*/function () {
13330
13981
  }, {
13331
13982
  key: "getRouteIds",
13332
13983
  value: function getRouteIds() {
13333
- return _toConsumableArray(this.routes.keys());
13984
+ return routeController_toConsumableArray(this.routes.keys());
13334
13985
  }
13335
13986
  }, {
13336
13987
  key: "internalPause",
@@ -14551,6 +15202,8 @@ var Loggeable = /*#__PURE__*/function () {
14551
15202
  }]);
14552
15203
  }();
14553
15204
  ;// ./src/domain/models/mapConfig.ts
15205
+
15206
+
14554
15207
  /**
14555
15208
  * Configuration options for map tile caching.
14556
15209
  * Allows controlling how map tiles are cached in memory and persisted via service worker.
@@ -14593,6 +15246,42 @@ var DEFAULT_TILE_CACHE_CONFIG = {
14593
15246
  */
14594
15247
  var MAPLIBRE_MAX_TILE_CACHE_HARD_CAP = 100000;
14595
15248
 
15249
+ /**
15250
+ * Configuration for automatic marker label collision avoidance.
15251
+ *
15252
+ * When enabled, a marker's text label (see `MarkerConfig.textPosition`) is
15253
+ * treated as a preferred starting side rather than a fixed placement: the
15254
+ * SDK checks it against neighboring markers' icons and labels, moves it to
15255
+ * a free side (right/top/left/bottom) when its preferred side is occupied,
15256
+ * and hides it entirely when no side is collision-free. The marker's icon
15257
+ * itself never moves.
15258
+ *
15259
+ * @example
15260
+ * ```typescript
15261
+ * const markerLayoutConfig: MarkerLayoutConfig = {
15262
+ * enabled: true,
15263
+ * minZoomForLabels: 17,
15264
+ * gapPx: 8,
15265
+ * maxLabelWidthPx: 160,
15266
+ * };
15267
+ * ```
15268
+ *
15269
+ * @group Configuration
15270
+ */
15271
+
15272
+ /**
15273
+ * Default values for marker layout configuration
15274
+ * @internal
15275
+ */
15276
+ var DEFAULT_MARKER_LAYOUT_CONFIG = {
15277
+ enabled: true,
15278
+ minZoomForLabels: Number.NEGATIVE_INFINITY,
15279
+ gapPx: 8,
15280
+ verticalGapPx: 4,
15281
+ paddingPx: 4,
15282
+ maxLabelWidthPx: DEFAULT_MAX_LABEL_WIDTH_PX
15283
+ };
15284
+
14596
15285
  /**
14597
15286
  * Basic configuration for the map.
14598
15287
  * Provides essential settings for initializing and customizing map behavior.
@@ -14642,6 +15331,7 @@ var MAPLIBRE_MAX_TILE_CACHE_HARD_CAP = 100000;
14642
15331
  * @property authToken - (Optional) Authentication token for accessing protected map resources. Required for loading map related data from Portal.
14643
15332
  * @property apiUrl - (Optional) Injected by SDK when creating map; base API URL for map requests.
14644
15333
  * @property mapvxRequestContext - (Optional) Injected by SDK when creating map; context sent as headers to public-api.mapvx.com.
15334
+ * @property markerLayout - (Optional) Automatic marker label collision avoidance configuration.
14645
15335
  */
14646
15336
  ;// ./node_modules/.pnpm/@googlemaps+polyline-codec@1.0.28/node_modules/@googlemaps/polyline-codec/dist/index.esm.js
14647
15337
  /**
@@ -15343,8 +16033,9 @@ function MVXTransport(data) {
15343
16033
  ;// ./src/utils/mapvxHostname.ts
15344
16034
  /**
15345
16035
  * Returns whether a URL hostname is part of the MapVX API domain tree.
15346
- * Matches `mapvx.com` and any subdomain (`api.mapvx.com`, `public-api.mapvx.com`,
15347
- * `integration.mapvx.com`, etc.). Does not match lookalike domains (e.g. `notmapvx.com`).
16036
+ * Matches `mapvx.com` and `mapvx.dev` plus any subdomain (`api.mapvx.com`,
16037
+ * `public-api.mapvx.com`, `api.mapvx.dev`, `portal.mapvx.dev`, etc.).
16038
+ * Does not match lookalike domains (e.g. `notmapvx.com`).
15348
16039
  *
15349
16040
  * @param hostname - Value from `URL.hostname` (no port)
15350
16041
  * @returns True if context headers should be attached for this host
@@ -15355,7 +16046,7 @@ function isMapVxRequestHostname(hostname) {
15355
16046
  if (!h) {
15356
16047
  return false;
15357
16048
  }
15358
- return /^(?:[a-z0-9-]+\.)*mapvx\.com$/.test(h);
16049
+ return /^(?:[a-z0-9-]+\.)*mapvx\.(?:com|dev)$/.test(h);
15359
16050
  }
15360
16051
  ;// ./src/repository/requester.ts
15361
16052
  function requester_toConsumableArray(r) { return requester_arrayWithoutHoles(r) || requester_iterableToArray(r) || requester_unsupportedIterableToArray(r) || requester_nonIterableSpread(); }
@@ -19835,6 +20526,7 @@ function convertPaddingToPixels(padding, containerWidth, containerHeight) {
19835
20526
 
19836
20527
 
19837
20528
 
20529
+
19838
20530
  /**
19839
20531
  * @group Map
19840
20532
  */
@@ -19908,6 +20600,12 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
19908
20600
  merged.maxTiles = Math.min(merged.maxTiles, MAPLIBRE_MAX_TILE_CACHE_HARD_CAP);
19909
20601
  return merged;
19910
20602
  }();
20603
+ _this.markerLayoutConfig = map_objectSpread(map_objectSpread({}, DEFAULT_MARKER_LAYOUT_CONFIG), mapConfig.markerLayout);
20604
+ _this.markerLayoutController = new MarkerLayoutController(_this, _this.markerLayoutConfig);
20605
+ // `container` is the very element handed to MapLibre below, and every
20606
+ // marker is mounted inside it, so publishing the gap/width variables here
20607
+ // covers markers added before the map's "load" event fires.
20608
+ _this.markerLayoutController.applyLayoutVariables(container);
19911
20609
  if (mapConfig.parentPlaceId != null) {
19912
20610
  _this.initialPlaceDetailSetUp(mapConfig.parentPlaceId, mapConfig.authToken);
19913
20611
  }
@@ -20075,6 +20773,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
20075
20773
  (_mapConfig$onMapReady = mapConfig.onMapReady) === null || _mapConfig$onMapReady === void 0 || _mapConfig$onMapReady.call(mapConfig);
20076
20774
  _this6.onHover();
20077
20775
  _this6.subscribeToFailedTiles();
20776
+ _this6.markerLayoutController.attach();
20078
20777
  });
20079
20778
  // Self-healing for circles: a full style reload wipes custom sources and
20080
20779
  // layers, and not every restyle path goes through whenStyleUpdates.
@@ -20182,6 +20881,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
20182
20881
  key: "destroyMap",
20183
20882
  value: function destroyMap() {
20184
20883
  this.circles = [];
20884
+ this.markerLayoutController.destroy();
20185
20885
  this.map.remove();
20186
20886
  this.unsubscribeFromFailedTiles();
20187
20887
  }
@@ -20373,12 +21073,31 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
20373
21073
  this.filterByFloorKey(this.currentFloor);
20374
21074
  (_this$scene3D4 = this.scene3D) === null || _this$scene3D4 === void 0 || _this$scene3D4.refreshOverlay(this.currentFloor);
20375
21075
  }
21076
+
21077
+ /**
21078
+ * Internal accessor for every marker currently on the map, used by
21079
+ * {@link MarkerLayoutController} to compute collision avoidance. Not part
21080
+ * of the public `MapVXMap` interface.
21081
+ *
21082
+ * The user-location marker and the route controller's markers live outside
21083
+ * `this.markers`, but they still occupy screen space — leaving them out
21084
+ * would let labels be placed straight on top of them. They carry no label
21085
+ * of their own, so they only ever act as obstacles.
21086
+ * @internal
21087
+ */
21088
+ }, {
21089
+ key: "getMarkers",
21090
+ value: function getMarkers() {
21091
+ var unmanaged = this.routeController.getRouteMarkers();
21092
+ if (this.userLocationMarker) unmanaged.push(this.userLocationMarker);
21093
+ return unmanaged.length > 0 ? [].concat(map_toConsumableArray(this.markers), map_toConsumableArray(unmanaged)) : this.markers;
21094
+ }
20376
21095
  }, {
20377
21096
  key: "addMarker",
20378
21097
  value: function addMarker(marker) {
20379
21098
  var _this1 = this;
20380
21099
  try {
20381
- var _this$innerFloors$fin4, _this$innerFloors$fin5;
21100
+ var _marker$maxLabelWidth, _this$innerFloors$fin4, _this$innerFloors$fin5;
20382
21101
  // Check if a marker with the same ID already exists and remove it
20383
21102
  if (marker.id) {
20384
21103
  var existingMarkerIndex = this.markers.findIndex(function (m) {
@@ -20394,12 +21113,15 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
20394
21113
  });
20395
21114
  }
20396
21115
  }
20397
- var markerCreated = new MarkerAttribute(marker);
21116
+ var markerCreated = new MarkerAttribute(map_objectSpread(map_objectSpread({}, marker), {}, {
21117
+ maxLabelWidthPx: (_marker$maxLabelWidth = marker.maxLabelWidthPx) !== null && _marker$maxLabelWidth !== void 0 ? _marker$maxLabelWidth : this.markerLayoutConfig.maxLabelWidthPx
21118
+ }));
20398
21119
  var isOutdoor = !this.parentPlace || ((_this$innerFloors$fin4 = (_this$innerFloors$fin5 = this.innerFloors.find(function (floor) {
20399
21120
  return floor.key === _this1.currentFloor;
20400
21121
  })) === null || _this$innerFloors$fin5 === void 0 ? void 0 : _this$innerFloors$fin5.reachableFromGPS) !== null && _this$innerFloors$fin4 !== void 0 ? _this$innerFloors$fin4 : false);
20401
21122
  markerCreated.changeFloor(this.currentFloor, this.map, isOutdoor);
20402
21123
  this.markers.push(markerCreated);
21124
+ this.markerLayoutController.scheduleRelayout();
20403
21125
  this.logEvent("addMarker");
20404
21126
  return markerCreated.id;
20405
21127
  } catch (error) {
@@ -20418,6 +21140,8 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
20418
21140
  });
20419
21141
  if (_marker !== undefined) {
20420
21142
  _marker.updateIcon(markerConfig);
21143
+ this.markerLayoutController.invalidateMarker(_marker);
21144
+ this.markerLayoutController.scheduleRelayout();
20421
21145
  }
20422
21146
  this.logEvent("updateMarker");
20423
21147
  return (_marker$id = _marker === null || _marker === void 0 ? void 0 : _marker.id) !== null && _marker$id !== void 0 ? _marker$id : "";
@@ -20436,6 +21160,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
20436
21160
  });
20437
21161
  if (_marker2 !== undefined) {
20438
21162
  _marker2.setLngLat(newPosition);
21163
+ this.markerLayoutController.scheduleRelayout();
20439
21164
  }
20440
21165
  this.logEvent("updateMarkerPosition");
20441
21166
  } catch (error) {
@@ -20453,6 +21178,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
20453
21178
  });
20454
21179
  if (_marker3 !== null && this.map) {
20455
21180
  _marker3 === null || _marker3 === void 0 || _marker3.show(this.map);
21181
+ this.markerLayoutController.scheduleRelayout();
20456
21182
  }
20457
21183
  this.logEvent("showMarker");
20458
21184
  } catch (error) {
@@ -20468,6 +21194,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
20468
21194
  });
20469
21195
  if (_marker4 !== null) {
20470
21196
  _marker4 === null || _marker4 === void 0 || _marker4.hide();
21197
+ this.markerLayoutController.scheduleRelayout();
20471
21198
  }
20472
21199
  this.logEvent("hideMarker");
20473
21200
  } catch (error) {
@@ -20486,6 +21213,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
20486
21213
  this.markers = this.markers.filter(function (e) {
20487
21214
  return e.id !== markerId;
20488
21215
  });
21216
+ this.markerLayoutController.scheduleRelayout();
20489
21217
  }
20490
21218
  this.logEvent("removeMarker");
20491
21219
  } catch (error) {
@@ -20502,6 +21230,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
20502
21230
  marker.remove();
20503
21231
  });
20504
21232
  this.markers = [];
21233
+ this.markerLayoutController.scheduleRelayout();
20505
21234
  this.logEvent("removeAllMarkers");
20506
21235
  } catch (error) {
20507
21236
  throw new Error("Failed to remove all markers", {
@@ -20935,6 +21664,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
20935
21664
  this.markers.forEach(function (e) {
20936
21665
  e.changeFloor(floorId, _this10.map, isOutdoor);
20937
21666
  });
21667
+ this.markerLayoutController.scheduleRelayout();
20938
21668
  }
20939
21669
  }, {
20940
21670
  key: "updateFiltersTo",
@@ -22008,6 +22738,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22008
22738
  }, {
22009
22739
  key: "addUserLocationTracking",
22010
22740
  value: function addUserLocationTracking(enableHighAccuracy, geoLocation, icon) {
22741
+ var _this14 = this;
22011
22742
  var initialCoordinate = {
22012
22743
  lat: 0.0,
22013
22744
  lng: 0.0
@@ -22028,8 +22759,10 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22028
22759
  var marker = new MarkerAttribute(markerConfig);
22029
22760
  marker.addToMap(this.map);
22030
22761
  this.userLocationMarker = marker;
22762
+ this.markerLayoutController.scheduleRelayout();
22031
22763
  var updateMarker = function updateMarker(position) {
22032
22764
  marker.setLngLat([position.coords.longitude, position.coords.latitude]);
22765
+ _this14.markerLayoutController.scheduleRelayout();
22033
22766
  };
22034
22767
  this.geoLocation = geoLocation !== null && geoLocation !== void 0 ? geoLocation : navigator.geolocation;
22035
22768
  this.watchPositionID = this.geoLocation.watchPosition(updateMarker, function (error) {
@@ -22044,6 +22777,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22044
22777
  if (this.userLocationMarker !== undefined) {
22045
22778
  this.userLocationMarker.remove();
22046
22779
  this.userLocationMarker = undefined;
22780
+ this.markerLayoutController.scheduleRelayout();
22047
22781
 
22048
22782
  // Check if watchPosition ID is valid and stop tracking
22049
22783
  if (this.watchPositionID) {
@@ -22112,7 +22846,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22112
22846
  key: "onWebGLContextLost",
22113
22847
  value: function onWebGLContextLost(e) {
22114
22848
  var _this$map,
22115
- _this14 = this;
22849
+ _this15 = this;
22116
22850
  e.originalEvent.preventDefault();
22117
22851
  console.warn("Contexto WebGL perdido 😢");
22118
22852
 
@@ -22123,8 +22857,8 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22123
22857
  var _ext$restoreContext;
22124
22858
  canvas.addEventListener("webglcontextrestored", function () {
22125
22859
  console.debug("Contexto WebGL restaurado, forzando re-render");
22126
- _this14.map.triggerRepaint(); // forzar repaint
22127
- _this14.failedTiles.clear();
22860
+ _this15.map.triggerRepaint(); // forzar repaint
22861
+ _this15.failedTiles.clear();
22128
22862
  }, {
22129
22863
  once: true
22130
22864
  });
@@ -22164,7 +22898,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22164
22898
  }, {
22165
22899
  key: "subscribeToFailedTiles",
22166
22900
  value: function subscribeToFailedTiles() {
22167
- var _this15 = this;
22901
+ var _this16 = this;
22168
22902
  if (this.retryTimer) return;
22169
22903
 
22170
22904
  // Store bound functions for proper cleanup
@@ -22173,20 +22907,20 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22173
22907
  this.boundOnWebGLContextLost = this.onWebGLContextLost.bind(this);
22174
22908
  this.boundOnWebGLContextRestored = this.onWebGLContextRestored.bind(this);
22175
22909
  this.retryTimer = setInterval(function () {
22176
- if (!_this15.map.getSource("indoorequal")) {
22177
- _this15.failedTiles.clear();
22910
+ if (!_this16.map.getSource("indoorequal")) {
22911
+ _this16.failedTiles.clear();
22178
22912
  return;
22179
22913
  }
22180
- var source = _this15.map.getSource("indoorequal");
22914
+ var source = _this16.map.getSource("indoorequal");
22181
22915
  if (!source || source.type !== "vector") {
22182
- _this15.failedTiles.clear();
22916
+ _this16.failedTiles.clear();
22183
22917
  return;
22184
22918
  }
22185
22919
 
22186
22920
  // Process tiles and remove invalid ones
22187
22921
  var tilesToRetry = [];
22188
22922
  var tilesToRemove = [];
22189
- _this15.failedTiles.forEach(function (tile) {
22923
+ _this16.failedTiles.forEach(function (tile) {
22190
22924
  // Check if tile is still valid before attempting reload
22191
22925
  if (!tile || tile.state === "unloaded" || tile.state === "loading") {
22192
22926
  tilesToRemove.push(tile);
@@ -22204,14 +22938,14 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22204
22938
 
22205
22939
  // Remove invalid tiles from the set
22206
22940
  tilesToRemove.forEach(function (tile) {
22207
- return _this15.failedTiles["delete"](tile);
22941
+ return _this16.failedTiles["delete"](tile);
22208
22942
  });
22209
22943
 
22210
22944
  // Attempt to reload valid failed tiles
22211
22945
  var now = Date.now();
22212
22946
  tilesToRetry.forEach(function (tile) {
22213
- var _this15$tileRetryStat;
22214
- var retry = (_this15$tileRetryStat = _this15.tileRetryState.get(tile)) !== null && _this15$tileRetryStat !== void 0 ? _this15$tileRetryStat : {
22947
+ var _this16$tileRetryStat;
22948
+ var retry = (_this16$tileRetryStat = _this16.tileRetryState.get(tile)) !== null && _this16$tileRetryStat !== void 0 ? _this16$tileRetryStat : {
22215
22949
  attempts: 0,
22216
22950
  nextAttemptAt: 0,
22217
22951
  pending: false
@@ -22222,16 +22956,16 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22222
22956
  var onFailure = function onFailure(error) {
22223
22957
  var attempts = retry.attempts + 1;
22224
22958
  if (attempts >= MAX_TILE_RETRIES) {
22225
- _this15.debugLog("Tile reload failed ".concat(attempts, " times, dropping it from the retry queue"), error);
22226
- _this15.failedTiles["delete"](tile);
22227
- _this15.tileRetryState["delete"](tile);
22959
+ _this16.debugLog("Tile reload failed ".concat(attempts, " times, dropping it from the retry queue"), error);
22960
+ _this16.failedTiles["delete"](tile);
22961
+ _this16.tileRetryState["delete"](tile);
22228
22962
  return;
22229
22963
  }
22230
22964
 
22231
22965
  // Stay in the queue and try again once the backoff has elapsed.
22232
22966
  var backoffMs = TILE_RETRY_BASE_DELAY_MS * Math.pow(2, attempts - 1);
22233
- _this15.debugLog("Tile reload failed (attempt ".concat(attempts, "/").concat(MAX_TILE_RETRIES, "), retrying in ").concat(backoffMs, "ms"), error);
22234
- _this15.tileRetryState.set(tile, {
22967
+ _this16.debugLog("Tile reload failed (attempt ".concat(attempts, "/").concat(MAX_TILE_RETRIES, "), retrying in ").concat(backoffMs, "ms"), error);
22968
+ _this16.tileRetryState.set(tile, {
22235
22969
  attempts: attempts,
22236
22970
  nextAttemptAt: Date.now() + backoffMs,
22237
22971
  pending: false
@@ -22240,7 +22974,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22240
22974
  try {
22241
22975
  // Verify tile still exists and is in a retryable state
22242
22976
  if (tile && (tile.state === "errored" || tile.state === "expired")) {
22243
- _this15.tileRetryState.set(tile, map_objectSpread(map_objectSpread({}, retry), {}, {
22977
+ _this16.tileRetryState.set(tile, map_objectSpread(map_objectSpread({}, retry), {}, {
22244
22978
  pending: true
22245
22979
  }));
22246
22980
  // loadTile is async, so a rejection escapes this try/catch and has to be handled
@@ -22250,7 +22984,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22250
22984
  // Loaded: forget the attempt history so a later failure starts from zero. The
22251
22985
  // tile leaves the queue through the tilesToRemove branch on the next tick,
22252
22986
  // once its state is no longer errored/expired.
22253
- _this15.tileRetryState["delete"](tile);
22987
+ _this16.tileRetryState["delete"](tile);
22254
22988
  })["catch"](onFailure);
22255
22989
  }
22256
22990
  } catch (error) {