@expofp/config 3.11.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +35 -0
  2. package/dist/index.d.ts +11 -0
  3. package/dist/index.js +8 -0
  4. package/dist/lib/apply-intents.d.ts +10 -0
  5. package/dist/lib/apply-intents.js +24 -0
  6. package/dist/lib/config-store.d.ts +6 -0
  7. package/dist/lib/config-store.js +12 -0
  8. package/dist/lib/debug-settings.d.ts +16 -0
  9. package/dist/lib/debug-settings.js +44 -0
  10. package/dist/lib/json-schema.d.ts +67 -0
  11. package/dist/lib/json-schema.js +136 -0
  12. package/dist/lib/legacy-url.d.ts +167 -0
  13. package/dist/lib/legacy-url.js +322 -0
  14. package/dist/lib/load-config.d.ts +26 -0
  15. package/dist/lib/load-config.js +286 -0
  16. package/dist/lib/local-storage-codec.d.ts +44 -0
  17. package/dist/lib/local-storage-codec.js +76 -0
  18. package/dist/lib/normalize-fp-svg.d.ts +26 -0
  19. package/dist/lib/normalize-fp-svg.js +36 -0
  20. package/dist/lib/normalize-legacy-data.d.ts +13 -0
  21. package/dist/lib/normalize-legacy-data.js +90 -0
  22. package/dist/lib/rebooking.d.ts +19 -0
  23. package/dist/lib/rebooking.js +48 -0
  24. package/dist/lib/resource-urls.d.ts +52 -0
  25. package/dist/lib/resource-urls.js +196 -0
  26. package/dist/lib/serialize-config-resources.d.ts +22 -0
  27. package/dist/lib/serialize-config-resources.js +20 -0
  28. package/dist/lib/strip-defaults.d.ts +27 -0
  29. package/dist/lib/strip-defaults.js +55 -0
  30. package/dist/lib/url-codec.d.ts +46 -0
  31. package/dist/lib/url-codec.js +165 -0
  32. package/dist/lib/url-intents.d.ts +30 -0
  33. package/dist/lib/url-intents.js +58 -0
  34. package/dist/lib/validate-flag.d.ts +10 -0
  35. package/dist/lib/validate-flag.js +36 -0
  36. package/dist/lib/validate-timing.d.ts +3 -0
  37. package/dist/lib/validate-timing.js +52 -0
  38. package/dist/lib/visit-resources.d.ts +85 -0
  39. package/dist/lib/visit-resources.js +198 -0
  40. package/package.json +36 -0
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @expofp/config
2
+
3
+ The effective-config layer for the ExpoFP SDK. `loadConfig()` assembles the runtime's single copy of
4
+ the expo data by layering manifest (+ legacy `data.js`) → options → localStorage → URL, resolving
5
+ `$ref`s via `@expofp/resolve` against `@expofp/schema`; `setConfig`/`getConfig` hold the result.
6
+
7
+ ```ts
8
+ setConfig(await loadConfig(manifestUrl));
9
+ const config = getConfig();
10
+ ```
11
+
12
+ **Schema validation** of the manifest and referenced documents (`data.js`, drawing, wayfinding) is an
13
+ opt-in diagnostic — off by default (parsing the large expo documents costs time). Enable it per page
14
+ load with `?validateSchema=1` in the URL or `efp-validateSchema=true` in localStorage (also a
15
+ `@expofp/debug` panel toggle); timing for the parses it turns on logs on the `efp:config:validate`
16
+ namespace. It is a load-time input only — the effective config never carries it.
17
+
18
+ **Intents from the URL** ride the query in two forms, merged canonical-first: the bracketed array
19
+ (`?intents[0][name]=selectBooth&intents[0][args][0]=A-12`) and a typable shortcut — the intent name
20
+ as the key (`?selectBooth=A-12`, `?changeLanguage=fr`; multi-arg via `?name[0]=…&name[1]=…`). The
21
+ shortcut keys are the `IntentsSchema` arm names, so the schema stays the URL allowlist; one shortcut
22
+ per intent name, and a value that doesn't fit its intent is dropped (a visitor-edited URL never
23
+ breaks boot).
24
+
25
+ **The legacy URL grammar** (`legacy-url.ts`) is the compact deep-link language the floor plan has
26
+ always spoken — `?<slug>`, `?route:B-12:A-3:false`, `?tour=t1`, `?planner=a:b&from=c`, one-shot
27
+ params like `?blue-dot=…`. `parseLegacyQuery` turns a raw `location.search` into typed commands and
28
+ `serializeSelection` writes a selection back in the same grammar (round-trip covered by tests), so
29
+ shared links stay bidirectional. The floor plan's `url-dispatch` service binds the commands to
30
+ stores and public methods; this codec stays pure — no DOM, no stores.
31
+
32
+ Also: `applyIntents` (dispatch `selectBooth` / `changeLanguage` / … to the floor plan),
33
+ `serializeConfigResources` (collect schema-tagged assets + payloads for offline copies), and
34
+ `toDebugSettings` (config schema → `@expofp/debug` panel). Types re-export from `@expofp/schema`.
35
+ Build/test: `nx build config` · `nx test config`.
@@ -0,0 +1,11 @@
1
+ export { applyIntents } from './lib/apply-intents.js';
2
+ export { getConfig, setConfig } from './lib/config-store.js';
3
+ export { type DebugSettingDescriptor, toDebugSettings } from './lib/debug-settings.js';
4
+ export { type LegacyOneShot, type LegacyPrimary, type LegacyUrlQuery, parseLegacyQuery, serializeSelection, type UrlSelection, } from './lib/legacy-url.js';
5
+ export { loadConfig } from './lib/load-config.js';
6
+ export { type StorageLike } from './lib/local-storage-codec.js';
7
+ export { type ConfigResourceStore, serializeConfigResources, } from './lib/serialize-config-resources.js';
8
+ export { parseFromUrlTolerant } from './lib/url-codec.js';
9
+ export { parseIntentsFromUrl } from './lib/url-intents.js';
10
+ export { type Config, type Consent, type Manifest, type Options } from '@expofp/schema';
11
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { applyIntents } from './lib/apply-intents.js';
2
+ export { getConfig, setConfig } from './lib/config-store.js';
3
+ export { toDebugSettings } from './lib/debug-settings.js';
4
+ export { parseLegacyQuery, serializeSelection, } from './lib/legacy-url.js';
5
+ export { loadConfig } from './lib/load-config.js';
6
+ export { serializeConfigResources, } from './lib/serialize-config-resources.js';
7
+ export { parseFromUrlTolerant } from './lib/url-codec.js';
8
+ export { parseIntentsFromUrl } from './lib/url-intents.js';
@@ -0,0 +1,10 @@
1
+ import { type Intent } from '@expofp/schema';
2
+ type IntentArgs = {
3
+ [I in Intent as I['name']]: I['args'];
4
+ };
5
+ type FloorPlanLike = {
6
+ [K in keyof IntentArgs]: (...args: IntentArgs[K]) => void;
7
+ };
8
+ export declare function applyIntents(fp: FloorPlanLike, intents: Intent[]): void;
9
+ export {};
10
+ //# sourceMappingURL=apply-intents.d.ts.map
@@ -0,0 +1,24 @@
1
+ // The generic `K` keeps name↔args correlated for TS, so `fp[name](...args)` checks
2
+ // out with no cast and no switch. Inlining this in the loop is what fails: there
3
+ // `intent` is the whole union, so name/args decorrelate (the original error).
4
+ function applyOne(fp, intent) {
5
+ // Intents ride on the deep-frozen effective config, but a method may
6
+ // normalize its input in place (RouteStore.selectCurrentPosition rewrites
7
+ // the point's fields) — hand it an owned mutable copy, the way any host
8
+ // calling the API would. Args are schema-validated plain data, so a JSON
9
+ // round-trip is lossless.
10
+ const args = JSON.parse(JSON.stringify(intent.args));
11
+ fp[intent.name](...args);
12
+ }
13
+ export function applyIntents(fp, intents) {
14
+ for (const intent of intents) {
15
+ try {
16
+ applyOne(fp, intent);
17
+ }
18
+ catch (error) {
19
+ // Intents ride in from the page URL — a bad deep link (unknown booth,
20
+ // half-formed args) must not take down the page or the intents after it.
21
+ console.error(`Intent ${intent.name} failed:`, error);
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,6 @@
1
+ import { type Config } from '@expofp/schema';
2
+ /** Install the active config (called once `loadConfig` has produced it). */
3
+ export declare function setConfig(newConfig: Config): void;
4
+ /** The active config. Throws if called before {@link setConfig}. */
5
+ export declare function getConfig(): Config;
6
+ //# sourceMappingURL=config-store.d.ts.map
@@ -0,0 +1,12 @@
1
+ let config;
2
+ /** Install the active config (called once `loadConfig` has produced it). */
3
+ export function setConfig(newConfig) {
4
+ config = newConfig;
5
+ }
6
+ /** The active config. Throws if called before {@link setConfig}. */
7
+ export function getConfig() {
8
+ if (!config) {
9
+ throw new Error('Config not loaded yet');
10
+ }
11
+ return config;
12
+ }
@@ -0,0 +1,16 @@
1
+ import type * as z from 'zod';
2
+ export type DebugSettingDescriptor = {
3
+ type: 'boolean';
4
+ optional: boolean;
5
+ } | {
6
+ type: 'string';
7
+ optional: boolean;
8
+ } | {
9
+ type: 'enum';
10
+ values: readonly string[];
11
+ optional: boolean;
12
+ };
13
+ export declare function toDebugSettings(schema: z.ZodType, options?: {
14
+ prefix?: string;
15
+ }): Record<string, DebugSettingDescriptor>;
16
+ //# sourceMappingURL=debug-settings.d.ts.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Debug-panel setting descriptors derived from a config schema — the bridge
3
+ * between the config's localStorage layer and `@expofp/debug`'s settings UI.
4
+ *
5
+ * `toDebugSettings(LocalStorageConfigSchema)` emits one plain-data descriptor
6
+ * per scalar field, keyed by the exact storage key the localStorage codec
7
+ * reads (`efp-` + field name), so a debug-panel edit is picked up by
8
+ * `loadConfig` on the next page load. Optional fields are marked `optional`:
9
+ * their editors get an explicit "default" state that removes the key, handing
10
+ * the decision back to the lower config layers. Fields with no scalar editor
11
+ * (objects, arrays, unions, numbers so far) are skipped and logged.
12
+ *
13
+ * The descriptor shape mirrors `@expofp/debug`'s `SettingDescriptor`
14
+ * structurally — no dependency between the packages; the host (the floorplan)
15
+ * passes the output to its `registerSettings`.
16
+ */
17
+ import debug from 'debug';
18
+ import { toJsonSchema } from './json-schema.js';
19
+ import { DEFAULT_STORAGE_PREFIX } from './local-storage-codec.js';
20
+ const log = debug('efp:config');
21
+ export function toDebugSettings(schema, options = {}) {
22
+ const prefix = options.prefix ?? DEFAULT_STORAGE_PREFIX;
23
+ const root = toJsonSchema(schema);
24
+ const required = new Set(root.required ?? []);
25
+ const settings = {};
26
+ for (const [field, node] of Object.entries(root.properties ?? {})) {
27
+ const descriptor = toDescriptor(node, !required.has(field));
28
+ if (!descriptor) {
29
+ log('toDebugSettings', 'no editor for field, skipping', field, node.type);
30
+ continue;
31
+ }
32
+ settings[prefix + field] = descriptor;
33
+ }
34
+ return settings;
35
+ }
36
+ function toDescriptor(node, optional) {
37
+ if (node.type === 'string' && node.enum) {
38
+ return { type: 'enum', values: node.enum, optional };
39
+ }
40
+ if (node.type === 'boolean' || node.type === 'string') {
41
+ return { type: node.type, optional };
42
+ }
43
+ return undefined;
44
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Shared JSON-Schema introspection for the schema-driven machinery here and in
3
+ * `@expofp/config` (asset resolution, resource serialization, url-codec,
4
+ * url-intents, local-storage-codec): they all walk a plain Zod schema alongside
5
+ * a value, so they need a structural view of `z.toJSONSchema` output and
6
+ * string→scalar coercion by declared type.
7
+ */
8
+ import * as z from 'zod';
9
+ /**
10
+ * The slice of a JSON Schema node the walkers care about. `.meta()` keys pass
11
+ * through `z.toJSONSchema` and surface here — they are how a schema declares
12
+ * where its external resources live.
13
+ */
14
+ export interface JsonSchemaNode {
15
+ type?: string;
16
+ properties?: Record<string, JsonSchemaNode>;
17
+ /**
18
+ * The schema for keys not covered by `properties`. A `z.record(z.string(), V)`
19
+ * has dynamic keys and no fixed `properties`, so its value schema `V` rides
20
+ * here (e.g. `fpSvgLayerJsRefs`); a loose object's open tail also surfaces
21
+ * here (as `{}`/`true`, both inert to the walk).
22
+ */
23
+ additionalProperties?: JsonSchemaNode | boolean;
24
+ required?: string[];
25
+ items?: JsonSchemaNode;
26
+ /** Per-position schemas of a tuple (`z.tuple`); its rest element rides on `items`. */
27
+ prefixItems?: JsonSchemaNode[];
28
+ anyOf?: JsonSchemaNode[];
29
+ oneOf?: JsonSchemaNode[];
30
+ /** Allowed values of a string-enum leaf. */
31
+ enum?: string[];
32
+ /** Value of a `z.literal` leaf — a discriminated union arm's tag. */
33
+ const?: unknown;
34
+ /** A string leaf holding one asset URL/path. */
35
+ asset?: boolean;
36
+ /** An `asset` whose URL is minted without an existence check — the resource may be missing. */
37
+ optionalAsset?: boolean;
38
+ /** A string leaf holding an SVG document whose `<image>` hrefs are asset paths. */
39
+ svgAsset?: boolean;
40
+ /** A string leaf holding CSS text whose `url(…)`s are asset paths. */
41
+ cssAsset?: boolean;
42
+ /** Stand-in for a `refSchema` meta — resolve it with {@link refSchemaOf}. */
43
+ refSchemaId?: string;
44
+ }
45
+ /** `schema` as a JSON Schema node tree (memoized per schema instance). */
46
+ export declare function toJsonSchema(schema: z.ZodType): JsonSchemaNode;
47
+ /** The live document schema a `$ref` node's `refSchema` meta points at. */
48
+ export declare function refSchemaOf(node: JsonSchemaNode): z.ZodType | undefined;
49
+ /** Does `value` plausibly belong to this schema branch? Used to pick union arms. */
50
+ export declare function typeMatches(node: JsonSchemaNode, value: unknown): boolean;
51
+ /**
52
+ * Walk the schema and a deflattened structure (raw strings at the leaves, the
53
+ * way a query string or storage yields values) together, coercing each leaf to
54
+ * its declared scalar type. Objects keep only the keys the schema names; a
55
+ * lone value destined for an array becomes a 1-element array; tuple positions
56
+ * coerce each by their own schema. A union coerces by the arm its literal
57
+ * discriminator names and is otherwise left as-is for the schema's `parse` to
58
+ * judge. Lenient like {@link coerceScalar} — this shapes the candidate, the
59
+ * schema's own `parse` does the real validation afterwards.
60
+ */
61
+ export declare function coerceValue(node: JsonSchemaNode, value: unknown): unknown;
62
+ /**
63
+ * Coerce a raw string to its declared scalar type. Lenient on purpose — the
64
+ * schema's own `parse` does the real validation afterwards.
65
+ */
66
+ export declare function coerceScalar(raw: string, type: string | undefined): string | number | boolean;
67
+ //# sourceMappingURL=json-schema.d.ts.map
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Shared JSON-Schema introspection for the schema-driven machinery here and in
3
+ * `@expofp/config` (asset resolution, resource serialization, url-codec,
4
+ * url-intents, local-storage-codec): they all walk a plain Zod schema alongside
5
+ * a value, so they need a structural view of `z.toJSONSchema` output and
6
+ * string→scalar coercion by declared type.
7
+ */
8
+ import * as z from 'zod';
9
+ const cache = new WeakMap();
10
+ // A `.meta({ refSchema: SomeSchema })` points a `$ref` field at its document's
11
+ // LIVE schema. `z.toJSONSchema` emits pure JSON (a live schema cannot ride
12
+ // through it), so the conversion stamps a string id on the node and parks the
13
+ // schema here; `refSchemaOf` joins them back up.
14
+ const refSchemas = new Map();
15
+ let nextRefSchemaId = 0;
16
+ /** `schema` as a JSON Schema node tree (memoized per schema instance). */
17
+ export function toJsonSchema(schema) {
18
+ let node = cache.get(schema);
19
+ if (!node) {
20
+ node = z.toJSONSchema(schema, {
21
+ io: 'input',
22
+ override: ({ zodSchema, jsonSchema }) => {
23
+ const refSchema = z.globalRegistry.get(zodSchema)?.refSchema;
24
+ if (refSchema) {
25
+ const id = String(nextRefSchemaId++);
26
+ refSchemas.set(id, refSchema);
27
+ jsonSchema.refSchemaId = id;
28
+ }
29
+ },
30
+ });
31
+ cache.set(schema, node);
32
+ }
33
+ return node;
34
+ }
35
+ /** The live document schema a `$ref` node's `refSchema` meta points at. */
36
+ export function refSchemaOf(node) {
37
+ return node.refSchemaId === undefined ? undefined : refSchemas.get(node.refSchemaId);
38
+ }
39
+ /** Does `value` plausibly belong to this schema branch? Used to pick union arms. */
40
+ export function typeMatches(node, value) {
41
+ switch (node.type) {
42
+ case 'object':
43
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
44
+ case 'array':
45
+ return Array.isArray(value);
46
+ case 'string':
47
+ return typeof value === 'string';
48
+ case 'number':
49
+ case 'integer':
50
+ return typeof value === 'number';
51
+ case 'boolean':
52
+ return typeof value === 'boolean';
53
+ default:
54
+ return true; // untyped branch — let the recursion decide
55
+ }
56
+ }
57
+ /**
58
+ * Walk the schema and a deflattened structure (raw strings at the leaves, the
59
+ * way a query string or storage yields values) together, coercing each leaf to
60
+ * its declared scalar type. Objects keep only the keys the schema names; a
61
+ * lone value destined for an array becomes a 1-element array; tuple positions
62
+ * coerce each by their own schema. A union coerces by the arm its literal
63
+ * discriminator names and is otherwise left as-is for the schema's `parse` to
64
+ * judge. Lenient like {@link coerceScalar} — this shapes the candidate, the
65
+ * schema's own `parse` does the real validation afterwards.
66
+ */
67
+ export function coerceValue(node, value) {
68
+ if (value === undefined) {
69
+ return undefined;
70
+ }
71
+ const variants = node.anyOf ?? node.oneOf;
72
+ if (variants) {
73
+ const variant = discriminatedVariant(variants, value);
74
+ return variant ? coerceValue(variant, value) : value; // no single arm — let schema.parse decide
75
+ }
76
+ if (node.type === 'object' && node.properties) {
77
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
78
+ return value; // shape mismatch — surface it as a schema error
79
+ }
80
+ const source = value;
81
+ const out = {};
82
+ for (const [key, child] of Object.entries(node.properties)) {
83
+ if (key in source) {
84
+ out[key] = coerceValue(child, source[key]);
85
+ }
86
+ }
87
+ return out;
88
+ }
89
+ if (node.type === 'array') {
90
+ const list = Array.isArray(value) ? value : [value]; // lenient: a lone value → 1-elem array
91
+ const tuple = node.prefixItems;
92
+ if (tuple) {
93
+ return list.map((element, index) => coerceValue(tuple[index] ?? node.items ?? {}, element));
94
+ }
95
+ const item = node.items ?? {};
96
+ return list.map((element) => coerceValue(item, element));
97
+ }
98
+ return typeof value === 'string' ? coerceScalar(value, node.type) : value;
99
+ }
100
+ /**
101
+ * The single union arm whose literal (`const`) properties all match `value` —
102
+ * how a discriminated union picks the arm to coerce by. The leaves are still
103
+ * raw strings when this runs, so a non-string literal matches its string form.
104
+ * `undefined` when the value is no object or when no (or several) arms match —
105
+ * nothing structural to say, the union stays uncoerced.
106
+ */
107
+ function discriminatedVariant(variants, value) {
108
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
109
+ return undefined;
110
+ }
111
+ const source = value;
112
+ const matches = variants.filter((variant) => {
113
+ const literals = Object.entries(variant.properties ?? {}).filter(([, property]) => property.const !== undefined);
114
+ return (literals.length > 0 &&
115
+ literals.every(([key, property]) => {
116
+ const raw = source[key];
117
+ return raw === property.const || raw === String(property.const);
118
+ }));
119
+ });
120
+ return matches.length === 1 ? matches[0] : undefined;
121
+ }
122
+ /**
123
+ * Coerce a raw string to its declared scalar type. Lenient on purpose — the
124
+ * schema's own `parse` does the real validation afterwards.
125
+ */
126
+ export function coerceScalar(raw, type) {
127
+ if (type === 'number' || type === 'integer') {
128
+ // `Number('') === 0` — a blank value (`?camera[bearing]=`) must stay a
129
+ // string for the schema's parse to reject, not silently become a real 0
130
+ return raw.trim() === '' ? raw : Number(raw);
131
+ }
132
+ if (type === 'boolean') {
133
+ return /^(1|true|yes|on)$/i.test(raw);
134
+ }
135
+ return raw; // string, enum, or unknown
136
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Legacy URL grammar — the compact deep-link language the floor plan has always
3
+ * spoken (`?<slug>`, `?route:B-12:A-3:false`, `?tour=t1`, `?planner=a:b&from=c`,
4
+ * one-shot params like `?blue-dot=…` or `?viewermode=1`), as one pure codec:
5
+ *
6
+ * parseLegacyQuery(search) raw `location.search` → typed commands
7
+ * serializeSelection(selection) selection descriptor → `location.search`
8
+ *
9
+ * Both halves speak the SAME grammar, so shortcuts stay bidirectional: the slug
10
+ * a shared link carries is the slug the plan writes back when the visitor picks
11
+ * an entity (round-trip covered by tests). The floor plan maps commands to its
12
+ * public methods / stores and its UI state to a selection descriptor — nothing
13
+ * here touches a store or the DOM.
14
+ *
15
+ * Encoding contract (inherited from the legacy router, byte-compatible):
16
+ * the WHOLE query payload is one `encodeURIComponent` unit — `route:a:b`
17
+ * travels as `?route%3Aa%3Ab`, `planner&source=bookmarks` as
18
+ * `?planner%26source%3Dbookmarks` — and the parser decodes the whole string
19
+ * before splitting. Keys owned by the config layer (`UrlConfigSchema`) and
20
+ * intent-shortcut keys (`IntentsSchema` arm names) are consumed by `loadConfig`
21
+ * at boot, so the parser strips them from the residual slug here — one place
22
+ * knows the full key partition of the query.
23
+ *
24
+ * LEGACY GRAMMAR — frozen. Do not add keys or forms here. New URL capabilities
25
+ * are born in `UrlConfigSchema` (config keys) or `IntentsSchema` (intent arms)
26
+ * — see docs/evgeny-thoughts/2026-07-09-legacy-url-retirement.md §2. The parse
27
+ * side stays alive for links already shared in the wild, but only as a
28
+ * translator into those two channels; the sole legitimate consumer is
29
+ * `packages/floorplan/src/services/url-dispatch.ts` (lint-enforced).
30
+ */
31
+ import { type Camera, type ListPanel } from '@expofp/schema';
32
+ /** The list panels addressable by keyword (`?bookmarks`, `?sessions`, …). */
33
+ export type LegacyListPanel = ListPanel;
34
+ /**
35
+ * The exclusive part of a query — at most one of these dispatches per URL,
36
+ * mirroring the legacy router's `if/else` chain (its order is the array order
37
+ * of {@link parseLegacyQuery}'s classifier).
38
+ */
39
+ export type LegacyPrimary = {
40
+ type: 'kiosk';
41
+ enabled: boolean | null;
42
+ } | {
43
+ type: 'list';
44
+ list: LegacyListPanel;
45
+ } | {
46
+ type: 'tour';
47
+ tourId: string;
48
+ } | {
49
+ type: 'planner';
50
+ fromBookmarks: boolean;
51
+ items: string[];
52
+ from?: string;
53
+ } | {
54
+ type: 'route';
55
+ to?: string;
56
+ from?: string;
57
+ accessible: boolean;
58
+ waypoints: string[];
59
+ title?: string;
60
+ } | {
61
+ type: 'language';
62
+ langId?: string;
63
+ } | {
64
+ type: 'printPdf';
65
+ } | {
66
+ type: 'visibility';
67
+ hidden: string[];
68
+ } | {
69
+ type: 'buildRoute';
70
+ } | {
71
+ type: 'pathway';
72
+ pathwayId: string;
73
+ boothIds: string[];
74
+ exhibitorIds: string[];
75
+ tourId?: string;
76
+ } | {
77
+ type: 'selectExhibitors';
78
+ values: string[];
79
+ } | {
80
+ type: 'ownedByFilters';
81
+ } | {
82
+ type: 'ownedByConfig';
83
+ } | {
84
+ type: 'select';
85
+ slug: string;
86
+ };
87
+ /**
88
+ * Independent one-shot boot effects — the params the legacy router scrubbed
89
+ * from the URL as it consumed them. They are not written back (the single
90
+ * writer re-serializes the URL from state after dispatch, which drops them).
91
+ */
92
+ export type LegacyOneShot = {
93
+ type: 'heatmap';
94
+ yah: boolean;
95
+ kiosk: boolean;
96
+ } | {
97
+ type: 'blueDot';
98
+ x: number;
99
+ y: number;
100
+ layer?: string;
101
+ lat?: number;
102
+ lng?: number;
103
+ } | {
104
+ type: 'viewerMode';
105
+ enabled: boolean;
106
+ } | {
107
+ type: 'previewMode';
108
+ enabled: boolean;
109
+ } | {
110
+ type: 'uiScale';
111
+ scale: number | null;
112
+ raw: string;
113
+ } | {
114
+ type: 'resetUiScale';
115
+ } | {
116
+ type: 'legacyBookmarks';
117
+ appendExhibitorId: number | null;
118
+ } | {
119
+ type: 'camera';
120
+ camera: Camera;
121
+ roll?: number;
122
+ };
123
+ export interface LegacyUrlQuery {
124
+ primary: LegacyPrimary;
125
+ oneShots: LegacyOneShot[];
126
+ }
127
+ /**
128
+ * What the floor plan currently shows, for the URL — the serializer's input.
129
+ * `slug` covers every entity kind and free search text alike: the value IS the
130
+ * query, and the parser's `select` command resolves it back by catalog lookup.
131
+ */
132
+ export type UrlSelection = {
133
+ type: 'none';
134
+ } | {
135
+ type: 'slug';
136
+ slug: string;
137
+ } | {
138
+ type: 'route';
139
+ to?: string;
140
+ from?: string;
141
+ accessible: boolean;
142
+ waypoints?: string[];
143
+ } | {
144
+ type: 'planner';
145
+ fromBookmarks: boolean;
146
+ items?: string[];
147
+ from?: string;
148
+ } | {
149
+ type: 'list';
150
+ list: LegacyListPanel;
151
+ } | {
152
+ type: 'tour';
153
+ tourId: string;
154
+ } | {
155
+ type: 'filter';
156
+ key: string;
157
+ value: string;
158
+ };
159
+ export declare function parseLegacyQuery(search: string): LegacyUrlQuery;
160
+ /**
161
+ * The selection's canonical query — `''` for none, else `?<payload>` with the
162
+ * whole payload as one `encodeURIComponent` unit (the legacy contract). This is
163
+ * the string the single URL writer puts in the address bar, byte-compatible
164
+ * with the URLs the legacy router wrote (shared links keep working).
165
+ */
166
+ export declare function serializeSelection(selection: UrlSelection): string;
167
+ //# sourceMappingURL=legacy-url.d.ts.map