@mapvx/web-js 3.5.0-dev.2 → 3.5.0-dev.4

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 (43) 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/cjs/utils/loadGlb.js +55 -20
  11. package/dist/cjs/utils/loadGlb.js.map +1 -1
  12. package/dist/cjs/utils/three.js +70 -34
  13. package/dist/cjs/utils/three.js.map +1 -1
  14. package/dist/es/controllers/routeController.d.ts +91 -13
  15. package/dist/es/controllers/routeController.d.ts.map +1 -1
  16. package/dist/es/controllers/routeController.js +396 -106
  17. package/dist/es/controllers/routeController.js.map +1 -1
  18. package/dist/es/domain/models/mapConfig.d.ts +76 -0
  19. package/dist/es/domain/models/mapConfig.d.ts.map +1 -1
  20. package/dist/es/domain/models/routeInstance.d.ts +70 -1
  21. package/dist/es/domain/models/routeInstance.d.ts.map +1 -1
  22. package/dist/es/domain/models/routeInstance.js +1 -0
  23. package/dist/es/domain/models/routeInstance.js.map +1 -1
  24. package/dist/es/logger/sentry.js +1 -1
  25. package/dist/es/map/map.d.ts +76 -0
  26. package/dist/es/map/map.d.ts.map +1 -1
  27. package/dist/es/map/map.js +250 -24
  28. package/dist/es/map/map.js.map +1 -1
  29. package/dist/es/map/mapInteractionOptions.d.ts +5 -1
  30. package/dist/es/map/mapInteractionOptions.d.ts.map +1 -1
  31. package/dist/es/map/mapInteractionOptions.js +18 -0
  32. package/dist/es/map/mapInteractionOptions.js.map +1 -1
  33. package/dist/es/utils/loadGlb.d.ts +41 -0
  34. package/dist/es/utils/loadGlb.d.ts.map +1 -1
  35. package/dist/es/utils/loadGlb.js +55 -21
  36. package/dist/es/utils/loadGlb.js.map +1 -1
  37. package/dist/es/utils/three.d.ts +37 -8
  38. package/dist/es/utils/three.d.ts.map +1 -1
  39. package/dist/es/utils/three.js +68 -32
  40. package/dist/es/utils/three.js.map +1 -1
  41. package/dist/umd/index.js +1012 -257
  42. package/dist/umd/index.js.map +1 -1
  43. 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.2";
11252
+ var sentry_SDK_VERSION = false ? 0 : "3.5.0-dev.4";
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
+ */
13731
13955
  }, {
13732
- key: "preparePaintForLayer",
13733
- value: function preparePaintForLayer(drawConfig) {
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. */
13963
+ }, {
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
  }));
@@ -19709,37 +20061,83 @@ function three_arrayWithHoles(r) { if (Array.isArray(r)) return r; }
19709
20061
 
19710
20062
  /** Runtime handle to the deck.gl classes used by the 3D scene. */
19711
20063
 
19712
- var threePromise;
19713
- var deckPromise;
20064
+ /**
20065
+ * Memoizes a dynamic-import chain, caching only a FULFILLED result.
20066
+ *
20067
+ * A plain `cached ??= import(...)` also memoizes a *rejection*, so one transient chunk-fetch
20068
+ * failure (a dropped connection, a cache-busted deploy mid-session) permanently disables the
20069
+ * feature for the rest of the page's life: every retry receives the same rejected promise and
20070
+ * fails identically. The 3D scene retries on its own — `reloadScene3DModelsForLod` reloads GLB
20071
+ * templates on every zoom-in past the LOD threshold — so a sticky rejection turns a blip into
20072
+ * models that never come back until the user reloads.
20073
+ *
20074
+ * Concurrent callers still share one in-flight import (that is the point of the cache); only the
20075
+ * rejected promise is evicted, and the error is re-thrown so every current caller still sees it.
20076
+ *
20077
+ * Exported only so the eviction behaviour can be tested directly — the loaders below wrap real
20078
+ * dynamic `import()` calls, which a unit test cannot fail on demand.
20079
+ *
20080
+ * @param load Builds the import promise. Called again on the first retry after a failure.
20081
+ * @returns An accessor that imports at most once per successful load.
20082
+ * @internal
20083
+ */
20084
+ function memoizeImport(load) {
20085
+ var cached;
20086
+ return function () {
20087
+ cached !== null && cached !== void 0 ? cached : cached = load()["catch"](function (error) {
20088
+ // Evict BEFORE re-throwing so the next call starts a fresh import. Safe to assign here:
20089
+ // a promise never settles synchronously, so `cached` is already assigned by now.
20090
+ cached = undefined;
20091
+ throw error;
20092
+ });
20093
+ return cached;
20094
+ };
20095
+ }
19714
20096
 
19715
20097
  /**
19716
20098
  * Dynamically imports Three.js and the GLTF/DRACO/KTX2 loaders once and caches the result.
19717
20099
  * Subsequent calls return the same promise, so a single Three.js instance is shared.
20100
+ * A failed import is not cached — see {@link memoizeImport}.
19718
20101
  */
19719
- function loadThree() {
19720
- threePromise !== null && threePromise !== void 0 ? threePromise : threePromise = Promise.all([Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 912, 23)), Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 833, 23)), Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 303, 23)), Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 373, 23)), Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 502, 23))]).then(function (_ref) {
19721
- var _ref2 = three_slicedToArray(_ref, 5),
20102
+ var loadThree = memoizeImport(function () {
20103
+ return Promise.all([Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 912, 23)), Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 833, 23)), Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 303, 23)), Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 373, 23))]).then(function (_ref) {
20104
+ var _ref2 = three_slicedToArray(_ref, 4),
19722
20105
  three = _ref2[0],
19723
20106
  gltf = _ref2[1],
19724
20107
  draco = _ref2[2],
19725
- ktx2 = _ref2[3],
19726
- meshopt = _ref2[4];
20108
+ ktx2 = _ref2[3];
19727
20109
  return {
19728
20110
  THREE: three,
19729
20111
  GLTFLoader: gltf.GLTFLoader,
19730
20112
  DRACOLoader: draco.DRACOLoader,
19731
- KTX2Loader: ktx2.KTX2Loader,
19732
- MeshoptDecoder: meshopt.MeshoptDecoder
20113
+ KTX2Loader: ktx2.KTX2Loader
19733
20114
  };
19734
20115
  });
19735
- return threePromise;
19736
- }
20116
+ });
20117
+
20118
+ /**
20119
+ * Dynamically imports the singleton meshopt decoder once and caches the result.
20120
+ *
20121
+ * Deliberately NOT part of {@link loadThree}'s import chain: only GLBs compressed with
20122
+ * `EXT_meshopt_compression` need it, so keeping it out of the eager `Promise.all` keeps the
20123
+ * decoder module (and its WASM instantiation) off the critical path of the first 3D frame.
20124
+ * It is fetched on the first meshopt buffer decode instead — see the lazy wrapper in
20125
+ * `loadGlb.ts`. That moves the fetch inside `parse()`, competing with hundreds of MB of GLB
20126
+ * downloads, so not caching a failed import (see {@link memoizeImport}) matters more here than
20127
+ * for the eager loaders.
20128
+ */
20129
+ var loadMeshoptDecoder = memoizeImport(function () {
20130
+ return Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 502, 23)).then(function (meshopt) {
20131
+ return meshopt.MeshoptDecoder;
20132
+ });
20133
+ });
19737
20134
 
19738
20135
  /**
19739
20136
  * Dynamically imports the deck.gl overlay and layer classes once and caches the result.
20137
+ * A failed import is not cached — see {@link memoizeImport}.
19740
20138
  */
19741
- function loadDeck() {
19742
- deckPromise !== null && deckPromise !== void 0 ? deckPromise : deckPromise = Promise.all([Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 452, 23)), Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 741, 23)), Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 589, 23))]).then(function (_ref3) {
20139
+ var loadDeck = memoizeImport(function () {
20140
+ return Promise.all([Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 452, 23)), Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 741, 23)), Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 589, 23))]).then(function (_ref3) {
19743
20141
  var _ref4 = three_slicedToArray(_ref3, 3),
19744
20142
  mapbox = _ref4[0],
19745
20143
  geoLayers = _ref4[1],
@@ -19752,31 +20150,104 @@ function loadDeck() {
19752
20150
  TextLayer: layers.TextLayer
19753
20151
  };
19754
20152
  });
19755
- return deckPromise;
19756
- }
20153
+ });
19757
20154
  ;// ./src/utils/loadGlb.ts
19758
- function loadGlb_regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return loadGlb_regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (loadGlb_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, loadGlb_regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, loadGlb_regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), loadGlb_regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", loadGlb_regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), loadGlb_regeneratorDefine2(u), loadGlb_regeneratorDefine2(u, o, "Generator"), loadGlb_regeneratorDefine2(u, n, function () { return this; }), loadGlb_regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (loadGlb_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
19759
- function loadGlb_regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } loadGlb_regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { loadGlb_regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, loadGlb_regeneratorDefine2(e, r, n, t); }
19760
20155
  function loadGlb_slicedToArray(r, e) { return loadGlb_arrayWithHoles(r) || loadGlb_iterableToArrayLimit(r, e) || loadGlb_unsupportedIterableToArray(r, e) || loadGlb_nonIterableRest(); }
19761
20156
  function loadGlb_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."); }
19762
20157
  function loadGlb_unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return loadGlb_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) ? loadGlb_arrayLikeToArray(r, a) : void 0; } }
19763
20158
  function loadGlb_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; }
19764
20159
  function loadGlb_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; } }
19765
20160
  function loadGlb_arrayWithHoles(r) { if (Array.isArray(r)) return r; }
20161
+ function loadGlb_regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return loadGlb_regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (loadGlb_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, loadGlb_regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, loadGlb_regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), loadGlb_regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", loadGlb_regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), loadGlb_regeneratorDefine2(u), loadGlb_regeneratorDefine2(u, o, "Generator"), loadGlb_regeneratorDefine2(u, n, function () { return this; }), loadGlb_regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (loadGlb_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
20162
+ function loadGlb_regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } loadGlb_regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { loadGlb_regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, loadGlb_regeneratorDefine2(e, r, n, t); }
19766
20163
  function loadGlb_asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
19767
20164
  function loadGlb_asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { loadGlb_asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { loadGlb_asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
20165
+ function loadGlb_typeof(o) { "@babel/helpers - typeof"; return loadGlb_typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, loadGlb_typeof(o); }
20166
+
19768
20167
 
19769
20168
 
20169
+ /**
20170
+ * The subset of the meshopt decoder contract `GLTFLoader` actually exercises when
20171
+ * `decodeGltfBufferAsync` is present: it checks `supported`, then calls `decodeGltfBufferAsync`
20172
+ * for each `EXT_meshopt_compression` buffer view (never touching `ready`/`decodeGltfBuffer`).
20173
+ *
20174
+ * The signature is derived from the module type again, now that
20175
+ * `src/types/three-addons.d.ts` declares the decoder's shape directly instead of forwarding to
20176
+ * `@types/three`'s unresolvable `export * from "meshoptimizer/decoder"` — which `skipLibCheck`
20177
+ * swallowed, collapsing the lookup to `any`. Deriving keeps this interface and the delegation
20178
+ * call in `createLazyMeshoptDecoder` checked against a single definition; hand-writing it here
20179
+ * left the delegation itself unchecked, because the value it delegates to was still `any`.
20180
+ *
20181
+ * That definition is transcribed from three's JS, so it is an assertion about that file rather
20182
+ * than a derivation from it: a three.js change needs the `.d.ts` updated by hand. What is
20183
+ * compiler-enforced is that this shim and its consumers agree with it.
20184
+ * @internal
20185
+ */
20186
+
20187
+ /**
20188
+ * Builds a stand-in meshopt decoder that defers importing the real module (and instantiating its
20189
+ * WASM) until the first `EXT_meshopt_compression` buffer actually needs decoding. GLBs without the
20190
+ * extension never trigger the import, so the decoder stays off the critical path of the first 3D
20191
+ * frame — it used to be part of the eager three.js import chain, delaying every scene load.
20192
+ *
20193
+ * `supported` mirrors the real module's own gate — it returns a bare `{ supported: false }` when
20194
+ * `typeof WebAssembly !== "object"` — so the answer is reproduced synchronously here without
20195
+ * importing anything. Reporting a flat `true` instead would break the platforms it claims to
20196
+ * support: `GLTFLoader` reads `supported` before deciding to decode, and on `false` with meshopt
20197
+ * merely OPTIONAL it returns `null` and falls back to the GLB's uncompressed buffer
20198
+ * (`GLTFLoader.js` `GLTFMeshoptCompression.loadBufferView`). Claiming support on a WASM-less
20199
+ * platform discards that fallback and fails the whole asset with a `TypeError` from this shim,
20200
+ * since `decodeGltfBufferAsync` does not exist on the real module there.
20201
+ *
20202
+ * @param load Injectable module loader, for tests. Defaults to the cached {@link loadMeshoptDecoder}.
20203
+ * @internal
20204
+ */
20205
+ function createLazyMeshoptDecoder() {
20206
+ var load = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : loadMeshoptDecoder;
20207
+ return {
20208
+ // Same condition the real decoder uses, evaluated without loading it.
20209
+ supported: (typeof WebAssembly === "undefined" ? "undefined" : loadGlb_typeof(WebAssembly)) === "object",
20210
+ decodeGltfBufferAsync: function () {
20211
+ var _decodeGltfBufferAsync = loadGlb_asyncToGenerator(/*#__PURE__*/loadGlb_regenerator().m(function _callee(count, size, source, mode, filter) {
20212
+ var decoder;
20213
+ return loadGlb_regenerator().w(function (_context) {
20214
+ while (1) switch (_context.n) {
20215
+ case 0:
20216
+ _context.n = 1;
20217
+ return load();
20218
+ case 1:
20219
+ decoder = _context.v;
20220
+ _context.n = 2;
20221
+ return decoder.ready;
20222
+ case 2:
20223
+ return _context.a(2, decoder.decodeGltfBufferAsync(count, size, source, mode, filter));
20224
+ }
20225
+ }, _callee);
20226
+ }));
20227
+ function decodeGltfBufferAsync(_x, _x2, _x3, _x4, _x5) {
20228
+ return _decodeGltfBufferAsync.apply(this, arguments);
20229
+ }
20230
+ return decodeGltfBufferAsync;
20231
+ }()
20232
+ };
20233
+ }
19770
20234
 
19771
20235
  /**
19772
20236
  * Lazily-initialized, cached GLTF loader configured with Draco + KTX2 support.
19773
20237
  *
19774
20238
  * The loaders (and their WebGLRenderer for KTX2 support detection) are heavy and pull in Three.js,
19775
20239
  * so they are created on first use inside the dynamic-import boundary rather than at module load.
19776
- */
19777
- var gltfLoaderPromise;
19778
- function getGltfLoader() {
19779
- gltfLoaderPromise !== null && gltfLoaderPromise !== void 0 ? gltfLoaderPromise : gltfLoaderPromise = loadThree().then(function (mod) {
20240
+ *
20241
+ * Memoized through {@link memoizeImport} for the same reason the loaders in `three.ts` are: a plain
20242
+ * `cached ??=` also caches a rejection. Fixing the three loaders was not enough, because this is a
20243
+ * SECOND cache wrapped around `loadThree()` — so a transient failure was evicted there and then
20244
+ * replayed from here forever, and `loadGlbRoot` awaits both, meaning every GLB kept failing with
20245
+ * the stale error long after `loadThree()` had recovered. Not only network: the `.then` below
20246
+ * constructs a `WebGLRenderer` for KTX2 support detection, and a failed context creation was
20247
+ * cached just the same.
20248
+ */
20249
+ var getGltfLoader = memoizeImport(function () {
20250
+ return loadThree().then(function (mod) {
19780
20251
  var dracoLoader = new mod.DRACOLoader();
19781
20252
  dracoLoader.setDecoderPath(DEFAULT_DRACO_DECODER_PATH);
19782
20253
  var gltfLoader = new mod.GLTFLoader();
@@ -19788,15 +20259,15 @@ function getGltfLoader() {
19788
20259
  antialias: true
19789
20260
  }));
19790
20261
 
19791
- // Enable EXT_meshopt_compression so meshopt-compressed GLBs decode. The decoder is a WASM
19792
- // singleton; GLTFLoader awaits its `.ready` promise internally before parsing. Assets that
19793
- // don't use the extension are unaffected. This lets the offline pipeline pick meshopt (often
19794
- // smaller than Draco for the scene's meshes) without a runtime change.
19795
- gltfLoader.setMeshoptDecoder(mod.MeshoptDecoder);
20262
+ // Enable EXT_meshopt_compression so meshopt-compressed GLBs decode, without paying for the
20263
+ // decoder module up front: the lazy stand-in imports it (and instantiates its WASM) only when
20264
+ // the first meshopt-compressed buffer is actually decoded. Assets that don't use the extension
20265
+ // never load it. The cast is safe — with `decodeGltfBufferAsync` present, GLTFLoader touches
20266
+ // only `supported` and `decodeGltfBufferAsync` (see LazyMeshoptDecoder).
20267
+ gltfLoader.setMeshoptDecoder(createLazyMeshoptDecoder());
19796
20268
  return gltfLoader;
19797
20269
  });
19798
- return gltfLoaderPromise;
19799
- }
20270
+ });
19800
20271
 
19801
20272
  /**
19802
20273
  * Default per-asset timeout for {@link loadGlbRoot}.
@@ -19861,11 +20332,11 @@ function assertGlbBufferComplete(buffer, url) {
19861
20332
  * @returns Root `THREE.Group` from the loaded glTF scene
19862
20333
  * @group Utils
19863
20334
  */
19864
- function loadGlbRoot(_x) {
20335
+ function loadGlbRoot(_x6) {
19865
20336
  return _loadGlbRoot.apply(this, arguments);
19866
20337
  }
19867
20338
  function _loadGlbRoot() {
19868
- _loadGlbRoot = loadGlb_asyncToGenerator(/*#__PURE__*/loadGlb_regenerator().m(function _callee(url) {
20339
+ _loadGlbRoot = loadGlb_asyncToGenerator(/*#__PURE__*/loadGlb_regenerator().m(function _callee2(url) {
19869
20340
  var options,
19870
20341
  _options$timeoutMs,
19871
20342
  timeoutMs,
@@ -19879,16 +20350,16 @@ function _loadGlbRoot() {
19879
20350
  load,
19880
20351
  timer,
19881
20352
  timeout,
19882
- _args = arguments;
19883
- return loadGlb_regenerator().w(function (_context) {
19884
- while (1) switch (_context.p = _context.n) {
20353
+ _args2 = arguments;
20354
+ return loadGlb_regenerator().w(function (_context2) {
20355
+ while (1) switch (_context2.p = _context2.n) {
19885
20356
  case 0:
19886
- options = _args.length > 1 && _args[1] !== undefined ? _args[1] : {};
20357
+ options = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : {};
19887
20358
  _options$timeoutMs = options.timeoutMs, timeoutMs = _options$timeoutMs === void 0 ? DEFAULT_GLB_LOAD_TIMEOUT_MS : _options$timeoutMs, onProgress = options.onProgress;
19888
- _context.n = 1;
20359
+ _context2.n = 1;
19889
20360
  return Promise.all([getGltfLoader(), loadThree()]);
19890
20361
  case 1:
19891
- _yield$Promise$all = _context.v;
20362
+ _yield$Promise$all = _context2.v;
19892
20363
  _yield$Promise$all2 = loadGlb_slicedToArray(_yield$Promise$all, 2);
19893
20364
  gltfLoader = _yield$Promise$all2[0];
19894
20365
  three = _yield$Promise$all2[1].THREE;
@@ -19922,31 +20393,31 @@ function _loadGlbRoot() {
19922
20393
  }, onError);
19923
20394
  });
19924
20395
  if (!(!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
19925
- _context.n = 5;
20396
+ _context2.n = 5;
19926
20397
  break;
19927
20398
  }
19928
- _context.p = 2;
19929
- _context.n = 3;
20399
+ _context2.p = 2;
20400
+ _context2.n = 3;
19930
20401
  return load;
19931
20402
  case 3:
19932
- return _context.a(2, _context.v);
20403
+ return _context2.a(2, _context2.v);
19933
20404
  case 4:
19934
- _context.p = 4;
20405
+ _context2.p = 4;
19935
20406
  settled = true;
19936
- return _context.f(4);
20407
+ return _context2.f(4);
19937
20408
  case 5:
19938
20409
  timeout = new Promise(function (_resolve, reject) {
19939
20410
  timer = setTimeout(function () {
19940
20411
  return reject(new Error("Timed out loading GLB after ".concat(timeoutMs, "ms: ").concat(url)));
19941
20412
  }, timeoutMs);
19942
20413
  });
19943
- _context.p = 6;
19944
- _context.n = 7;
20414
+ _context2.p = 6;
20415
+ _context2.n = 7;
19945
20416
  return Promise.race([load, timeout]);
19946
20417
  case 7:
19947
- return _context.a(2, _context.v);
20418
+ return _context2.a(2, _context2.v);
19948
20419
  case 8:
19949
- _context.p = 8;
20420
+ _context2.p = 8;
19950
20421
  settled = true;
19951
20422
  clearTimeout(timer);
19952
20423
  // Cancels the underlying fetch (and, by never delivering its buffer, the Draco/KTX2 decode)
@@ -19954,11 +20425,11 @@ function _loadGlbRoot() {
19954
20425
  // unattended — otherwise GLB_LOAD_CONCURRENCY only bounds how many loads the caller awaits,
19955
20426
  // not how many are truly in flight. A no-op if `load` already won the race.
19956
20427
  fileLoader.abort();
19957
- return _context.f(8);
20428
+ return _context2.f(8);
19958
20429
  case 9:
19959
- return _context.a(2);
20430
+ return _context2.a(2);
19960
20431
  }
19961
- }, _callee, null, [[6,, 8, 9], [2,, 4, 5]]);
20432
+ }, _callee2, null, [[6,, 8, 9], [2,, 4, 5]]);
19962
20433
  }));
19963
20434
  return _loadGlbRoot.apply(this, arguments);
19964
20435
  }
@@ -21634,6 +22105,13 @@ function refineCameraForPitch(transform, coordinates, initial, params) {
21634
22105
  return best;
21635
22106
  }
21636
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
+
21637
22115
  /**
21638
22116
  * Subset of MapLibre {@link MapOptions} that can be derived from the interaction
21639
22117
  * restriction flags on {@link MapConfig}.
@@ -21655,6 +22133,10 @@ function refineCameraForPitch(transform, coordinates, initial, params) {
21655
22133
  * pinch-to-zoom, so it is handled separately after construction — see
21656
22134
  * {@link shouldDisableTouchRotation}.
21657
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
+ *
21658
22140
  * @internal
21659
22141
  */
21660
22142
  function buildInteractionOptions(mapConfig) {
@@ -21670,6 +22152,14 @@ function buildInteractionOptions(mapConfig) {
21670
22152
  options.dragRotate = false;
21671
22153
  options.pitchWithRotate = false;
21672
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
+ }
21673
22163
  return options;
21674
22164
  }
21675
22165
 
@@ -21759,6 +22249,24 @@ var TILE_RETRY_BASE_DELAY_MS = 1000;
21759
22249
  */
21760
22250
  var STYLE_READY_TIMEOUT_MS = 4000;
21761
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
+
21762
22270
  /** Bookkeeping for one tile in the retry queue. */
21763
22271
 
21764
22272
  /** Fill/line layer ids for one circle render-order bucket. */
@@ -21996,6 +22504,20 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
21996
22504
  */
21997
22505
  map_defineProperty(_this, "tileRetryState", new WeakMap());
21998
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);
21999
22521
  /** Owns the deck.gl overlay + GLB scene; created on style load only when 3D is active. */
22000
22522
  map_defineProperty(_this, "scene3D", undefined);
22001
22523
  if (rtlLanguages.includes((_mapConfig$lang = mapConfig.lang) !== null && _mapConfig$lang !== void 0 ? _mapConfig$lang : "")) {
@@ -22017,6 +22539,8 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22017
22539
  _this.onFloorChange = mapConfig.onFloorChange;
22018
22540
  _this.onParentPlaceChange = mapConfig.onParentPlaceChange;
22019
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;
22020
22544
  _this.scene3DConfig = mapConfig.scene3D;
22021
22545
  _this.initialCenter = mapConfig.center;
22022
22546
  _this.tileCacheConfig = function () {
@@ -22042,6 +22566,20 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22042
22566
  }
22043
22567
  map_inherits(InternalMapVXMap, _Loggeable);
22044
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
+ }, {
22045
22583
  key: "setRTLSupport",
22046
22584
  value: function setRTLSupport() {
22047
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 () {
@@ -22132,9 +22670,18 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22132
22670
  })["catch"](console.error);
22133
22671
  return;
22134
22672
  }
22135
- 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) {
22136
22679
  return _this5.onMapStyleLoaded(mapConfig, container, style);
22137
- })["catch"](console.error);
22680
+ })["catch"](function (error) {
22681
+ _this5.logError(error, {
22682
+ functionName: "initialMapStyleSetUp"
22683
+ });
22684
+ });
22138
22685
  }
22139
22686
  }, {
22140
22687
  key: "onMapStyleLoaded",
@@ -22168,6 +22715,11 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22168
22715
  }, buildInteractionOptions(mapConfig));
22169
22716
  if (mapConfig.maxZoom) mapOptions.maxZoom = mapConfig.maxZoom;
22170
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;
22171
22723
  if (mapConfig.maxBounds && mapConfig.maxBounds.length > 1) {
22172
22724
  var boundingBox = getBoundingBox(mapConfig.maxBounds);
22173
22725
  mapOptions.maxBounds = new maplibre_gl.LngLatBounds([boundingBox[0].lng, boundingBox[0].lat], [boundingBox[1].lng, boundingBox[1].lat]);
@@ -22337,6 +22889,11 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22337
22889
  clearTimeout(this.styleReadyTimer);
22338
22890
  this.styleReadyTimer = undefined;
22339
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;
22340
22897
  // Dispose the 3D scene's GPU resources before the map (and its shared WebGL context) go away —
22341
22898
  // MapLibre's own teardown never calls the custom GLB layer's `onRemove`.
22342
22899
  (_this$scene3D = this.scene3D) === null || _this$scene3D === void 0 || _this$scene3D.dispose();
@@ -22430,7 +22987,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22430
22987
  }
22431
22988
  (_this$scene3D3 = this.scene3D) === null || _this$scene3D3 === void 0 || _this$scene3D3.onFloorChange(this.currentFloor);
22432
22989
  if (updateStyle) {
22433
- 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) {
22434
22991
  var useServiceWorkerCaching = _this9.tileCacheConfig.enabled && _this9.tileCacheConfig.persistToServiceWorker;
22435
22992
  var finalStyle = useServiceWorkerCaching ? _this9.transformStyleForCaching(style) : style;
22436
22993
  _this9.map.setStyle(finalStyle);
@@ -22443,10 +23000,39 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22443
23000
  _this9.resetBaseFilters();
22444
23001
  _this9.whenStyleUpdates(finalStyle);
22445
23002
  onStyleReady === null || onStyleReady === void 0 || onStyleReady();
22446
- })["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
+ });
22447
23012
  }
22448
23013
  }
22449
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
+
22450
23036
  /**
22451
23037
  * Update the parent place and the current floor.
22452
23038
  * Does not update layers, markers, etc.
@@ -22562,18 +23148,20 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22562
23148
  reportReady(error);
22563
23149
  return;
22564
23150
  }
22565
-
22566
- // When a user bearer token is present, the style is resolved through the
22567
- // bearer-authenticated endpoint, which authorizes by the user's client access
22568
- // rather than the api key's scopes — so a client's branded style can be
22569
- // previewed/applied without granting that place to a shared api key.
22570
- // Api-key consumers keep the `/api/sdk/v2/mapStyleJson` path.
22571
- 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);
22572
23152
  stylePromise.then(function (style) {
22573
23153
  // A newer swap started while this style was in flight. Applying this one now
22574
23154
  // would fight the newer style layer by layer, so drop it and let the newer swap
22575
23155
  // own the map and its caller's callback.
22576
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;
22577
23165
  var useServiceWorkerCaching = _this1.tileCacheConfig.enabled && _this1.tileCacheConfig.persistToServiceWorker;
22578
23166
  var finalStyle = useServiceWorkerCaching ? _this1.transformStyleForCaching(style) : style;
22579
23167
  // Full (non-diff) swap so the previewed style completely replaces the base —
@@ -22811,7 +23399,11 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
22811
23399
  var markerCreated = new MarkerAttribute(map_objectSpread(map_objectSpread({}, marker), {}, {
22812
23400
  maxLabelWidthPx: (_marker$maxLabelWidth = marker.maxLabelWidthPx) !== null && _marker$maxLabelWidth !== void 0 ? _marker$maxLabelWidth : this.markerLayoutConfig.maxLabelWidthPx
22813
23401
  }));
22814
- 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
+ }
22815
23407
  this.markers.push(markerCreated);
22816
23408
  this.markerLayoutController.scheduleRelayout();
22817
23409
  this.logEvent("addMarker");
@@ -23319,12 +23911,131 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23319
23911
  floorKey: floorKeyString
23320
23912
  });
23321
23913
  }
23322
- this.updateFiltersTo(floorKeyString);
23323
- 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);
23324
23922
  this.refreshCircles();
23325
23923
  this.routeController.updateRouteLayers(floorKeyString);
23326
23924
  this.routeController.updateRouteMarkerVisibility(floorKeyString);
23327
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
+ }
23328
24039
  }, {
23329
24040
  key: "getContainer",
23330
24041
  value: function getContainer() {
@@ -23345,26 +24056,51 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23345
24056
  }, {
23346
24057
  key: "updateMarkersTo",
23347
24058
  value: function updateMarkersTo(floorId) {
23348
- var _this11 = this;
24059
+ var _this12 = this;
23349
24060
  var isOutdoor = this.isOutdoorFloor(floorId);
23350
24061
  this.markers.forEach(function (e) {
23351
- e.changeFloor(floorId, _this11.map, isOutdoor);
24062
+ e.changeFloor(floorId, _this12.map, isOutdoor);
23352
24063
  });
23353
24064
  this.markerLayoutController.scheduleRelayout();
23354
24065
  }
23355
24066
  }, {
23356
24067
  key: "updateFiltersTo",
23357
- value: function updateFiltersTo(floorId) {
23358
- var _this$map$getStyle$la3,
23359
- _this$map$getStyle3,
23360
- _this12 = this,
24068
+ value: function updateFiltersTo(floorId, styleLayers) {
24069
+ var _ref4,
24070
+ _this13 = this,
23361
24071
  _this$innerFloors$fin4;
23362
24072
  if (!this.map) return;
23363
- 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
+ }
23364
24100
  layers.filter(function (l) {
23365
24101
  return l.id.startsWith("base-indoor-");
23366
24102
  }).forEach(function (l) {
23367
- _this12.map.removeLayer(l.id);
24103
+ _this13.map.removeLayer(l.id);
23368
24104
  });
23369
24105
  var baseFloorId = (_this$innerFloors$fin4 = this.innerFloors.find(function (floor) {
23370
24106
  return floor.key === floorId;
@@ -23382,7 +24118,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23382
24118
  // resolved are skipped, so a filter this class wrote is never mistaken for a
23383
24119
  // declared one.
23384
24120
  indoorLayers.forEach(function (layer) {
23385
- return _this12.captureDeclaredBaseFilter(layer.id);
24121
+ return _this13.captureDeclaredBaseFilter(layer.id);
23386
24122
  });
23387
24123
  if (baseFloorId != undefined) {
23388
24124
  var _indoorLayers$;
@@ -23392,13 +24128,13 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23392
24128
  }).forEach(function (layer) {
23393
24129
  var baseLayer = deepClone(layer);
23394
24130
  baseLayer.id = "base-" + layer.id;
23395
- _this12.baseFilters[baseLayer.id] = _this12.baseFilters[layer.id];
23396
- _this12.map.addLayer(baseLayer, firstLayerId);
23397
- _this12.applyLayerFilters(baseLayer, baseFloorId);
24131
+ _this13.baseFilters[baseLayer.id] = _this13.baseFilters[layer.id];
24132
+ _this13.map.addLayer(baseLayer, firstLayerId);
24133
+ _this13.applyLayerFilters(baseLayer, baseFloorId);
23398
24134
  });
23399
24135
  }
23400
24136
  indoorLayers.forEach(function (layer) {
23401
- _this12.applyLayerFilters(layer, floorId);
24137
+ _this13.applyLayerFilters(layer, floorId);
23402
24138
  });
23403
24139
  }
23404
24140
 
@@ -23412,7 +24148,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23412
24148
  value: function safeGetFilter(layerId) {
23413
24149
  try {
23414
24150
  return this.map.getFilter(layerId);
23415
- } catch (_unused) {
24151
+ } catch (_unused2) {
23416
24152
  return undefined;
23417
24153
  }
23418
24154
  }
@@ -23600,11 +24336,11 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23600
24336
  var minLng;
23601
24337
  var maxLat;
23602
24338
  var maxLng;
23603
- var _iterator2 = map_createForOfIteratorHelper(coordinates),
23604
- _step2;
24339
+ var _iterator3 = map_createForOfIteratorHelper(coordinates),
24340
+ _step3;
23605
24341
  try {
23606
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
23607
- var _coord = _step2.value;
24342
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
24343
+ var _coord = _step3.value;
23608
24344
  var lat = _coord.lat,
23609
24345
  lng = _coord.lng;
23610
24346
  minLat = minLat === undefined ? lat : Math.min(minLat, lat);
@@ -23613,9 +24349,9 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23613
24349
  maxLng = maxLng === undefined ? lng : Math.max(maxLng, lng);
23614
24350
  }
23615
24351
  } catch (err) {
23616
- _iterator2.e(err);
24352
+ _iterator3.e(err);
23617
24353
  } finally {
23618
- _iterator2.f();
24354
+ _iterator3.f();
23619
24355
  }
23620
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];
23621
24357
 
@@ -23750,8 +24486,9 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23750
24486
  try {
23751
24487
  var _this$onFloorChange3;
23752
24488
  this.updatePlaceAndFloor(placeId, floorId);
23753
- this.updateFiltersTo(floorId);
23754
- this.updateMarkersTo(floorId);
24489
+ var layers = this.safeStyleLayers();
24490
+ this.updateFiltersTo(floorId, layers);
24491
+ this.updateMarkersWhenFloorSettles(floorId, layers);
23755
24492
  (_this$onFloorChange3 = this.onFloorChange) === null || _this$onFloorChange3 === void 0 || _this$onFloorChange3.call(this, floorId);
23756
24493
  this.logEvent("updatePlaceAndFloorForRouting", {
23757
24494
  floorId: floorId
@@ -23968,17 +24705,17 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23968
24705
  }, {
23969
24706
  key: "defaultClickListener",
23970
24707
  value: function defaultClickListener() {
23971
- var _this13 = this;
24708
+ var _this14 = this;
23972
24709
  this.map.on("click", /*#__PURE__*/function () {
23973
- var _ref4 = map_asyncToGenerator(/*#__PURE__*/map_regenerator().m(function _callee3(e) {
23974
- 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;
23975
24712
  return map_regenerator().w(function (_context3) {
23976
24713
  while (1) switch (_context3.p = _context3.n) {
23977
24714
  case 0:
23978
- 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
23979
24716
  roomFeatures = features.filter(function (feature) {
23980
24717
  var _feature$properties;
23981
- 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;
23982
24719
  });
23983
24720
  touchedFeature = roomFeatures[0];
23984
24721
  ref = touchedFeature === null || touchedFeature === void 0 ? void 0 : touchedFeature.properties.ref;
@@ -23989,23 +24726,23 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
23989
24726
  console.log("touchedFeature", ref);
23990
24727
 
23991
24728
  // First, search in current subPlaces
23992
- clickedPlace = _this13.subPlaces.find(function (subPlace) {
24729
+ clickedPlace = _this14.subPlaces.find(function (subPlace) {
23993
24730
  return subPlace.mapvxId === ref;
23994
24731
  }); // If not found, search in subplaces of potential parent places
23995
24732
  if (!(clickedPlace === undefined)) {
23996
24733
  _context3.n = 7;
23997
24734
  break;
23998
24735
  }
23999
- _iterator3 = map_createForOfIteratorHelper(_this13.potentialParentPlaces);
24736
+ _iterator4 = map_createForOfIteratorHelper(_this14.potentialParentPlaces);
24000
24737
  _context3.p = 1;
24001
- _iterator3.s();
24738
+ _iterator4.s();
24002
24739
  case 2:
24003
- if ((_step3 = _iterator3.n()).done) {
24740
+ if ((_step4 = _iterator4.n()).done) {
24004
24741
  _context3.n = 4;
24005
24742
  break;
24006
24743
  }
24007
- potentialParentPlace = _step3.value;
24008
- cachedSubPlaces = _this13.repository.getCachedSubPlaces(potentialParentPlace.mapvxId);
24744
+ potentialParentPlace = _step4.value;
24745
+ cachedSubPlaces = _this14.repository.getCachedSubPlaces(potentialParentPlace.mapvxId);
24009
24746
  if (!cachedSubPlaces) {
24010
24747
  _context3.n = 3;
24011
24748
  break;
@@ -24027,24 +24764,24 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24027
24764
  case 5:
24028
24765
  _context3.p = 5;
24029
24766
  _t3 = _context3.v;
24030
- _iterator3.e(_t3);
24767
+ _iterator4.e(_t3);
24031
24768
  case 6:
24032
24769
  _context3.p = 6;
24033
- _iterator3.f();
24770
+ _iterator4.f();
24034
24771
  return _context3.f(6);
24035
24772
  case 7:
24036
24773
  if (!(clickedPlace === undefined)) {
24037
24774
  _context3.n = 10;
24038
24775
  break;
24039
24776
  }
24040
- cachedPlaces = _this13.repository.getCachedPlaces([ref]);
24777
+ cachedPlaces = _this14.repository.getCachedPlaces([ref]);
24041
24778
  if (!(cachedPlaces.length === 0)) {
24042
24779
  _context3.n = 9;
24043
24780
  break;
24044
24781
  }
24045
24782
  console.log("fetching place detail from server", ref);
24046
24783
  _context3.n = 8;
24047
- return _this13.repository.fetchAndParsePlaceDetail({
24784
+ return _this14.repository.fetchAndParsePlaceDetail({
24048
24785
  id: ref
24049
24786
  });
24050
24787
  case 8:
@@ -24056,9 +24793,9 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24056
24793
  clickedPlace = cachedPlaces === null || cachedPlaces === void 0 ? void 0 : cachedPlaces[0];
24057
24794
  case 10:
24058
24795
  console.log("clickedPlace", (_clickedPlace = clickedPlace) === null || _clickedPlace === void 0 ? void 0 : _clickedPlace.title);
24059
- 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 : "";
24060
- if (clickedPlace !== undefined && _this13.clickCallback) {
24061
- _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);
24062
24799
  }
24063
24800
  case 11:
24064
24801
  return _context3.a(2);
@@ -24066,36 +24803,36 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24066
24803
  }, _callee3, null, [[1, 5, 6, 7]]);
24067
24804
  }));
24068
24805
  return function (_x3) {
24069
- return _ref4.apply(this, arguments);
24806
+ return _ref5.apply(this, arguments);
24070
24807
  };
24071
24808
  }());
24072
24809
  }
24073
24810
  }, {
24074
24811
  key: "onHover",
24075
24812
  value: function onHover() {
24076
- var _this14 = this;
24813
+ var _this15 = this;
24077
24814
  if (!this.enableHover) return;
24078
24815
  this.map.on("mousemove", "hoverable", function (e) {
24079
24816
  var _e$features;
24080
24817
  var feature = (_e$features = e.features) === null || _e$features === void 0 ? void 0 : _e$features.find(function (feature) {
24081
24818
  var _feature$properties$r, _feature$properties2;
24082
- 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;
24083
24820
  });
24084
24821
  var ref = feature === null || feature === void 0 ? void 0 : feature.properties.ref;
24085
24822
  if (ref != null && typeof ref === "string" && ref.length > 0) {
24086
- _this14.hoveredId = ref;
24087
- _this14.updateFiltersTo(_this14.currentFloor);
24088
- } else if (ref === undefined && _this14.hoveredId !== "unselected") {
24089
- _this14.hoveredId = "unselected";
24090
- _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);
24091
24828
  }
24092
24829
  });
24093
24830
 
24094
24831
  // When the mouse leaves the state-fill layer, update the feature state of the
24095
24832
  // previously hovered feature.
24096
24833
  this.map.on("mouseleave", "hoverable", function () {
24097
- _this14.hoveredId = "unselected";
24098
- _this14.updateFiltersTo(_this14.currentFloor);
24834
+ _this15.hoveredId = "unselected";
24835
+ _this15.updateFiltersTo(_this15.currentFloor);
24099
24836
  });
24100
24837
  }
24101
24838
  }, {
@@ -24496,7 +25233,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24496
25233
  }, {
24497
25234
  key: "addUserLocationTracking",
24498
25235
  value: function addUserLocationTracking(enableHighAccuracy, geoLocation, icon) {
24499
- var _this15 = this;
25236
+ var _this16 = this;
24500
25237
  var initialCoordinate = {
24501
25238
  lat: 0.0,
24502
25239
  lng: 0.0
@@ -24520,7 +25257,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24520
25257
  this.markerLayoutController.scheduleRelayout();
24521
25258
  var updateMarker = function updateMarker(position) {
24522
25259
  marker.setLngLat([position.coords.longitude, position.coords.latitude]);
24523
- _this15.markerLayoutController.scheduleRelayout();
25260
+ _this16.markerLayoutController.scheduleRelayout();
24524
25261
  };
24525
25262
  this.geoLocation = geoLocation !== null && geoLocation !== void 0 ? geoLocation : navigator.geolocation;
24526
25263
  this.watchPositionID = this.geoLocation.watchPosition(updateMarker, function (error) {
@@ -24616,7 +25353,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24616
25353
  key: "onWebGLContextLost",
24617
25354
  value: function onWebGLContextLost(e) {
24618
25355
  var _this$map2,
24619
- _this16 = this;
25356
+ _this17 = this;
24620
25357
  e.originalEvent.preventDefault();
24621
25358
  console.warn("Contexto WebGL perdido 😢");
24622
25359
 
@@ -24627,8 +25364,8 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24627
25364
  var _ext$restoreContext;
24628
25365
  canvas.addEventListener("webglcontextrestored", function () {
24629
25366
  console.debug("Contexto WebGL restaurado, forzando re-render");
24630
- _this16.map.triggerRepaint(); // forzar repaint
24631
- _this16.failedTiles.clear();
25367
+ _this17.map.triggerRepaint(); // forzar repaint
25368
+ _this17.failedTiles.clear();
24632
25369
  }, {
24633
25370
  once: true
24634
25371
  });
@@ -24672,6 +25409,24 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24672
25409
  * ("Cannot read properties of undefined"), so call sites that only need the style to
24673
25410
  * exist should gate on this rather than on `isStyleLoaded()`.
24674
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
+ }
24675
25430
  }, {
24676
25431
  key: "hasStyle",
24677
25432
  value: function hasStyle() {
@@ -24682,9 +25437,9 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24682
25437
  // or privatizes it — disabling both the tile-retry loop and `setStyle`'s apply pass
24683
25438
  // with no throw and no log. `safeGetFilter` already uses this try/catch shape.
24684
25439
  try {
24685
- var _this$map3;
24686
- return ((_this$map3 = this.map) === null || _this$map3 === void 0 ? void 0 : _this$map3.getStyle()) != null;
24687
- } 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) {
24688
25443
  return false;
24689
25444
  }
24690
25445
  }
@@ -24704,7 +25459,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24704
25459
  }, {
24705
25460
  key: "subscribeToFailedTiles",
24706
25461
  value: function subscribeToFailedTiles() {
24707
- var _this17 = this;
25462
+ var _this18 = this;
24708
25463
  if (this.retryTimer) return;
24709
25464
 
24710
25465
  // Store bound functions for proper cleanup
@@ -24721,23 +25476,23 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24721
25476
  // exactly when tiles fail — gating on it would skip every tick during a
24722
25477
  // bad-network burst. The queue is left untouched so the tiles queued
24723
25478
  // mid-swap are still retried on the next tick.
24724
- if (!_this17.hasStyle()) {
25479
+ if (!_this18.hasStyle()) {
24725
25480
  return;
24726
25481
  }
24727
- if (!_this17.map.getSource("indoorequal")) {
24728
- _this17.failedTiles.clear();
25482
+ if (!_this18.map.getSource("indoorequal")) {
25483
+ _this18.failedTiles.clear();
24729
25484
  return;
24730
25485
  }
24731
- var source = _this17.map.getSource("indoorequal");
25486
+ var source = _this18.map.getSource("indoorequal");
24732
25487
  if (!source || source.type !== "vector") {
24733
- _this17.failedTiles.clear();
25488
+ _this18.failedTiles.clear();
24734
25489
  return;
24735
25490
  }
24736
25491
 
24737
25492
  // Process tiles and remove invalid ones
24738
25493
  var tilesToRetry = [];
24739
25494
  var tilesToRemove = [];
24740
- _this17.failedTiles.forEach(function (tile) {
25495
+ _this18.failedTiles.forEach(function (tile) {
24741
25496
  // Check if tile is still valid before attempting reload
24742
25497
  if (!tile || tile.state === "unloaded" || tile.state === "loading") {
24743
25498
  tilesToRemove.push(tile);
@@ -24755,14 +25510,14 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24755
25510
 
24756
25511
  // Remove invalid tiles from the set
24757
25512
  tilesToRemove.forEach(function (tile) {
24758
- return _this17.failedTiles["delete"](tile);
25513
+ return _this18.failedTiles["delete"](tile);
24759
25514
  });
24760
25515
 
24761
25516
  // Attempt to reload valid failed tiles
24762
25517
  var now = Date.now();
24763
25518
  tilesToRetry.forEach(function (tile) {
24764
- var _this17$tileRetryStat;
24765
- 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 : {
24766
25521
  attempts: 0,
24767
25522
  nextAttemptAt: 0,
24768
25523
  pending: false
@@ -24773,16 +25528,16 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24773
25528
  var onFailure = function onFailure(error) {
24774
25529
  var attempts = retry.attempts + 1;
24775
25530
  if (attempts >= MAX_TILE_RETRIES) {
24776
- _this17.debugLog("Tile reload failed ".concat(attempts, " times, dropping it from the retry queue"), error);
24777
- _this17.failedTiles["delete"](tile);
24778
- _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);
24779
25534
  return;
24780
25535
  }
24781
25536
 
24782
25537
  // Stay in the queue and try again once the backoff has elapsed.
24783
25538
  var backoffMs = TILE_RETRY_BASE_DELAY_MS * Math.pow(2, attempts - 1);
24784
- _this17.debugLog("Tile reload failed (attempt ".concat(attempts, "/").concat(MAX_TILE_RETRIES, "), retrying in ").concat(backoffMs, "ms"), error);
24785
- _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, {
24786
25541
  attempts: attempts,
24787
25542
  nextAttemptAt: Date.now() + backoffMs,
24788
25543
  pending: false
@@ -24791,7 +25546,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24791
25546
  try {
24792
25547
  // Verify tile still exists and is in a retryable state
24793
25548
  if (tile && (tile.state === "errored" || tile.state === "expired")) {
24794
- _this17.tileRetryState.set(tile, map_objectSpread(map_objectSpread({}, retry), {}, {
25549
+ _this18.tileRetryState.set(tile, map_objectSpread(map_objectSpread({}, retry), {}, {
24795
25550
  pending: true
24796
25551
  }));
24797
25552
  // loadTile is async, so a rejection escapes this try/catch and has to be handled
@@ -24801,7 +25556,7 @@ var InternalMapVXMap = /*#__PURE__*/function (_Loggeable) {
24801
25556
  // Loaded: forget the attempt history so a later failure starts from zero. The
24802
25557
  // tile leaves the queue through the tilesToRemove branch on the next tick,
24803
25558
  // once its state is no longer errored/expired.
24804
- _this17.tileRetryState["delete"](tile);
25559
+ _this18.tileRetryState["delete"](tile);
24805
25560
  })["catch"](onFailure);
24806
25561
  }
24807
25562
  } catch (error) {