@mapvx/web-js 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/umd/index.js CHANGED
@@ -5721,6 +5721,7 @@ __webpack_require__.r(__webpack_exports__);
5721
5721
  __webpack_require__.d(__webpack_exports__, {
5722
5722
  AnnounceFormat: () => (/* reexport */ AnnounceFormat),
5723
5723
  Banner: () => (/* reexport */ Banner),
5724
+ CIRCLE_DEFAULTS: () => (/* reexport */ CIRCLE_DEFAULTS),
5724
5725
  CacheManager: () => (/* reexport */ CacheManager),
5725
5726
  CountlyLogger: () => (/* reexport */ CountlyLogger),
5726
5727
  DEFAULT_CACHE_CONFIGS: () => (/* reexport */ DEFAULT_CACHE_CONFIGS),
@@ -5728,6 +5729,7 @@ __webpack_require__.d(__webpack_exports__, {
5728
5729
  FetchHttpClient: () => (/* reexport */ FetchHttpClient),
5729
5730
  Institution: () => (/* reexport */ Institution),
5730
5731
  LRUCache: () => (/* reexport */ LRUCache),
5732
+ MAPVX_BRAND_COLOR: () => (/* reexport */ MAPVX_BRAND_COLOR),
5731
5733
  MAPVX_DEFAULT_PRECONNECT_HOSTS: () => (/* reexport */ MAPVX_DEFAULT_PRECONNECT_HOSTS),
5732
5734
  MVXPlace: () => (/* reexport */ place_MVXPlace),
5733
5735
  MVXRoute: () => (/* reexport */ MVXRoute),
@@ -5739,12 +5741,14 @@ __webpack_require__.d(__webpack_exports__, {
5739
5741
  TextPosition: () => (/* reexport */ TextPosition),
5740
5742
  TransportationMode: () => (/* reexport */ TransportationMode),
5741
5743
  UnitSystem: () => (/* reexport */ UnitSystem),
5744
+ circleRing: () => (/* reexport */ circleRing),
5742
5745
  createRouteAnimationIconDataUrl: () => (/* reexport */ createRouteAnimationIconDataUrl),
5743
5746
  initializeSDK: () => (/* reexport */ initializeSDK),
5744
5747
  injectPreconnects: () => (/* reexport */ injectPreconnects),
5745
5748
  isBasicWithIcon: () => (/* reexport */ isBasicWithIcon),
5746
5749
  isBasicWithLogo: () => (/* reexport */ isBasicWithLogo),
5747
5750
  isMapVxRequestHostname: () => (/* reexport */ isMapVxRequestHostname),
5751
+ isValidCircleConfig: () => (/* reexport */ isValidCircleConfig),
5748
5752
  loadCustomization: () => (/* reexport */ loadCustomization),
5749
5753
  loadStyles: () => (/* binding */ loadStyles)
5750
5754
  });
@@ -6790,7 +6794,7 @@ var rollbar_umd_min_default = /*#__PURE__*/__webpack_require__.n(rollbar_umd_min
6790
6794
  // bundle. The tsc-built ES/CJS outputs keep it as a bare global, so the
6791
6795
  // typeof guard makes the lookup safe everywhere; the version placeholder is
6792
6796
  // replaced with the package version by scripts/inject-build-defines.js.
6793
- var SDK_VERSION = true ? "1.1.1" : 0;
6797
+ var SDK_VERSION = true ? "1.2.0" : 0;
6794
6798
  var _rollbarConfig = {
6795
6799
  accessToken: "28279d52df43411ebd138c2bee0ab1df",
6796
6800
  // Only report what the SDK logs explicitly via logError. Capturing every
@@ -6838,7 +6842,7 @@ var logger_countly = {
6838
6842
  // The typeof guards make the lookup safe everywhere; the version placeholder
6839
6843
  // is replaced with the package version by scripts/inject-build-defines.js.
6840
6844
  var IS_DEBUG = true ? false : 0;
6841
- var logger_SDK_VERSION = true ? "1.1.1" : 0;
6845
+ var logger_SDK_VERSION = true ? "1.2.0" : 0;
6842
6846
  var Logger = /*#__PURE__*/function () {
6843
6847
  function Logger() {
6844
6848
  logger_classCallCheck(this, Logger);
@@ -9624,6 +9628,230 @@ var DEFAULT_TILE_CACHE_CONFIG = {
9624
9628
  * @property apiUrl - (Optional) Injected by SDK when creating map; base API URL for map requests.
9625
9629
  * @property mapvxRequestContext - (Optional) Injected by SDK when creating map; context sent as headers to public-api.mapvx.com.
9626
9630
  */
9631
+ ;// ./src/domain/models/circle.ts
9632
+ function circle_typeof(o) { "@babel/helpers - typeof"; return circle_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; }, circle_typeof(o); }
9633
+
9634
+ /**
9635
+ * MapVX brand color used as the default for circle styling and other
9636
+ * SDK-drawn overlays (colored places, borders).
9637
+ *
9638
+ * @group Circles
9639
+ */
9640
+ var MAPVX_BRAND_COLOR = "#276EF1";
9641
+
9642
+ /**
9643
+ * Default styling values applied to a circle when the corresponding
9644
+ * {@link CircleConfig} fields are omitted.
9645
+ *
9646
+ * @group Circles
9647
+ */
9648
+ var CIRCLE_DEFAULTS = {
9649
+ fillColor: MAPVX_BRAND_COLOR,
9650
+ fillOpacity: 0.14,
9651
+ strokeColor: MAPVX_BRAND_COLOR,
9652
+ strokeWidth: 2,
9653
+ strokeOpacity: 0.65
9654
+ };
9655
+
9656
+ /**
9657
+ * Represents the configuration for the creation of a metric radius circle on a map.
9658
+ * The radius is expressed in meters (geographic), so the drawn circle stays
9659
+ * metrically accurate at every zoom level.
9660
+ *
9661
+ * @example
9662
+ * ```typescript
9663
+ * const circleConfig: CircleConfig = {
9664
+ * id: "geofence-1",
9665
+ * coordinate: { lat: 40.7128, lng: -74.0060 },
9666
+ * radiusMeters: 150,
9667
+ * fillOpacity: 0.2,
9668
+ * };
9669
+ * ```
9670
+ *
9671
+ * @group Circles
9672
+ */
9673
+
9674
+ /**
9675
+ * Internal resolved representation of a circle kept by the map.
9676
+ * Holds the full configuration with defaults applied, plus visibility state.
9677
+ *
9678
+ * Unlike markers, the hidden flag persists across floor changes and map
9679
+ * restyles: a circle hidden via `hideCircle` stays hidden until `showCircle`.
9680
+ */
9681
+
9682
+ /**
9683
+ * Validates that a radius in meters is positive and finite.
9684
+ * @param radius The radius value to validate.
9685
+ * @throws {Error} If radius is ≤ 0 or not finite (NaN/Infinity).
9686
+ * @internal
9687
+ */
9688
+ function validateRadiusMeters(radius) {
9689
+ if (!Number.isFinite(radius) || radius <= 0) {
9690
+ throw new Error("Invalid radiusMeters: ".concat(radius, ". Must be a positive finite number."));
9691
+ }
9692
+ }
9693
+
9694
+ /**
9695
+ * Validates that a coordinate is within valid geographic bounds.
9696
+ * @param coord The coordinate to validate.
9697
+ * @throws {Error} If latitude is outside [-90, 90] or longitude outside [-180, 180], or not finite.
9698
+ * @internal
9699
+ */
9700
+ function validateCoordinate(coord) {
9701
+ if (coord == null || circle_typeof(coord) !== "object") {
9702
+ throw new Error("Invalid coordinate: expected an object with lat and lng properties.");
9703
+ }
9704
+ if (!Number.isFinite(coord.lat) || !Number.isFinite(coord.lng) || coord.lat < -90 || coord.lat > 90 || coord.lng < -180 || coord.lng > 180) {
9705
+ throw new Error("Invalid coordinate: lat=".concat(coord.lat, ", lng=").concat(coord.lng, ". ") + "latitude must be in [-90, 90], longitude in [-180, 180], and both finite.");
9706
+ }
9707
+ }
9708
+
9709
+ /**
9710
+ * Clamps an opacity value to the valid range [0, 1].
9711
+ * @param opacity The opacity value to clamp.
9712
+ * @param defaultValue Value to use if opacity is not a finite number.
9713
+ * @returns The clamped opacity value.
9714
+ * @internal
9715
+ */
9716
+ function clampOpacity(opacity, defaultValue) {
9717
+ if (!Number.isFinite(opacity)) return defaultValue;
9718
+ return Math.max(0, Math.min(1, opacity));
9719
+ }
9720
+
9721
+ /**
9722
+ * Type guard for validating circle configurations without throwing.
9723
+ * Returns true if the configuration is valid and can be added to the map.
9724
+ *
9725
+ * @param config Configuration to validate.
9726
+ * @returns True if config is a valid CircleConfig, false otherwise.
9727
+ *
9728
+ * @example
9729
+ * ```typescript
9730
+ * if (isValidCircleConfig(userInput)) {
9731
+ * map.addCircle(userInput);
9732
+ * } else {
9733
+ * console.error('Invalid circle configuration');
9734
+ * }
9735
+ * ```
9736
+ *
9737
+ * @group Circles
9738
+ */
9739
+ function isValidCircleConfig(config) {
9740
+ if (config === null || config === undefined || circle_typeof(config) !== "object") {
9741
+ return false;
9742
+ }
9743
+ var c = config;
9744
+ if (circle_typeof(c.coordinate) !== "object" || c.coordinate === null) return false;
9745
+ var coord = c.coordinate;
9746
+ if (typeof coord.lat !== "number" || typeof coord.lng !== "number" || !Number.isFinite(coord.lat) || !Number.isFinite(coord.lng) || coord.lat < -90 || coord.lat > 90 || coord.lng < -180 || coord.lng > 180) {
9747
+ return false;
9748
+ }
9749
+ if (typeof c.radiusMeters !== "number" || !Number.isFinite(c.radiusMeters) || c.radiusMeters <= 0) {
9750
+ return false;
9751
+ }
9752
+ if (c.id !== undefined && typeof c.id !== "string") return false;
9753
+ if (c.fillColor !== undefined && typeof c.fillColor !== "string") return false;
9754
+ if (c.fillOpacity !== undefined) {
9755
+ if (typeof c.fillOpacity !== "number" || !Number.isFinite(c.fillOpacity)) return false;
9756
+ }
9757
+ if (c.strokeColor !== undefined && typeof c.strokeColor !== "string") return false;
9758
+ if (c.strokeWidth !== undefined) {
9759
+ if (typeof c.strokeWidth !== "number" || !Number.isFinite(c.strokeWidth)) return false;
9760
+ }
9761
+ if (c.strokeOpacity !== undefined) {
9762
+ if (typeof c.strokeOpacity !== "number" || !Number.isFinite(c.strokeOpacity)) return false;
9763
+ }
9764
+ if (c.floorId !== undefined && typeof c.floorId !== "string") return false;
9765
+ return true;
9766
+ }
9767
+
9768
+ /**
9769
+ * Resolves a {@link CircleConfig} into a {@link CircleRecord}, applying
9770
+ * default styling, generating an id when one is not provided, and validating inputs.
9771
+ *
9772
+ * @param config Circle configuration provided by the SDK consumer.
9773
+ * @param hidden Initial hidden state, defaults to visible.
9774
+ * @returns The resolved circle record ready to be stored and rendered.
9775
+ * @throws {Error} If radiusMeters is ≤ 0, not finite, or coordinates are invalid.
9776
+ *
9777
+ * @group Circles
9778
+ */
9779
+ function resolveCircleConfig(config) {
9780
+ var _config$id, _config$fillColor, _config$strokeColor, _config$strokeWidth;
9781
+ var hidden = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
9782
+ validateRadiusMeters(config.radiusMeters);
9783
+ validateCoordinate(config.coordinate);
9784
+ return {
9785
+ id: (_config$id = config.id) !== null && _config$id !== void 0 ? _config$id : generateHexadecimalKey(),
9786
+ coordinate: config.coordinate,
9787
+ radiusMeters: config.radiusMeters,
9788
+ fillColor: (_config$fillColor = config.fillColor) !== null && _config$fillColor !== void 0 ? _config$fillColor : CIRCLE_DEFAULTS.fillColor,
9789
+ fillOpacity: clampOpacity(config.fillOpacity, CIRCLE_DEFAULTS.fillOpacity),
9790
+ strokeColor: (_config$strokeColor = config.strokeColor) !== null && _config$strokeColor !== void 0 ? _config$strokeColor : CIRCLE_DEFAULTS.strokeColor,
9791
+ strokeWidth: (_config$strokeWidth = config.strokeWidth) !== null && _config$strokeWidth !== void 0 ? _config$strokeWidth : CIRCLE_DEFAULTS.strokeWidth,
9792
+ strokeOpacity: clampOpacity(config.strokeOpacity, CIRCLE_DEFAULTS.strokeOpacity),
9793
+ floorId: config.floorId,
9794
+ hidden: hidden
9795
+ };
9796
+ }
9797
+
9798
+ /**
9799
+ * Builds a closed GeoJSON polygon ring approximating a geographic circle.
9800
+ *
9801
+ * The ring is computed with an equirectangular approximation: meters are
9802
+ * converted to degrees independently per axis, with the longitude scale
9803
+ * corrected by the cosine of the latitude. For the radii the SDK works with
9804
+ * (tens to hundreds of meters) the metric error is negligible (≤1%) at any latitude
9805
+ * where the projection is defined. At extreme polar latitudes, the approximation
9806
+ * breaks down, so circles near poles (|lat| > 85°) should be used with caution.
9807
+ *
9808
+ * @param lng Longitude of the circle center in degrees.
9809
+ * @param lat Latitude of the circle center in degrees.
9810
+ * @param radiusMeters Radius of the circle in meters. Must be positive and finite.
9811
+ * @param points Number of segments in the ring. Defaults to 64. Non-integer
9812
+ * values are floored and the count is clamped to a minimum of 3,
9813
+ * the smallest valid GeoJSON linear ring; non-finite values fall
9814
+ * back to the default.
9815
+ * @returns A closed ring of `[lng, lat]` positions (first equals last).
9816
+ *
9817
+ * @example
9818
+ * ```typescript
9819
+ * import { circleRing } from '@mapvx/web-js';
9820
+ *
9821
+ * // Create a 150-meter radius circle ring
9822
+ * const ring = circleRing(-74.0060, 40.7128, 150);
9823
+ * const polygon = { type: "Polygon", coordinates: [ring] };
9824
+ *
9825
+ * // Custom segments (32 instead of 64) for lower resolution
9826
+ * const coarseRing = circleRing(-74.0060, 40.7128, 150, 32);
9827
+ * ```
9828
+ *
9829
+ * @note Equirectangular approximation is accurate for radii up to ~500 km and
9830
+ * latitudes between approximately ±80°. Beyond these limits, visual
9831
+ * distortion may occur near the poles.
9832
+ * @note Generated rings do not account for geographic obstacles, walls, or
9833
+ * buildings. For complex geofencing, consider server-side validation.
9834
+ *
9835
+ * @group Circles
9836
+ */
9837
+ function circleRing(lng, lat, radiusMeters) {
9838
+ var points = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 64;
9839
+ // A linear ring needs at least 3 distinct positions to be valid GeoJSON
9840
+ var segments = Number.isFinite(points) ? Math.max(3, Math.floor(points)) : 64;
9841
+ var ring = [];
9842
+ var latRad = lat * Math.PI / 180;
9843
+ var metersPerDegLat = 110574;
9844
+ var metersPerDegLng = 111320 * Math.cos(latRad || 0.000001);
9845
+ var latR = radiusMeters / metersPerDegLat;
9846
+ var lngR = radiusMeters / (metersPerDegLng || 1);
9847
+ for (var i = 0; i < segments; i += 1) {
9848
+ var a = i / segments * Math.PI * 2;
9849
+ ring.push([lng + lngR * Math.cos(a), lat + latR * Math.sin(a)]);
9850
+ }
9851
+ // Close the ring with an exact copy of the first position, as GeoJSON requires
9852
+ ring.push([ring[0][0], ring[0][1]]);
9853
+ return ring;
9854
+ }
9627
9855
  ;// ./node_modules/.pnpm/@googlemaps+polyline-codec@1.0.28/node_modules/@googlemaps/polyline-codec/dist/index.esm.js
9628
9856
  /**
9629
9857
  * Copyright 2020 Google LLC
@@ -13213,6 +13441,12 @@ function map_defineProperty(e, r, t) { return (r = map_toPropertyKey(r)) in e ?
13213
13441
  function map_toPropertyKey(t) { var i = map_toPrimitive(t, "string"); return "symbol" == map_typeof(i) ? i : i + ""; }
13214
13442
  function map_toPrimitive(t, r) { if ("object" != map_typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != map_typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
13215
13443
 
13444
+ /** Shared GeoJSON source holding every circle drawn through the circle API. */
13445
+ var CIRCLE_SOURCE_ID = "mapvx-circles";
13446
+ /** Fill layer rendering the translucent interior of the circles. */
13447
+ var CIRCLE_FILL_LAYER_ID = "mapvx-circles-fill";
13448
+ /** Line layer rendering the circle outlines. */
13449
+ var CIRCLE_LINE_LAYER_ID = "mapvx-circles-line";
13216
13450
 
13217
13451
  // Flag to track if cached-tile protocol has been registered
13218
13452
  var cachedTileProtocolRegistered = false;
@@ -13327,6 +13561,7 @@ function convertPaddingToPixels(padding, containerWidth, containerHeight) {
13327
13561
 
13328
13562
 
13329
13563
 
13564
+
13330
13565
  /**
13331
13566
  * @group Map
13332
13567
  */
@@ -13356,6 +13591,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
13356
13591
  map_defineProperty(_this, "currentFloor", "");
13357
13592
  map_defineProperty(_this, "baseFilters", {});
13358
13593
  map_defineProperty(_this, "markers", []);
13594
+ map_defineProperty(_this, "circles", []);
13359
13595
  map_defineProperty(_this, "enableHover", false);
13360
13596
  map_defineProperty(_this, "hoveredId", "unselected");
13361
13597
  map_defineProperty(_this, "failedTiles", new Set());
@@ -13522,6 +13758,11 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
13522
13758
  _this6.onHover();
13523
13759
  _this6.subscribeToFailedTiles();
13524
13760
  });
13761
+ // Self-healing for circles: a full style reload wipes custom sources and
13762
+ // layers, and not every restyle path goes through whenStyleUpdates.
13763
+ this.map.on("styledata", function () {
13764
+ if (_this6.circles.length > 0) _this6.ensureCircleLayers();
13765
+ });
13525
13766
  this.map.on("zoomend", function () {
13526
13767
  var _mapConfig$onZoomEnd;
13527
13768
  (_mapConfig$onZoomEnd = mapConfig.onZoomEnd) === null || _mapConfig$onZoomEnd === void 0 || _mapConfig$onZoomEnd.call(mapConfig, _this6.getZoomLevel());
@@ -13619,6 +13860,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
13619
13860
  }, {
13620
13861
  key: "destroyMap",
13621
13862
  value: function destroyMap() {
13863
+ this.circles = [];
13622
13864
  this.map.remove();
13623
13865
  this.unsubscribeFromFailedTiles();
13624
13866
  }
@@ -13756,6 +13998,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
13756
13998
  this.setBaseFilters(newStyle);
13757
13999
  }
13758
14000
  this.routeController.addSourcesAndLayers();
14001
+ this.refreshCircles();
13759
14002
  this.filterByFloorKey(this.currentFloor);
13760
14003
  }
13761
14004
  }, {
@@ -13884,6 +14127,295 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
13884
14127
  throw new Error("Failed to remove all markers");
13885
14128
  }
13886
14129
  }
14130
+ }, {
14131
+ key: "addCircle",
14132
+ value: function addCircle(circle) {
14133
+ try {
14134
+ // Check if a circle with the same ID already exists and replace it
14135
+ if (circle.id) {
14136
+ this.circles = this.circles.filter(function (c) {
14137
+ return c.id !== circle.id;
14138
+ });
14139
+ }
14140
+ // resolveCircleConfig validates radiusMeters and coordinates
14141
+ var record = resolveCircleConfig(circle);
14142
+ this.circles.push(record);
14143
+ this.refreshCircles();
14144
+ this.logEvent("addCircle");
14145
+ return record.id;
14146
+ } catch (error) {
14147
+ throw new Error("Failed to add circle: ".concat(error instanceof Error ? error.message : String(error)));
14148
+ }
14149
+ }
14150
+ }, {
14151
+ key: "updateCircle",
14152
+ value: function updateCircle(circleConfig) {
14153
+ try {
14154
+ var index = this.circles.findIndex(function (c) {
14155
+ return c.id === circleConfig.id;
14156
+ });
14157
+ if (index === -1) return null;
14158
+
14159
+ // resolveCircleConfig validates radiusMeters and coordinates
14160
+ this.circles[index] = resolveCircleConfig(circleConfig, this.circles[index].hidden);
14161
+ this.refreshCircles();
14162
+ this.logEvent("updateCircle");
14163
+ return this.circles[index].id;
14164
+ } catch (error) {
14165
+ throw new Error("Failed to update circle: ".concat(error instanceof Error ? error.message : String(error)));
14166
+ }
14167
+ }
14168
+ }, {
14169
+ key: "getCircle",
14170
+ value: function getCircle(circleId) {
14171
+ return this.circles.find(function (c) {
14172
+ return c.id === circleId;
14173
+ });
14174
+ }
14175
+ }, {
14176
+ key: "getCircles",
14177
+ value: function getCircles() {
14178
+ return this.circles.slice();
14179
+ }
14180
+ }, {
14181
+ key: "hasCircle",
14182
+ value: function hasCircle(circleId) {
14183
+ return this.circles.some(function (c) {
14184
+ return c.id === circleId;
14185
+ });
14186
+ }
14187
+ }, {
14188
+ key: "updateCirclePosition",
14189
+ value: function updateCirclePosition(circleId, center, radiusMeters) {
14190
+ try {
14191
+ var _circle = this.circles.find(function (c) {
14192
+ return c.id === circleId;
14193
+ });
14194
+ if (_circle === undefined) return false;
14195
+
14196
+ // Validate inputs before modifying
14197
+ if (!center) {
14198
+ throw new Error("Circle center is required");
14199
+ }
14200
+ var lat = center.lat,
14201
+ lng = center.lng;
14202
+ if (!Number.isFinite(lat) || lat < -90 || lat > 90) {
14203
+ throw new Error("Invalid latitude: ".concat(lat, ". Must be between -90 and 90."));
14204
+ }
14205
+ if (!Number.isFinite(lng) || lng < -180 || lng > 180) {
14206
+ throw new Error("Invalid longitude: ".concat(lng, ". Must be between -180 and 180."));
14207
+ }
14208
+ if (radiusMeters !== undefined) {
14209
+ if (!Number.isFinite(radiusMeters) || radiusMeters <= 0) {
14210
+ throw new Error("Invalid radiusMeters: ".concat(radiusMeters, ". Must be a positive finite number."));
14211
+ }
14212
+ }
14213
+ _circle.coordinate = center;
14214
+ if (radiusMeters !== undefined) {
14215
+ _circle.radiusMeters = radiusMeters;
14216
+ }
14217
+ this.refreshCircles();
14218
+ this.logEvent("updateCirclePosition");
14219
+ return true;
14220
+ } catch (error) {
14221
+ throw new Error("Failed to update circle position: ".concat(error instanceof Error ? error.message : String(error)));
14222
+ }
14223
+ }
14224
+ }, {
14225
+ key: "updateCircleStyle",
14226
+ value: function updateCircleStyle(circleId, style) {
14227
+ try {
14228
+ var _circle2 = this.circles.find(function (c) {
14229
+ return c.id === circleId;
14230
+ });
14231
+ if (_circle2 === undefined) return false;
14232
+
14233
+ // Update only style properties, clamp opacities
14234
+ if (style.fillColor !== undefined) _circle2.fillColor = style.fillColor;
14235
+ if (style.fillOpacity !== undefined) _circle2.fillOpacity = Math.max(0, Math.min(1, style.fillOpacity));
14236
+ if (style.strokeColor !== undefined) _circle2.strokeColor = style.strokeColor;
14237
+ if (style.strokeWidth !== undefined) _circle2.strokeWidth = style.strokeWidth;
14238
+ if (style.strokeOpacity !== undefined) _circle2.strokeOpacity = Math.max(0, Math.min(1, style.strokeOpacity));
14239
+ this.refreshCircles();
14240
+ this.logEvent("updateCircleStyle");
14241
+ return true;
14242
+ } catch (error) {
14243
+ throw new Error("Failed to update circle style: ".concat(error instanceof Error ? error.message : String(error)));
14244
+ }
14245
+ }
14246
+ }, {
14247
+ key: "removeCircle",
14248
+ value: function removeCircle(circleId) {
14249
+ try {
14250
+ this.circles = this.circles.filter(function (c) {
14251
+ return c.id !== circleId;
14252
+ });
14253
+ this.refreshCircles();
14254
+ this.logEvent("removeCircle");
14255
+ } catch (error) {
14256
+ throw new Error("Failed to remove circle");
14257
+ }
14258
+ }
14259
+ }, {
14260
+ key: "removeAllCircles",
14261
+ value: function removeAllCircles() {
14262
+ try {
14263
+ this.circles = [];
14264
+ this.refreshCircles();
14265
+ this.logEvent("removeAllCircles");
14266
+ } catch (error) {
14267
+ throw new Error("Failed to remove all circles");
14268
+ }
14269
+ }
14270
+ }, {
14271
+ key: "showCircle",
14272
+ value: function showCircle(circleId) {
14273
+ try {
14274
+ var _circle3 = this.circles.find(function (c) {
14275
+ return c.id === circleId;
14276
+ });
14277
+ if (_circle3 === undefined) {
14278
+ return false;
14279
+ }
14280
+ _circle3.hidden = false;
14281
+ this.refreshCircles();
14282
+ this.logEvent("showCircle");
14283
+ return true;
14284
+ } catch (error) {
14285
+ return false;
14286
+ }
14287
+ }
14288
+ }, {
14289
+ key: "hideCircle",
14290
+ value: function hideCircle(circleId) {
14291
+ try {
14292
+ var _circle4 = this.circles.find(function (c) {
14293
+ return c.id === circleId;
14294
+ });
14295
+ if (_circle4 === undefined) {
14296
+ return false;
14297
+ }
14298
+ _circle4.hidden = true;
14299
+ this.refreshCircles();
14300
+ this.logEvent("hideCircle");
14301
+ return true;
14302
+ } catch (error) {
14303
+ return false;
14304
+ }
14305
+ }
14306
+
14307
+ /**
14308
+ * Builds the GeoJSON FeatureCollection for every currently visible circle.
14309
+ * Visibility mirrors marker semantics: a circle with a floor is shown only
14310
+ * while that floor is displayed, and a circle without a floor is shown only
14311
+ * in outdoor contexts. Hidden circles are always omitted.
14312
+ */
14313
+ }, {
14314
+ key: "circleFeatureCollection",
14315
+ value: function circleFeatureCollection() {
14316
+ var _this$currentFloor3, _this$innerFloors$fin6, _this$innerFloors$fin7;
14317
+ var floorId = (_this$currentFloor3 = this.currentFloor) !== null && _this$currentFloor3 !== void 0 ? _this$currentFloor3 : "";
14318
+ var isOutdoor = !this.parentPlace || ((_this$innerFloors$fin6 = (_this$innerFloors$fin7 = this.innerFloors.find(function (floor) {
14319
+ return floor.key === floorId;
14320
+ })) === null || _this$innerFloors$fin7 === void 0 ? void 0 : _this$innerFloors$fin7.reachableFromGPS) !== null && _this$innerFloors$fin6 !== void 0 ? _this$innerFloors$fin6 : false);
14321
+ var features = this.circles.filter(function (circle) {
14322
+ var _circle$floorId;
14323
+ if (circle.hidden) return false;
14324
+ var circleFloor = (_circle$floorId = circle.floorId) !== null && _circle$floorId !== void 0 ? _circle$floorId : "";
14325
+ return circleFloor === floorId || isOutdoor && circleFloor === "";
14326
+ }).map(function (circle) {
14327
+ return {
14328
+ type: "Feature",
14329
+ properties: {
14330
+ id: circle.id,
14331
+ fillColor: circle.fillColor,
14332
+ fillOpacity: circle.fillOpacity,
14333
+ strokeColor: circle.strokeColor,
14334
+ strokeWidth: circle.strokeWidth,
14335
+ strokeOpacity: circle.strokeOpacity
14336
+ },
14337
+ geometry: {
14338
+ type: "Polygon",
14339
+ coordinates: [circleRing(circle.coordinate.lng, circle.coordinate.lat, circle.radiusMeters)]
14340
+ }
14341
+ };
14342
+ });
14343
+ return {
14344
+ type: "FeatureCollection",
14345
+ features: features
14346
+ };
14347
+ }
14348
+
14349
+ /**
14350
+ * Idempotently adds the shared circle source and its fill/line layers.
14351
+ * Layers are inserted below the first symbol layer so place labels and
14352
+ * markers stay readable above the translucent fill. Safe to call at any
14353
+ * time: a style that is still loading simply rejects the calls, and the
14354
+ * styledata listener retries once the style is ready.
14355
+ */
14356
+ }, {
14357
+ key: "ensureCircleLayers",
14358
+ value: function ensureCircleLayers() {
14359
+ if (!this.map) return;
14360
+ try {
14361
+ var _this$map$getStyle2;
14362
+ if (!this.map.getSource(CIRCLE_SOURCE_ID)) {
14363
+ this.map.addSource(CIRCLE_SOURCE_ID, {
14364
+ type: "geojson",
14365
+ data: this.circleFeatureCollection()
14366
+ });
14367
+ }
14368
+ var beforeId = (_this$map$getStyle2 = this.map.getStyle()) === null || _this$map$getStyle2 === void 0 || (_this$map$getStyle2 = _this$map$getStyle2.layers) === null || _this$map$getStyle2 === void 0 || (_this$map$getStyle2 = _this$map$getStyle2.find(function (layer) {
14369
+ return layer.type === "symbol";
14370
+ })) === null || _this$map$getStyle2 === void 0 ? void 0 : _this$map$getStyle2.id;
14371
+ if (!this.map.getLayer(CIRCLE_FILL_LAYER_ID)) {
14372
+ var fillLayer = {
14373
+ id: CIRCLE_FILL_LAYER_ID,
14374
+ type: "fill",
14375
+ source: CIRCLE_SOURCE_ID,
14376
+ paint: {
14377
+ "fill-color": ["get", "fillColor"],
14378
+ "fill-opacity": ["get", "fillOpacity"]
14379
+ }
14380
+ };
14381
+ this.map.addLayer(fillLayer, beforeId);
14382
+ }
14383
+ if (!this.map.getLayer(CIRCLE_LINE_LAYER_ID)) {
14384
+ var lineLayer = {
14385
+ id: CIRCLE_LINE_LAYER_ID,
14386
+ type: "line",
14387
+ source: CIRCLE_SOURCE_ID,
14388
+ paint: {
14389
+ "line-color": ["get", "strokeColor"],
14390
+ "line-width": ["get", "strokeWidth"],
14391
+ "line-opacity": ["get", "strokeOpacity"]
14392
+ }
14393
+ };
14394
+ this.map.addLayer(lineLayer, beforeId);
14395
+ }
14396
+ } catch (error) {
14397
+ // Style may not be loaded yet; the styledata listener re-adds the layers
14398
+ }
14399
+ }
14400
+
14401
+ /**
14402
+ * Re-renders all circles: ensures the source and layers exist, then pushes
14403
+ * the current FeatureCollection. Called after every mutation of the circle
14404
+ * list, on floor changes, and when the map style reloads.
14405
+ */
14406
+ }, {
14407
+ key: "refreshCircles",
14408
+ value: function refreshCircles() {
14409
+ if (!this.map) return;
14410
+ if (this.circles.length === 0 && !this.map.getSource(CIRCLE_SOURCE_ID)) return;
14411
+ this.ensureCircleLayers();
14412
+ try {
14413
+ var source = this.map.getSource(CIRCLE_SOURCE_ID);
14414
+ source === null || source === void 0 || source.setData(this.circleFeatureCollection());
14415
+ } catch (error) {
14416
+ // Source may not exist while a new style is loading
14417
+ }
14418
+ }
13887
14419
 
13888
14420
  /**
13889
14421
  * Use it to change the current layer
@@ -13903,6 +14435,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
13903
14435
  }
13904
14436
  this.updateFiltersTo(floorKeyString);
13905
14437
  this.updateMarkersTo(floorKeyString);
14438
+ this.refreshCircles();
13906
14439
  this.routeController.updateRouteLayers(floorKeyString);
13907
14440
  this.routeController.updateRouteMarkerVisibility(floorKeyString);
13908
14441
  }
@@ -13914,12 +14447,12 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
13914
14447
  }, {
13915
14448
  key: "updateMarkersTo",
13916
14449
  value: function updateMarkersTo(floorId) {
13917
- var _this$innerFloors$fin6,
13918
- _this$innerFloors$fin7,
14450
+ var _this$innerFloors$fin8,
14451
+ _this$innerFloors$fin9,
13919
14452
  _this1 = this;
13920
- var isOutdoor = !this.parentPlace || ((_this$innerFloors$fin6 = (_this$innerFloors$fin7 = this.innerFloors.find(function (floor) {
14453
+ var isOutdoor = !this.parentPlace || ((_this$innerFloors$fin8 = (_this$innerFloors$fin9 = this.innerFloors.find(function (floor) {
13921
14454
  return floor.key === floorId;
13922
- })) === null || _this$innerFloors$fin7 === void 0 ? void 0 : _this$innerFloors$fin7.reachableFromGPS) !== null && _this$innerFloors$fin6 !== void 0 ? _this$innerFloors$fin6 : false);
14455
+ })) === null || _this$innerFloors$fin9 === void 0 ? void 0 : _this$innerFloors$fin9.reachableFromGPS) !== null && _this$innerFloors$fin8 !== void 0 ? _this$innerFloors$fin8 : false);
13923
14456
  this.markers.forEach(function (e) {
13924
14457
  e.changeFloor(floorId, _this1.map, isOutdoor);
13925
14458
  });
@@ -13928,19 +14461,19 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
13928
14461
  key: "updateFiltersTo",
13929
14462
  value: function updateFiltersTo(floorId) {
13930
14463
  var _this$map$getStyle$la2,
13931
- _this$map$getStyle2,
14464
+ _this$map$getStyle3,
13932
14465
  _this10 = this,
13933
- _this$innerFloors$fin8;
14466
+ _this$innerFloors$fin0;
13934
14467
  if (!this.map) return;
13935
- var layers = (_this$map$getStyle$la2 = (_this$map$getStyle2 = this.map.getStyle()) === null || _this$map$getStyle2 === void 0 ? void 0 : _this$map$getStyle2.layers) !== null && _this$map$getStyle$la2 !== void 0 ? _this$map$getStyle$la2 : [];
14468
+ var layers = (_this$map$getStyle$la2 = (_this$map$getStyle3 = this.map.getStyle()) === null || _this$map$getStyle3 === void 0 ? void 0 : _this$map$getStyle3.layers) !== null && _this$map$getStyle$la2 !== void 0 ? _this$map$getStyle$la2 : [];
13936
14469
  layers.filter(function (l) {
13937
14470
  return l.id.startsWith("base-indoor-");
13938
14471
  }).forEach(function (l) {
13939
14472
  _this10.map.removeLayer(l.id);
13940
14473
  });
13941
- var baseFloorId = (_this$innerFloors$fin8 = this.innerFloors.find(function (floor) {
14474
+ var baseFloorId = (_this$innerFloors$fin0 = this.innerFloors.find(function (floor) {
13942
14475
  return floor.key === floorId;
13943
- })) === null || _this$innerFloors$fin8 === void 0 ? void 0 : _this$innerFloors$fin8.baseFloor;
14476
+ })) === null || _this$innerFloors$fin0 === void 0 ? void 0 : _this$innerFloors$fin0.baseFloor;
13944
14477
  var indoorLayers = layers.filter(function (layer) {
13945
14478
  return layer.id.startsWith("indoor-");
13946
14479
  });
@@ -14457,7 +14990,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
14457
14990
  filter: ["all", ["has", "ref"], ["==", ["get", "ref"], "unselected"]],
14458
14991
  paint: {
14459
14992
  "fill-extrusion-height": 2.5,
14460
- "fill-extrusion-color": "#276EF1",
14993
+ "fill-extrusion-color": MAPVX_BRAND_COLOR,
14461
14994
  "fill-extrusion-opacity": 0.8
14462
14995
  }
14463
14996
  };
@@ -14470,7 +15003,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
14470
15003
  "source-layer": "area",
14471
15004
  filter: ["all", ["has", "ref"], ["==", ["get", "ref"], "unselected"]],
14472
15005
  paint: {
14473
- "fill-color": "#276EF1"
15006
+ "fill-color": MAPVX_BRAND_COLOR
14474
15007
  }
14475
15008
  };
14476
15009
  this.map.addLayer(_layer);
@@ -14479,10 +15012,10 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
14479
15012
  }, {
14480
15013
  key: "createBasicFilters",
14481
15014
  value: function createBasicFilters() {
14482
- var _this$parentPlaceId3, _this$currentFloor3;
15015
+ var _this$parentPlaceId3, _this$currentFloor4;
14483
15016
  var publicPlaceFilter = ["==", ["get", "public_place"], true];
14484
15017
  var subPlaceFilter = ["!", ["in", (_this$parentPlaceId3 = this.parentPlaceId) !== null && _this$parentPlaceId3 !== void 0 ? _this$parentPlaceId3 : "noRef", ["get", "is_subplace"]]];
14485
- var floorId = (_this$currentFloor3 = this.currentFloor) !== null && _this$currentFloor3 !== void 0 ? _this$currentFloor3 : "noRef";
15018
+ var floorId = (_this$currentFloor4 = this.currentFloor) !== null && _this$currentFloor4 !== void 0 ? _this$currentFloor4 : "noRef";
14486
15019
  var floorFilter = ["in", floorId, ["get", "floor_key"]];
14487
15020
  var showInFloorFilter = ["in", floorId, ["get", "show_in_floor"]];
14488
15021
  var basicFilters = publicPlaceFilter;
@@ -14864,7 +15397,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
14864
15397
  "source-layer": "area",
14865
15398
  filter: ["all", ["has", "ref"], ["==", ["get", "ref"], "unselected"]],
14866
15399
  paint: {
14867
- "line-color": "#276EF1",
15400
+ "line-color": MAPVX_BRAND_COLOR,
14868
15401
  "line-width": 4
14869
15402
  }
14870
15403
  };
@@ -17248,6 +17781,8 @@ function normalizeOrigin(raw) {
17248
17781
 
17249
17782
  // ─── Domain Models: Categories ───────────────────────────────────────────────
17250
17783
 
17784
+ // ─── Domain Models: Circle ───────────────────────────────────────────────────
17785
+
17251
17786
 
17252
17787
  // ─── Domain Models: City Filter ──────────────────────────────────────────────
17253
17788