@minmaps-dev/mm-web-sdk 1.0.0-rc.46 → 1.0.0-rc.48

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
@@ -233,6 +233,11 @@ interface Zone {
233
233
  /** Authored hex color (e.g. "#d32f2f"); may be empty when the author
234
234
  * hasn't picked one — such zones are skipped for color-coding. */
235
235
  color: string;
236
+ /** The color this zone's rooms are drawn in under the active theme — `color`
237
+ * in the default theme, its high-contrast palette entry under high contrast.
238
+ * Use this, not `color`, for a legend swatch, and re-read it on
239
+ * `themeChanged`. Set on `getZones()` results only. */
240
+ displayColor?: string;
236
241
  description?: string;
237
242
  }
238
243
 
@@ -272,6 +277,46 @@ type WayfindCenterMode = 'none' | 'destination' | 'route';
272
277
  type RoutingOptions = {
273
278
  avoidStairs?: boolean;
274
279
  };
280
+ /**
281
+ * A spot on a floor that need not be a POI — a tap in the middle of a
282
+ * corridor. Routes and walk times accept one wherever they accept a POI:
283
+ * it's snapped to the closest point along a path on its floor and routed
284
+ * from (or to) there, without any CMS waypoint at that spot.
285
+ *
286
+ * `kind` tells it apart from a `POI`, which also has `floorId` and
287
+ * `coordinates`. Build one with `sdk.snapToPath`, or by hand from a stored
288
+ * point (a shared link) — the SDK snaps it again either way.
289
+ */
290
+ type MapLocation = {
291
+ kind: 'location';
292
+ floorId: string | number;
293
+ /** `[lng, lat]`. */
294
+ coordinates: [number, number];
295
+ /** What the route calls it — the arrive step's landmark and the end
296
+ * flag's label. Omitted, the arrive step reads "You have arrived". */
297
+ label?: string;
298
+ /** Where the visitor actually tapped, when it's off the walking network and
299
+ * `coordinates` (where the route starts or ends) had to be moved onto it.
300
+ * Set by `snapToPath`; a location built from a stored point is snapped from
301
+ * `tapped ?? coordinates`, so store this to rebuild it later. */
302
+ tapped?: [number, number];
303
+ /** The walkable way from `tapped` to `coordinates` — around rooms, back-of-
304
+ * house areas and other obstacles, leaving a room by its door — drawn as a
305
+ * dotted line with a marker on the tap. `tapped` without `approach` means
306
+ * no walkable way was found (a room with no known door, somewhere nothing
307
+ * walks), so the point was simply moved to the nearest path. */
308
+ approach?: [number, number][];
309
+ /** `tapped` is inside a building or room, so `approach` leads to a hallway
310
+ * rather than an outdoor walkway — it picks the approach step's wording.
311
+ * Set by `snapToPath` alongside `approach`. */
312
+ indoors?: boolean;
313
+ /** `tapped` was in a hallway, a little way off its path line: the point was
314
+ * moved to the middle of the hall, with no `approach` to draw. Set by
315
+ * `snapToPath` with `tapped`; a "choose on map" UI can say so. */
316
+ centered?: boolean;
317
+ };
318
+ /** True for a `MapLocation`, false for a `POI`. */
319
+ declare function isMapLocation(value: unknown): value is MapLocation;
275
320
 
276
321
  /**
277
322
  * A single point along the rendered route. `mapId` lets the renderer
@@ -299,7 +344,7 @@ type RoutePoint = {
299
344
  isEndpoint?: boolean;
300
345
  };
301
346
 
302
- type WayfindStep = DepartStep | TurnStep | ContinueStep | TransitionStep$1 | ArriveStep;
347
+ type WayfindStep = DepartStep | TurnStep | ContinueStep | TransitionStep$1 | ApproachStep | ArriveStep;
303
348
  type StepBase = {
304
349
  /** Indices into the input `points` array that this step spans. */
305
350
  pointRange: [startInclusive: number, endInclusive: number];
@@ -335,20 +380,44 @@ type TransitionStep$1 = StepBase & {
335
380
  type ArriveStep = StepBase & {
336
381
  type: 'arrive';
337
382
  };
383
+ /**
384
+ * The walk between a tapped spot and the walking network (`MapLocation.approach`):
385
+ * `'start'` from where the visitor tapped to the hallway, before the depart
386
+ * step; `'end'` from the hallway to the tapped destination, before arrive.
387
+ * Its geometry isn't on the route line, so it carries its own `path` (in
388
+ * walking order) — highlight and frame that, not `pointRange`, which only
389
+ * marks the route point it joins.
390
+ */
391
+ type ApproachStep = StepBase & {
392
+ type: 'approach';
393
+ approach: 'start' | 'end';
394
+ path: [number, number][];
395
+ };
338
396
 
339
397
  interface MapEvent {
340
398
  floor?: Floor;
399
+ /** `routeReady`: the destination routed to — the exact instance, so for
400
+ * `navigateFromKioskToClosestAmenity` it carries the picked `waypointId`. */
341
401
  poi?: POI;
342
402
  /** All POIs co-located at a clicked point (overlapping pins / a shared
343
403
  * suite). Emitted by `poiSelected`; length ≥ 1, and `poi` mirrors
344
404
  * `pois[0]` for single-POI consumers. */
345
405
  pois?: POI[];
346
406
  coordinates?: [number, number];
407
+ /** `locationPicked`: the tap snapped to the closest point along a path, or
408
+ * `null` when no path on the floor was within reach (`coordinates` is the
409
+ * raw tap either way). `routeFailed`: the location that didn't resolve.
410
+ * `routeReady`: the tapped spot routed to, in place of `poi`. */
411
+ location?: MapLocation | null;
412
+ /** `locationPicked`: the tap was outside every building and room on the
413
+ * floor. With `hasOutdoorPaths(floor.id)` false, nothing outdoors on this
414
+ * floor is walkable — the visitor wants a floor that has outdoor paths. */
415
+ outdoors?: boolean;
347
416
  error?: any;
348
417
  venue?: any;
349
418
  camera?: CameraState;
350
419
  /** Emitted by `themeChanged`; `'custom'` when consumer passed a raw style. */
351
- theme?: 'default' | 'high-contrast' | 'custom';
420
+ theme?: 'default' | 'high-contrast' | 'color-blind' | 'custom';
352
421
  /** Emitted by `localeChanged` after `setLocale()` finishes patching the
353
422
  * venue model. BCP-47 code (`'en'`, `'es'`, `'es-MX'`). */
354
423
  locale?: string;
@@ -383,7 +452,7 @@ type BoundsPadding = number | {
383
452
  * `options.theme` init option. Pass a `StyleSpecification` directly for
384
453
  * custom themes.
385
454
  */
386
- type ThemeName = 'default' | 'high-contrast';
455
+ type ThemeName = 'default' | 'high-contrast' | 'color-blind';
387
456
  /**
388
457
  * Style of the disc + ring rendered behind each amenity icon. The SDK
389
458
  * composites this into the icon bitmap at registration time, so badge +
@@ -414,7 +483,10 @@ interface SDKOptions {
414
483
  debugGlobal?: string | false;
415
484
  /**
416
485
  * Initial theme. `'default'` uses the bundled hybrid 3D theme.
417
- * `'high-contrast'` uses the WCAG-AA tuned theme. Pass a
486
+ * `'high-contrast'` uses the WCAG-AA tuned theme. `'color-blind'` keeps the
487
+ * default look but swaps the colors that red-green and blue-yellow color
488
+ * blindness merge (the route, room categories and department zones,
489
+ * restrooms, parks). Pass a
418
490
  * `StyleSpecification` for fully custom styling.
419
491
  */
420
492
  theme?: ThemeName | any;
@@ -519,6 +591,16 @@ interface SDKOptions {
519
591
  * zoom.
520
592
  */
521
593
  initialZoom?: number;
594
+ /**
595
+ * Zoom floor for selecting a place — a map tap or `selectPOI` (default
596
+ * `19.25`, one level into the Kiosk view). A selection made from further out
597
+ * zooms in to this; a closer zoom is kept. At the Kiosk view's edge (18.25)
598
+ * the selected dot and name are drawn but still lose collision to their
599
+ * neighbours, and in the Building view (a room tapped from 16.5 up) they
600
+ * aren't drawn at all — either way the visitor selects a place they can't
601
+ * see. `0` keeps the current zoom on every selection.
602
+ */
603
+ selectionZoom?: number;
522
604
  /**
523
605
  * Clamp zoom-out relative to the opening view. When set, the map's
524
606
  * `minZoom` becomes `(initialZoom − this)`, so visitors can nudge out by
@@ -653,6 +735,12 @@ type SDKConfig = {
653
735
  options?: SDKOptions;
654
736
  };
655
737
 
738
+ /** The chosen ends; either may be missing. */
739
+ type RouteEndPreview = {
740
+ start?: POI | MapLocation | null;
741
+ destination?: POI | MapLocation | null;
742
+ };
743
+
656
744
  type Visibility = 'visible' | 'none';
657
745
  type DebugLayersDeps = {
658
746
  getMap: () => Map | null;
@@ -1073,6 +1161,24 @@ interface OpenStatusOptions {
1073
1161
  * wrong answer at a kiosk is worse than a missing one.
1074
1162
  */
1075
1163
  declare function parseOpenHours(raw: unknown): OpenHours | null;
1164
+ /**
1165
+ * The weekday `now` falls on in `timeZone` (host clock when omitted or
1166
+ * unrecognised) — the same day `getOpenStatus` evaluates against. Exported so
1167
+ * a week table can highlight "today" without disagreeing with the status line
1168
+ * around the venue's midnight.
1169
+ */
1170
+ declare function dayKeyAt(now: Date, timeZone?: string): DayKey;
1171
+ /**
1172
+ * A short label for `timeZone` (host zone when omitted or unrecognised) — the
1173
+ * zone `getOpenStatus` reads hours against — so a UI can say "Opens 9:00 AM ET".
1174
+ *
1175
+ * US zones get the generic form (`ET`, `CT`, `MT`, `PT`, `AKT`), which doesn't
1176
+ * flip with daylight saving. Elsewhere the engine has no generic abbreviation
1177
+ * (it would spell out "Germany Time"), so this falls back to the specific short
1178
+ * name, which may be an offset (`GMT+2`). `now` only matters for that fallback
1179
+ * and for zones like Phoenix that print `MST` year-round.
1180
+ */
1181
+ declare function timeZoneAbbreviation(now?: Date, timeZone?: string): string;
1076
1182
  /**
1077
1183
  * The live open/closed state for a parsed week.
1078
1184
  *
@@ -1083,10 +1189,35 @@ declare function parseOpenHours(raw: unknown): OpenHours | null;
1083
1189
  *
1084
1190
  * Hours carry no timezone of their own, so they are read against the host clock
1085
1191
  * unless `options.timeZone` names the venue's zone. The kiosk case needs no
1086
- * option: the machine stands in the building it is describing.
1192
+ * option: the machine stands in the building it is describing. A web consumer
1193
+ * whose visitors may be elsewhere should resolve the venue's zone (e.g. from
1194
+ * `MinuteMaps.getVenueCenter()`) and pass it.
1087
1195
  */
1088
1196
  declare function getOpenStatus(hours: OpenHours, now?: Date, options?: OpenStatusOptions): OpenStatus;
1089
1197
 
1198
+ /** One floor of the active route, drawn on its own for a printout. */
1199
+ type RouteFloorImage = {
1200
+ floor: Floor;
1201
+ /** The bare map canvas (no DOM chrome), as a `data:` URL. */
1202
+ dataUrl: string;
1203
+ };
1204
+ type RouteImageOptions = {
1205
+ /** CSS-pixel size the map is laid out at while capturing. Default 1200×800. */
1206
+ width?: number;
1207
+ height?: number;
1208
+ /** Canvas pixels per CSS pixel. Default 2, crisp enough for paper. */
1209
+ pixelRatio?: number;
1210
+ /** Padding around each floor's route segment, in CSS px. Default 40. */
1211
+ padding?: number;
1212
+ /** Draw in flat (2D) mode, which reads better on paper. The previous mode
1213
+ * is put back afterwards. Default `true`. */
1214
+ flat?: boolean;
1215
+ /** Image MIME type passed to `toDataURL`. Default `'image/png'`. */
1216
+ type?: string;
1217
+ /** Encoder quality for lossy types (`'image/jpeg'`, `'image/webp'`). */
1218
+ quality?: number;
1219
+ };
1220
+
1090
1221
  type JacsProviderConfig = {
1091
1222
  mode: 'proxy';
1092
1223
  proxyBaseUrl?: string;
@@ -1404,6 +1535,8 @@ declare class SnapshotProvider implements JacsReader {
1404
1535
  private fetchJson;
1405
1536
  }
1406
1537
 
1538
+ /** A built-in theme name, or `'custom'` when a raw style was passed. */
1539
+ type ActiveThemeName = ThemeName | 'custom';
1407
1540
  declare class MinuteMaps {
1408
1541
  private config;
1409
1542
  private map;
@@ -1461,8 +1594,9 @@ declare class MinuteMaps {
1461
1594
  /** Multiplier applied to every theme label's `text-size`, mutable via
1462
1595
  * `setTextScale`. Re-applied after every `setTheme()` swap. */
1463
1596
  private textScale;
1464
- /** Each label layer's unscaled `text-size`, captured by `setTextScale`.
1465
- * Cleared on `setTheme()` — the recreated layers carry new base values. */
1597
+ /** Each label layer's unscaled `text-size` and em offsets, captured by
1598
+ * `setTextScale` and keyed `"<layer id>|<property>"`. Cleared on
1599
+ * `setTheme()` — the recreated layers carry new base values. */
1466
1600
  private baseTextSizes;
1467
1601
  /** Tracks the active theme name so `setTheme()` is a no-op when re-requested. */
1468
1602
  private activeThemeName;
@@ -1473,11 +1607,22 @@ declare class MinuteMaps {
1473
1607
  /** The destination of the last successfully computed route — kept so
1474
1608
  * `setLocale()` can rebuild + re-emit `routeReady` with retranslated
1475
1609
  * step text without recomputing the path. Cleared on route failure. */
1476
- private lastRouteDestinationPoi;
1610
+ private lastRouteDestination;
1611
+ /** `setLocationPicking` is on: a tap snaps to a path and emits
1612
+ * `locationPicked` instead of selecting what's under it. */
1613
+ private locationPicking;
1614
+ /** The active route's two ends, for redrawing its approaches after a
1615
+ * location-pick preview borrowed their source. */
1616
+ private routeApproachEnds;
1617
+ /** The running "moved to the nearest path" animation frame, if any. */
1618
+ private pickAnimationFrame;
1477
1619
  /** Waypoint ids (stringified) of the connector(s) the active transition
1478
1620
  * step rides — empty unless `setActiveStep` is on a transition. Drives
1479
1621
  * `applyRouteConnectorHighlight`. */
1480
1622
  private activeTransitionWaypointIds;
1623
+ /** Set while `captureRouteFloorImages` runs; holds the bounds padding to
1624
+ * put back afterwards (updated by any `setBoundsPadding` in between). */
1625
+ private routeCapture;
1481
1626
  private amenityManager;
1482
1627
  private wayfinding;
1483
1628
  private highlightManager;
@@ -1511,6 +1656,13 @@ declare class MinuteMaps {
1511
1656
  * fresh insets, the same way it already re-fits an active route. Cleared by
1512
1657
  * `clearHighlight`. */
1513
1658
  private selectedCoordinates;
1659
+ /** Zoom floor for the current selection's camera move, or `null` to keep
1660
+ * the current zoom. Set by `selectPOI` and map taps (`selectionZoom()` — a
1661
+ * pick can arrive from the Campus or Building view, where the selected
1662
+ * marker isn't drawn); kept beside
1663
+ * `selectedCoordinates` so a `setBoundsPadding` re-nudge carries the same
1664
+ * zoom instead of cancelling the zoom-in halfway. Cleared with it. */
1665
+ private selectedMinZoom;
1514
1666
  private youAreHerePulse;
1515
1667
  /** Theme layers whose features represent a tappable destination/amenity.
1516
1668
  * A click hit-tests these (see `SelectionManager`) → `poiSelected`. The
@@ -1651,16 +1803,62 @@ declare class MinuteMaps {
1651
1803
  */
1652
1804
  private registerRuntimeIcons;
1653
1805
  /**
1654
- * Set the active map theme by name (`'default'` / `'high-contrast'`) or by
1806
+ * Set the active map theme by name (`'default'` / `'high-contrast'` /
1807
+ * `'color-blind'`) or by
1655
1808
  * passing a custom `StyleSpecification`. The current floor, route, POIs
1656
1809
  * and camera are preserved across the swap — only layer styling changes.
1657
1810
  *
1658
1811
  * Used by kiosk consumers to wire the OS-level / popover high-contrast
1659
1812
  * toggle to the map. See `KIOSK.md` for the pattern.
1813
+ *
1814
+ * Switching between the two built-in themes happens in place (paint
1815
+ * properties plus the sprite — see `swapBuiltinThemeInPlace`); a custom style,
1816
+ * or leaving one, rebuilds the style with `setStyle`. Calls run one at a time
1817
+ * in the order made, so a quick double toggle can't interleave two swaps.
1660
1818
  */
1661
1819
  setTheme(theme: ThemeName | StyleSpecification): Promise<void>;
1820
+ /** Tail of the `setTheme` queue. */
1821
+ private themeQueue;
1822
+ private applyTheme;
1823
+ /**
1824
+ * Switch between built-in themes without `setStyle`. They share every layer
1825
+ * and differ only in paint and sprite (high contrast and color-blind are
1826
+ * derived from hybrid — see `themes/`), so this rewrites the paint
1827
+ * properties that differ and points the map at the other sprite sheet.
1828
+ * Sources, filters, zoom ranges, layout overrides (text scale, dyslexic font)
1829
+ * and the selection's destination focus are untouched, so unlike the
1830
+ * `setStyle` path there is nothing to capture and restore.
1831
+ *
1832
+ * Returns `false`, having changed nothing, when the two themes differ in
1833
+ * anything paint can't express; the caller then falls back to `setStyle`.
1834
+ */
1835
+ private swapBuiltinThemeInPlace;
1836
+ /** Room layers whose color a zone palette replaces. The
1837
+ * highlight pair is here so a selected room's raised block keeps its
1838
+ * palette color instead of reverting to the authored one. */
1839
+ private static readonly ZONE_PAINT_TARGETS;
1840
+ /** Zone id → palette fill for `themeName`, over the zones in use (see
1841
+ * `assignZonePaletteColors` for why only those), or `null` for a theme that
1842
+ * draws the authored colors. */
1843
+ private paletteZoneColors;
1844
+ /**
1845
+ * Under a theme with a zone palette (high contrast, color-blind), color each
1846
+ * zoned room from it, keeping the theme's own value for everything else.
1847
+ * Under the default theme, write the theme's own value back: its expression
1848
+ * already tints by the authored `zoneColor`, and an in-place swap away from
1849
+ * color-blind wouldn't reset these layers otherwise, since the two static
1850
+ * themes' unit fills are identical and `diffThemePaint` skips them.
1851
+ *
1852
+ * Writes the room layers' paint, so it must run before `setUnitFocusDim(true)`
1853
+ * captures them — otherwise clearing the selection would hand back the
1854
+ * palette-less value.
1855
+ */
1856
+ private applyZonePaint;
1857
+ /** The color a zone's rooms are drawn in under the active theme: its palette
1858
+ * entry under high contrast or color-blind, its authored color otherwise. */
1859
+ private zoneDisplayColor;
1662
1860
  /** Currently active theme name (or `'custom'` when a raw style was passed). */
1663
- getActiveTheme(): 'default' | 'high-contrast' | 'custom';
1861
+ getActiveTheme(): ActiveThemeName;
1664
1862
  /**
1665
1863
  * Update the SDK's reduced-motion preference at runtime. When true, every
1666
1864
  * imperative camera move (`setView`, `resetView`, `refit`, recenter, route
@@ -1704,6 +1902,10 @@ declare class MinuteMaps {
1704
1902
  * wrapped), so zoom interpolation and data-driven sizing keep working. Re-applied after
1705
1903
  * `setTheme()`, since `setStyle({ diff: false })` recreates every layer from
1706
1904
  * the pristine theme JSON. Non-finite or non-positive values reset to 1.
1905
+ *
1906
+ * The em-based `text-offset` / `text-variable-anchor-offset` are divided by
1907
+ * the same factor, so each label keeps its pixel distance from an icon that
1908
+ * doesn't scale (see `scaleTextEmOffset`).
1707
1909
  */
1708
1910
  setTextScale(scale: number): void;
1709
1911
  getTextScale(): number;
@@ -1761,6 +1963,28 @@ declare class MinuteMaps {
1761
1963
  setFlatMode(enabled: boolean): void;
1762
1964
  toggleFlatMode(): void;
1763
1965
  getIsFlatMode(): boolean;
1966
+ /**
1967
+ * Street address of the venue, from its first JACS building's `address`.
1968
+ * The CMS's Edit Venue form saves its Address field there, since it binds a
1969
+ * venue to a single building. `null` before the venue loads or when the
1970
+ * address is blank. In the venue's default language: `/all` ignores
1971
+ * `?locale=` and the locale refetch doesn't cover buildings.
1972
+ */
1973
+ getVenueAddress(): string | null;
1974
+ /**
1975
+ * Description of the venue, from its first JACS building's `description` —
1976
+ * stored there by the CMS's Edit Venue form, like `getVenueAddress()`.
1977
+ * `null` before the venue loads or when the description is blank. Same
1978
+ * locale caveat: always the venue's default language.
1979
+ */
1980
+ getVenueDescription(): string | null;
1981
+ /**
1982
+ * Center of the venue's bounds as `[lng, lat]`. `null` before the venue
1983
+ * loads or when it has no coordinates. JACS records no time zone, so a
1984
+ * consumer whose visitors may be outside the venue's zone resolves one from
1985
+ * this point and passes it to `getOpenStatus` / `dayKeyAt`.
1986
+ */
1987
+ getVenueCenter(): [number, number] | null;
1764
1988
  getFloors(): Floor[];
1765
1989
  getCurrentFloor(): Floor | null;
1766
1990
  getDefaultFloor(): Floor | null;
@@ -1784,12 +2008,14 @@ declare class MinuteMaps {
1784
2008
  floorShortName?: string;
1785
2009
  zoneName?: string;
1786
2010
  zoneDescription?: string;
1787
- /** The department's authored hex colour (`Zone.color`) — the same value
1788
- * that tints this room's polygon on the map, so a consumer can print a
1789
- * swatch beside the department name and connect the two. Absent when the
1790
- * author never picked one, which is also when the map leaves the room
1791
- * untinted; treat "no colour" as "this department isn't colour-coded",
1792
- * not as "fall back to a default swatch". */
2011
+ /** The colour that tints this room's polygon on the map under the active
2012
+ * theme — the department's authored `Zone.color`, or its high-contrast
2013
+ * palette entry under high contrast (`Zone.displayColor`) — so a consumer
2014
+ * can print a swatch beside the department name and connect the two.
2015
+ * Re-read it on `themeChanged`. Absent when the author never picked one,
2016
+ * which is also when the map leaves the room untinted; treat "no colour"
2017
+ * as "this department isn't colour-coded", not as "fall back to a default
2018
+ * swatch". */
1793
2019
  zoneColor?: string;
1794
2020
  keywords?: string[];
1795
2021
  description?: string;
@@ -2015,22 +2241,128 @@ declare class MinuteMaps {
2015
2241
  /**
2016
2242
  * Route between two arbitrary POIs — the browser/mobile counterpart of
2017
2243
  * `navigateFromKioskToPOI`, for visitors who aren't standing at a kiosk
2018
- * and pick their own starting point. Same events (`routeReady` /
2019
- * `routeFailed`) and the same `centerMode: 'route'` framing. Switches to
2020
- * the origin's floor first, because the route starts there.
2021
- *
2022
- * Emits `routeFailed` with `origin_unavailable` / `destination_unavailable`
2023
- * when either POI has no waypoint, `no_route` when no path connects them.
2244
+ * and pick their own starting point. `navigateBetween` with POIs only.
2024
2245
  */
2025
2246
  navigateBetweenPOIs(from: POI, to: POI, options?: {
2026
2247
  avoidStairs?: boolean;
2027
2248
  }): Promise<any>;
2028
2249
  /**
2029
- * Distance + walking time between two arbitrary POIs over the path graph —
2030
- * `getWalkTimeFromKiosk` with a caller-chosen origin. `null` when either POI
2031
- * has no waypoint on the graph or no path resolves.
2250
+ * Route between two picks, each a POI or a `MapLocation` (a tapped spot
2251
+ * that isn't a POI — see `snapToPath`). A location is snapped to the
2252
+ * closest point along a path on its floor and routed from there, so a
2253
+ * visitor can start mid-corridor with no waypoint authored at that spot.
2254
+ * Same events (`routeReady` / `routeFailed`) and the same
2255
+ * `centerMode: 'route'` framing as the kiosk routes. Switches to the
2256
+ * origin's floor first, because the route starts there.
2257
+ *
2258
+ * Emits `routeFailed` with `origin_unavailable` / `destination_unavailable`
2259
+ * when a POI has no waypoint or a location has no path within reach,
2260
+ * `no_route` when no path connects them.
2032
2261
  */
2033
- getWalkTimeBetween(from: POI, target: Parameters<MinuteMaps['getWalkTimeFromKiosk']>[0], opts?: {
2262
+ navigateBetween(from: POI | MapLocation, to: POI | MapLocation, options?: {
2263
+ avoidStairs?: boolean;
2264
+ }): Promise<any>;
2265
+ /**
2266
+ * What a tap on the map stands for as one end of a route — the rule "choose
2267
+ * on map" (`setLocationPicking`) applies, and the one to apply to a stored
2268
+ * tap so it resolves the same way:
2269
+ *
2270
+ * - **Inside a room that holds a destination** → that destination (`POI`),
2271
+ * routed to its entrance. A clinic's examination rooms and the like aren't
2272
+ * somewhere to walk a visitor through, so a tap there never becomes a spot
2273
+ * with an approach (`destinationInRoomAt`: smallest such room, nearest
2274
+ * destination in it).
2275
+ * - **Anywhere else** — an unplotted corridor, outdoors, a room with no
2276
+ * destination → `snapToPath`.
2277
+ *
2278
+ * `null` when the floor is unknown or no path is within reach.
2279
+ */
2280
+ resolveMapTap(location: Omit<MapLocation, 'kind'> | MapLocation): POI | MapLocation | null;
2281
+ /** `destinationInRoomAt` against a floor's own geometry and destinations. */
2282
+ private destinationInRoomAtTap;
2283
+ /**
2284
+ * Move a spot on a floor onto the walking network — where a route from (or
2285
+ * to) it will actually start (or end). The path point chosen is the one
2286
+ * closest **to walk to**, not the closest in a straight line: rooms,
2287
+ * back-of-house areas, stairs, elevators, obstacles and other buildings are
2288
+ * walked around, and a tap inside a room leaves by its door (inferred where
2289
+ * a path crosses the room's outline) rather than through the wall to the
2290
+ * corridor behind it. See `data/wayfinding/approach.ts`.
2291
+ *
2292
+ * Returns a `MapLocation` with `coordinates` on a path (`label` carried
2293
+ * over) and, when the tap was off the network, `tapped` plus — if a
2294
+ * walkable way was found — `approach`, the dotted line to draw. `tapped`
2295
+ * without `approach` means the point was simply moved (a room with no known
2296
+ * door, somewhere nothing walks) — or, with `centered`, moved from the side
2297
+ * of a hallway to its middle. `null` when the floor is unknown or no path
2298
+ * is within `maxDistanceMeters` (default 30 m).
2299
+ *
2300
+ * Planned from `tapped ?? coordinates`, so re-snapping a result (or a stored
2301
+ * tap) gives the same answer. Until the floor's geometry has loaded it's the
2302
+ * plain nearest point. Cross-floor paths are never snapped to.
2303
+ */
2304
+ snapToPath(location: Omit<MapLocation, 'kind'> | MapLocation, opts?: {
2305
+ maxDistanceMeters?: number;
2306
+ }): MapLocation | null;
2307
+ /**
2308
+ * Whether floor `floorId` has walkways outdoors — a path edge outside every
2309
+ * building and room. A multi-storey campus usually draws its outdoor paths
2310
+ * on the ground floor only, so a tap outside on another floor
2311
+ * (`locationPicked`'s `outdoors`) can't reach one: a "choose on map" UI can
2312
+ * point the visitor to a floor where this is `true`. `false` when the floor
2313
+ * is unknown or its geometry hasn't loaded.
2314
+ */
2315
+ hasOutdoorPaths(floorId: Floor['id']): boolean;
2316
+ /**
2317
+ * Turn map taps into location picks — for a "choose on map" control. While
2318
+ * on, a tap selects nothing; it's snapped to the closest point along a path
2319
+ * on the visible floor (`snapToPath`), a ring marks the snapped spot, and
2320
+ * **`locationPicked`** is emitted with `location` (the snapped
2321
+ * `MapLocation`, or `null` when no path was in reach — the ring is taken
2322
+ * down), `coordinates` (the raw tap), `floor`, and `outdoors` (the tap is
2323
+ * outside every building and room — see `hasOutdoorPaths`). Turning it off removes
2324
+ * the ring.
2325
+ */
2326
+ setLocationPicking(enabled: boolean): void;
2327
+ /**
2328
+ * Pin the ends a visitor has chosen for a route that isn't drawn yet — a
2329
+ * route form's start and destination — so each choice reads as saved: the
2330
+ * sprite's `marker-start` on `start`, `marker-destination` on
2331
+ * `destination`. A location tapped off the walking network is pinned where
2332
+ * it was tapped. Shown on the end's own floor only. Replaces the previous
2333
+ * preview; `{}` clears it. Independent of the route — clear it once the
2334
+ * route is drawn, so the route's own markers stand alone.
2335
+ */
2336
+ setRouteEndPreview(ends: RouteEndPreview): void;
2337
+ /**
2338
+ * A pick on the map: the ring on the on-path point, and the approach to it
2339
+ * from the tap. A tap that had to be *moved* (no walkable way) is shown
2340
+ * moving — its marker slides from the tap onto the path before the ring
2341
+ * appears — so the jump reads as deliberate. With reduced motion it's
2342
+ * simply there.
2343
+ */
2344
+ private showPickedLocation;
2345
+ /** A pick that resolved to a destination (`resolveMapTap`): its room
2346
+ * raised and the ring on its pin, as `highlightPOI` locates one. */
2347
+ private showPickedDestination;
2348
+ private stopPickAnimation;
2349
+ /** The floor's path-graph map id: for JACS venues the floor id *is* the
2350
+ * mapId, but read `mapId` first rather than lean on that. */
2351
+ private mapIdForFloorId;
2352
+ /** What the router takes for one end of a route: a POI's waypoint, or a
2353
+ * location's floor + on-path point (spliced in by the provider as a
2354
+ * virtual node). A location must already be through `snapToPath`, so its
2355
+ * `coordinates` are on a path. `null` when it can't be routed. */
2356
+ private routeEndpointWaypoint;
2357
+ /**
2358
+ * Distance + walking time between two arbitrary picks over the path graph —
2359
+ * `getWalkTimeFromKiosk` with a caller-chosen origin. Either end may be a
2360
+ * `MapLocation`, snapped exactly as `navigateBetween` snaps it, and its
2361
+ * `approach` (the walk from the tap to the path) is counted in `feet` /
2362
+ * `seconds` / `effortSeconds`. `null` when a POI has no waypoint on the
2363
+ * graph, a location has no path within reach, or no path resolves.
2364
+ */
2365
+ getWalkTimeBetween(from: POI | MapLocation, target: Parameters<MinuteMaps['getWalkTimeFromKiosk']>[0] | MapLocation, opts?: {
2034
2366
  walkingSpeedFps?: number;
2035
2367
  avoidStairs?: boolean;
2036
2368
  }): {
@@ -2039,10 +2371,17 @@ declare class MinuteMaps {
2039
2371
  effortSeconds: number;
2040
2372
  pathNodeCount: number;
2041
2373
  } | null;
2374
+ /** One end of a walk-time query: a POI's waypoint, or a location's
2375
+ * on-path point (already through `snapToPath`). */
2376
+ private walkTimeRef;
2377
+ /** `routeReady`'s destination: `poi` for a place — the exact instance
2378
+ * routed to, so `navigateFromKioskToClosestAmenity`'s pick is visible —
2379
+ * or `location` for a tapped spot. */
2380
+ private routeDestinationEventField;
2042
2381
  /** Shared tail of the route entry points: path, camera fit, steps, events.
2043
- * `originPoi` is the start a visitor picked (`navigateBetweenPOIs`), which
2382
+ * `originPoi` is the start a visitor picked (`navigateBetween`), which
2044
2383
  * gets a start pin; a kiosk route passes none — "You are here" marks it. */
2045
- private routeToPoi;
2384
+ private routeTo;
2046
2385
  /** Build the landmark / floor-name context the directions module needs.
2047
2386
  * Pulled out so `setLocale()` or `setActiveStep()` can rebuild it on
2048
2387
  * demand if we ever surface a locale-aware variant. */
@@ -2058,6 +2397,9 @@ declare class MinuteMaps {
2058
2397
  * instance under every floor's list.
2059
2398
  */
2060
2399
  private redrawFloorText;
2400
+ /** The route's directions: `buildDirections` over the path, plus a step
2401
+ * for each tapped end's walk on to / off the network (`withApproachSteps`). */
2402
+ private buildRouteSteps;
2061
2403
  private buildDirectionsContext;
2062
2404
  /**
2063
2405
  * Drive the turn-by-turn UI. Two effects:
@@ -2074,10 +2416,26 @@ declare class MinuteMaps {
2074
2416
  * instead swap the connector(s) the step rides to their highlighted
2075
2417
  * sprite badge (`CONNECTOR_HIGHLIGHTED_ICON_SUFFIX`).
2076
2418
  *
2419
+ * `opts.fit` moves the camera too:
2420
+ * - `'step'` frames just this step (zoomed no closer than
2421
+ * `ROUTE_STEP_FIT_MAX_ZOOM`) instead of the floor's whole segment, and
2422
+ * keeps framing it through later re-fits — `setBoundsPadding` as the
2423
+ * consumer's directions card resizes, a floor switch back to the step's
2424
+ * floor — until another fit or a new route.
2425
+ * - `'route'` goes back to the route's own framing (its floor segment),
2426
+ * as when it was first drawn — e.g. a "Restart" on the first step.
2427
+ *
2428
+ * Omitted, the camera is left alone, so a route's first framing stays
2429
+ * the whole route.
2430
+ *
2077
2431
  * Pass `null` to clear the highlight without changing the floor or
2078
2432
  * tearing down the route.
2079
2433
  */
2080
- setActiveStep(step: WayfindStep | null): Promise<void>;
2434
+ setActiveStep(step: WayfindStep | null, opts?: {
2435
+ fit?: 'step' | 'route';
2436
+ }): Promise<void>;
2437
+ /** The coordinates of `step`'s point range, for the camera to frame. */
2438
+ private stepFocusFor;
2081
2439
  private writeActiveStepHighlight;
2082
2440
  private clearActiveStepHighlight;
2083
2441
  /**
@@ -2091,6 +2449,25 @@ declare class MinuteMaps {
2091
2449
  * that predates the variant) rather than drawing nothing.
2092
2450
  */
2093
2451
  private applyRouteConnectorHighlight;
2452
+ /**
2453
+ * Draw each floor the active route crosses, in route order, and return
2454
+ * one image per floor — for printing directions.
2455
+ *
2456
+ * Images are the map canvas alone, so the consumer's DOM chrome (panels,
2457
+ * buttons, the floor picker) never appears in them. For the duration the
2458
+ * map container is laid out at a fixed `width` × `height`, the camera
2459
+ * fits each floor's segment of the route with uniform `padding`, and the
2460
+ * line is drawn solid (no draw-on sweep, no step highlight). The map is
2461
+ * drawn flat (2D) unless `flat: false`. Afterwards the container size,
2462
+ * floor, camera, flat/3D mode and padding are put back. Hide the
2463
+ * map behind a loading screen while this runs; the visitor would
2464
+ * otherwise see it jump between floors.
2465
+ *
2466
+ * The step highlight is cleared, not restored: call `setActiveStep`
2467
+ * again with the step your UI is showing. Resolves to `[]` when no route
2468
+ * is displayed.
2469
+ */
2470
+ captureRouteFloorImages(opts?: RouteImageOptions): Promise<RouteFloorImage[]>;
2094
2471
  clearRoute(): void;
2095
2472
  /**
2096
2473
  * Put the end-of-route marker on `poi`, or take it down (`null`). With
@@ -2124,8 +2501,11 @@ declare class MinuteMaps {
2124
2501
  *
2125
2502
  * This is deliberately *not* `highlightPOI`, which is the search-locate
2126
2503
  * gesture — it zooms and recenters and draws a pulsing ring, and emits
2127
- * nothing. Selection is the quieter one: current zoom kept, no ring, the
2128
- * raised room is the indicator.
2504
+ * nothing. Selection is the quieter one: no ring, the raised room is the
2505
+ * indicator, and the current zoom is kept — unless it is further out than
2506
+ * `options.selectionZoom` (default 19.25, just inside the Kiosk view), where
2507
+ * the room or its marker may not be drawn, in which case the camera zooms in
2508
+ * to it. A map tap gets the same floor.
2129
2509
  *
2130
2510
  * Accepts a `POI` (preferred — its `type` and `waypointId` pin down the exact
2131
2511
  * instance, which matters for an amenity that shares one id across every
@@ -2147,6 +2527,11 @@ declare class MinuteMaps {
2147
2527
  selectPOI(target: POI | string | number, options?: {
2148
2528
  includeCoLocated?: boolean;
2149
2529
  }): Promise<POI[]>;
2530
+ /** Zoom floor for a tap or `selectPOI` camera move (`options.selectionZoom`),
2531
+ * or `null` to keep the current zoom. The default sits a level past the
2532
+ * Kiosk view's edge: right at `KIOSK_VIEW_MIN_ZOOM` the selected dot and
2533
+ * name are drawn but routinely lose collision to amenities and room names. */
2534
+ private selectionZoom;
2150
2535
  /** Remove the POI highlight set by `highlightPOI`, un-dim the other units,
2151
2536
  * and bring back the destination markers hidden by a room selection. */
2152
2537
  clearHighlight(): void;
@@ -2206,7 +2591,8 @@ declare class MinuteMaps {
2206
2591
  /** Gentle pan so the selection sits in the centre of the map area MapLibre
2207
2592
  * frames within `boundsPadding` (which the consumer sets to reserve for its
2208
2593
  * bottom-docked chrome / info card). A straight `easeTo`, current zoom kept,
2209
- * so it reads as a nudge, not a recenter. */
2594
+ * so it reads as a nudge, not a recenter — unless `selectedMinZoom` is set
2595
+ * and the camera is further out, in which case it zooms in to that floor. */
2210
2596
  private easeToSelection;
2211
2597
  /** Dim every unit's paint (or restore it) so a selected room reads as the
2212
2598
  * focus. Idempotent: capturing only happens on the first activate, and the
@@ -2652,5 +3038,5 @@ type ResolvedIconSource = {
2652
3038
  */
2653
3039
  declare function resolveAmenityIconSource(amenity: AmenityLike, preferredLocale?: string): ResolvedIconSource | null;
2654
3040
 
2655
- export { BUILDING_NAME_EXTENSOR_KEY, DAY_KEYS, IMAGE_ALT_EXTENSOR_KEY, MinuteMaps, OPEN_HOURS_EXTENSOR_KEY, OPEN_HOURS_VERSION, PHONE_EXTENSOR_KEY, POINT_OF_INTEREST_EXTENSOR_KEY, RESERVED_EXTENSOR_PREFIX, SNAPSHOT_SCHEMA_VERSION, SnapshotProvider, SnapshotUnavailableError, VenueDirectory, activeSectionIndex, createMinuteMapsSDK, customerDisplayName, getOpenStatus, groupStepsIntoFloorSections, isConnectorAmenity, parseBuildingName, parseImageAlt, parseOpenHours, parsePhone, parsePointOfInterest, resolveAmenityIconSource, resolveSnapshotLocale };
2656
- export type { Amenity, AmenityBadgeStyle, AmenityWithFloor, Bounds, BoundsPadding, CameraState, Customer, CustomerVenues, DayKey, Destination, DestinationPhone, EventCallback, Floor, FloorMetadata, HoursRange, JMapAuth, JMapConfig, JacsAuth, JacsConfig, JacsProviderConfig, JacsReader, ListCustomerVenuesOptions, MapEvent, OpenHours, OpenHoursTransition, OpenStatus, OpenStatusOptions, POI, POISearchResult, ResolvedIconSource, RouteFloorSection, RoutePoint, RoutingOptions, SDKConfig, SDKOptions, SnapshotProviderConfig, SnapshotUpdateCheck, SnapshotVersions, ThemeName, TransitionStep, VenueDirectoryConfig, VenueSummary, ViewOptions, WayfindCenterMode, WayfindStep, Waypoint, Zone };
3041
+ export { BUILDING_NAME_EXTENSOR_KEY, DAY_KEYS, IMAGE_ALT_EXTENSOR_KEY, MinuteMaps, OPEN_HOURS_EXTENSOR_KEY, OPEN_HOURS_VERSION, PHONE_EXTENSOR_KEY, POINT_OF_INTEREST_EXTENSOR_KEY, RESERVED_EXTENSOR_PREFIX, SNAPSHOT_SCHEMA_VERSION, SnapshotProvider, SnapshotUnavailableError, VenueDirectory, activeSectionIndex, createMinuteMapsSDK, customerDisplayName, dayKeyAt, getOpenStatus, groupStepsIntoFloorSections, isConnectorAmenity, isMapLocation, parseBuildingName, parseImageAlt, parseOpenHours, parsePhone, parsePointOfInterest, resolveAmenityIconSource, resolveSnapshotLocale, timeZoneAbbreviation };
3042
+ export type { Amenity, AmenityBadgeStyle, AmenityWithFloor, Bounds, BoundsPadding, CameraState, Customer, CustomerVenues, DayKey, Destination, DestinationPhone, EventCallback, Floor, FloorMetadata, HoursRange, JMapAuth, JMapConfig, JacsAuth, JacsConfig, JacsProviderConfig, JacsReader, ListCustomerVenuesOptions, MapEvent, MapLocation, OpenHours, OpenHoursTransition, OpenStatus, OpenStatusOptions, POI, POISearchResult, ResolvedIconSource, RouteEndPreview, RouteFloorImage, RouteFloorSection, RouteImageOptions, RoutePoint, RoutingOptions, SDKConfig, SDKOptions, SnapshotProviderConfig, SnapshotUpdateCheck, SnapshotVersions, ThemeName, TransitionStep, VenueDirectoryConfig, VenueSummary, ViewOptions, WayfindCenterMode, WayfindStep, Waypoint, Zone };