@minmaps-dev/mm-web-sdk 1.0.0-rc.35 → 1.0.0-rc.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -80,6 +80,12 @@ interface POI {
80
80
  /** For kiosk POIs only: compass heading the device physically faces, in
81
81
  * degrees clockwise from north. Sourced from JACS `Device.heading`. */
82
82
  heading?: number | null;
83
+ /** Per-instance name for this specific placement of an amenity (e.g.
84
+ * "Parking Lot A" for a "Parking" amenity), resolved from the waypoint's
85
+ * `amenityAssociations`. Distinct from `name`, which is the amenity's
86
+ * type-level name shared by every instance — see `Amenity.name`. Amenities
87
+ * only, and only set when JACS actually returned an instance name. */
88
+ instanceName?: string;
83
89
  }
84
90
  /**
85
91
  * Amenity - a specific type of POI (facilities, services)
@@ -185,6 +191,15 @@ interface Waypoint {
185
191
  * link between a location and its department — read at runtime across the
186
192
  * data provider (see the package CLAUDE.md zone landmine). */
187
193
  zoneId?: string | number;
194
+ /** Per-instance names JACS attaches to this waypoint's amenities (e.g. a
195
+ * "Parking" amenity placed twice as "Parking Lot A" / "Parking Lot B").
196
+ * `id` matches an `Amenity.id`, not this waypoint — a waypoint can carry
197
+ * more than one amenity, so a consumer must find the entry for the
198
+ * specific amenity it's rendering rather than assuming index 0. */
199
+ amenityAssociations?: Array<{
200
+ id: string | number;
201
+ name: string;
202
+ }>;
188
203
  }
189
204
  /**
190
205
  * Amenity enriched with the floor it belongs to
@@ -381,6 +396,11 @@ type AmenityBadgeStyle = {
381
396
  ringColor?: string;
382
397
  /** Ring width in logical px. Default `2`. */
383
398
  ringWidth?: number;
399
+ /** Extra inset between the icon and the badge's inner edge, in logical px
400
+ * (applied on all sides). Default `0` — the icon fills ~85% of the disc
401
+ * as before. Raise this if an icon's own artwork reaches close to its
402
+ * viewBox edges and ends up touching the ring. */
403
+ padding?: number;
384
404
  };
385
405
  interface SDKOptions {
386
406
  debug?: boolean;
@@ -437,18 +457,43 @@ interface SDKOptions {
437
457
  customSprite?: string;
438
458
  minIndoorZoom?: number;
439
459
  /**
440
- * Interior unit-wall thickness in feet (default 1). Each wall is built by
441
- * insetting a ring into each unit by `wallThickness / 2`, so adjacent units
442
- * share a wall (their rings abut at the common edge). Floored at 0.5 — a
443
- * thinner value collapses the negative buffer and drops the wall.
460
+ * How far, in **metres** (default `0.3`, ≈1 ft), extruded interior polygons
461
+ * (restrooms, back-of-house, obstacles, connectors) are inset so their edges
462
+ * don't z-fight the neighbouring room block. A polygon too thin to survive
463
+ * the inset (it would keep under half its area, e.g. a ~0.3 m back-of-house
464
+ * perimeter band) is left at its original size rather than erased. Metres to match the theme's
465
+ * `fill-extrusion-height` values, which MapLibre defines in metres — unlike
466
+ * the distances shown to visitors, which are in feet. Was feet before this
467
+ * option changed units; a value of `1` is now ~3.3× thicker.
444
468
  */
445
469
  wallThickness?: number;
470
+ /**
471
+ * How much costlier a candidate on a **different floor** from the kiosk is
472
+ * treated when choosing the closest instance of an amenity (default `1.5`).
473
+ * Used by `findClosestByWalkTime`, and so by `navigateFromKioskToClosestAmenity`
474
+ * and `highlightAmenity`.
475
+ *
476
+ * A multiplier on the trip's effort — walk time plus the elevator wait — not
477
+ * a distance: `1.5` means another floor's instance has to be a third cheaper
478
+ * to beat one on the kiosk's floor. It is a *preference* for not making the
479
+ * visitor change floors, layered on top of the physical cost of doing so
480
+ * (which is already in the effort). `1` turns it off and ranks by effort
481
+ * alone; a value below `1` is treated as `1`. It only affects which instance
482
+ * is chosen — never a route.
483
+ *
484
+ * For scale: at the Las Vegas VA the nearest other-floor Lactation Pod costs
485
+ * ~400 ft-equivalent against ~530 for the one on the kiosk's floor, so it
486
+ * takes a value above ~1.32 to keep the visitor on their floor.
487
+ */
488
+ otherFloorPenalty?: number;
446
489
  boundsPadding?: BoundsPadding;
447
490
  /**
448
491
  * Caps how far a visitor can pan/zoom out. By default the SDK derives this
449
492
  * from the venue's own bounds, scaled to 3x its width/height around the
450
493
  * same center — enough room to pan around the building without drifting
451
- * into an empty, un-tiled region. Pass a number to use a different
494
+ * into an empty, un-tiled region. The default box is never smaller than
495
+ * ~3.7 km a side, so a small venue isn't forced to zoom in tighter than a
496
+ * large one. Pass a number to use a different
452
497
  * multiplier (e.g. `1.5` for a tighter leash), explicit `[[west, south],
453
498
  * [east, north]]` bounds to override entirely, or `false` to disable
454
499
  * max-bounds clamping.
@@ -474,6 +519,16 @@ interface SDKOptions {
474
519
  * massing/region. `0` locks zoom-out exactly to the opening view.
475
520
  */
476
521
  minZoomBelowInitialFit?: number;
522
+ /**
523
+ * Zoom-out floor expressed as ground scale: the most metres one CSS pixel
524
+ * may span. The SDK converts it to a `minZoom` at the venue's latitude, so
525
+ * every venue shows the same amount of surrounding map when fully zoomed
526
+ * out — a plain zoom number doesn't, because Web Mercator shows less ground
527
+ * per zoom level the further a venue is from the equator. Takes precedence
528
+ * over `minZoomBelowInitialFit`. For scale, `1.207` is zoom 15.8 at Orlando
529
+ * (about 2.3 km x 1.3 km on a 1920x1080 screen).
530
+ */
531
+ minZoomMetersPerPixel?: number;
477
532
  /**
478
533
  * Override the colour the SDK recolours every amenity SVG to before it
479
534
  * composites the badge. `none` / `transparent` fills are preserved so
@@ -1044,11 +1099,15 @@ declare class MinuteMaps {
1044
1099
  private debugGlobalKey;
1045
1100
  private defaultCamera;
1046
1101
  /** This venue's opening zoom, captured once after the initial fit resolves.
1047
- * Anchors `applyRelativeIconZoomRanges` so icon/label reveal thresholds
1048
- * are relative to this venue's own opening view rather than an absolute
1049
- * MapLibre zoom — re-applied after every `setTheme()` swap too, since
1050
- * `setStyle({ diff: false })` recreates layers from the pristine theme. */
1102
+ * Anchors the pinned zoom profile's icon/label reveal thresholds (idle
1103
+ * spotlight, active route) to this venue's own opening view rather than an
1104
+ * absolute MapLibre zoom, and the roof fill's fade-in. See `zoomViews`. */
1051
1105
  private iconZoomBase;
1106
+ /** Which layers show at which zoom: the Campus / Building / Kiosk views, or
1107
+ * the pinned profile while the idle spotlight or a route is up. Re-applied
1108
+ * after every `setTheme()` swap, since `setStyle({ diff: false })`
1109
+ * recreates layers from the pristine theme. */
1110
+ private zoomViews;
1052
1111
  private viewModes;
1053
1112
  /** Mirror of `config.options.reducedMotion`, mutable via `setReducedMotion`. */
1054
1113
  private reducedMotion;
@@ -1081,6 +1140,14 @@ declare class MinuteMaps {
1081
1140
  * *previous* filters, not the ids) so `setTheme` can re-apply the same focus
1082
1141
  * to the new style's layers after a swap discards the old ones. */
1083
1142
  private destFocusIds;
1143
+ /** The predicate currently narrowing the POI marker layers to a curated set,
1144
+ * or `null` when no spotlight is active. See `setPOISpotlight`. Held as the
1145
+ * predicate, not a resolved id list, so a floor change or locale redraw can
1146
+ * re-resolve it against the POIs now on screen. */
1147
+ private poiSpotlight;
1148
+ /** Layer filters as they were before the spotlight narrowed them, put back
1149
+ * verbatim when it releases. `null` when no spotlight filter is applied. */
1150
+ private poiSpotlightRestore;
1084
1151
  /** Coordinates of the current tap/Info selection, or `null` when nothing is
1085
1152
  * selected. `easeToSelection` reads `getBoundsPadding()` at the moment a
1086
1153
  * selection is made, which is often *before* the consumer's info card has
@@ -1133,7 +1200,20 @@ declare class MinuteMaps {
1133
1200
  * `poi-highlight-*` layers — are intentionally unaffected; they're
1134
1201
  * anchors or transient focus state, not categorical content. */
1135
1202
  private static readonly POI_CATEGORY_LAYERS;
1203
+ /** Marker layers `setPOISpotlight` narrows. Every categorical POI layer
1204
+ * except `poi-other-amenity-icons`, whose filter the restroom-group manager
1205
+ * and the zoom-range pass rewrite as the camera moves — a spotlight
1206
+ * narrowing would be silently overwritten mid-orbit — plus the per-instance
1207
+ * amenity label, which is a marker in every sense but isn't a category. */
1208
+ private static readonly POI_SPOTLIGHT_LAYERS;
1136
1209
  private poiVisibleTypes;
1210
+ /** Independent show/hide state for destination dots and name labels, set
1211
+ * by `setDestinationCirclesVisible`/`setDestinationLabelsVisible`. Kept
1212
+ * separate from `poiVisibleTypes` so the two controls compose instead of
1213
+ * clobbering each other — `applyPOIFilter` and `applyDestinationDisplay`
1214
+ * each own one paint/layout axis of the same layers. */
1215
+ private destinationCirclesVisible;
1216
+ private destinationLabelsVisible;
1137
1217
  constructor(config: SDKConfig);
1138
1218
  init(): Promise<void>;
1139
1219
  /**
@@ -1463,8 +1543,14 @@ declare class MinuteMaps {
1463
1543
  * `{ id, mapId? }` shape. Returns `null` when the kiosk isn't anchored,
1464
1544
  * the target waypoint isn't on the graph, or no path resolves.
1465
1545
  *
1466
- * Walking speed defaults to 1.2 m/s (indoor wayfinding norm). Pass
1467
- * `walkingSpeedMps` to estimate for accessibility (e.g. 0.9).
1546
+ * Walking speed defaults to 2.953 ft/s (0.9 m/s, average adult walking speed —
1547
+ * PMC2967707). Pass `walkingSpeedFps` to estimate for accessibility (e.g. 2).
1548
+ *
1549
+ * `feet` / `seconds` are the *physical* walk — horizontal legs only, what a
1550
+ * visitor is shown. `effortSeconds` adds the router's charge for each floor
1551
+ * change (the elevator wait) and is what to *rank* candidates by; see
1552
+ * `findClosestByWalkTime`. Pass `avoidStairs` to price the path as a route
1553
+ * with that option would be.
1468
1554
  */
1469
1555
  getWalkTimeFromKiosk(target: number | string | {
1470
1556
  id?: number | string;
@@ -1474,10 +1560,12 @@ declare class MinuteMaps {
1474
1560
  id?: number | string;
1475
1561
  }>;
1476
1562
  }, opts?: {
1477
- walkingSpeedMps?: number;
1563
+ walkingSpeedFps?: number;
1564
+ avoidStairs?: boolean;
1478
1565
  }): {
1479
- meters: number;
1566
+ feet: number;
1480
1567
  seconds: number;
1568
+ effortSeconds: number;
1481
1569
  pathNodeCount: number;
1482
1570
  } | null;
1483
1571
  /**
@@ -1661,7 +1749,69 @@ declare class MinuteMaps {
1661
1749
  setPOIFilter(types: Array<'amenity' | 'destination'>): void;
1662
1750
  /** Restore the default — every POI category renders. */
1663
1751
  clearPOIFilter(): void;
1752
+ /**
1753
+ * Narrow the POI markers to a curated set: only POIs for which `match`
1754
+ * returns true keep their icon and label, every other pin on the categorical
1755
+ * marker layers (destinations, entrances, parking, bus, connectors,
1756
+ * information, and the per-instance amenity names) is filtered out. Built for
1757
+ * an attract loop that wants a few chosen places on screen instead of either
1758
+ * every marker or none.
1759
+ *
1760
+ * `match` runs against the current floor's POIs — the same objects
1761
+ * `getAllPOIs()` returns, `instanceName` and `amenityCategory` included — and
1762
+ * is re-run on every floor change, theme swap and locale redraw, so the
1763
+ * spotlight follows the map rather than the floor it was set on. Kiosk
1764
+ * "You are here" is never touched.
1765
+ *
1766
+ * Two things it deliberately leaves alone:
1767
+ * - `poi-other-amenity-icons` and the restroom badge. Their filters are owned
1768
+ * by the restroom-group manager and rewritten as the camera moves, so a
1769
+ * narrowing there would not survive a frame. Hide them with `hideLayers`
1770
+ * if the spotlight should exclude them.
1771
+ * - `setPOIFilter`'s category visibility, which composes: a category the
1772
+ * filter hides stays hidden whatever `match` says.
1773
+ *
1774
+ * Selecting a place (`selectPOI`, a map tap) releases the spotlight. Pass
1775
+ * `null` — or call `clearPOISpotlight()` — to restore every marker.
1776
+ */
1777
+ setPOISpotlight(match: ((poi: POI) => boolean) | null): void;
1778
+ /** Re-apply the zoom profile if the idle spotlight or a route just changed
1779
+ * which one is in force, and refresh what hangs off it. */
1780
+ private syncZoomViews;
1781
+ /** Restore every POI marker narrowed by `setPOISpotlight`. */
1782
+ clearPOISpotlight(): void;
1783
+ private applyPOISpotlight;
1664
1784
  private applyPOIFilter;
1785
+ /**
1786
+ * Show or hide destination dots, independently of their name labels and of
1787
+ * `setPOIFilter`'s combined `'destination'` category (which still toggles
1788
+ * both together). Affects every destination dot — both the dot+label
1789
+ * marker on `poi-destination-labels` and the dot-only fallback on
1790
+ * `poi-destination-circles` that a `showLabel: false` POI renders on.
1791
+ *
1792
+ * Implemented as `icon-opacity`, not a layer swap: dot and label are
1793
+ * placed as one collision unit on `poi-destination-labels` (`icon-optional:
1794
+ * false` / `text-optional: true`), which is what guarantees a label never
1795
+ * renders without a dot beneath it. Splitting them into independently
1796
+ * *placed* layers would let MapLibre's collision index resolve each half
1797
+ * on its own, so a label could win placement while its dot lost — the
1798
+ * "floating name" bug this theme's combined layer was built to fix (see
1799
+ * `themes.test.ts` :: "a destination is one marker").
1800
+ */
1801
+ setDestinationCirclesVisible(visible: boolean): void;
1802
+ /**
1803
+ * Show or hide destination name labels, independently of their dots. See
1804
+ * `setDestinationCirclesVisible` for why this is an opacity toggle on the
1805
+ * shared marker layer rather than a separate label layer.
1806
+ *
1807
+ * `visible: true` (the default) doesn't mean "always on" — labels still
1808
+ * fade in only once the visitor has zoomed in past the venue's opening
1809
+ * view (`DESTINATION_LABEL_REVEAL_OFFSET` in `iconZoomRanges.ts`), so a
1810
+ * zoomed-out visitor sees dots without the screen filling with names. Pass
1811
+ * `false` to suppress labels outright regardless of zoom.
1812
+ */
1813
+ setDestinationLabelsVisible(visible: boolean): void;
1814
+ private applyDestinationDisplay;
1665
1815
  /**
1666
1816
  * Return the waypoint in `waypoints` closest to `from`. Useful for
1667
1817
  * "route to the nearest X" against a venue-wide amenity record that has
@@ -1672,24 +1822,48 @@ declare class MinuteMaps {
1672
1822
  * single-venue scale, and avoids a turf dependency on the hot path.
1673
1823
  */
1674
1824
  findClosestWaypoint(waypoints: Waypoint[], from?: [number, number]): Waypoint | null;
1825
+ /**
1826
+ * Pick the candidate cheapest to reach from the kiosk — the one rule for
1827
+ * "closest instance of an amenity". `navigateFromKioskToClosestAmenity` and
1828
+ * `highlightAmenity` use it, and a consumer collapsing a venue-wide amenity
1829
+ * to a single search row should too, so a tap and a search result can never
1830
+ * resolve the same amenity to different floors.
1831
+ *
1832
+ * Each candidate is scored by `getWalkTimeFromKiosk().effortSeconds` — walk
1833
+ * time over the JACS path graph **plus the elevator wait** for any floor
1834
+ * change — and a candidate on a *different floor* from the kiosk has that
1835
+ * score multiplied by `options.otherFloorPenalty` (default `1.5`; `1`
1836
+ * disables it). The wait is the physical cost of leaving the floor; the
1837
+ * multiplier is a preference for not making the visitor do it, and only
1838
+ * applies here — it never changes a route.
1839
+ *
1840
+ * `toWaypoint` maps a candidate to the waypoint it stands for (identity for
1841
+ * a `Waypoint[]`, `poi => poi.waypoint` for a `POI[]`). Candidates whose
1842
+ * waypoint is missing or has no coordinates are ignored, as are waypoints
1843
+ * the graph can't reach from the kiosk. If none resolve a cost, falls back
1844
+ * to the planar `findClosestWaypoint`, **restricted to the kiosk's floor
1845
+ * when any candidate is on it** (planar distance is meaningless across
1846
+ * overlaid floors). Returns `null` for no usable candidate. Pass the same
1847
+ * `avoidStairs` the route will use so the pick and the route agree.
1848
+ */
1849
+ findClosestByWalkTime<T>(items: readonly T[], toWaypoint: (item: T) => Waypoint | null | undefined, opts?: {
1850
+ avoidStairs?: boolean;
1851
+ }): T | null;
1675
1852
  /**
1676
1853
  * Route from the kiosk to the closest physical instance of an amenity.
1677
1854
  * Resolves the amenity venue-wide, picks the instance with the smallest
1678
- * path-graph walk time from the kiosk (falling back to planar Euclidean
1679
- * when the walk-time graph can't resolve any waypoint), switches the
1855
+ * path-graph cost from the kiosk (`findClosestByWalkTime`: walk time plus
1856
+ * the elevator wait, with a preference for the kiosk's floor; falling back
1857
+ * to planar Euclidean when the walk-time graph can't resolve any waypoint), switches the
1680
1858
  * active floor if needed, and routes via the same plumbing as
1681
1859
  * `navigateFromKioskToPOI` — so step-by-step directions, the
1682
1860
  * `routeReady` event, and the active-floor camera fit all "just work."
1683
1861
  *
1684
1862
  * Pass `avoidStairs` to hard-filter stairs from the *route* (the SDK's
1685
1863
  * routing engine reads it per call; the CMS's per-path-type weight
1686
- * otherwise handles accessibility preference automatically). Note:
1687
- * closest-instance picking itself is currently distance-based and does
1688
- * not honor `avoidStairs` — the underlying walk-time graph applies
1689
- * pixel-length only. In practice this matters when an amenity has
1690
- * multiple instances and the geometrically closest is reachable only
1691
- * via stairs; the picked instance won't change today, but the *route*
1692
- * to it will avoid stairs (or fail gracefully) if `avoidStairs` is set.
1864
+ * otherwise handles accessibility preference automatically). The same
1865
+ * flag is passed to the closest-instance pick, so an instance reachable
1866
+ * only by stairs is skipped in favour of one the route can actually reach.
1693
1867
  */
1694
1868
  navigateFromKioskToClosestAmenity(amenityId: string | number, options?: {
1695
1869
  avoidStairs?: boolean;
@@ -1712,6 +1886,32 @@ declare class MinuteMaps {
1712
1886
  private findFloorForWaypoint;
1713
1887
  getCameraPosition(): CameraState | null;
1714
1888
  getMap(): Map | null;
1889
+ /**
1890
+ * Hide one or more layers by id — a public, always-live counterpart to
1891
+ * `debug.hide()` for consumers that want to toggle arbitrary layers
1892
+ * without reaching into the console-facing debug API. Sticky: the
1893
+ * override is re-asserted across floor changes, `setPOIFilter`, view-mode
1894
+ * toggles and theme swaps until `showLayers` undoes it. Ids absent from
1895
+ * the active style are skipped silently. Returns the ids actually found
1896
+ * and hidden.
1897
+ */
1898
+ hideLayers(layerIds: string[]): string[];
1899
+ /**
1900
+ * Undo `hideLayers` for these ids. Each layer returns to whatever
1901
+ * visibility the SDK's own state — current floor, POI filter, view mode —
1902
+ * says it should have; it is **not** forced to `visible`. A layer the SDK
1903
+ * is independently keeping hidden (another floor's layers, an
1904
+ * off-view-mode extrusion) stays hidden rather than being force-revealed.
1905
+ * This is the correct inverse of `hideLayers` (mirrors `debug.restore()` —
1906
+ * see `debugLayers.ts` for why a blanket "show" is usually the wrong
1907
+ * undo). Returns the ids that actually had an override to release.
1908
+ */
1909
+ showLayers(layerIds: string[]): string[];
1910
+ /**
1911
+ * Whether a layer is currently rendering (`visibility !== 'none'`).
1912
+ * `false` for a layer absent from the active style.
1913
+ */
1914
+ isLayerVisible(layerId: string): boolean;
1715
1915
  /**
1716
1916
  * Put this instance on `window` so a kiosk running in a browser can be
1717
1917
  * driven from devtools — `mm.debug.help()`, `mm.getMap()`, `mm.setTheme(…)`.
@@ -1760,6 +1960,12 @@ declare class MinuteMaps {
1760
1960
  * disables it, explicit bounds pass through as-is, and a number (or the
1761
1961
  * unset default of 3) scales the venue's own bounds outward around its
1762
1962
  * center — see `scaleBounds`.
1963
+ *
1964
+ * The unset default is also floored at `DEFAULT_MIN_MAX_BOUNDS_SPAN_M` per
1965
+ * side. MapLibre won't zoom out past the point where `maxBounds` fills the
1966
+ * viewport, so a small venue's 3x box would otherwise raise the effective
1967
+ * zoom-out floor above `minZoom` and show far less surrounding map than a
1968
+ * large venue does. An explicit number is a deliberate leash and is left as-is.
1763
1969
  */
1764
1970
  private getMaxBounds;
1765
1971
  /**