@mapvx/web-js 3.5.0-dev.3 → 3.5.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.
Files changed (31) hide show
  1. package/dist/cjs/controllers/routeController.js +396 -106
  2. package/dist/cjs/controllers/routeController.js.map +1 -1
  3. package/dist/cjs/domain/models/routeInstance.js +1 -0
  4. package/dist/cjs/domain/models/routeInstance.js.map +1 -1
  5. package/dist/cjs/logger/sentry.js +1 -1
  6. package/dist/cjs/map/map.js +250 -24
  7. package/dist/cjs/map/map.js.map +1 -1
  8. package/dist/cjs/map/mapInteractionOptions.js +18 -0
  9. package/dist/cjs/map/mapInteractionOptions.js.map +1 -1
  10. package/dist/es/controllers/routeController.d.ts +91 -13
  11. package/dist/es/controllers/routeController.d.ts.map +1 -1
  12. package/dist/es/controllers/routeController.js +396 -106
  13. package/dist/es/controllers/routeController.js.map +1 -1
  14. package/dist/es/domain/models/mapConfig.d.ts +76 -0
  15. package/dist/es/domain/models/mapConfig.d.ts.map +1 -1
  16. package/dist/es/domain/models/routeInstance.d.ts +70 -1
  17. package/dist/es/domain/models/routeInstance.d.ts.map +1 -1
  18. package/dist/es/domain/models/routeInstance.js +1 -0
  19. package/dist/es/domain/models/routeInstance.js.map +1 -1
  20. package/dist/es/logger/sentry.js +1 -1
  21. package/dist/es/map/map.d.ts +76 -0
  22. package/dist/es/map/map.d.ts.map +1 -1
  23. package/dist/es/map/map.js +250 -24
  24. package/dist/es/map/map.js.map +1 -1
  25. package/dist/es/map/mapInteractionOptions.d.ts +5 -1
  26. package/dist/es/map/mapInteractionOptions.d.ts.map +1 -1
  27. package/dist/es/map/mapInteractionOptions.js +18 -0
  28. package/dist/es/map/mapInteractionOptions.js.map +1 -1
  29. package/dist/umd/index.js +844 -208
  30. package/dist/umd/index.js.map +1 -1
  31. package/package.json +1 -1
package/dist/umd/index.js CHANGED
@@ -11249,7 +11249,7 @@ const extractSafariExtensionDetails = (func, filename) => {
11249
11249
  // exists only for the type-checker, so in the ES/CJS builds the identifier is
11250
11250
  // genuinely absent and a direct comparison throws ReferenceError at module load.
11251
11251
  // `typeof` is the only safe way to probe a possibly-undeclared global.
11252
- var sentry_SDK_VERSION = false ? 0 : "3.5.0-dev.3";
11252
+ var sentry_SDK_VERSION = false ? 0 : "3.5.0";
11253
11253
  var DSN = false ? 0 : "https://872f598f020e3cd73a0933b562d24c81@sentry.mapvx.app/5";
11254
11254
 
11255
11255
  /**
@@ -13057,6 +13057,20 @@ function hasLabelSize(entry) {
13057
13057
  return entry.labelSize !== undefined;
13058
13058
  }
13059
13059
  ;// ./src/domain/models/routeInstance.ts
13060
+ /**
13061
+ * A floor switch the animation has decided on but not yet shown. It is applied
13062
+ * together with the next step's geometry, once that geometry is on screen, so
13063
+ * the marker never lands on a floor whose route line is not drawn yet.
13064
+ * @group Routes
13065
+ */
13066
+
13067
+ /**
13068
+ * Distances along the step being animated, in meters: `cumulative[i]` is the
13069
+ * length up to `step.coordinates[i]`, `total` the full step length. The drawn
13070
+ * part of the line is revealed as `traveled / total` via `line-progress`.
13071
+ * @group Routes
13072
+ */
13073
+
13060
13074
  /**
13061
13075
  * Represents the conditions for the step animation.
13062
13076
  * @group Routes
@@ -13099,7 +13113,8 @@ function createRouteInstance(route) {
13099
13113
  }
13100
13114
  },
13101
13115
  animationStatus: "NO_ROUTE",
13102
- stepAnimationStatus: "NO_ROUTE"
13116
+ stepAnimationStatus: "NO_ROUTE",
13117
+ layerDrawConfigs: {}
13103
13118
  };
13104
13119
  }
13105
13120
  ;// ./src/utils/nav-functions.ts
@@ -13289,6 +13304,10 @@ function routeController_toConsumableArray(r) { return routeController_arrayWith
13289
13304
  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."); }
13290
13305
  function routeController_iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
13291
13306
  function routeController_arrayWithoutHoles(r) { if (Array.isArray(r)) return routeController_arrayLikeToArray(r); }
13307
+ function routeController_slicedToArray(r, e) { return routeController_arrayWithHoles(r) || routeController_iterableToArrayLimit(r, e) || routeController_unsupportedIterableToArray(r, e) || routeController_nonIterableRest(); }
13308
+ function routeController_nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
13309
+ function routeController_iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
13310
+ function routeController_arrayWithHoles(r) { if (Array.isArray(r)) return r; }
13292
13311
  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; } } }; }
13293
13312
  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; } }
13294
13313
  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; }
@@ -13314,6 +13333,27 @@ var DEFAULT_AUTO_FIT_PADDING = {
13314
13333
  left: 150
13315
13334
  };
13316
13335
 
13336
+ /**
13337
+ * Longest a step waits for its line to be uploaded before animating anyway, so
13338
+ * a stalled worker never freezes the route.
13339
+ *
13340
+ * A deadline rather than a frame count. `animateStep` is handed a `timestamp`
13341
+ * already, and a frame count would stretch to three seconds or more on the
13342
+ * 20-30 fps panels this wait exists for — the devices where a frozen marker at
13343
+ * every step boundary is most visible.
13344
+ */
13345
+ var MAX_SOURCE_WAIT_MS = 1500;
13346
+
13347
+ /** Fully transparent: the part of a step line the reveal has not reached yet. */
13348
+ var TRANSPARENT_COLOR = "rgba(0, 0, 0, 0)";
13349
+
13350
+ /**
13351
+ * Width of the reveal cut-off, as a fraction of the step line. One texel of
13352
+ * maplibre's 256-sample gradient ramp, i.e. the narrowest edge the ramp can
13353
+ * actually resolve.
13354
+ */
13355
+ var REVEAL_EDGE = 1 / 256;
13356
+
13317
13357
  /**
13318
13358
  * Longest gap between two frames, in milliseconds, that still counts as
13319
13359
  * animation time.
@@ -13372,9 +13412,10 @@ var RouteController = /*#__PURE__*/function () {
13372
13412
  * Add MapLibre sources and layers for a single route instance.
13373
13413
  *
13374
13414
  * All three sources set `lineMetrics`: maplibre computes `line-progress` only
13375
- * when a source asks it to, and `preparePaintForLayer` emits a
13376
- * `line-progress` gradient for a GradientStyle. Without it, a consumer
13377
- * setting a gradient route style silently got no gradient at all.
13415
+ * when a source asks it to. The CURRENT layer needs it to reveal the step
13416
+ * line as the marker advances, and `preparePaintForLayer` emits a
13417
+ * `line-progress` gradient for a GradientStyle on any of the three — without
13418
+ * it, a consumer setting a gradient route style silently got no gradient.
13378
13419
  */
13379
13420
  }, {
13380
13421
  key: "addSourcesAndLayersForRoute",
@@ -13458,6 +13499,7 @@ var RouteController = /*#__PURE__*/function () {
13458
13499
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
13459
13500
  var instance = _step.value;
13460
13501
  this.addSourcesAndLayersForRoute(instance);
13502
+ this.restoreLayerPaint(instance);
13461
13503
  }
13462
13504
  } catch (err) {
13463
13505
  _iterator.e(err);
@@ -13467,6 +13509,38 @@ var RouteController = /*#__PURE__*/function () {
13467
13509
  this.syncRouteCollision();
13468
13510
  }
13469
13511
 
13512
+ /**
13513
+ * Re-apply the paint a style reload dropped.
13514
+ *
13515
+ * {@link addSourcesAndLayersForRoute} re-adds the layers with no paint at all, so
13516
+ * without this the route keeps its geometry but loses its colors — and the CURRENT
13517
+ * layer loses its `line-gradient`, which is the only thing hiding the part of the
13518
+ * step the marker has not reached yet: the whole step would be drawn at once.
13519
+ */
13520
+ }, {
13521
+ key: "restoreLayerPaint",
13522
+ value: function restoreLayerPaint(instance) {
13523
+ var _instance$revealFract;
13524
+ for (var _i2 = 0, _Object$entries = Object.entries(instance.layerDrawConfigs); _i2 < _Object$entries.length; _i2++) {
13525
+ var _Object$entries$_i = routeController_slicedToArray(_Object$entries[_i2], 2),
13526
+ layerId = _Object$entries$_i[0],
13527
+ drawConfig = _Object$entries$_i[1];
13528
+ this.setLayerPaint(instance, layerId, drawConfig);
13529
+ }
13530
+ // `?? 0`: a step whose geometry was loaded while the style was mid-swap
13531
+ // never got a reveal recorded, because `applyReveal` bails when there is no
13532
+ // layer to paint. Leaving the gradient off entirely then draws the whole
13533
+ // step at once in the traveled color, and the `awaitingSources` hold, the
13534
+ // rotation phase, the floor-change wait and a pause can each postpone the
13535
+ // frame that would correct it — indefinitely, in the case of a pause.
13536
+ // Revealing nothing is the safe default.
13537
+ var reveal = (_instance$revealFract = instance.revealFraction) !== null && _instance$revealFract !== void 0 ? _instance$revealFract : 0;
13538
+ // Force the update: `applyReveal` skips a fraction it believes is already on
13539
+ // the map, and the fresh layer has no gradient at all.
13540
+ instance.revealFraction = undefined;
13541
+ this.applyReveal(instance, reveal);
13542
+ }
13543
+
13470
13544
  /**
13471
13545
  * Recompute the GLB route-transparency collision path from all active routes and push it to the
13472
13546
  * map. Every polyline point of every step is tagged with the floor its segment is drawn on (same
@@ -13533,7 +13607,7 @@ var RouteController = /*#__PURE__*/function () {
13533
13607
  this.lastAddedRouteId = instance.id;
13534
13608
  this.addSourcesAndLayersForRoute(instance);
13535
13609
  this.setSourceData(instance, instance.aheadId, route.steps);
13536
- this.setLayerPaint(instance.aheadId, drawConfig);
13610
+ this.setLayerPaint(instance, instance.aheadId, drawConfig);
13537
13611
  this.updateLayerFilter(instance.aheadId);
13538
13612
  this.syncRouteCollision();
13539
13613
  }
@@ -13659,7 +13733,7 @@ var RouteController = /*#__PURE__*/function () {
13659
13733
  aheadSource === null || aheadSource === void 0 || aheadSource.setData(instance.aheadSourceData);
13660
13734
 
13661
13735
  // Ensure paint is set on the behind layer
13662
- this.setLayerPaint(instance.behindId, behindDrawConfig);
13736
+ this.setLayerPaint(instance, instance.behindId, behindDrawConfig);
13663
13737
  this.updateLayerFilter(instance.behindId);
13664
13738
  this.updateLayerFilter(instance.aheadId);
13665
13739
  }
@@ -13705,57 +13779,222 @@ var RouteController = /*#__PURE__*/function () {
13705
13779
  source === null || source === void 0 || source.setData(instance.behindSourceData);
13706
13780
  }
13707
13781
  }
13782
+
13783
+ /**
13784
+ * Upload the whole polyline of `step` as the CURRENT feature and reset its
13785
+ * reveal to zero. The line is then drawn progressively by moving a
13786
+ * `line-progress` cut-off (see {@link applyReveal}) instead of re-uploading a
13787
+ * growing geometry every frame: a paint change is applied synchronously, while
13788
+ * a `setData` goes through the worker and lands several frames after the DOM
13789
+ * marker already moved — on a slow device the marker visibly ran ahead of
13790
+ * its line.
13791
+ */
13708
13792
  }, {
13709
- key: "setCurrentSourceData",
13710
- value: function setCurrentSourceData(instance, step) {
13793
+ key: "loadStepGeometry",
13794
+ value: function loadStepGeometry(instance, step) {
13795
+ var _step$coordinates;
13711
13796
  instance.currentSourceData.properties = {
13712
13797
  floor_key: this.getFloorKeyForStep(step)
13713
13798
  };
13714
- var coordinate = step === null || step === void 0 ? void 0 : step.coordinates[0];
13715
- if (coordinate != null) {
13716
- instance.currentSourceData.geometry.coordinates = [[coordinate.lng, coordinate.lat]];
13717
- } else {
13718
- instance.currentSourceData.geometry.coordinates = [];
13719
- }
13799
+ var coordinates = (_step$coordinates = step === null || step === void 0 ? void 0 : step.coordinates) !== null && _step$coordinates !== void 0 ? _step$coordinates : [];
13800
+ // A LineString needs at least two positions; anything shorter draws nothing.
13801
+ instance.currentSourceData.geometry.coordinates = coordinates.length >= 2 ? coordinates.map(function (c) {
13802
+ return [c.lng, c.lat];
13803
+ }) : [];
13804
+ var cumulative = [0];
13805
+ for (var i = 1; i < coordinates.length; i++) {
13806
+ cumulative.push(cumulative[i - 1] + NavFunctions.distance(coordinates[i - 1], coordinates[i]));
13807
+ }
13808
+ instance.stepMetrics = {
13809
+ cumulative: cumulative,
13810
+ total: cumulative[cumulative.length - 1]
13811
+ };
13812
+ instance.revealFraction = undefined;
13813
+ this.applyReveal(instance, 0);
13720
13814
  var source = this.map.getSource(instance.currentId);
13721
13815
  source === null || source === void 0 || source.setData(instance.currentSourceData);
13722
13816
  }
13817
+
13818
+ /**
13819
+ * Fraction of the step line up to `coordinatesIndex` plus `extraMeters` along
13820
+ * the following segment.
13821
+ */
13822
+ }, {
13823
+ key: "fractionAlongStep",
13824
+ value: function fractionAlongStep(instance, coordinatesIndex, extraMeters) {
13825
+ var _metrics$cumulative$i;
13826
+ var metrics = instance.stepMetrics;
13827
+ if (!metrics || metrics.total <= 0) return 1;
13828
+ var index = Math.min(coordinatesIndex, metrics.cumulative.length - 1);
13829
+ return (((_metrics$cumulative$i = metrics.cumulative[index]) !== null && _metrics$cumulative$i !== void 0 ? _metrics$cumulative$i : 0) + extraMeters) / metrics.total;
13830
+ }
13831
+
13832
+ /**
13833
+ * Show the first `fraction` of the CURRENT step line: a `line-gradient` step on
13834
+ * `line-progress` that turns transparent past the cut-off. MapLibre treats it
13835
+ * as a plain paint update (a 256px ramp re-rendered on the next draw), so the
13836
+ * line head and the marker move in the same frame.
13837
+ *
13838
+ * The paint is set with `validate: false`: this runs on every animation frame and
13839
+ * the expression is built here, not taken from a consumer, so re-parsing and
13840
+ * re-validating it 60 times a second is pure overhead on a totem-class device.
13841
+ */
13842
+ }, {
13843
+ key: "applyReveal",
13844
+ value: function applyReveal(instance, fraction) {
13845
+ var color = instance.currentLineColor;
13846
+ if (color === undefined) return;
13847
+ var clamped = Math.max(0, Math.min(1, fraction));
13848
+ if (instance.revealFraction === clamped) return;
13849
+ // A full style swap removes the route's sources and layers and re-adds them a
13850
+ // few frames later. Painting a layer that is not there fires a MapLibre error on
13851
+ // every animation frame; `revealFraction` is deliberately left untouched so
13852
+ // `restoreLayerPaint` re-applies this reveal once the layers are back.
13853
+ if (!this.hasSource(instance.currentId)) return;
13854
+ instance.revealFraction = clamped;
13855
+ this.map.setPaintProperty(instance.currentId, "line-gradient", this.revealGradient(color, clamped), {
13856
+ validate: false
13857
+ });
13858
+ }
13859
+
13860
+ /**
13861
+ * The `line-gradient` that shows the first `fraction` of the step line and
13862
+ * nothing after it.
13863
+ *
13864
+ * Deliberately built on `interpolate` rather than the `step` this reads like.
13865
+ * `LineStyleLayer._handleSpecialPaintPropertyUpdate` sets `stepInterpolant`
13866
+ * for any `step` root, and `stepInterpolant` swaps the gradient ramp from a
13867
+ * fixed 256 texels to
13868
+ * `clamp(nextPowerOfTwo(lineLength * 1024 * potentialOverzoom), 256, maxTextureSize)`
13869
+ * — 4096 to 16384 in practice. Every `setPaintProperty` here bumps
13870
+ * `gradientVersion`, so that ramp would be re-rendered and re-uploaded once
13871
+ * per visible tile per frame, which is the opposite of what this reveal is
13872
+ * for. An `interpolate` root keeps the ramp at 256 texels; the cost is a
13873
+ * one-texel soft edge at the cut-off instead of a hard one.
13874
+ */
13875
+ }, {
13876
+ key: "revealGradient",
13877
+ value: function revealGradient(color, fraction) {
13878
+ if (fraction <= 0) return TRANSPARENT_COLOR;
13879
+ // `line-progress` reaches exactly 1 at the last vertex, so a fully revealed
13880
+ // line is just the line's own color, with no cut-off to express.
13881
+ if (fraction >= 1) return color;
13882
+ return ["interpolate", ["linear"], ["line-progress"], fraction, color,
13883
+ // `interpolate` clamps to its domain, so one texel past the cut-off is
13884
+ // enough to make everything beyond it transparent.
13885
+ Math.min(fraction + REVEAL_EDGE, 1), TRANSPARENT_COLOR];
13886
+ }
13887
+
13888
+ /**
13889
+ * Whether the route's three sources have finished uploading their latest
13890
+ * data, i.e. what is on screen matches what was last set. Maps without
13891
+ * `isSourceLoaded` (test doubles) count as ready.
13892
+ */
13893
+ }, {
13894
+ key: "stepSourcesReady",
13895
+ value: function stepSourcesReady(instance) {
13896
+ try {
13897
+ // CURRENT alone. It carries the step being drawn, which is what has to be
13898
+ // on screen for the line, the floor and the marker to change together.
13899
+ // AHEAD and BEHIND hold the whole route and are rewritten in full at every
13900
+ // boundary, so waiting on them made the freeze at each step scale with the
13901
+ // route's total length rather than the step's — and on a long route that
13902
+ // is the difference between imperceptible and hitting the deadline.
13903
+ if (!this.map.getSource(instance.currentId)) return false;
13904
+ return this.map.isSourceLoaded(instance.currentId) !== false;
13905
+ } catch (_unused3) {
13906
+ // The map is briefly style-less mid-swap and `getSource` throws there. This runs
13907
+ // inside a `requestAnimationFrame` callback, where an exception would kill the
13908
+ // animation loop for good, so treat it as "not ready yet" instead.
13909
+ return false;
13910
+ }
13911
+ }
13912
+
13913
+ /** Whether `sourceId` is currently on the map (false while a style swap is in flight). */
13914
+ }, {
13915
+ key: "hasSource",
13916
+ value: function hasSource(sourceId) {
13917
+ try {
13918
+ return this.map.getSource(sourceId) != null;
13919
+ } catch (_unused4) {
13920
+ return false;
13921
+ }
13922
+ }
13923
+
13924
+ /**
13925
+ * Apply a floor switch that was deferred until the next step's line was
13926
+ * uploaded, then put the marker where that step starts. Floor, line and
13927
+ * marker change in the same frame.
13928
+ */
13929
+ }, {
13930
+ key: "applyPendingFloor",
13931
+ value: function applyPendingFloor(instance) {
13932
+ var _instance$conditions;
13933
+ var pending = (_instance$conditions = instance.conditions) === null || _instance$conditions === void 0 ? void 0 : _instance$conditions.pendingFloor;
13934
+ if (!pending || !instance.conditions) return;
13935
+ instance.conditions.pendingFloor = undefined;
13936
+ this.updatePlaceFloorForRouting(instance, pending.placeId, pending.floorKey);
13937
+ this.setMarkerLngLat(instance, pending.location);
13938
+ }
13723
13939
  }, {
13724
13940
  key: "setLayerPaint",
13725
- value: function setLayerPaint(layerId, drawConfig) {
13941
+ value: function setLayerPaint(instance, layerId, drawConfig) {
13942
+ instance.layerDrawConfigs[layerId] = drawConfig;
13726
13943
  var paintConfig = this.preparePaintForLayer(drawConfig);
13727
13944
  for (var property in paintConfig) {
13728
13945
  this.map.setPaintProperty(layerId, property, paintConfig[property]);
13729
13946
  }
13730
13947
  }
13948
+
13949
+ /**
13950
+ * Set the paint the step line is drawn with, resolving its color once.
13951
+ *
13952
+ * See {@link RouteInstance.currentLineColor}: resolving a gradient allocates
13953
+ * and can throw, and the reveal would otherwise redo it on every frame.
13954
+ */
13955
+ }, {
13956
+ key: "setCurrentDrawConfig",
13957
+ value: function setCurrentDrawConfig(instance, drawConfig) {
13958
+ instance.currentDrawConfig = drawConfig;
13959
+ instance.currentLineColor = this.lineColorExpression(drawConfig);
13960
+ }
13961
+
13962
+ /** The color of a route line: a solid color or a `line-progress` gradient ramp. */
13731
13963
  }, {
13732
- key: "preparePaintForLayer",
13733
- value: function preparePaintForLayer(drawConfig) {
13964
+ key: "lineColorExpression",
13965
+ value: function lineColorExpression(drawConfig) {
13734
13966
  if ("color" in drawConfig.routeStyle) {
13735
13967
  // Check for propriety unique to SolidStyle
13968
+ return drawConfig.routeStyle.color;
13969
+ }
13970
+ // Is GradienteStyle
13971
+ var _drawConfig$routeStyl = drawConfig.routeStyle,
13972
+ colors = _drawConfig$routeStyl.colors,
13973
+ positions = _drawConfig$routeStyl.positions;
13974
+ var positionsToUse = positions !== null && positions !== void 0 ? positions : createPositionsForStops(colors.length);
13975
+ if (positionsToUse.length !== colors.length) {
13976
+ throw new Error("Error: position array has different length than colors array");
13977
+ }
13978
+ var stops = createStops(positionsToUse, colors);
13979
+ return ["interpolate", ["linear"], ["line-progress"]].concat(routeController_toConsumableArray(stops));
13980
+ }
13981
+ }, {
13982
+ key: "preparePaintForLayer",
13983
+ value: function preparePaintForLayer(drawConfig) {
13984
+ var color = this.lineColorExpression(drawConfig);
13985
+ if (typeof color === "string") {
13736
13986
  return {
13737
- "line-color": drawConfig.routeStyle.color,
13987
+ "line-color": color,
13738
13988
  "line-gradient": null,
13739
13989
  // null to unset the existing value
13740
13990
  "line-width": drawConfig.polylineWidth
13741
13991
  };
13742
- } else {
13743
- // Is GradienteStyle
13744
- var _drawConfig$routeStyl = drawConfig.routeStyle,
13745
- colors = _drawConfig$routeStyl.colors,
13746
- positions = _drawConfig$routeStyl.positions;
13747
- var positionsToUse = positions !== null && positions !== void 0 ? positions : createPositionsForStops(colors.length);
13748
- if (positionsToUse.length !== colors.length) {
13749
- throw new Error("Error: position array has different length than colors array");
13750
- }
13751
- var stops = createStops(positionsToUse, colors);
13752
- var gradient = ["interpolate", ["linear"], ["line-progress"]].concat(routeController_toConsumableArray(stops));
13753
- return {
13754
- "line-color": null,
13755
- "line-gradient": gradient,
13756
- "line-width": drawConfig.polylineWidth
13757
- };
13758
13992
  }
13993
+ return {
13994
+ "line-color": null,
13995
+ "line-gradient": color,
13996
+ "line-width": drawConfig.polylineWidth
13997
+ };
13759
13998
  }
13760
13999
 
13761
14000
  /**
@@ -13948,13 +14187,11 @@ var RouteController = /*#__PURE__*/function () {
13948
14187
  this.updatePlaceFloorForRouting(instance, firstStep.startInsidePlace, firstStep.startInsideFloor);
13949
14188
  this.setSourceData(instance, instance.aheadId, route.steps);
13950
14189
  this.setSourceData(instance, instance.behindId, []);
13951
- instance.currentSourceData.properties = {
13952
- floor_key: this.getFloorKeyForStep(firstStep)
13953
- };
13954
- instance.currentSourceData.geometry.coordinates = [[firstStep.coordinates[0].lng, firstStep.coordinates[0].lat]];
13955
- this.setLayerPaint(instance.aheadId, drawConfig.aheadConfig);
13956
- this.setLayerPaint(instance.behindId, drawConfig.behindConfig);
13957
- this.setLayerPaint(instance.currentId, drawConfig.behindConfig);
14190
+ this.setLayerPaint(instance, instance.aheadId, drawConfig.aheadConfig);
14191
+ this.setLayerPaint(instance, instance.behindId, drawConfig.behindConfig);
14192
+ this.setLayerPaint(instance, instance.currentId, drawConfig.behindConfig);
14193
+ this.setCurrentDrawConfig(instance, drawConfig.behindConfig);
14194
+ this.loadStepGeometry(instance, firstStep);
13958
14195
  instance.animationConfig = animationConfig;
13959
14196
  instance.routeMarker = new MarkerAttribute({
13960
14197
  coordinate: firstStep.startLocation,
@@ -13991,6 +14228,7 @@ var RouteController = /*#__PURE__*/function () {
13991
14228
  progress: 0,
13992
14229
  resetTime: false,
13993
14230
  waitStartTime: undefined,
14231
+ awaitingSources: true,
13994
14232
  animationId: requestAnimationFrame(function (time) {
13995
14233
  return _this2.animateStep(time, instance);
13996
14234
  }),
@@ -14004,10 +14242,30 @@ var RouteController = /*#__PURE__*/function () {
14004
14242
  });
14005
14243
  this.syncRouteCollision();
14006
14244
  }
14245
+
14246
+ /**
14247
+ * Tear one route instance down.
14248
+ *
14249
+ * `applyPendingFloorChange` decides what happens to a floor switch this
14250
+ * instance had decided on but not yet shown. Removing a single route mid-walk
14251
+ * applies it: the consumer may be removing the route from the step callback,
14252
+ * and dropping the switch would leave the map on the floor the route has
14253
+ * already left. Clearing every route does not — a floor switch is a visible
14254
+ * camera and filter change plus an `onFloorChange` to the consumer, and
14255
+ * `removeAllRoutes` is what teardown calls, where moving the map is
14256
+ * a side effect nobody asked for and a consumer that re-enters from
14257
+ * `onFloorChange` re-enters teardown.
14258
+ */
14007
14259
  }, {
14008
14260
  key: "clearRouteInstance",
14009
14261
  value: function clearRouteInstance(instance) {
14010
14262
  var _instance$routeMarker6;
14263
+ var applyPendingFloorChange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
14264
+ if (applyPendingFloorChange) {
14265
+ this.applyPendingFloor(instance);
14266
+ } else if (instance.conditions) {
14267
+ instance.conditions.pendingFloor = undefined;
14268
+ }
14011
14269
  (_instance$routeMarker6 = instance.routeMarker) === null || _instance$routeMarker6 === void 0 || _instance$routeMarker6.remove();
14012
14270
  instance.routeMarker = undefined;
14013
14271
  this.setSourceData(instance, instance.aheadId, []);
@@ -14055,7 +14313,7 @@ var RouteController = /*#__PURE__*/function () {
14055
14313
  try {
14056
14314
  for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
14057
14315
  var instance = _step8.value;
14058
- this.clearRouteInstance(instance);
14316
+ this.clearRouteInstance(instance, false);
14059
14317
  this.removeSourcesAndLayersForRoute(instance);
14060
14318
  }
14061
14319
  } catch (err) {
@@ -14152,12 +14410,7 @@ var RouteController = /*#__PURE__*/function () {
14152
14410
  this.updatePlaceFloorForRouting(instance, firstStep.startInsidePlace, firstStep.startInsideFloor);
14153
14411
  this.setSourceData(instance, instance.aheadId, instance.route.steps);
14154
14412
  this.setSourceData(instance, instance.behindId, []);
14155
- instance.currentSourceData.properties = {
14156
- floor_key: this.getFloorKeyForStep(firstStep)
14157
- };
14158
- instance.currentSourceData.geometry.coordinates = [[firstStep.coordinates[0].lng, firstStep.coordinates[0].lat]];
14159
- var source = this.map.getSource(instance.currentId);
14160
- source === null || source === void 0 || source.setData(instance.currentSourceData);
14413
+ this.loadStepGeometry(instance, firstStep);
14161
14414
  (_this$setMarkerRotati = this.setMarkerRotation(instance, 0)) === null || _this$setMarkerRotati === void 0 || _this$setMarkerRotati.setLngLat(firstStep.startLocation).addTo(this.map);
14162
14415
 
14163
14416
  // Auto-fit by place if enabled
@@ -14189,6 +14442,7 @@ var RouteController = /*#__PURE__*/function () {
14189
14442
  progress: 0,
14190
14443
  resetTime: false,
14191
14444
  waitStartTime: undefined,
14445
+ awaitingSources: true,
14192
14446
  animationId: requestAnimationFrame(function (time) {
14193
14447
  return _this4.animateStep(time, instance);
14194
14448
  }),
@@ -14240,10 +14494,11 @@ var RouteController = /*#__PURE__*/function () {
14240
14494
  this.updatePlaceFloorForRouting(instance, firstStep.startInsidePlace, firstStep.startInsideFloor);
14241
14495
  this.setSourceData(instance, instance.aheadId, route.steps);
14242
14496
  this.setSourceData(instance, instance.behindId, []);
14243
- this.setCurrentSourceData(instance, firstStep);
14244
- this.setLayerPaint(instance.aheadId, drawConfig.aheadConfig);
14245
- this.setLayerPaint(instance.behindId, drawConfig.behindConfig);
14246
- this.setLayerPaint(instance.currentId, drawConfig.behindConfig);
14497
+ this.setLayerPaint(instance, instance.aheadId, drawConfig.aheadConfig);
14498
+ this.setLayerPaint(instance, instance.behindId, drawConfig.behindConfig);
14499
+ this.setLayerPaint(instance, instance.currentId, drawConfig.behindConfig);
14500
+ this.setCurrentDrawConfig(instance, drawConfig.behindConfig);
14501
+ this.loadStepGeometry(instance, firstStep);
14247
14502
  instance.animationConfig = animationConfig;
14248
14503
  instance.routeMarker = new MarkerAttribute({
14249
14504
  coordinate: firstStep.startLocation,
@@ -14293,7 +14548,7 @@ var RouteController = /*#__PURE__*/function () {
14293
14548
  var step = instance.route.steps[index];
14294
14549
  _this5.setSourceData(instance, instance.behindId, behindSteps);
14295
14550
  _this5.setSourceData(instance, instance.aheadId, aheadSteps);
14296
- _this5.setCurrentSourceData(instance, step);
14551
+ _this5.loadStepGeometry(instance, step);
14297
14552
  instance.conditions.index = index;
14298
14553
 
14299
14554
  // The status settles before either callback, as everywhere else: no frame
@@ -14406,7 +14661,7 @@ var RouteController = /*#__PURE__*/function () {
14406
14661
  var step = instance.route.steps[index];
14407
14662
  this.setSourceData(instance, instance.behindId, behindSteps);
14408
14663
  this.setSourceData(instance, instance.aheadId, aheadSteps);
14409
- this.setCurrentSourceData(instance, step);
14664
+ this.loadStepGeometry(instance, step);
14410
14665
  if (step.startInsideFloor != step.endInsideFloor && step.startInsideFloor) {
14411
14666
  this.updatePlaceFloorForRouting(instance, step.startInsidePlace, step.startInsideFloor);
14412
14667
  }
@@ -14484,19 +14739,40 @@ var RouteController = /*#__PURE__*/function () {
14484
14739
  var aheadSteps = instance.route.steps.slice(index + 1);
14485
14740
  this.setSourceData(instance, instance.behindId, behindSteps);
14486
14741
  this.setSourceData(instance, instance.aheadId, aheadSteps);
14487
- instance.currentSourceData.geometry.coordinates = [];
14488
14742
  if (index + 1 < instance.route.steps.length) {
14489
- var _instance$animationCo9, _instance$animationCo1, _instance$animationCo10;
14743
+ var _instance$animationCo9;
14490
14744
  var step = instance.route.steps[index + 1];
14491
- instance.currentSourceData.geometry.coordinates.push([step.startLocation.lng, step.startLocation.lat]);
14492
- instance.currentSourceData.properties = {
14493
- floor_key: this.getFloorKeyForStep(step)
14494
- };
14745
+ this.loadStepGeometry(instance, step);
14495
14746
  instance.conditions.startTime = timestamp;
14496
14747
  instance.conditions.index++;
14497
14748
  instance.conditions.coordinatesIndex = 0;
14498
14749
  instance.conditions.progress = 0;
14499
14750
  instance.conditions.rotatingIcon = true;
14751
+ // The step starts once its line (and the updated behind/ahead lines) are
14752
+ // on screen; a floor switch decided by the previous step, or implied by a
14753
+ // step that starts on another floor, is applied at that same moment.
14754
+ instance.conditions.awaitingSources = true;
14755
+ instance.conditions.awaitDeadline = undefined;
14756
+ // Compared against the floor the PREVIOUS step ended on, not the map's current
14757
+ // floor: the route decides its own floor changes, so a user who browsed to
14758
+ // another floor mid-animation is not yanked back at every step boundary.
14759
+ var previousStep = instance.route.steps[index];
14760
+ // Against the previous step's *effective* floor, not its raw
14761
+ // `endInsideFloor`. Venue data routinely carries `startInsideFloor` with
14762
+ // no `endInsideFloor` — `getFloorKeyForStep` exists to resolve exactly
14763
+ // that shape — and comparing against the raw field made every boundary
14764
+ // on such a route look like a floor change: the map moved, the consumer
14765
+ // got an `onFloorChange`, a settle poll ran and the step callback was
14766
+ // deferred a frame, at each step. A user who had browsed to another floor
14767
+ // was yanked back, which is what the deferral above is meant to avoid.
14768
+ var previousFloor = this.getFloorKeyForStep(previousStep);
14769
+ if (!instance.conditions.pendingFloor && step.startInsideFloor && step.startInsideFloor !== previousFloor) {
14770
+ instance.conditions.pendingFloor = {
14771
+ placeId: step.startInsidePlace,
14772
+ floorKey: step.startInsideFloor,
14773
+ location: step.startLocation
14774
+ };
14775
+ }
14500
14776
  if (!compareLatLng(instance.route.steps[index].endLocation, step.startLocation)) instance.conditions.nextFloorFirstStepIndex = index + 2;
14501
14777
  instance.conditions.animationId = requestAnimationFrame(function (time) {
14502
14778
  return _this7.animateStep(time, instance);
@@ -14514,12 +14790,30 @@ var RouteController = /*#__PURE__*/function () {
14514
14790
  this.mapvxMap.fitRouteByPlace(instance.route, stepPlaceId, fitOptions);
14515
14791
  }
14516
14792
  }
14517
- (_instance$animationCo1 = (_instance$animationCo10 = instance.animationConfig).callBack) === null || _instance$animationCo1 === void 0 || _instance$animationCo1.call(_instance$animationCo10, {
14518
- isFinished: false,
14519
- step: instance.route.steps[index + 1]
14520
- });
14793
+ if (instance.conditions.pendingFloor) {
14794
+ // The switch lands a few frames later, in `animateStep`. Firing the callback
14795
+ // now would report the step while the map is still on the previous floor —
14796
+ // and a consumer that stops the route from it would drop the switch entirely.
14797
+ instance.conditions.pendingStepCallback = step;
14798
+ } else {
14799
+ var _instance$animationCo1, _instance$animationCo10;
14800
+ (_instance$animationCo1 = (_instance$animationCo10 = instance.animationConfig).callBack) === null || _instance$animationCo1 === void 0 || _instance$animationCo1.call(_instance$animationCo10, {
14801
+ isFinished: false,
14802
+ step: step
14803
+ });
14804
+ }
14521
14805
  } else {
14522
14806
  var _instance$animationCo11, _instance$animationCo12;
14807
+ // finishes animation
14808
+ this.applyPendingFloor(instance);
14809
+ // That reached `onFloorChange`, and a consumer removing the route from it
14810
+ // has already had `clearRouteInstance` null `animationConfig` and settle
14811
+ // the status at "NO_ROUTE". Both lines below write through it — the
14812
+ // `?.` is on `callBack`, not on `animationConfig` — so without this the
14813
+ // branch threw a TypeError out of the rAF callback, and overwrote a
14814
+ // finished instance's status with "FINISHED". `animateStep`'s release
14815
+ // path takes the same guard after the same call.
14816
+ if (instance.conditions == null || instance.animationConfig == null) return;
14523
14817
  // The status settles before the consumer hears that the route finished,
14524
14818
  // for the same reason `startAnimating` and `restartAnimation` order it
14525
14819
  // that way. A consumer that relaunches from this callback — looping a
@@ -14542,17 +14836,17 @@ var RouteController = /*#__PURE__*/function () {
14542
14836
  if (instance.conditions == null || instance.animationConfig == null || instance.route == null) {
14543
14837
  throw new Error("Error: conditions and animationConfig should not be null at this point");
14544
14838
  }
14545
- var _instance$conditions = instance.conditions,
14546
- startTime = _instance$conditions.startTime,
14547
- resetTime = _instance$conditions.resetTime,
14548
- progress = _instance$conditions.progress,
14549
- waitStartTime = _instance$conditions.waitStartTime;
14550
14839
  var _instance$conditions2 = instance.conditions,
14551
- coordinatesIndex = _instance$conditions2.coordinatesIndex,
14552
- iconRotation = _instance$conditions2.iconRotation,
14553
- index = _instance$conditions2.index,
14554
- onFinish = _instance$conditions2.onFinish,
14555
- rotatingIcon = _instance$conditions2.rotatingIcon;
14840
+ startTime = _instance$conditions2.startTime,
14841
+ resetTime = _instance$conditions2.resetTime,
14842
+ progress = _instance$conditions2.progress,
14843
+ waitStartTime = _instance$conditions2.waitStartTime;
14844
+ var _instance$conditions3 = instance.conditions,
14845
+ coordinatesIndex = _instance$conditions3.coordinatesIndex,
14846
+ iconRotation = _instance$conditions3.iconRotation,
14847
+ index = _instance$conditions3.index,
14848
+ onFinish = _instance$conditions3.onFinish,
14849
+ rotatingIcon = _instance$conditions3.rotatingIcon;
14556
14850
  var _instance$animationCo13 = instance.animationConfig,
14557
14851
  stepTime = _instance$animationCo13.stepTime,
14558
14852
  minimumSpeed = _instance$animationCo13.minimumSpeed,
@@ -14571,6 +14865,62 @@ var RouteController = /*#__PURE__*/function () {
14571
14865
  });
14572
14866
  return;
14573
14867
  }
14868
+ if (instance.conditions.awaitingSources) {
14869
+ var _instance$conditions$;
14870
+ var deadline = (_instance$conditions$ = instance.conditions.awaitDeadline) !== null && _instance$conditions$ !== void 0 ? _instance$conditions$ : timestamp + MAX_SOURCE_WAIT_MS;
14871
+ // The deadline is wall clock, and this block returns before the frame-gap
14872
+ // branch below can ever run, so it needs that branch's shift applied here
14873
+ // — the same one `waitStartTime` gets, for the same reason. A backgrounded
14874
+ // tab or a pause longer than the deadline would otherwise release the step
14875
+ // on the first frame back without once asking whether its line is on
14876
+ // screen, which is the desync the wait exists to prevent.
14877
+ if (previousFrameTime != null && timestamp - previousFrameTime > MAX_FRAME_GAP_MS) {
14878
+ deadline += timestamp - previousFrameTime;
14879
+ }
14880
+ instance.conditions.awaitDeadline = deadline;
14881
+ if (!this.stepSourcesReady(instance) && timestamp < deadline) {
14882
+ // Hold the clock: the step starts when its line can be seen.
14883
+ instance.conditions.startTime = timestamp;
14884
+ instance.conditions.animationId = requestAnimationFrame(function (time) {
14885
+ return _this8.animateStep(time, instance);
14886
+ });
14887
+ return;
14888
+ }
14889
+ instance.conditions.awaitingSources = false;
14890
+ instance.conditions.awaitDeadline = undefined;
14891
+ instance.conditions.startTime = timestamp;
14892
+ var heldStep = instance.conditions.pendingStepCallback;
14893
+ instance.conditions.pendingStepCallback = undefined;
14894
+
14895
+ // The frame that releases the wait draws nothing: it queues its successor
14896
+ // and hands control to the consumer, and that successor does the drawing.
14897
+ //
14898
+ // The queue has to come first. Both calls below reach consumer code —
14899
+ // `applyPendingFloor` through the `onFloorChange` handler, and the held
14900
+ // step callback directly — and a `pauseAnimation()` from either one
14901
+ // cancels whatever id sits in `conditions.animationId`. While that id was
14902
+ // the frame already executing, cancelling it did nothing: the loop kept
14903
+ // running under a `PAUSED` status, and the matching `resumeAnimation()`
14904
+ // passed its status guard and installed a second concurrent rAF chain on
14905
+ // the same instance. Queueing first gives that pause a pending frame to
14906
+ // cancel, and returning afterwards means this frame does not queue a
14907
+ // replacement for the one it just cancelled.
14908
+ instance.conditions.animationId = requestAnimationFrame(function (time) {
14909
+ return _this8.animateStep(time, instance);
14910
+ });
14911
+ this.applyPendingFloor(instance);
14912
+ // `onFloorChange` may have removed the route; everything below reads
14913
+ // `conditions`.
14914
+ if (instance.conditions == null || instance.animationConfig == null) return;
14915
+ if (heldStep) {
14916
+ var _instance$animationCo14, _instance$animationCo15;
14917
+ (_instance$animationCo14 = (_instance$animationCo15 = instance.animationConfig).callBack) === null || _instance$animationCo14 === void 0 || _instance$animationCo14.call(_instance$animationCo15, {
14918
+ isFinished: false,
14919
+ step: heldStep
14920
+ });
14921
+ }
14922
+ return;
14923
+ }
14574
14924
  if (resetTime) {
14575
14925
  startTime = timestamp - progress;
14576
14926
  instance.conditions.startTime = startTime;
@@ -14644,7 +14994,16 @@ var RouteController = /*#__PURE__*/function () {
14644
14994
  return;
14645
14995
  }
14646
14996
  }
14647
- if (step.startInsideFloor !== step.endInsideFloor) {
14997
+
14998
+ // There has to be a floor to switch *to*. An absent `startInsideFloor` is
14999
+ // meaningful — that is a step entering the building from outdoors — but an
15000
+ // absent `endInsideFloor` is not: venue data routinely omits it on a step
15001
+ // that does not change floor, and reading that as "different" made every
15002
+ // such step stall for `changeFloorTime` and then queue a switch whose
15003
+ // `floorKey` was `undefined`. `updatePlaceFloorForRouting` drops that
15004
+ // silently, but not before it has occupied the `pendingFloor` slot the
15005
+ // step boundary needed for a real one.
15006
+ if (step.endInsideFloor && step.startInsideFloor !== step.endInsideFloor) {
14648
15007
  if (waitStartTime == null) {
14649
15008
  instance.conditions.waitStartTime = timestamp;
14650
15009
  instance.conditions.animationId = requestAnimationFrame(function (time) {
@@ -14658,9 +15017,20 @@ var RouteController = /*#__PURE__*/function () {
14658
15017
  });
14659
15018
  return;
14660
15019
  } else {
14661
- this.updatePlaceFloorForRouting(instance, step.endInsidePlace, step.endInsideFloor);
14662
- this.setMarkerLngLat(instance, step.endLocation);
14663
15020
  instance.conditions.waitStartTime = undefined;
15021
+ if (instance.conditions.isStepByStep) {
15022
+ // Step-by-step mode is user-driven: switch right away, as before.
15023
+ this.updatePlaceFloorForRouting(instance, step.endInsidePlace, step.endInsideFloor);
15024
+ this.setMarkerLngLat(instance, step.endLocation);
15025
+ } else {
15026
+ // Defer the switch until the next step's line is uploaded, so floor,
15027
+ // line and marker change together (see applyPendingFloor).
15028
+ instance.conditions.pendingFloor = {
15029
+ placeId: step.endInsidePlace,
15030
+ floorKey: step.endInsideFloor,
15031
+ location: step.endLocation
15032
+ };
15033
+ }
14664
15034
  onFinish === null || onFinish === void 0 || onFinish(timestamp);
14665
15035
  return;
14666
15036
  }
@@ -14677,23 +15047,17 @@ var RouteController = /*#__PURE__*/function () {
14677
15047
  // ignored `stepTime` altogether and collapsed to `vertexCount / fps`.
14678
15048
  // Encoded polylines quantise near 1.1 m, so that regime was the common
14679
15049
  // one, not the corner case.
15050
+ //
15051
+ // The step's whole polyline is already uploaded (see `loadStepGeometry`),
15052
+ // so a frame commits nothing: it only moves the reveal cut-off, which is
15053
+ // the same distance that moves the marker.
14680
15054
  var segmentStart = start;
14681
15055
  var segmentEnd = end;
14682
15056
  var segmentDistance = NavFunctions.distance(segmentStart, segmentEnd);
14683
-
14684
- // Drop the previous frame's interpolated tip before committing anything.
14685
- // Coordinates up to and including the current vertex are settled geometry
14686
- // — exactly `coordinatesIndex + 1` of them — and the tip is a single
14687
- // moving point after them, re-placed at the end of this frame. Appending
14688
- // one per frame instead grew the drawn line with elapsed frames, well
14689
- // past the polyline's own vertex count, and `setData` re-indexed every
14690
- // one of those points on the next frame.
14691
- var coordinates = instance.currentSourceData.geometry.coordinates;
14692
- coordinates.length = Math.min(coordinates.length, instance.conditions.coordinatesIndex + 1);
14693
15057
  while (speed * progressInSeconds > segmentDistance) {
14694
- coordinates.push([segmentEnd.lng, segmentEnd.lat]);
14695
15058
  if (instance.conditions.coordinatesIndex + 2 >= step.coordinates.length) {
14696
- this.flushCurrentSourceData(instance);
15059
+ // `segmentEnd` is the step's last vertex: reveal the line in full.
15060
+ this.applyReveal(instance, this.fractionAlongStep(instance, instance.conditions.coordinatesIndex + 1, 0));
14697
15061
  onFinish === null || onFinish === void 0 || onFinish(timestamp);
14698
15062
  return;
14699
15063
  }
@@ -14724,7 +15088,8 @@ var RouteController = /*#__PURE__*/function () {
14724
15088
  instance.conditions.rotatingIcon = true;
14725
15089
  instance.conditions.startTime = timestamp;
14726
15090
  instance.conditions.progress = 0;
14727
- this.flushCurrentSourceData(instance);
15091
+ // The marker stops on the vertex it turns about, so the line does too.
15092
+ this.applyReveal(instance, this.fractionAlongStep(instance, instance.conditions.coordinatesIndex, 0));
14728
15093
  instance.conditions.animationId = requestAnimationFrame(function (time) {
14729
15094
  return _this8.animateStep(time, instance);
14730
15095
  });
@@ -14738,10 +15103,12 @@ var RouteController = /*#__PURE__*/function () {
14738
15103
  segmentEnd = next;
14739
15104
  segmentDistance = NavFunctions.distance(segmentStart, segmentEnd);
14740
15105
  }
14741
- var fraction = speed * progressInSeconds / segmentDistance;
14742
- var point = segmentDistance === 0 ? segmentEnd : NavFunctions.intermediatePointBetween(segmentStart, segmentEnd, Math.min(fraction, 1));
14743
- coordinates.push([point.lng, point.lat]);
14744
- this.flushCurrentSourceData(instance);
15106
+ var traveled = Math.min(speed * progressInSeconds, segmentDistance);
15107
+ var point = segmentDistance === 0 ? segmentEnd : NavFunctions.intermediatePointBetween(segmentStart, segmentEnd, traveled / segmentDistance);
15108
+ // The same distance drives the reveal cut-off and the marker, and the
15109
+ // reveal is a synchronous paint update rather than a `setData` that lands
15110
+ // several frames later, so the two cannot drift apart.
15111
+ this.applyReveal(instance, this.fractionAlongStep(instance, instance.conditions.coordinatesIndex, traveled));
14745
15112
  this.setMarkerLngLat(instance, point);
14746
15113
  // Only when it has genuinely left the DOM. maplibre's `Marker.addTo`
14747
15114
  // opens with `remove()`, so re-attaching an attached marker tears down
@@ -14769,26 +15136,11 @@ var RouteController = /*#__PURE__*/function () {
14769
15136
  return;
14770
15137
  }
14771
15138
  }
14772
-
14773
- /**
14774
- * Pushes the in-progress segment's geometry to its map source.
14775
- *
14776
- * Called once per frame rather than once per vertex crossed: `setData`
14777
- * rebuilds the whole geojson-vt index and reloads every in-view tile, so its
14778
- * cost is a large fixed one per call and is worth paying only once, with the
14779
- * frame's final geometry.
14780
- */
14781
- }, {
14782
- key: "flushCurrentSourceData",
14783
- value: function flushCurrentSourceData(instance) {
14784
- var source = this.map.getSource(instance.currentId);
14785
- source === null || source === void 0 || source.setData(instance.currentSourceData);
14786
- }
14787
15139
  }, {
14788
15140
  key: "setMarkerLngLat",
14789
15141
  value: function setMarkerLngLat(instance, point) {
14790
- var _instance$conditions3, _instance$routeMarker9;
14791
- if ((_instance$conditions3 = instance.conditions) !== null && _instance$conditions3 !== void 0 && _instance$conditions3.isStepByStep) {
15142
+ var _instance$conditions4, _instance$routeMarker9;
15143
+ if ((_instance$conditions4 = instance.conditions) !== null && _instance$conditions4 !== void 0 && _instance$conditions4.isStepByStep) {
14792
15144
  this.cameraFollow(instance, point);
14793
15145
  }
14794
15146
  (_instance$routeMarker9 = instance.routeMarker) === null || _instance$routeMarker9 === void 0 || _instance$routeMarker9.setLngLat(point);
@@ -14796,11 +15148,11 @@ var RouteController = /*#__PURE__*/function () {
14796
15148
  }, {
14797
15149
  key: "setMarkerRotation",
14798
15150
  value: function setMarkerRotation(instance, angle) {
14799
- var _instance$conditions4, _instance$animationCo14, _instance$routeMarker1;
14800
- if ((_instance$conditions4 = instance.conditions) !== null && _instance$conditions4 !== void 0 && _instance$conditions4.isStepByStep && !((_instance$animationCo14 = instance.animationConfig) !== null && _instance$animationCo14 !== void 0 && _instance$animationCo14.keepFixedBearing)) {
14801
- var _instance$animationCo15, _instance$routeMarker0;
15151
+ var _instance$conditions5, _instance$animationCo16, _instance$routeMarker1;
15152
+ if ((_instance$conditions5 = instance.conditions) !== null && _instance$conditions5 !== void 0 && _instance$conditions5.isStepByStep && !((_instance$animationCo16 = instance.animationConfig) !== null && _instance$animationCo16 !== void 0 && _instance$animationCo16.keepFixedBearing)) {
15153
+ var _instance$animationCo17, _instance$routeMarker0;
14802
15154
  this.mapvxMap.updateCamera({
14803
- duration: (_instance$animationCo15 = instance.animationConfig) === null || _instance$animationCo15 === void 0 ? void 0 : _instance$animationCo15.iconRotationTime,
15155
+ duration: (_instance$animationCo17 = instance.animationConfig) === null || _instance$animationCo17 === void 0 ? void 0 : _instance$animationCo17.iconRotationTime,
14804
15156
  animate: true,
14805
15157
  bearing: angle,
14806
15158
  center: (_instance$routeMarker0 = instance.routeMarker) === null || _instance$routeMarker0 === void 0 ? void 0 : _instance$routeMarker0.getLngLat()
@@ -14818,12 +15170,12 @@ var RouteController = /*#__PURE__*/function () {
14818
15170
  }, {
14819
15171
  key: "cameraFollow",
14820
15172
  value: function cameraFollow(instance, point) {
14821
- var _instance$animationCo16, _instance$animationCo17, _instance$animationCo18, _instance$routeMarker11;
14822
- var keepFixedBearing = (_instance$animationCo16 = (_instance$animationCo17 = instance.animationConfig) === null || _instance$animationCo17 === void 0 ? void 0 : _instance$animationCo17.keepFixedBearing) !== null && _instance$animationCo16 !== void 0 ? _instance$animationCo16 : false;
15173
+ var _instance$animationCo18, _instance$animationCo19, _instance$animationCo20, _instance$routeMarker11;
15174
+ var keepFixedBearing = (_instance$animationCo18 = (_instance$animationCo19 = instance.animationConfig) === null || _instance$animationCo19 === void 0 ? void 0 : _instance$animationCo19.keepFixedBearing) !== null && _instance$animationCo18 !== void 0 ? _instance$animationCo18 : false;
14823
15175
  this.mapvxMap.updateCamera(routeController_objectSpread({
14824
15176
  center: point,
14825
15177
  animate: true,
14826
- duration: (_instance$animationCo18 = instance.animationConfig) === null || _instance$animationCo18 === void 0 ? void 0 : _instance$animationCo18.iconRotationTime
15178
+ duration: (_instance$animationCo20 = instance.animationConfig) === null || _instance$animationCo20 === void 0 ? void 0 : _instance$animationCo20.iconRotationTime
14827
15179
  }, keepFixedBearing ? {} : {
14828
15180
  bearing: (_instance$routeMarker11 = instance.routeMarker) === null || _instance$routeMarker11 === void 0 ? void 0 : _instance$routeMarker11.getRotation()
14829
15181
  }));
@@ -21753,6 +22105,13 @@ function refineCameraForPitch(transform, coordinates, initial, params) {
21753
22105
  return best;
21754
22106
  }
21755
22107
  ;// ./src/map/mapInteractionOptions.ts
22108
+ /**
22109
+ * Highest pitch MapLibre accepts for `minPitch`/`maxPitch` (it rejects anything
22110
+ * above 180); a locked pitch is clamped to it so the lock can never contradict
22111
+ * the {@link MapConfig.pitch} the map was built with.
22112
+ */
22113
+ var MAX_LOCKED_PITCH = 180;
22114
+
21756
22115
  /**
21757
22116
  * Subset of MapLibre {@link MapOptions} that can be derived from the interaction
21758
22117
  * restriction flags on {@link MapConfig}.
@@ -21774,6 +22133,10 @@ function refineCameraForPitch(transform, coordinates, initial, params) {
21774
22133
  * pinch-to-zoom, so it is handled separately after construction — see
21775
22134
  * {@link shouldDisableTouchRotation}.
21776
22135
  *
22136
+ * `pitchEnabled: false` freezes the tilt: the pitch gestures are disabled and
22137
+ * `minPitch`/`maxPitch` are pinned to {@link MapConfig.pitch}, so neither the
22138
+ * user nor a later camera update can change it.
22139
+ *
21777
22140
  * @internal
21778
22141
  */
21779
22142
  function buildInteractionOptions(mapConfig) {
@@ -21789,6 +22152,14 @@ function buildInteractionOptions(mapConfig) {
21789
22152
  options.dragRotate = false;
21790
22153
  options.pitchWithRotate = false;
21791
22154
  }
22155
+ if (mapConfig.pitchEnabled === false) {
22156
+ var _mapConfig$pitch;
22157
+ var lockedPitch = Math.min(Math.max((_mapConfig$pitch = mapConfig.pitch) !== null && _mapConfig$pitch !== void 0 ? _mapConfig$pitch : 0, 0), MAX_LOCKED_PITCH);
22158
+ options.touchPitch = false;
22159
+ options.pitchWithRotate = false;
22160
+ options.minPitch = lockedPitch;
22161
+ options.maxPitch = lockedPitch;
22162
+ }
21792
22163
  return options;
21793
22164
  }
21794
22165
 
@@ -21878,6 +22249,24 @@ var TILE_RETRY_BASE_DELAY_MS = 1000;
21878
22249
  */
21879
22250
  var STYLE_READY_TIMEOUT_MS = 4000;
21880
22251
 
22252
+ /**
22253
+ * Frames a floor change waits before it will believe the indoor tiles are
22254
+ * already rebuilt (see `updateMarkersWhenFloorSettles`). Genuinely a frame
22255
+ * count: the reload is dispatched on the map's next render, so this is how
22256
+ * many renders it gets to be observed in.
22257
+ */
22258
+ var FLOOR_SETTLE_MIN_FRAMES = 3;
22259
+
22260
+ /**
22261
+ * Longest a floor change waits for the new floor's tiles before switching the
22262
+ * HTML markers anyway, so a stalled source never hides markers for good.
22263
+ *
22264
+ * A deadline rather than a frame count. The panels this matters on run at
22265
+ * 20-30 fps, where a 60-frame cap is two to three seconds — and the wait also
22266
+ * gates `addMarker`, so overshooting it is not merely a late marker switch.
22267
+ */
22268
+ var FLOOR_SETTLE_MAX_MS = 1000;
22269
+
21881
22270
  /** Bookkeeping for one tile in the retry queue. */
21882
22271
 
21883
22272
  /** Fill/line layer ids for one circle render-order bucket. */
@@ -22115,6 +22504,20 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22115
22504
  */
22116
22505
  map_defineProperty(_this, "tileRetryState", new WeakMap());
22117
22506
  map_defineProperty(_this, "geoLocation", navigator.geolocation);
22507
+ /** Floor-change settle bookkeeping (see `updateMarkersWhenFloorSettles`). */
22508
+ map_defineProperty(_this, "floorSettleToken", 0);
22509
+ /**
22510
+ * When the floor-change settle stops holding markers back, or `null` when no
22511
+ * change is in flight.
22512
+ *
22513
+ * A deadline rather than a flag, because `addMarker` reads it: the poll that
22514
+ * would clear a flag runs on `requestAnimationFrame`, which does not fire in
22515
+ * a backgrounded tab and does not run at all for a map torn down through
22516
+ * MapLibre's own `remove()` rather than `destroyMap`. A flag left set there
22517
+ * makes every later `addMarker` skip the call that attaches the marker, for
22518
+ * good; a deadline expires on its own.
22519
+ */
22520
+ map_defineProperty(_this, "floorSettleDeadline", null);
22118
22521
  /** Owns the deck.gl overlay + GLB scene; created on style load only when 3D is active. */
22119
22522
  map_defineProperty(_this, "scene3D", undefined);
22120
22523
  if (rtlLanguages.includes((_mapConfig$lang = mapConfig.lang) !== null && _mapConfig$lang !== void 0 ? _mapConfig$lang : "")) {
@@ -22136,6 +22539,8 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22136
22539
  _this.onFloorChange = mapConfig.onFloorChange;
22137
22540
  _this.onParentPlaceChange = mapConfig.onParentPlaceChange;
22138
22541
  _this.mode = (_mapConfig$mode = mapConfig.mode) !== null && _mapConfig$mode !== void 0 ? _mapConfig$mode : "2D";
22542
+ _this.styleRef = mapConfig.styleRef;
22543
+ _this.styleTheme = mapConfig.styleTheme;
22139
22544
  _this.scene3DConfig = mapConfig.scene3D;
22140
22545
  _this.initialCenter = mapConfig.center;
22141
22546
  _this.tileCacheConfig = function () {
@@ -22161,6 +22566,20 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22161
22566
  }
22162
22567
  map_inherits(InternalMapVXMap, _Loggeable);
22163
22568
  return map_createClass(InternalMapVXMap, [{
22569
+ key: "floorSettlePending",
22570
+ get: /** Whether a floor change is still rebuilding, so markers wait for it. */
22571
+ function get() {
22572
+ return this.floorSettleDeadline != null && this.now() < this.floorSettleDeadline;
22573
+ }
22574
+
22575
+ /** Wall clock for the settle deadline; `Date.now` where `performance` is absent. */
22576
+ }, {
22577
+ key: "now",
22578
+ value: function now() {
22579
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
22580
+ }
22581
+ /** Style variant/theme requested on every mapStyleJson fetch for this map. */
22582
+ }, {
22164
22583
  key: "setRTLSupport",
22165
22584
  value: function setRTLSupport() {
22166
22585
  (0,maplibre_gl.setRTLTextPlugin)("https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.3.0/dist/mapbox-gl-rtl-text.js", false)["catch"](function () {
@@ -22251,9 +22670,18 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22251
22670
  })["catch"](console.error);
22252
22671
  return;
22253
22672
  }
22254
- this.repository.fetchAndParseMapStyle(mapConfig.parentPlaceId).then(function (style) {
22673
+ // Through the same helper as `setStyle` and `changeParentPlaceTo`. This PR
22674
+ // forwards `styleRef`/`styleTheme` to the initial load too, and a ref scoped
22675
+ // to the user's client rather than to the api key resolves only over bearer
22676
+ // — fetched over the api-key path it 4xx'd, and since this is the call that
22677
+ // creates the map, the map was never created at all.
22678
+ this.fetchVenueStyle(mapConfig.parentPlaceId, mapConfig.styleRef, mapConfig.styleTheme).then(function (style) {
22255
22679
  return _this5.onMapStyleLoaded(mapConfig, container, style);
22256
- })["catch"](console.error);
22680
+ })["catch"](function (error) {
22681
+ _this5.logError(error, {
22682
+ functionName: "initialMapStyleSetUp"
22683
+ });
22684
+ });
22257
22685
  }
22258
22686
  }, {
22259
22687
  key: "onMapStyleLoaded",
@@ -22287,6 +22715,11 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22287
22715
  }, buildInteractionOptions(mapConfig));
22288
22716
  if (mapConfig.maxZoom) mapOptions.maxZoom = mapConfig.maxZoom;
22289
22717
  if (mapConfig.minZoom) mapOptions.minZoom = mapConfig.minZoom;
22718
+ // Reduced internal resolution on request (fill-rate relief for low-end GPUs);
22719
+ // MapLibre keeps devicePixelRatio when the option is left out.
22720
+ if (mapConfig.pixelRatio !== undefined) mapOptions.pixelRatio = mapConfig.pixelRatio;
22721
+ // `0` disables the post-gesture fade tail entirely; keep MapLibre's default otherwise.
22722
+ if (mapConfig.fadeDuration !== undefined) mapOptions.fadeDuration = mapConfig.fadeDuration;
22290
22723
  if (mapConfig.maxBounds && mapConfig.maxBounds.length > 1) {
22291
22724
  var boundingBox = getBoundingBox(mapConfig.maxBounds);
22292
22725
  mapOptions.maxBounds = new maplibre_gl.LngLatBounds([boundingBox[0].lng, boundingBox[0].lat], [boundingBox[1].lng, boundingBox[1].lat]);
@@ -22456,6 +22889,11 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22456
22889
  clearTimeout(this.styleReadyTimer);
22457
22890
  this.styleReadyTimer = undefined;
22458
22891
  }
22892
+ // Drop any floor-settle poll in flight: bumping the token makes the next
22893
+ // `requestAnimationFrame` callback return instead of re-queueing itself and
22894
+ // re-attaching markers to a map that has already been removed.
22895
+ this.floorSettleToken++;
22896
+ this.floorSettleDeadline = null;
22459
22897
  // Dispose the 3D scene's GPU resources before the map (and its shared WebGL context) go away —
22460
22898
  // MapLibre's own teardown never calls the custom GLB layer's `onRemove`.
22461
22899
  (_this$scene3D = this.scene3D) === null || _this$scene3D === void 0 || _this$scene3D.dispose();
@@ -22549,7 +22987,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22549
22987
  }
22550
22988
  (_this$scene3D3 = this.scene3D) === null || _this$scene3D3 === void 0 || _this$scene3D3.onFloorChange(this.currentFloor);
22551
22989
  if (updateStyle) {
22552
- this.repository.fetchAndParseMapStyle(place === null || place === void 0 ? void 0 : place.mapvxId).then(function (style) {
22990
+ this.fetchVenueStyle(place === null || place === void 0 ? void 0 : place.mapvxId, this.styleRef, this.styleTheme).then(function (style) {
22553
22991
  var useServiceWorkerCaching = _this9.tileCacheConfig.enabled && _this9.tileCacheConfig.persistToServiceWorker;
22554
22992
  var finalStyle = useServiceWorkerCaching ? _this9.transformStyleForCaching(style) : style;
22555
22993
  _this9.map.setStyle(finalStyle);
@@ -22562,10 +23000,39 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22562
23000
  _this9.resetBaseFilters();
22563
23001
  _this9.whenStyleUpdates(finalStyle);
22564
23002
  onStyleReady === null || onStyleReady === void 0 || onStyleReady();
22565
- })["catch"](console.error);
23003
+ })["catch"](function (error) {
23004
+ _this9.logError(error, {
23005
+ functionName: "changeParentPlaceTo"
23006
+ });
23007
+ // Release the caller either way. `onStyleReady` is what a consumer
23008
+ // hides its loading overlay on, so swallowing the failure here left
23009
+ // the overlay up for good rather than merely unstyled.
23010
+ onStyleReady === null || onStyleReady === void 0 || onStyleReady();
23011
+ });
22566
23012
  }
22567
23013
  }
22568
23014
 
23015
+ /**
23016
+ * Fetch the venue style over whichever endpoint this map is authorized on.
23017
+ *
23018
+ * A bearer token authorizes by the user's client access rather than the api
23019
+ * key's scopes, so a client's branded style resolves without granting that
23020
+ * place to a shared api key; api-key consumers keep the
23021
+ * `/api/sdk/v2/mapStyleJson` path.
23022
+ *
23023
+ * Both callers go through here so they cannot disagree. They used to: once
23024
+ * `setStyle` had stored a `styleRef` it resolved over bearer, the next
23025
+ * parent-place change refetched that same ref over the api-key path, whose
23026
+ * key does not scope it — a non-ok response, a rejected promise, and a style
23027
+ * that was never applied, with `onStyleReady` left unfired under a caller's
23028
+ * loading overlay.
23029
+ */
23030
+ }, {
23031
+ key: "fetchVenueStyle",
23032
+ value: function fetchVenueStyle(placeId, styleRef, styleTheme) {
23033
+ return this.authToken ? this.repository.fetchAndParseMapStyleByBearer(placeId, this.authToken, styleRef, styleTheme) : this.repository.fetchAndParseMapStyle(placeId, styleRef, styleTheme);
23034
+ }
23035
+
22569
23036
  /**
22570
23037
  * Update the parent place and the current floor.
22571
23038
  * Does not update layers, markers, etc.
@@ -22681,18 +23148,20 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22681
23148
  reportReady(error);
22682
23149
  return;
22683
23150
  }
22684
-
22685
- // When a user bearer token is present, the style is resolved through the
22686
- // bearer-authenticated endpoint, which authorizes by the user's client access
22687
- // rather than the api key's scopes — so a client's branded style can be
22688
- // previewed/applied without granting that place to a shared api key.
22689
- // Api-key consumers keep the `/api/sdk/v2/mapStyleJson` path.
22690
- var stylePromise = this.authToken ? this.repository.fetchAndParseMapStyleByBearer(this.parentPlaceId, this.authToken, styleRef, styleTheme) : this.repository.fetchAndParseMapStyle(this.parentPlaceId, styleRef, styleTheme);
23151
+ var stylePromise = this.fetchVenueStyle(this.parentPlaceId, styleRef, styleTheme);
22691
23152
  stylePromise.then(function (style) {
22692
23153
  // A newer swap started while this style was in flight. Applying this one now
22693
23154
  // would fight the newer style layer by layer, so drop it and let the newer swap
22694
23155
  // own the map and its caller's callback.
22695
23156
  if (!isCurrent()) return;
23157
+
23158
+ // Remember what is actually on screen: `changeParentPlaceTo` refetches the
23159
+ // style with these, so without this an explicitly applied style would be
23160
+ // silently reverted to the constructor's on the next parent-place change.
23161
+ // After the generation check: a superseded swap never reached the map, so it
23162
+ // must not claim to be what is on it.
23163
+ _this1.styleRef = styleRef;
23164
+ _this1.styleTheme = styleTheme;
22696
23165
  var useServiceWorkerCaching = _this1.tileCacheConfig.enabled && _this1.tileCacheConfig.persistToServiceWorker;
22697
23166
  var finalStyle = useServiceWorkerCaching ? _this1.transformStyleForCaching(style) : style;
22698
23167
  // Full (non-diff) swap so the previewed style completely replaces the base —
@@ -22930,7 +23399,11 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22930
23399
  var markerCreated = new MarkerAttribute(map_objectSpread(map_objectSpread({}, marker), {}, {
22931
23400
  maxLabelWidthPx: (_marker$maxLabelWidth = marker.maxLabelWidthPx) !== null && _marker$maxLabelWidth !== void 0 ? _marker$maxLabelWidth : this.markerLayoutConfig.maxLabelWidthPx
22932
23401
  }));
22933
- markerCreated.changeFloor(this.currentFloor, this.map, this.isOutdoorFloor(this.currentFloor));
23402
+ // While a floor change is still being rebuilt, the marker is shown by that
23403
+ // change's settle step, together with the new floor.
23404
+ if (!this.floorSettlePending) {
23405
+ markerCreated.changeFloor(this.currentFloor, this.map, this.isOutdoorFloor(this.currentFloor));
23406
+ }
22934
23407
  this.markers.push(markerCreated);
22935
23408
  this.markerLayoutController.scheduleRelayout();
22936
23409
  this.logEvent("addMarker");
@@ -23438,12 +23911,131 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23438
23911
  floorKey: floorKeyString
23439
23912
  });
23440
23913
  }
23441
- this.updateFiltersTo(floorKeyString);
23442
- this.updateMarkersTo(floorKeyString);
23914
+ // One style read for the whole floor change. `getStyle()` serializes every
23915
+ // source and layer of the venue style, and the second read is the expensive
23916
+ // one: `updateFiltersTo` writes filters and layout properties in between,
23917
+ // and each write nulls maplibre's `_serializedLayers` cache, so anything
23918
+ // that reads the style afterwards rebuilds it from scratch.
23919
+ var layers = this.safeStyleLayers();
23920
+ this.updateFiltersTo(floorKeyString, layers);
23921
+ this.updateMarkersWhenFloorSettles(floorKeyString, layers);
23443
23922
  this.refreshCircles();
23444
23923
  this.routeController.updateRouteLayers(floorKeyString);
23445
23924
  this.routeController.updateRouteMarkerVisibility(floorKeyString);
23446
23925
  }
23926
+
23927
+ /**
23928
+ * Show/hide the HTML markers for `floorId` once the indoor tiles have been
23929
+ * rebuilt with the new floor filters.
23930
+ *
23931
+ * A floor change is a `setFilter` on every indoor layer: MapLibre rebuilds the
23932
+ * tile buckets in its worker, so the new floor appears a few frames later
23933
+ * (hundreds of ms on a low-end panel) while a marker is a DOM element that
23934
+ * would move instantly. Waiting for the source to report loaded makes floor
23935
+ * polygons and markers switch in the same frame. The wait is bounded, is
23936
+ * dropped when a newer floor change arrives, and is skipped outside a
23937
+ * browser (no `requestAnimationFrame`).
23938
+ */
23939
+ }, {
23940
+ key: "updateMarkersWhenFloorSettles",
23941
+ value: function updateMarkersWhenFloorSettles(floorId, styleLayers) {
23942
+ var _this11 = this;
23943
+ var token = ++this.floorSettleToken;
23944
+ // Resolved rather than probed with `hasStyle()`, which is another full
23945
+ // `getStyle()` — and by this point the most expensive kind, because
23946
+ // `updateFiltersTo` has just invalidated the serialized-layer cache.
23947
+ var layers = styleLayers === undefined ? this.safeStyleLayers() : styleLayers;
23948
+ if (typeof requestAnimationFrame !== "function" || layers === null) {
23949
+ // No poll will run for this floor change, so nothing would ever clear the
23950
+ // flag later: leaving it set makes every subsequent `addMarker` skip
23951
+ // `changeFloor` and the marker is never attached to the map.
23952
+ this.floorSettleDeadline = null;
23953
+ this.updateMarkersTo(floorId);
23954
+ return;
23955
+ }
23956
+ var sourceIds = this.floorSettleSourceIds(floorId, layers);
23957
+ var deadline = this.now() + FLOOR_SETTLE_MAX_MS;
23958
+ this.floorSettleDeadline = deadline;
23959
+ var sawReloading = false;
23960
+ var frames = 0;
23961
+ var _poll = function poll() {
23962
+ if (token !== _this11.floorSettleToken) return;
23963
+ frames++;
23964
+ var loaded = sourceIds.every(function (id) {
23965
+ return _this11.isSourceSettled(id);
23966
+ });
23967
+ if (!loaded) sawReloading = true;
23968
+ // The reload is dispatched on the map's next render, which may run after
23969
+ // this callback within the same frame: give it a couple of frames to be
23970
+ // observed before concluding there was nothing to reload.
23971
+ var settled = loaded && (sawReloading || frames >= FLOOR_SETTLE_MIN_FRAMES);
23972
+ if (settled || _this11.now() >= deadline) {
23973
+ _this11.floorSettleDeadline = null;
23974
+ _this11.updateMarkersTo(floorId);
23975
+ return;
23976
+ }
23977
+ requestAnimationFrame(_poll);
23978
+ };
23979
+ requestAnimationFrame(_poll);
23980
+ }
23981
+
23982
+ /**
23983
+ * Sources whose tiles a switch to `floorId` actually reloads, and which the
23984
+ * marker settle therefore has to wait on.
23985
+ *
23986
+ * With a vector basemap that is the source the `indoor-*` layers draw from,
23987
+ * because the floor change is a `setFilter` over those layers. An offline
23988
+ * bundle's raster basemap switches floors by making `raster-floor-<key>`
23989
+ * visible instead, and those tiles only begin loading at that moment — while
23990
+ * its `indoor-*` layers carry labels and hover, are already loaded, and would
23991
+ * report settled at once, letting the markers switch a floor before its image
23992
+ * had painted. That is the configuration this wait exists for, so both are
23993
+ * waited on.
23994
+ */
23995
+ }, {
23996
+ key: "floorSettleSourceIds",
23997
+ value: function floorSettleSourceIds(floorId, layers) {
23998
+ var ids = new Set();
23999
+ var rasterFloor = layers.find(function (layer) {
24000
+ return layer.id === "raster-floor-".concat(floorId);
24001
+ });
24002
+ if (rasterFloor && "source" in rasterFloor && typeof rasterFloor.source === "string") {
24003
+ ids.add(rasterFloor.source);
24004
+ }
24005
+ ids.add(this.indoorSourceId(layers));
24006
+ return map_toConsumableArray(ids);
24007
+ }
24008
+
24009
+ /** The vector source the `indoor-*` layers draw from (`indoorequal` by default). */
24010
+ }, {
24011
+ key: "indoorSourceId",
24012
+ value: function indoorSourceId(layers) {
24013
+ var _iterator2 = map_createForOfIteratorHelper(layers),
24014
+ _step2;
24015
+ try {
24016
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
24017
+ var layer = _step2.value;
24018
+ if (layer.id.startsWith("indoor-") && "source" in layer && typeof layer.source === "string") {
24019
+ return layer.source;
24020
+ }
24021
+ }
24022
+ } catch (err) {
24023
+ _iterator2.e(err);
24024
+ } finally {
24025
+ _iterator2.f();
24026
+ }
24027
+ return "indoorequal";
24028
+ }
24029
+ }, {
24030
+ key: "isSourceSettled",
24031
+ value: function isSourceSettled(sourceId) {
24032
+ try {
24033
+ if (!this.map.getSource(sourceId)) return true;
24034
+ return this.map.isSourceLoaded(sourceId) !== false;
24035
+ } catch (_unused) {
24036
+ return true;
24037
+ }
24038
+ }
23447
24039
  }, {
23448
24040
  key: "getContainer",
23449
24041
  value: function getContainer() {
@@ -23464,26 +24056,51 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23464
24056
  }, {
23465
24057
  key: "updateMarkersTo",
23466
24058
  value: function updateMarkersTo(floorId) {
23467
- var _this11 = this;
24059
+ var _this12 = this;
23468
24060
  var isOutdoor = this.isOutdoorFloor(floorId);
23469
24061
  this.markers.forEach(function (e) {
23470
- e.changeFloor(floorId, _this11.map, isOutdoor);
24062
+ e.changeFloor(floorId, _this12.map, isOutdoor);
23471
24063
  });
23472
24064
  this.markerLayoutController.scheduleRelayout();
23473
24065
  }
23474
24066
  }, {
23475
24067
  key: "updateFiltersTo",
23476
- value: function updateFiltersTo(floorId) {
23477
- var _this$map$getStyle$la3,
23478
- _this$map$getStyle3,
23479
- _this12 = this,
24068
+ value: function updateFiltersTo(floorId, styleLayers) {
24069
+ var _ref4,
24070
+ _this13 = this,
23480
24071
  _this$innerFloors$fin4;
23481
24072
  if (!this.map) return;
23482
- var layers = (_this$map$getStyle$la3 = (_this$map$getStyle3 = this.map.getStyle()) === null || _this$map$getStyle3 === void 0 ? void 0 : _this$map$getStyle3.layers) !== null && _this$map$getStyle$la3 !== void 0 ? _this$map$getStyle$la3 : [];
24073
+ // `null` means the caller looked and there was no style; only `undefined`
24074
+ // means it did not look.
24075
+ var layers = (_ref4 = styleLayers === undefined ? this.safeStyleLayers() : styleLayers) !== null && _ref4 !== void 0 ? _ref4 : [];
24076
+
24077
+ // Raster basemap (offline bundles baked with one raster archive per floor):
24078
+ // a floor is switched by showing its `raster-floor-<key>` layer and hiding
24079
+ // the others. The remaining `indoor-*` layers (labels, hover, highlights)
24080
+ // are still vector and keep going through the filter logic below.
24081
+ var rasterFloorPrefix = "raster-floor-";
24082
+ // Only switch the raster basemap when there is a baked layer to switch to.
24083
+ // Hiding every raster floor leaves the basemap blank, and no id can match in
24084
+ // two cases: no floor at all (an outdoor place, or a venue with no floors,
24085
+ // where `currentFloor` is `""`), and a floor the bundle has no raster
24086
+ // archive for — a venue where only some floors are baked, or a floor added
24087
+ // after the bundle was built. Keeping the current one visible is the safe
24088
+ // fallback for both.
24089
+ var rasterFloorLayerIds = layers.filter(function (l) {
24090
+ return l.id.startsWith(rasterFloorPrefix);
24091
+ }).map(function (l) {
24092
+ return l.id;
24093
+ });
24094
+ var targetRasterFloor = rasterFloorPrefix + floorId;
24095
+ if (floorId && rasterFloorLayerIds.includes(targetRasterFloor)) {
24096
+ rasterFloorLayerIds.forEach(function (id) {
24097
+ _this13.map.setLayoutProperty(id, "visibility", id === targetRasterFloor ? "visible" : "none");
24098
+ });
24099
+ }
23483
24100
  layers.filter(function (l) {
23484
24101
  return l.id.startsWith("base-indoor-");
23485
24102
  }).forEach(function (l) {
23486
- _this12.map.removeLayer(l.id);
24103
+ _this13.map.removeLayer(l.id);
23487
24104
  });
23488
24105
  var baseFloorId = (_this$innerFloors$fin4 = this.innerFloors.find(function (floor) {
23489
24106
  return floor.key === floorId;
@@ -23501,7 +24118,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23501
24118
  // resolved are skipped, so a filter this class wrote is never mistaken for a
23502
24119
  // declared one.
23503
24120
  indoorLayers.forEach(function (layer) {
23504
- return _this12.captureDeclaredBaseFilter(layer.id);
24121
+ return _this13.captureDeclaredBaseFilter(layer.id);
23505
24122
  });
23506
24123
  if (baseFloorId != undefined) {
23507
24124
  var _indoorLayers$;
@@ -23511,13 +24128,13 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23511
24128
  }).forEach(function (layer) {
23512
24129
  var baseLayer = deepClone(layer);
23513
24130
  baseLayer.id = "base-" + layer.id;
23514
- _this12.baseFilters[baseLayer.id] = _this12.baseFilters[layer.id];
23515
- _this12.map.addLayer(baseLayer, firstLayerId);
23516
- _this12.applyLayerFilters(baseLayer, baseFloorId);
24131
+ _this13.baseFilters[baseLayer.id] = _this13.baseFilters[layer.id];
24132
+ _this13.map.addLayer(baseLayer, firstLayerId);
24133
+ _this13.applyLayerFilters(baseLayer, baseFloorId);
23517
24134
  });
23518
24135
  }
23519
24136
  indoorLayers.forEach(function (layer) {
23520
- _this12.applyLayerFilters(layer, floorId);
24137
+ _this13.applyLayerFilters(layer, floorId);
23521
24138
  });
23522
24139
  }
23523
24140
 
@@ -23531,7 +24148,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23531
24148
  value: function safeGetFilter(layerId) {
23532
24149
  try {
23533
24150
  return this.map.getFilter(layerId);
23534
- } catch (_unused) {
24151
+ } catch (_unused2) {
23535
24152
  return undefined;
23536
24153
  }
23537
24154
  }
@@ -23719,11 +24336,11 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23719
24336
  var minLng;
23720
24337
  var maxLat;
23721
24338
  var maxLng;
23722
- var _iterator2 = map_createForOfIteratorHelper(coordinates),
23723
- _step2;
24339
+ var _iterator3 = map_createForOfIteratorHelper(coordinates),
24340
+ _step3;
23724
24341
  try {
23725
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
23726
- var _coord = _step2.value;
24342
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
24343
+ var _coord = _step3.value;
23727
24344
  var lat = _coord.lat,
23728
24345
  lng = _coord.lng;
23729
24346
  minLat = minLat === undefined ? lat : Math.min(minLat, lat);
@@ -23732,9 +24349,9 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23732
24349
  maxLng = maxLng === undefined ? lng : Math.max(maxLng, lng);
23733
24350
  }
23734
24351
  } catch (err) {
23735
- _iterator2.e(err);
24352
+ _iterator3.e(err);
23736
24353
  } finally {
23737
- _iterator2.f();
24354
+ _iterator3.f();
23738
24355
  }
23739
24356
  var bounds = [minLng !== null && minLng !== void 0 ? minLng : 0, minLat !== null && minLat !== void 0 ? minLat : 0, maxLng !== null && maxLng !== void 0 ? maxLng : 0, maxLat !== null && maxLat !== void 0 ? maxLat : 0];
23740
24357
 
@@ -23869,8 +24486,9 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23869
24486
  try {
23870
24487
  var _this$onFloorChange3;
23871
24488
  this.updatePlaceAndFloor(placeId, floorId);
23872
- this.updateFiltersTo(floorId);
23873
- this.updateMarkersTo(floorId);
24489
+ var layers = this.safeStyleLayers();
24490
+ this.updateFiltersTo(floorId, layers);
24491
+ this.updateMarkersWhenFloorSettles(floorId, layers);
23874
24492
  (_this$onFloorChange3 = this.onFloorChange) === null || _this$onFloorChange3 === void 0 || _this$onFloorChange3.call(this, floorId);
23875
24493
  this.logEvent("updatePlaceAndFloorForRouting", {
23876
24494
  floorId: floorId
@@ -24087,17 +24705,17 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24087
24705
  }, {
24088
24706
  key: "defaultClickListener",
24089
24707
  value: function defaultClickListener() {
24090
- var _this13 = this;
24708
+ var _this14 = this;
24091
24709
  this.map.on("click", /*#__PURE__*/function () {
24092
- var _ref4 = map_asyncToGenerator(/*#__PURE__*/map_regenerator().m(function _callee3(e) {
24093
- var features, roomFeatures, touchedFeature, ref, _clickedPlace, _ref5, _clickedPlace$mapvxId, _clickedPlace2, _clickedPlace3, clickedPlace, _iterator3, _step3, potentialParentPlace, cachedSubPlaces, cachedPlaces, clickedPlaceId, _t3;
24710
+ var _ref5 = map_asyncToGenerator(/*#__PURE__*/map_regenerator().m(function _callee3(e) {
24711
+ var features, roomFeatures, touchedFeature, ref, _clickedPlace, _ref6, _clickedPlace$mapvxId, _clickedPlace2, _clickedPlace3, clickedPlace, _iterator4, _step4, potentialParentPlace, cachedSubPlaces, cachedPlaces, clickedPlaceId, _t3;
24094
24712
  return map_regenerator().w(function (_context3) {
24095
24713
  while (1) switch (_context3.p = _context3.n) {
24096
24714
  case 0:
24097
- features = _this13.map.queryRenderedFeatures(e.point); // Filter room features by current floor
24715
+ features = _this14.map.queryRenderedFeatures(e.point); // Filter room features by current floor
24098
24716
  roomFeatures = features.filter(function (feature) {
24099
24717
  var _feature$properties;
24100
- return feature.properties["class"] === "room" && ((_feature$properties = feature.properties) === null || _feature$properties === void 0 ? void 0 : _feature$properties.floor_key) === _this13.currentFloor;
24718
+ return feature.properties["class"] === "room" && ((_feature$properties = feature.properties) === null || _feature$properties === void 0 ? void 0 : _feature$properties.floor_key) === _this14.currentFloor;
24101
24719
  });
24102
24720
  touchedFeature = roomFeatures[0];
24103
24721
  ref = touchedFeature === null || touchedFeature === void 0 ? void 0 : touchedFeature.properties.ref;
@@ -24108,23 +24726,23 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24108
24726
  console.log("touchedFeature", ref);
24109
24727
 
24110
24728
  // First, search in current subPlaces
24111
- clickedPlace = _this13.subPlaces.find(function (subPlace) {
24729
+ clickedPlace = _this14.subPlaces.find(function (subPlace) {
24112
24730
  return subPlace.mapvxId === ref;
24113
24731
  }); // If not found, search in subplaces of potential parent places
24114
24732
  if (!(clickedPlace === undefined)) {
24115
24733
  _context3.n = 7;
24116
24734
  break;
24117
24735
  }
24118
- _iterator3 = map_createForOfIteratorHelper(_this13.potentialParentPlaces);
24736
+ _iterator4 = map_createForOfIteratorHelper(_this14.potentialParentPlaces);
24119
24737
  _context3.p = 1;
24120
- _iterator3.s();
24738
+ _iterator4.s();
24121
24739
  case 2:
24122
- if ((_step3 = _iterator3.n()).done) {
24740
+ if ((_step4 = _iterator4.n()).done) {
24123
24741
  _context3.n = 4;
24124
24742
  break;
24125
24743
  }
24126
- potentialParentPlace = _step3.value;
24127
- cachedSubPlaces = _this13.repository.getCachedSubPlaces(potentialParentPlace.mapvxId);
24744
+ potentialParentPlace = _step4.value;
24745
+ cachedSubPlaces = _this14.repository.getCachedSubPlaces(potentialParentPlace.mapvxId);
24128
24746
  if (!cachedSubPlaces) {
24129
24747
  _context3.n = 3;
24130
24748
  break;
@@ -24146,24 +24764,24 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24146
24764
  case 5:
24147
24765
  _context3.p = 5;
24148
24766
  _t3 = _context3.v;
24149
- _iterator3.e(_t3);
24767
+ _iterator4.e(_t3);
24150
24768
  case 6:
24151
24769
  _context3.p = 6;
24152
- _iterator3.f();
24770
+ _iterator4.f();
24153
24771
  return _context3.f(6);
24154
24772
  case 7:
24155
24773
  if (!(clickedPlace === undefined)) {
24156
24774
  _context3.n = 10;
24157
24775
  break;
24158
24776
  }
24159
- cachedPlaces = _this13.repository.getCachedPlaces([ref]);
24777
+ cachedPlaces = _this14.repository.getCachedPlaces([ref]);
24160
24778
  if (!(cachedPlaces.length === 0)) {
24161
24779
  _context3.n = 9;
24162
24780
  break;
24163
24781
  }
24164
24782
  console.log("fetching place detail from server", ref);
24165
24783
  _context3.n = 8;
24166
- return _this13.repository.fetchAndParsePlaceDetail({
24784
+ return _this14.repository.fetchAndParsePlaceDetail({
24167
24785
  id: ref
24168
24786
  });
24169
24787
  case 8:
@@ -24175,9 +24793,9 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24175
24793
  clickedPlace = cachedPlaces === null || cachedPlaces === void 0 ? void 0 : cachedPlaces[0];
24176
24794
  case 10:
24177
24795
  console.log("clickedPlace", (_clickedPlace = clickedPlace) === null || _clickedPlace === void 0 ? void 0 : _clickedPlace.title);
24178
- clickedPlaceId = (_ref5 = (_clickedPlace$mapvxId = (_clickedPlace2 = clickedPlace) === null || _clickedPlace2 === void 0 ? void 0 : _clickedPlace2.mapvxId) !== null && _clickedPlace$mapvxId !== void 0 ? _clickedPlace$mapvxId : (_clickedPlace3 = clickedPlace) === null || _clickedPlace3 === void 0 ? void 0 : _clickedPlace3.clientId) !== null && _ref5 !== void 0 ? _ref5 : "";
24179
- if (clickedPlace !== undefined && _this13.clickCallback) {
24180
- _this13.clickCallback(clickedPlaceId);
24796
+ clickedPlaceId = (_ref6 = (_clickedPlace$mapvxId = (_clickedPlace2 = clickedPlace) === null || _clickedPlace2 === void 0 ? void 0 : _clickedPlace2.mapvxId) !== null && _clickedPlace$mapvxId !== void 0 ? _clickedPlace$mapvxId : (_clickedPlace3 = clickedPlace) === null || _clickedPlace3 === void 0 ? void 0 : _clickedPlace3.clientId) !== null && _ref6 !== void 0 ? _ref6 : "";
24797
+ if (clickedPlace !== undefined && _this14.clickCallback) {
24798
+ _this14.clickCallback(clickedPlaceId);
24181
24799
  }
24182
24800
  case 11:
24183
24801
  return _context3.a(2);
@@ -24185,36 +24803,36 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24185
24803
  }, _callee3, null, [[1, 5, 6, 7]]);
24186
24804
  }));
24187
24805
  return function (_x3) {
24188
- return _ref4.apply(this, arguments);
24806
+ return _ref5.apply(this, arguments);
24189
24807
  };
24190
24808
  }());
24191
24809
  }
24192
24810
  }, {
24193
24811
  key: "onHover",
24194
24812
  value: function onHover() {
24195
- var _this14 = this;
24813
+ var _this15 = this;
24196
24814
  if (!this.enableHover) return;
24197
24815
  this.map.on("mousemove", "hoverable", function (e) {
24198
24816
  var _e$features;
24199
24817
  var feature = (_e$features = e.features) === null || _e$features === void 0 ? void 0 : _e$features.find(function (feature) {
24200
24818
  var _feature$properties$r, _feature$properties2;
24201
- return ((_feature$properties$r = feature.properties.ref) === null || _feature$properties$r === void 0 ? void 0 : _feature$properties$r.length) > 0 && ((_feature$properties2 = feature.properties) === null || _feature$properties2 === void 0 ? void 0 : _feature$properties2.floor_key) === _this14.currentFloor;
24819
+ return ((_feature$properties$r = feature.properties.ref) === null || _feature$properties$r === void 0 ? void 0 : _feature$properties$r.length) > 0 && ((_feature$properties2 = feature.properties) === null || _feature$properties2 === void 0 ? void 0 : _feature$properties2.floor_key) === _this15.currentFloor;
24202
24820
  });
24203
24821
  var ref = feature === null || feature === void 0 ? void 0 : feature.properties.ref;
24204
24822
  if (ref != null && typeof ref === "string" && ref.length > 0) {
24205
- _this14.hoveredId = ref;
24206
- _this14.updateFiltersTo(_this14.currentFloor);
24207
- } else if (ref === undefined && _this14.hoveredId !== "unselected") {
24208
- _this14.hoveredId = "unselected";
24209
- _this14.updateFiltersTo(_this14.currentFloor);
24823
+ _this15.hoveredId = ref;
24824
+ _this15.updateFiltersTo(_this15.currentFloor);
24825
+ } else if (ref === undefined && _this15.hoveredId !== "unselected") {
24826
+ _this15.hoveredId = "unselected";
24827
+ _this15.updateFiltersTo(_this15.currentFloor);
24210
24828
  }
24211
24829
  });
24212
24830
 
24213
24831
  // When the mouse leaves the state-fill layer, update the feature state of the
24214
24832
  // previously hovered feature.
24215
24833
  this.map.on("mouseleave", "hoverable", function () {
24216
- _this14.hoveredId = "unselected";
24217
- _this14.updateFiltersTo(_this14.currentFloor);
24834
+ _this15.hoveredId = "unselected";
24835
+ _this15.updateFiltersTo(_this15.currentFloor);
24218
24836
  });
24219
24837
  }
24220
24838
  }, {
@@ -24615,7 +25233,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24615
25233
  }, {
24616
25234
  key: "addUserLocationTracking",
24617
25235
  value: function addUserLocationTracking(enableHighAccuracy, geoLocation, icon) {
24618
- var _this15 = this;
25236
+ var _this16 = this;
24619
25237
  var initialCoordinate = {
24620
25238
  lat: 0.0,
24621
25239
  lng: 0.0
@@ -24639,7 +25257,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24639
25257
  this.markerLayoutController.scheduleRelayout();
24640
25258
  var updateMarker = function updateMarker(position) {
24641
25259
  marker.setLngLat([position.coords.longitude, position.coords.latitude]);
24642
- _this15.markerLayoutController.scheduleRelayout();
25260
+ _this16.markerLayoutController.scheduleRelayout();
24643
25261
  };
24644
25262
  this.geoLocation = geoLocation !== null && geoLocation !== void 0 ? geoLocation : navigator.geolocation;
24645
25263
  this.watchPositionID = this.geoLocation.watchPosition(updateMarker, function (error) {
@@ -24735,7 +25353,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24735
25353
  key: "onWebGLContextLost",
24736
25354
  value: function onWebGLContextLost(e) {
24737
25355
  var _this$map2,
24738
- _this16 = this;
25356
+ _this17 = this;
24739
25357
  e.originalEvent.preventDefault();
24740
25358
  console.warn("Contexto WebGL perdido 😢");
24741
25359
 
@@ -24746,8 +25364,8 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24746
25364
  var _ext$restoreContext;
24747
25365
  canvas.addEventListener("webglcontextrestored", function () {
24748
25366
  console.debug("Contexto WebGL restaurado, forzando re-render");
24749
- _this16.map.triggerRepaint(); // forzar repaint
24750
- _this16.failedTiles.clear();
25367
+ _this17.map.triggerRepaint(); // forzar repaint
25368
+ _this17.failedTiles.clear();
24751
25369
  }, {
24752
25370
  once: true
24753
25371
  });
@@ -24791,6 +25409,24 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24791
25409
  * ("Cannot read properties of undefined"), so call sites that only need the style to
24792
25410
  * exist should gate on this rather than on `isStyleLoaded()`.
24793
25411
  */
25412
+ /**
25413
+ * This style's layers, or `null` when the map has no style right now.
25414
+ *
25415
+ * The one place the style is read for a floor change, so callers can pass the
25416
+ * result on instead of each probing again — `getStyle()` serializes every
25417
+ * source and layer, and `Style._updateLayer` drops the cache behind it on
25418
+ * every filter or layout write.
25419
+ */
25420
+ }, {
25421
+ key: "safeStyleLayers",
25422
+ value: function safeStyleLayers() {
25423
+ try {
25424
+ var _this$map$getStyle$la3, _this$map3;
25425
+ return (_this$map$getStyle$la3 = (_this$map3 = this.map) === null || _this$map3 === void 0 || (_this$map3 = _this$map3.getStyle()) === null || _this$map3 === void 0 ? void 0 : _this$map3.layers) !== null && _this$map$getStyle$la3 !== void 0 ? _this$map$getStyle$la3 : null;
25426
+ } catch (_unused3) {
25427
+ return null;
25428
+ }
25429
+ }
24794
25430
  }, {
24795
25431
  key: "hasStyle",
24796
25432
  value: function hasStyle() {
@@ -24801,9 +25437,9 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24801
25437
  // or privatizes it — disabling both the tile-retry loop and `setStyle`'s apply pass
24802
25438
  // with no throw and no log. `safeGetFilter` already uses this try/catch shape.
24803
25439
  try {
24804
- var _this$map3;
24805
- return ((_this$map3 = this.map) === null || _this$map3 === void 0 ? void 0 : _this$map3.getStyle()) != null;
24806
- } catch (_unused2) {
25440
+ var _this$map4;
25441
+ return ((_this$map4 = this.map) === null || _this$map4 === void 0 ? void 0 : _this$map4.getStyle()) != null;
25442
+ } catch (_unused4) {
24807
25443
  return false;
24808
25444
  }
24809
25445
  }
@@ -24823,7 +25459,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24823
25459
  }, {
24824
25460
  key: "subscribeToFailedTiles",
24825
25461
  value: function subscribeToFailedTiles() {
24826
- var _this17 = this;
25462
+ var _this18 = this;
24827
25463
  if (this.retryTimer) return;
24828
25464
 
24829
25465
  // Store bound functions for proper cleanup
@@ -24840,23 +25476,23 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24840
25476
  // exactly when tiles fail — gating on it would skip every tick during a
24841
25477
  // bad-network burst. The queue is left untouched so the tiles queued
24842
25478
  // mid-swap are still retried on the next tick.
24843
- if (!_this17.hasStyle()) {
25479
+ if (!_this18.hasStyle()) {
24844
25480
  return;
24845
25481
  }
24846
- if (!_this17.map.getSource("indoorequal")) {
24847
- _this17.failedTiles.clear();
25482
+ if (!_this18.map.getSource("indoorequal")) {
25483
+ _this18.failedTiles.clear();
24848
25484
  return;
24849
25485
  }
24850
- var source = _this17.map.getSource("indoorequal");
25486
+ var source = _this18.map.getSource("indoorequal");
24851
25487
  if (!source || source.type !== "vector") {
24852
- _this17.failedTiles.clear();
25488
+ _this18.failedTiles.clear();
24853
25489
  return;
24854
25490
  }
24855
25491
 
24856
25492
  // Process tiles and remove invalid ones
24857
25493
  var tilesToRetry = [];
24858
25494
  var tilesToRemove = [];
24859
- _this17.failedTiles.forEach(function (tile) {
25495
+ _this18.failedTiles.forEach(function (tile) {
24860
25496
  // Check if tile is still valid before attempting reload
24861
25497
  if (!tile || tile.state === "unloaded" || tile.state === "loading") {
24862
25498
  tilesToRemove.push(tile);
@@ -24874,14 +25510,14 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24874
25510
 
24875
25511
  // Remove invalid tiles from the set
24876
25512
  tilesToRemove.forEach(function (tile) {
24877
- return _this17.failedTiles["delete"](tile);
25513
+ return _this18.failedTiles["delete"](tile);
24878
25514
  });
24879
25515
 
24880
25516
  // Attempt to reload valid failed tiles
24881
25517
  var now = Date.now();
24882
25518
  tilesToRetry.forEach(function (tile) {
24883
- var _this17$tileRetryStat;
24884
- var retry = (_this17$tileRetryStat = _this17.tileRetryState.get(tile)) !== null && _this17$tileRetryStat !== void 0 ? _this17$tileRetryStat : {
25519
+ var _this18$tileRetryStat;
25520
+ var retry = (_this18$tileRetryStat = _this18.tileRetryState.get(tile)) !== null && _this18$tileRetryStat !== void 0 ? _this18$tileRetryStat : {
24885
25521
  attempts: 0,
24886
25522
  nextAttemptAt: 0,
24887
25523
  pending: false
@@ -24892,16 +25528,16 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24892
25528
  var onFailure = function onFailure(error) {
24893
25529
  var attempts = retry.attempts + 1;
24894
25530
  if (attempts >= MAX_TILE_RETRIES) {
24895
- _this17.debugLog("Tile reload failed ".concat(attempts, " times, dropping it from the retry queue"), error);
24896
- _this17.failedTiles["delete"](tile);
24897
- _this17.tileRetryState["delete"](tile);
25531
+ _this18.debugLog("Tile reload failed ".concat(attempts, " times, dropping it from the retry queue"), error);
25532
+ _this18.failedTiles["delete"](tile);
25533
+ _this18.tileRetryState["delete"](tile);
24898
25534
  return;
24899
25535
  }
24900
25536
 
24901
25537
  // Stay in the queue and try again once the backoff has elapsed.
24902
25538
  var backoffMs = TILE_RETRY_BASE_DELAY_MS * Math.pow(2, attempts - 1);
24903
- _this17.debugLog("Tile reload failed (attempt ".concat(attempts, "/").concat(MAX_TILE_RETRIES, "), retrying in ").concat(backoffMs, "ms"), error);
24904
- _this17.tileRetryState.set(tile, {
25539
+ _this18.debugLog("Tile reload failed (attempt ".concat(attempts, "/").concat(MAX_TILE_RETRIES, "), retrying in ").concat(backoffMs, "ms"), error);
25540
+ _this18.tileRetryState.set(tile, {
24905
25541
  attempts: attempts,
24906
25542
  nextAttemptAt: Date.now() + backoffMs,
24907
25543
  pending: false
@@ -24910,7 +25546,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24910
25546
  try {
24911
25547
  // Verify tile still exists and is in a retryable state
24912
25548
  if (tile && (tile.state === "errored" || tile.state === "expired")) {
24913
- _this17.tileRetryState.set(tile, map_objectSpread(map_objectSpread({}, retry), {}, {
25549
+ _this18.tileRetryState.set(tile, map_objectSpread(map_objectSpread({}, retry), {}, {
24914
25550
  pending: true
24915
25551
  }));
24916
25552
  // loadTile is async, so a rejection escapes this try/catch and has to be handled
@@ -24920,7 +25556,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24920
25556
  // Loaded: forget the attempt history so a later failure starts from zero. The
24921
25557
  // tile leaves the queue through the tilesToRemove branch on the next tick,
24922
25558
  // once its state is no longer errored/expired.
24923
- _this17.tileRetryState["delete"](tile);
25559
+ _this18.tileRetryState["delete"](tile);
24924
25560
  })["catch"](onFailure);
24925
25561
  }
24926
25562
  } catch (error) {