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

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
@@ -14,6 +14,10 @@ interface Floor {
14
14
  mapId?: string | number;
15
15
  /** Display name of the floor */
16
16
  name: string;
17
+ /** Short display name of the floor (e.g. "L2"). Optional: JACS leaves it
18
+ * unset on plenty of floors, and `JacsDataProvider` maps a blank one to
19
+ * `undefined` rather than inventing a value. */
20
+ shortName?: string;
17
21
  /** Floor sequence/order (e.g., -1 for basement, 0 for ground, 1 for first floor) */
18
22
  sequence?: number;
19
23
  /** Associated map data */
@@ -29,6 +33,7 @@ interface Floor {
29
33
  interface FloorMetadata {
30
34
  id: string | number;
31
35
  name: string;
36
+ shortName?: string;
32
37
  sequence?: number;
33
38
  isDefault?: boolean;
34
39
  isActive?: boolean;
@@ -51,12 +56,17 @@ interface POI {
51
56
  floorId: string | number;
52
57
  /** Category/type of amenity (e.g., 'restroom', 'elevator') */
53
58
  amenityType?: string;
59
+ /** Locale-stable classification text (name + type + keywords, frozen at
60
+ * load before any locale patch can translate them). Copied from the
61
+ * source `Amenity.classifyText`; used by `amenityCategoryFor`/rank
62
+ * instead of the live, possibly-translated fields. See `Amenity.classifyText`. */
63
+ classifyText?: string;
54
64
  /** Which amenity icon layer this POI renders in — `'connector'`,
55
- * `'entrance'`, `'parking'`, `'bus'` or `'other'`. Derived from the
56
- * amenity's connector flag, name, type and keywords; emitted as the
57
- * `amenityCategory` feature property the theme filters on. Amenities
58
- * only. See `amenityCategoryFor`. */
59
- amenityCategory?: 'connector' | 'entrance' | 'parking' | 'bus' | 'other';
65
+ * `'entrance'`, `'parking'`, `'bus'`, `'information'` or `'other'`.
66
+ * Derived from the amenity's connector flag, name, type and keywords;
67
+ * emitted as the `amenityCategory` feature property the theme filters on.
68
+ * Amenities only. See `amenityCategoryFor`. */
69
+ amenityCategory?: 'connector' | 'entrance' | 'parking' | 'bus' | 'information' | 'other';
60
70
  /** Search keywords */
61
71
  keywords?: string[];
62
72
  /** Additional properties */
@@ -104,6 +114,17 @@ interface Amenity {
104
114
  waypoints?: Waypoint[];
105
115
  /** Amenity type/category */
106
116
  type?: string;
117
+ /**
118
+ * Locale-stable snapshot of `name + type + keywords`, captured once from
119
+ * the `/all` payload before `applyLocale` ever runs. `amenityCategoryFor`
120
+ * and the icon-rank heuristics in `poi.ts` match hard-coded English
121
+ * vocabulary against those fields; `name`/`keywords` are legitimately
122
+ * overwritten with translated text on a language switch
123
+ * (`LOCALIZED_FIELDS.amenity`), which reclassified every translated
124
+ * amenity to `'other'`/tier-3 and dropped its icon until the next zoom
125
+ * recomputed the rank gate. Classification reads this frozen copy instead.
126
+ */
127
+ classifyText?: string;
107
128
  /** Extended properties */
108
129
  extensors?: Record<string, unknown>;
109
130
  }
@@ -119,6 +140,12 @@ interface Destination {
119
140
  waypoints?: Waypoint[];
120
141
  /** Category/classification */
121
142
  category?: string;
143
+ /** Room number — the CMS's "Room Number" field. A **real JACS column**, not
144
+ * one of the `mm_` stand-in extensors, so it arrives on the entity rather
145
+ * than in `extensors`. Surfaced as `getPOIDetails().roomNumber`. */
146
+ unitNumber?: string | null;
147
+ /** Search keywords */
148
+ keywords?: string[];
122
149
  /** Additional properties */
123
150
  properties?: Record<string, unknown>;
124
151
  /** The CMS "Location Image" — a photo or logo for the popover, not a map
@@ -218,17 +245,16 @@ type WayfindCenterMode = 'none' | 'destination' | 'route';
218
245
  /**
219
246
  * Per-call routing preferences forwarded to the wayfinding provider.
220
247
  *
221
- * - `accessible` — prefer accessible paths; providers should drop or heavily
222
- * penalize edges marked inaccessible (stairs by default, anything else
223
- * the provider's data flags).
224
- * - `avoidStairs` — hard-filter stairs from the graph.
248
+ * - `avoidStairs` — hard-filter stairs from the graph. Accessibility
249
+ * preference otherwise comes from the CMS's per-path-type `weight`
250
+ * (elevators score low, stairs/escalators score high) — see
251
+ * `data/wayfinding/weights.ts` — not from a caller-supplied flag.
225
252
  *
226
253
  * The bundled stub provider doesn't compute routes; consumers wire a real
227
- * provider (see `mm-web-sdk-example/lib/wayfinding`) that reads these
228
- * flags when building edge weights.
254
+ * provider (see `mm-web-sdk-example/lib/wayfinding`) that reads this
255
+ * flag when building edge weights.
229
256
  */
230
257
  type RoutingOptions = {
231
- accessible?: boolean;
232
258
  avoidStairs?: boolean;
233
259
  };
234
260
 
@@ -266,13 +292,18 @@ type StepBase = {
266
292
  floorId: number | null;
267
293
  /** Localized instruction text. */
268
294
  text: string;
269
- /** Total walking distance within this step, in meters (rounded). */
270
- distanceMeters?: number;
295
+ /** Total walking distance within this step, in feet (rounded). */
296
+ distanceFeet?: number;
271
297
  /** Landmark name used for anchoring, if any. */
272
298
  landmark?: string;
273
299
  };
274
300
  type DepartStep = StepBase & {
275
301
  type: 'depart';
302
+ /** Which way the traveller turns before walking off, relative to the
303
+ * way they're facing at the origin. Absent when `originFacingBearing`
304
+ * wasn't supplied (direction unknowable) or when the route heads
305
+ * straight ahead. Consumers use it to pick the step's icon. */
306
+ initialTurn?: 'left' | 'right' | 'around';
276
307
  };
277
308
  type TurnStep = StepBase & {
278
309
  type: 'turn-left' | 'turn-right' | 'u-turn';
@@ -387,6 +418,20 @@ interface SDKOptions {
387
418
  * controls the automatic highlight on `routeReady`.
388
419
  */
389
420
  routeStepHighlight?: boolean;
421
+ /**
422
+ * Whether restroom pins that are too close together to draw individually
423
+ * collapse into a single Material `wc` badge. Defaults to `true`.
424
+ *
425
+ * Venues author men's and women's restrooms as two amenities a few metres
426
+ * apart. Both are wayfinding-critical (rank 0) and both draw on a layer that
427
+ * doesn't allow icon overlap, so without this MapLibre resolves the collision
428
+ * by hiding one of them outright until the camera is very close in. Grouping
429
+ * shows one badge in their place and hands back *all* the members when it's
430
+ * tapped, so the consumer can offer directions to each.
431
+ *
432
+ * Set `false` to opt out and get the raw collision behaviour back.
433
+ */
434
+ groupRestroomIcons?: boolean;
390
435
  initialFloor?: string | number;
391
436
  enableInteractions?: boolean;
392
437
  customSprite?: string;
@@ -399,6 +444,16 @@ interface SDKOptions {
399
444
  */
400
445
  wallThickness?: number;
401
446
  boundsPadding?: BoundsPadding;
447
+ /**
448
+ * Caps how far a visitor can pan/zoom out. By default the SDK derives this
449
+ * from the venue's own bounds, scaled to 3x its width/height around the
450
+ * 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
452
+ * multiplier (e.g. `1.5` for a tighter leash), explicit `[[west, south],
453
+ * [east, north]]` bounds to override entirely, or `false` to disable
454
+ * max-bounds clamping.
455
+ */
456
+ maxBounds?: Bounds | number | false;
402
457
  /**
403
458
  * Pitch (deg) for the opening view, applied centred on the kiosk ("You
404
459
  * are here"). Omit for top-down.
@@ -449,6 +504,21 @@ interface SDKOptions {
449
504
  * unless this is set explicitly.
450
505
  */
451
506
  connectorBadge?: AmenityBadgeStyle | false;
507
+ /**
508
+ * Badge for information-desk amenities (curated `information`/`info`
509
+ * icon). These render as their own visual class — a green disc with a
510
+ * white glyph by default — so information reads distinct from the gold
511
+ * service amenities. Pass `false` to fall them back into the regular gold
512
+ * badge, or an object to tune the disc / ring. When `amenityBadge` is
513
+ * `false`, information icons are badge-less too unless this is set
514
+ * explicitly.
515
+ */
516
+ informationBadge?: AmenityBadgeStyle | false;
517
+ /**
518
+ * Glyph colour for information icons on their badge. Default white
519
+ * (matches `informationBadge`'s default green disc).
520
+ */
521
+ informationIconColor?: string;
452
522
  styleMode?: 'venueStyleUrl' | 'sdkTemplate';
453
523
  templateOverrideMode?: 'colorsOnly' | 'colorsAndConstants' | 'all';
454
524
  }
@@ -748,8 +818,13 @@ declare class AmenityManager {
748
818
  * three times in a venue list); this collapses those into a single record
749
819
  * with all its waypoints merged across floors.
750
820
  *
751
- * Also folds any duplicate-id records from the provider (a CMS data bug)
752
- * into a single entry so React lists don't trip on dup keys.
821
+ * Also folds any duplicate-id records from the provider into a single entry
822
+ * so React lists don't trip on dup keys. This used to say those duplicates
823
+ * were "a CMS data bug" — they weren't. JACS returns one record per amenity;
824
+ * the SDK's own floor index pushed it once per waypoint, so an amenity with
825
+ * four placements on a floor came back four times. Fixed at the source in
826
+ * `jacsDataProvider.floorIdsForWaypoints`; the fold stays as a cheap guard
827
+ * for a provider that genuinely repeats an id.
753
828
  *
754
829
  * Use this for venue-wide browse UIs; pair it with `findClosestWaypoint`
755
830
  * (on the SDK) to resolve a tap to the nearest physical instance.
@@ -761,6 +836,175 @@ declare class AmenityManager {
761
836
  logKioskForFloor(floorId: Floor['id']): void;
762
837
  }
763
838
 
839
+ /**
840
+ * The `mm_`-prefixed extensor keys the CMS reserves for fields JACS has no
841
+ * column for yet.
842
+ *
843
+ * This file is the read half of a cross-repo table: the write half is
844
+ * `cms/packages/map-manager/app/utils/reserved-extensors.js`, which owns the key
845
+ * names, the value encodings, and the rule that a default value is deleted
846
+ * rather than stored. Change them together.
847
+ *
848
+ * The `mm_` prefix is load-bearing on both sides. Authors can type anything into
849
+ * the CMS's free-form Properties editor, so an unprefixed key like
850
+ * `point_of_interest` could collide with a hand-entered one; the prefix also
851
+ * makes "is this key reserved?" a prefix scan rather than a hand-maintained list
852
+ * once JACS grows real columns and these are migrated away.
853
+ *
854
+ * Two things that look like they belong here and don't:
855
+ *
856
+ * - **Room number.** The CMS labels it "Room Number", but it writes the real
857
+ * `Destination.unitNumber` field, not an extensor. `getPOIDetails().roomNumber`
858
+ * reads that field (see `sdk.ts`).
859
+ * - **`mm_point_of_interest`.** Registered by the CMS and parsed here for
860
+ * completeness, but deliberately not surfaced on `getPOIDetails` — nothing has
861
+ * pinned down whether an unflagged destination should drop out of search, out
862
+ * of the map, or neither, and guessing wrong is worse than omitting it.
863
+ */
864
+ declare const RESERVED_EXTENSOR_PREFIX = "mm_";
865
+ /** Weekly opening hours. Value shape and parser live in `openHours.ts`. */
866
+ declare const OPEN_HOURS_EXTENSOR_KEY = "mm_open_hours";
867
+ /** Phone number, with the extension folded into the same value. */
868
+ declare const PHONE_EXTENSOR_KEY = "mm_phone";
869
+ /** Alt text for the destination's uploaded Location Image. */
870
+ declare const IMAGE_ALT_EXTENSOR_KEY = "mm_image_alt";
871
+ /** Author-set "this is a point of interest" flag. Parsed, not surfaced. */
872
+ declare const POINT_OF_INTEREST_EXTENSOR_KEY = "mm_point_of_interest";
873
+ /**
874
+ * Which building on the campus the destination stands in.
875
+ *
876
+ * The venue's own `Building` record can't answer this: the CMS binds a venue to
877
+ * a single building and hangs the floor list off it, so a campus with a dozen
878
+ * numbered buildings has one `Building` and no way to say which one a room is
879
+ * in. The author types it as free text, and that text is what a kiosk prints —
880
+ * it is not a reference to anything, so don't try to resolve it against
881
+ * `venue.buildings`.
882
+ */
883
+ declare const BUILDING_NAME_EXTENSOR_KEY = "mm_building_name";
884
+ interface DestinationPhone {
885
+ /** Exactly as the author typed it — `"(408) 300-9294"`. Deliberately not
886
+ * normalised: doing that properly across international formats needs a real
887
+ * phone library, and the CMS makes the same choice on the way in. */
888
+ number: string;
889
+ /** Internal extension digits, when there is one. */
890
+ ext?: string;
891
+ }
892
+ /**
893
+ * `"(408) 300-9294;ext=123"` → `{ number, ext }`. `null` when there's no number
894
+ * — including for the empty string, which is how JACS spells a deleted key.
895
+ */
896
+ declare function parsePhone(raw: unknown): DestinationPhone | null;
897
+ /** Alt text for the Location Image, or `undefined` when the author set none. */
898
+ declare function parseImageAlt(raw: unknown): string | undefined;
899
+ /** The building name, or `undefined` when the author set none. */
900
+ declare function parseBuildingName(raw: unknown): string | undefined;
901
+ /** The point-of-interest flag. Absence means false — only the checked state is
902
+ * ever stored. */
903
+ declare function parsePointOfInterest(raw: unknown): boolean;
904
+
905
+ /**
906
+ * Weekly opening hours for a destination.
907
+ *
908
+ * JACS has no hours column, so the CMS ships the whole week in a single
909
+ * destination extensor — key `mm_open_hours`, value a JSON string:
910
+ *
911
+ * {"v":1,"days":{"mon":[["09:00","17:30"]],"sat":[],"sun":[["00:00","24:00"]]}}
912
+ *
913
+ * - Times are 24-hour `"HH:MM"` and **venue-local**; no timezone is recorded.
914
+ * - Each day maps to an array of `[open, close]` ranges. The editor exposes one
915
+ * range per day today, but the array shape leaves room for split shifts (a
916
+ * cafeteria that closes over the afternoon) and extra ranges round-trip.
917
+ * - An empty array — or a missing day — means closed.
918
+ * - `[["00:00","24:00"]]` means open 24 hours.
919
+ *
920
+ * This module owns the read half: parse the value (`parseOpenHours`) and derive
921
+ * the live open/closed state from it (`getOpenStatus`). The write half lives in
922
+ * the CMS (`map-manager`'s `utils/open-hours.js`) — the two must agree on the
923
+ * wire shape above, so change them together. The key itself is registered in
924
+ * `reservedExtensors.ts` alongside the CMS's other stand-in fields, mirroring
925
+ * the same split on the authoring side.
926
+ */
927
+
928
+ /** Wire-format version this module understands. */
929
+ declare const OPEN_HOURS_VERSION = 1;
930
+ type DayKey = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun';
931
+ /** Monday-first, matching the wire format's own day order. `getOpenStatus`
932
+ * reports `OpenHoursTransition.day` as one of these. */
933
+ declare const DAY_KEYS: readonly DayKey[];
934
+ /** One open→close stretch within a single day. 24-hour `"HH:MM"`, venue-local.
935
+ * `close` may be `"24:00"` (end of day); `open` never is. */
936
+ interface HoursRange {
937
+ open: string;
938
+ close: string;
939
+ }
940
+ /** A parsed week. Every day is present; a closed day is an empty array. */
941
+ interface OpenHours {
942
+ days: Record<DayKey, HoursRange[]>;
943
+ /** Open every minute of the week (a 24/7 emergency department, say). The
944
+ * status has no `closesAt` in this case — there is nothing to count down to. */
945
+ alwaysOpen: boolean;
946
+ }
947
+ /** A point in the week the status is counting toward. */
948
+ interface OpenHoursTransition {
949
+ /** Day the transition falls on, venue-local. */
950
+ day: DayKey;
951
+ /** 24-hour `"HH:MM"`, venue-local. Midnight is always `"00:00"` on the
952
+ * following day — `"24:00"` never surfaces here. */
953
+ time: string;
954
+ /** Whole minutes from `now`. */
955
+ minutesUntil: number;
956
+ /** Calendar days ahead: `0` today, `1` tomorrow. */
957
+ daysAhead: number;
958
+ }
959
+ interface OpenStatus {
960
+ /** Open right now. */
961
+ open: boolean;
962
+ /** Open every minute of the week — no closing time to show. */
963
+ alwaysOpen: boolean;
964
+ /** Open, and closing within `soonMinutes`. */
965
+ closingSoon: boolean;
966
+ /** Closed, and opening within `soonMinutes`. */
967
+ openingSoon: boolean;
968
+ /** When open: the end of the current stretch. Absent when `alwaysOpen`. */
969
+ closesAt?: OpenHoursTransition;
970
+ /** When closed: the next time it opens. Absent when no day is open. */
971
+ opensAt?: OpenHoursTransition;
972
+ }
973
+ interface OpenStatusOptions {
974
+ /** How near a transition counts as "soon". Default `30`. */
975
+ soonMinutes?: number;
976
+ /** IANA zone the hours are expressed in (`'America/Denver'`). Omit — the
977
+ * normal case for a kiosk standing in the venue — to read the host clock.
978
+ * An unrecognised zone falls back to the host clock rather than throwing. */
979
+ timeZone?: string;
980
+ }
981
+ /**
982
+ * Extensor value → a parsed week, or `null` when there is nothing usable.
983
+ *
984
+ * Accepts the stored JSON string or an already-parsed object. Anything
985
+ * unreadable — absent key, malformed JSON, no open day — is `null` rather than
986
+ * a throw, so a bad value costs the consumer an omitted section, not a crash.
987
+ *
988
+ * An unknown `v` is also `null`, deliberately. A future version could add
989
+ * holiday overrides or a timezone; reading only the `days` it recognises would
990
+ * let the SDK announce "Open" on a day the venue marked closed, and a confidently
991
+ * wrong answer at a kiosk is worse than a missing one.
992
+ */
993
+ declare function parseOpenHours(raw: unknown): OpenHours | null;
994
+ /**
995
+ * The live open/closed state for a parsed week.
996
+ *
997
+ * Pure and time-explicit: pass the `now` you want evaluated (default: the
998
+ * moment of the call). Nothing is cached, so a consumer that wants a status
999
+ * that stays true — a kiosk card sitting open — re-calls this on a tick rather
1000
+ * than holding the result.
1001
+ *
1002
+ * Hours carry no timezone of their own, so they are read against the host clock
1003
+ * unless `options.timeZone` names the venue's zone. The kiosk case needs no
1004
+ * option: the machine stands in the building it is describing.
1005
+ */
1006
+ declare function getOpenStatus(hours: OpenHours, now?: Date, options?: OpenStatusOptions): OpenStatus;
1007
+
764
1008
  declare class MinuteMaps {
765
1009
  private config;
766
1010
  private map;
@@ -799,19 +1043,33 @@ declare class MinuteMaps {
799
1043
  /** The `window` key `debug` mode installed this instance under, if any. */
800
1044
  private debugGlobalKey;
801
1045
  private defaultCamera;
1046
+ /** 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. */
1051
+ private iconZoomBase;
802
1052
  private viewModes;
803
1053
  /** Mirror of `config.options.reducedMotion`, mutable via `setReducedMotion`. */
804
1054
  private reducedMotion;
1055
+ /** Whether map labels render in OpenDyslexic, mutable via `setDyslexicFont`.
1056
+ * Re-applied after every `setTheme()` swap, same reason as `iconZoomBase`. */
1057
+ private dyslexicFont;
805
1058
  /** Tracks the active theme name so `setTheme()` is a no-op when re-requested. */
806
1059
  private activeThemeName;
807
1060
  /** Tracks the active locale so `setLocale()` is a no-op when re-requested.
808
1061
  * Seeded from `config.jmap.locale` at construction; `undefined` means
809
1062
  * whatever the customer's default locale is on the JACS side. */
810
1063
  private currentLocale;
1064
+ /** The destination of the last successfully computed route — kept so
1065
+ * `setLocale()` can rebuild + re-emit `routeReady` with retranslated
1066
+ * step text without recomputing the path. Cleared on route failure. */
1067
+ private lastRouteDestinationPoi;
811
1068
  private amenityManager;
812
1069
  private wayfinding;
813
1070
  private highlightManager;
814
1071
  private selectionManager;
1072
+ private restroomGroups;
815
1073
  /** Captured unit paint values to restore when the selection clears, or
816
1074
  * `null` when no room is currently focused. See `UNIT_DIM_TARGETS`. */
817
1075
  private unitDimRestore;
@@ -823,6 +1081,15 @@ declare class MinuteMaps {
823
1081
  * *previous* filters, not the ids) so `setTheme` can re-apply the same focus
824
1082
  * to the new style's layers after a swap discards the old ones. */
825
1083
  private destFocusIds;
1084
+ /** Coordinates of the current tap/Info selection, or `null` when nothing is
1085
+ * selected. `easeToSelection` reads `getBoundsPadding()` at the moment a
1086
+ * selection is made, which is often *before* the consumer's info card has
1087
+ * mounted and been measured into that padding — so the first pan can land
1088
+ * the POI behind the card. Kept here so `setBoundsPadding` can re-nudge the
1089
+ * camera once the consumer measures its now-mounted chrome and reports
1090
+ * fresh insets, the same way it already re-fits an active route. Cleared by
1091
+ * `clearHighlight`. */
1092
+ private selectedCoordinates;
826
1093
  private youAreHerePulse;
827
1094
  /** Theme layers whose features represent a tappable destination/amenity.
828
1095
  * A click hit-tests these (see `SelectionManager`) → `poiSelected`. The
@@ -836,6 +1103,15 @@ declare class MinuteMaps {
836
1103
  * identically. Kept separate from the pin list because these carry no POI
837
1104
  * identity of their own — they resolve spatially. */
838
1105
  private static readonly CLICKABLE_UNIT_LAYERS;
1106
+ /** The kiosk's own "you are here" pin — icon, directional heading wedge,
1107
+ * and the ring drawn behind them. Deliberately **not** part of
1108
+ * `CLICKABLE_POI_LAYERS`: that list feeds `emitPoiSelected`, which opens
1109
+ * a selection card for a destination/amenity, and this pin isn't one —
1110
+ * it's where the kiosk itself is standing. A tap here means "where am
1111
+ * I", so it gets its own delegated listener emitting `youAreHereClicked`
1112
+ * instead, for a consumer to wire to the same recenter action as its
1113
+ * own recenter control. */
1114
+ private static readonly YOU_ARE_HERE_LAYERS;
839
1115
  /** While a room is selected, the other units are recolored to a flat, muted
840
1116
  * gray so the raised, full-strength highlight block reads as the focus. We
841
1117
  * recolor rather than lower opacity on purpose: translucent extrusions blend
@@ -883,6 +1159,16 @@ declare class MinuteMaps {
883
1159
  off(event: string, cb?: (e: MapEvent) => void): void;
884
1160
  addControl(control: IControl, position?: ControlPosition): void;
885
1161
  setView(options: ViewOptions): void;
1162
+ /**
1163
+ * Force a camera move to land instantly while reduced motion is on.
1164
+ *
1165
+ * The single gate every consumer-driven move goes through — zoom
1166
+ * buttons, compass reset, go-home, recenter, route fits — so the one
1167
+ * toggle covers them all instead of each call site remembering to ask.
1168
+ * Returns `opts` untouched when the preference is off, so a caller that
1169
+ * asked for `animate: false` keeps it either way.
1170
+ */
1171
+ private motionGated;
886
1172
  get amenities(): AmenityManager;
887
1173
  resetView(opts?: {
888
1174
  animate?: boolean;
@@ -921,10 +1207,13 @@ declare class MinuteMaps {
921
1207
  * `styleimagemissing`; everything else must be re-run explicitly here or
922
1208
  * it silently goes blank after a theme swap.
923
1209
  *
924
- * `themeName === 'high-contrast'` recolors the amenity badge disc to
925
- * `HIGH_CONTRAST_AMENITY_BADGE_COLOR`, leaving ring/glyph colors and
926
- * connector (elevator/stairs/escalator) badges untouched. Failures per
927
- * icon set are logged and skipped so one bad icon doesn't block the rest.
1210
+ * `themeName === 'high-contrast'` recolors both the amenity badge disc
1211
+ * and the connector (elevator/stairs/escalator) badge disc to
1212
+ * `HIGH_CONTRAST_AMENITY_BADGE_COLOR`, and forces both the regular and
1213
+ * connector glyph to `HIGH_CONTRAST_AMENITY_ICON_COLOR` so neither
1214
+ * washes out against its now-yellow disc and the two match each other.
1215
+ * Ring colors are left untouched. Failures per icon set are logged and
1216
+ * skipped so one bad icon doesn't block the rest.
928
1217
  */
929
1218
  private registerRuntimeIcons;
930
1219
  /**
@@ -939,12 +1228,39 @@ declare class MinuteMaps {
939
1228
  /** Currently active theme name (or `'custom'` when a raw style was passed). */
940
1229
  getActiveTheme(): 'default' | 'high-contrast' | 'custom';
941
1230
  /**
942
- * Update the SDK's reduced-motion preference at runtime. When true,
943
- * imperative camera animations (route fits, idle re-frames) and the
944
- * route line-draw animation run with `duration: 0`.
1231
+ * Update the SDK's reduced-motion preference at runtime. When true, every
1232
+ * imperative camera move (`setView`, `resetView`, `refit`, recenter, route
1233
+ * fits, selection pans, floor fits) runs with `duration: 0`, and every
1234
+ * looping animation the SDK drives — the route line-draw, the flowing route
1235
+ * arrows, the "You are here" pulse, the selection ring's ping — is stopped.
1236
+ *
1237
+ * Anything already on screen is switched over here, not just on the next
1238
+ * action: a visitor who flips the toggle mid-route is doing it *because* of
1239
+ * the motion they can see, so waiting for the next event would be the one
1240
+ * moment the preference doesn't work.
945
1241
  */
946
1242
  setReducedMotion(enabled: boolean): void;
947
1243
  getReducedMotion(): boolean;
1244
+ /**
1245
+ * Swap every map label (street names, POI names, building labels, "You are
1246
+ * here") between the bundled themes' default font and OpenDyslexic. Mirrors
1247
+ * the DOM-side dyslexic-font accommodation onto the canvas, which CSS can't
1248
+ * reach.
1249
+ *
1250
+ * Goes through the same local-glyph fallback the themes already use for
1251
+ * `Material Icons` (`LOCAL_GLYPH_FONTS`): `text-font` values are swapped to
1252
+ * fontstack names intentionally absent from the glyph server
1253
+ * (`OpenDyslexic Bold` etc.), so MapLibre rasterizes them from a
1254
+ * page-loaded `@font-face` instead. The consuming app owns loading that
1255
+ * font — the SDK ships no assets — so this is a no-op glyph-wise (labels
1256
+ * fall back to tofu-free default rendering) if the app never declared it.
1257
+ *
1258
+ * Re-applied after `setTheme()`, since `setStyle({ diff: false })`
1259
+ * recreates every layer from the pristine (non-dyslexic) theme JSON.
1260
+ */
1261
+ setDyslexicFont(enabled: boolean): Promise<void>;
1262
+ getDyslexicFont(): boolean;
1263
+ private applyDyslexicFontToLabels;
948
1264
  /**
949
1265
  * Switch the locale used for POI / amenity / destination / floor names at
950
1266
  * runtime. The SDK refetches localized names from JACS (per-entity
@@ -958,9 +1274,12 @@ declare class MinuteMaps {
958
1274
  * Emits a `localeChanged` event with the new code after the patch
959
1275
  * completes. No-op when the code matches the current locale.
960
1276
  *
961
- * Throws if the underlying data provider doesn't implement locale support
962
- * (only `JacsDataProvider` does today), or if the JACS fetch fails — the
963
- * prior locale is left intact in that case.
1277
+ * Throws only on programmer error — a provider without locale support
1278
+ * (only `JacsDataProvider` has it today) or an empty code. A **failed
1279
+ * JACS fetch is not fatal**: the locale still switches and is reported,
1280
+ * it's only the localized names that stay on the prior language. That
1281
+ * split is deliberate — generated directions text ships inside the SDK
1282
+ * and must not be held hostage to a network round-trip.
964
1283
  */
965
1284
  setLocale(locale: string): Promise<void>;
966
1285
  /** Currently active locale (BCP-47), or `undefined` when no locale has
@@ -992,15 +1311,53 @@ declare class MinuteMaps {
992
1311
  type: 'amenity' | 'destination' | 'kiosk';
993
1312
  category?: string;
994
1313
  floorName?: string;
1314
+ floorShortName?: string;
995
1315
  zoneName?: string;
996
1316
  zoneDescription?: string;
1317
+ /** The department's authored hex colour (`Zone.color`) — the same value
1318
+ * that tints this room's polygon on the map, so a consumer can print a
1319
+ * swatch beside the department name and connect the two. Absent when the
1320
+ * author never picked one, which is also when the map leaves the room
1321
+ * untinted; treat "no colour" as "this department isn't colour-coded",
1322
+ * not as "fall back to a default swatch". */
1323
+ zoneColor?: string;
997
1324
  keywords?: string[];
998
1325
  description?: string;
1326
+ /** CMS-authored extras from the entity's `extensors` bag (opening hours,
1327
+ * days, room, building). Free-form — the consumer decides what to render.
1328
+ * Artwork is not in here; see `imageUrl`. */
1329
+ properties?: Record<string, unknown>;
999
1330
  /** The destination's uploaded location image, ready for an `<img src>`.
1000
1331
  * Destinations only — amenities use sprite glyphs, not photos. Inline SVG
1001
1332
  * from `/all` comes back as a data URI; otherwise it's the uploaded uri
1002
1333
  * path. Absent when nothing was uploaded in the CMS. */
1003
1334
  imageUrl?: string;
1335
+ /** Author-written alt text for `imageUrl` (`mm_image_alt`) — a description
1336
+ * of the place, for screen readers and for anyone the image fails to load
1337
+ * for. Absent when the author wrote none, in which case the image is
1338
+ * decorative and belongs in an `<img alt="">`, not an unlabelled one. */
1339
+ imageAlt?: string;
1340
+ /** Room number — the CMS's "Room Number". Reads the real
1341
+ * `Destination.unitNumber` column, falling back to a legacy hand-typed
1342
+ * `room_number` property for venues authored before that field existed. */
1343
+ roomNumber?: string;
1344
+ /** Which building on the campus this destination is in (`mm_building_name`),
1345
+ * as the author typed it — "200", "Ambulatory Care". Free text, not a
1346
+ * reference to a `Building` record; render it, don't resolve it. Falls back
1347
+ * to the legacy hand-typed `building_name` property, which is unprefixed
1348
+ * and therefore a different key. */
1349
+ buildingName?: string;
1350
+ /** Phone number and internal extension (`mm_phone`). The number is exactly
1351
+ * as the author typed it, not normalised — see `parsePhone`. */
1352
+ phone?: DestinationPhone;
1353
+ /** The CMS-authored opening hours (`mm_open_hours`), parsed. Absent when
1354
+ * the author set none, or the stored value is unreadable.
1355
+ *
1356
+ * Static — it describes the week, not this moment. Pass it to
1357
+ * `getOpenStatus()` for the live open/closed state, and re-call that on a
1358
+ * tick if the UI stays on screen. The raw string also remains in
1359
+ * `properties` for consumers that would rather parse it themselves. */
1360
+ openHours?: OpenHours;
1004
1361
  };
1005
1362
  /** Resolve the source Destination/Amenity record backing a rendered POI,
1006
1363
  * for detail fields (category, localized description) that don't live on
@@ -1028,6 +1385,40 @@ declare class MinuteMaps {
1028
1385
  private reportLayerAudit;
1029
1386
  getFloorMapTemplate3d(floorId: string | number): any[];
1030
1387
  getAllPOIs(floor?: Floor): POI[];
1388
+ /**
1389
+ * Put the map on the kiosk's floor before drawing a route from it.
1390
+ *
1391
+ * **A route from the kiosk always starts on the kiosk's floor**, so that is
1392
+ * the floor the visitor has to be looking at when it appears. The renderer
1393
+ * agrees with that already — `wayfinding` reveals and frames *the active
1394
+ * floor's* slice of the route — which is exactly why this matters: leave the
1395
+ * map where the visitor had wandered to and the route arrives framed on the
1396
+ * wrong leg, or on no leg at all.
1397
+ *
1398
+ * Both halves of that were reported from a real kiosk. Browse up to Level 3,
1399
+ * search, press Directions: if Level 3 isn't on the route the fit falls back
1400
+ * to the union of every floor and nothing reads as a path from here; if it
1401
+ * *is* on the route — because the destination is up there — the visitor is
1402
+ * looking at the last leg of a walk they haven't started, with no indication
1403
+ * that the first one is two floors down.
1404
+ *
1405
+ * Called before the route is computed rather than after it's drawn, so the
1406
+ * camera makes one move instead of fitting the wrong floor and correcting.
1407
+ * No-op when the venue has no kiosk anchor (those routes fail anyway) or the
1408
+ * map is already there.
1409
+ */
1410
+ private showKioskFloorForRoute;
1411
+ /**
1412
+ * The floor the kiosk is physically anchored to — not necessarily the
1413
+ * floor currently displayed (the visitor may have panned the floor
1414
+ * selector to preview a destination on another level, and the SDK opens
1415
+ * on the venue's configured default floor rather than the kiosk's own —
1416
+ * see `FloorManager.selectInitialFloor`). Anchor resolution (routing,
1417
+ * recentering, closest-waypoint) must resolve against this floor, never
1418
+ * `getCurrentFloor()`, or it silently misses the "you are here" POI
1419
+ * whenever the two diverge. `null` when the venue has no kiosk anchor.
1420
+ */
1421
+ getKioskFloor(): Floor | null;
1031
1422
  getYouAreHerePOI(floor?: Floor): POI | null;
1032
1423
  getYouAreHereCoordinates(floor?: Floor): [number, number] | null;
1033
1424
  /**
@@ -1042,6 +1433,14 @@ declare class MinuteMaps {
1042
1433
  * cone rendered next to the "You are here" marker.
1043
1434
  */
1044
1435
  getKioskHeading(): number | null;
1436
+ /**
1437
+ * Bearing (degrees clockwise from north) the visitor at the kiosk is
1438
+ * FACING — the device heading rotated 180°, since they stand in front
1439
+ * of the screen looking back at it. `null` when the device record
1440
+ * carries no heading, in which case direction-relative wording ("turn
1441
+ * right and walk 40 ft") is suppressed rather than guessed.
1442
+ */
1443
+ private getVisitorFacingBearing;
1045
1444
  /**
1046
1445
  * Display name of the JACS device the kiosk is anchored to (e.g.
1047
1446
  * "Main Lobby Kiosk", "Information Desk Kiosk"). `null` when the venue
@@ -1112,8 +1511,6 @@ declare class MinuteMaps {
1112
1511
  wayfindBetweenWaypoints(fromWaypoint: any, toWaypoint: any, options?: {
1113
1512
  centerMode?: 'none' | 'destination' | 'route';
1114
1513
  zoom?: number;
1115
- /** Prefer accessible paths (drops or penalizes stairs / inaccessible edges). */
1116
- accessible?: boolean;
1117
1514
  /** Hard-filter stairs from the graph. */
1118
1515
  avoidStairs?: boolean;
1119
1516
  }): Promise<any>;
@@ -1126,12 +1523,23 @@ declare class MinuteMaps {
1126
1523
  * you want a visual focus rather than a route.
1127
1524
  */
1128
1525
  navigateFromKioskToPOI(poi: POI, options?: {
1129
- accessible?: boolean;
1130
1526
  avoidStairs?: boolean;
1131
1527
  }): Promise<any>;
1132
1528
  /** Build the landmark / floor-name context the directions module needs.
1133
1529
  * Pulled out so `setLocale()` or `setActiveStep()` can rebuild it on
1134
1530
  * demand if we ever surface a locale-aware variant. */
1531
+ /**
1532
+ * Rewrite every source whose features carry venue-authored *text* for a
1533
+ * floor: POI labels and map labels. Both read their names straight out of
1534
+ * the venue model, so anything that mutates that model in place — today
1535
+ * `setLocale`'s translation patch — has to call this or the map keeps
1536
+ * rendering the old strings until the next floor change.
1537
+ *
1538
+ * Map labels ride the same lifecycle as POIs and carry the `allLevels`
1539
+ * flag, which the projector has already honored by duplicating the
1540
+ * instance under every floor's list.
1541
+ */
1542
+ private redrawFloorText;
1135
1543
  private buildDirectionsContext;
1136
1544
  /**
1137
1545
  * Drive the turn-by-turn UI. Two effects:
@@ -1160,6 +1568,42 @@ declare class MinuteMaps {
1160
1568
  * it lives on another one. Pass a POI / amenity / destination id.
1161
1569
  */
1162
1570
  highlightPOI(id: string | number): Promise<void>;
1571
+ /**
1572
+ * Select a POI programmatically — the same end state as tapping it on the
1573
+ * map, from a list or any other chrome.
1574
+ *
1575
+ * Switches the active floor first when the POI lives on another one, raises
1576
+ * its room as a highlighted block, mutes the neighbouring units, declutters
1577
+ * to the selected destination(s), pans it into the unobscured map area and
1578
+ * emits **`poiSelected`** carrying every co-located POI. A consumer that
1579
+ * already renders an info card off that event therefore needs no second code
1580
+ * path: a tap and a list pick arrive the same way.
1581
+ *
1582
+ * This is deliberately *not* `highlightPOI`, which is the search-locate
1583
+ * gesture — it zooms and recenters and draws a pulsing ring, and emits
1584
+ * nothing. Selection is the quieter one: current zoom kept, no ring, the
1585
+ * raised room is the indicator.
1586
+ *
1587
+ * Accepts a `POI` (preferred — its `type` and `waypointId` pin down the exact
1588
+ * instance, which matters for an amenity that shares one id across every
1589
+ * physical pin) or a bare id.
1590
+ *
1591
+ * **`includeCoLocated: false` emits only the requested POI.** By default the
1592
+ * payload carries everything sharing the room, which is right when the
1593
+ * gesture was a tap — the visitor pointed at a place on a floor plan and the
1594
+ * consumer has to show them what's there. It's wrong when they picked one
1595
+ * entry out of a named list: they have already disambiguated, and handing
1596
+ * back four places would ask them to do it again, on a card that opened
1597
+ * *because* they were specific. The map treatment is identical either way
1598
+ * (same room raised, same neighbours muted, same pan); only the payload —
1599
+ * and so the declutter's allow-list — narrows.
1600
+ *
1601
+ * Returns the selected POIs, `pois[0]` being the requested one; an empty
1602
+ * array if no POI matched, which is also when nothing on the map changes.
1603
+ */
1604
+ selectPOI(target: POI | string | number, options?: {
1605
+ includeCoLocated?: boolean;
1606
+ }): Promise<POI[]>;
1163
1607
  /** Remove the POI highlight set by `highlightPOI`, un-dim the other units,
1164
1608
  * and bring back the destination markers hidden by a room selection. */
1165
1609
  clearHighlight(): void;
@@ -1237,17 +1681,17 @@ declare class MinuteMaps {
1237
1681
  * `navigateFromKioskToPOI` — so step-by-step directions, the
1238
1682
  * `routeReady` event, and the active-floor camera fit all "just work."
1239
1683
  *
1240
- * Pass `accessible` / `avoidStairs` to weight the *route* (the SDK's
1241
- * routing engine reads these per call). Note: closest-instance picking
1242
- * itself is currently distance-based and does not yet honor those
1243
- * weights the underlying walk-time graph applies pixel-length only.
1244
- * In practice this matters when an amenity has multiple instances and
1245
- * the geometrically closest is reachable only via stairs; the picked
1246
- * instance won't change today, but the *route* to it will avoid stairs
1247
- * (or fail gracefully) if `accessible`/`avoidStairs` is set.
1684
+ * Pass `avoidStairs` to hard-filter stairs from the *route* (the SDK's
1685
+ * 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.
1248
1693
  */
1249
1694
  navigateFromKioskToClosestAmenity(amenityId: string | number, options?: {
1250
- accessible?: boolean;
1251
1695
  avoidStairs?: boolean;
1252
1696
  }): Promise<any>;
1253
1697
  /**
@@ -1311,6 +1755,13 @@ declare class MinuteMaps {
1311
1755
  */
1312
1756
  private clampPadding;
1313
1757
  private getVenueBounds;
1758
+ /**
1759
+ * Resolve `options.maxBounds` into the actual pan/zoom-out cap. `false`
1760
+ * disables it, explicit bounds pass through as-is, and a number (or the
1761
+ * unset default of 3) scales the venue's own bounds outward around its
1762
+ * center — see `scaleBounds`.
1763
+ */
1764
+ private getMaxBounds;
1314
1765
  /**
1315
1766
  * Opening-framing policy applied once after the initial floor fit.
1316
1767
  * - `initialPitch`: tilt to this and re-fit the building footprint so the
@@ -1364,5 +1815,76 @@ declare function groupStepsIntoFloorSections(steps: WayfindStep[]): RouteFloorSe
1364
1815
  */
1365
1816
  declare function activeSectionIndex(sections: RouteFloorSection[], activeStepIndex: number): number;
1366
1817
 
1367
- export { MinuteMaps, activeSectionIndex, createMinuteMapsSDK, groupStepsIntoFloorSections };
1368
- export type { Amenity, AmenityBadgeStyle, AmenityWithFloor, Bounds, BoundsPadding, CameraState, Destination, EventCallback, Floor, FloorMetadata, JMapAuth, JMapConfig, JacsAuth, JacsConfig, MapEvent, POI, POISearchResult, RouteFloorSection, RoutePoint, RoutingOptions, SDKConfig, SDKOptions, ThemeName, TransitionStep, ViewOptions, WayfindCenterMode, WayfindStep, Waypoint, Zone };
1818
+ /** True when an amenity is a vertical-circulation connector. The provider
1819
+ * stamps `_connector` on every synthetic elevator / stairs / escalator, which
1820
+ * is the reliable signal — `extensors.iconName` is only the *display* glyph
1821
+ * and may be any CMS-assigned icon (we still honour it as a fallback for
1822
+ * amenities authored with a connector icon name directly). */
1823
+ declare function isConnectorAmenity(amenity: AmenityLike): boolean;
1824
+ type LocaleField = {
1825
+ locale?: string;
1826
+ uriPath?: string;
1827
+ };
1828
+ type UriField = {
1829
+ mimeType?: string;
1830
+ resourceType?: string;
1831
+ locales?: LocaleField[];
1832
+ };
1833
+ type AmenityLike = {
1834
+ id: number | string;
1835
+ iconId?: string;
1836
+ uris?: UriField[] | null;
1837
+ /** Inline SVG markup. Used by the SDK for synthetic connector
1838
+ * amenities (elevator / stairs / escalator) where the CMS has no
1839
+ * uploaded icon to fetch. When set, this short-circuits the
1840
+ * `uris[].locales[].uriPath` lookup. */
1841
+ svg?: string;
1842
+ /** Free-form CMS metadata. When `extensors.iconName` names a curated
1843
+ * Material Symbols icon, the SDK renders it from the bundled registry
1844
+ * (no upload, no network) — see `pickMaterialSvg`. */
1845
+ extensors?: Record<string, unknown> | null;
1846
+ /** Set by the JACS provider on synthetic vertical-circulation amenities
1847
+ * (elevator / stairs / escalator built from path types). The reliable
1848
+ * connector signal — `extensors.iconName` is the *display* glyph, which
1849
+ * may be any CMS-assigned icon, not the connector kind. */
1850
+ _connector?: boolean;
1851
+ };
1852
+ type ResolvedIconSource = {
1853
+ /** Map image id to register/reference. Shared across amenities for
1854
+ * connectors (preset) and curated Material icons (`material-<name>`). */
1855
+ iconId: string;
1856
+ /** Source for `composeAmenityBitmap` — exactly one of url / inlineSvg. */
1857
+ source: {
1858
+ url?: string;
1859
+ inlineSvg?: string;
1860
+ };
1861
+ /**
1862
+ * Where the artwork came from. The map doesn't care — it rasterises all
1863
+ * three the same way — but a DOM consumer must, because provenance decides
1864
+ * both trust and styling:
1865
+ *
1866
+ * - `material` — from the SDK's bundled registry. Ours, so it's safe to
1867
+ * inline into the DOM, and safe to recolour (it's a monochrome glyph).
1868
+ * - `inline` — an `svg` string off the CMS record. **Author-supplied
1869
+ * markup**: an inline `<svg>` can carry `<script>`, so render it through
1870
+ * an `<img src="data:…">` (which never executes script), not
1871
+ * `dangerouslySetInnerHTML`.
1872
+ * - `upload` — a URL to a CMS-uploaded file. Also author-supplied, also an
1873
+ * `<img>`; and it arrives in whatever colours the author chose, so don't
1874
+ * assume you can tint it to match a badge.
1875
+ */
1876
+ kind: 'material' | 'inline' | 'upload';
1877
+ };
1878
+ /**
1879
+ * Pure icon-source resolution by precedence — no map/DOM side effects, so it's
1880
+ * unit-testable:
1881
+ * 1. inline svg — explicit inline-SVG override (rare)
1882
+ * 2. extensors.iconName — curated Material icon (bundled, shared bitmap);
1883
+ * also how synthetic connectors render now
1884
+ * 3. uris[].uriPath — legacy / custom-uploaded SVG (fetched)
1885
+ * Returns null when the amenity has no resolvable icon.
1886
+ */
1887
+ declare function resolveAmenityIconSource(amenity: AmenityLike, preferredLocale?: string): ResolvedIconSource | null;
1888
+
1889
+ 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, activeSectionIndex, createMinuteMapsSDK, getOpenStatus, groupStepsIntoFloorSections, isConnectorAmenity, parseBuildingName, parseImageAlt, parseOpenHours, parsePhone, parsePointOfInterest, resolveAmenityIconSource };
1890
+ export type { Amenity, AmenityBadgeStyle, AmenityWithFloor, Bounds, BoundsPadding, CameraState, DayKey, Destination, DestinationPhone, EventCallback, Floor, FloorMetadata, HoursRange, JMapAuth, JMapConfig, JacsAuth, JacsConfig, MapEvent, OpenHours, OpenHoursTransition, OpenStatus, OpenStatusOptions, POI, POISearchResult, ResolvedIconSource, RouteFloorSection, RoutePoint, RoutingOptions, SDKConfig, SDKOptions, ThemeName, TransitionStep, ViewOptions, WayfindCenterMode, WayfindStep, Waypoint, Zone };