@virtualdisplay.io/client 4.0.0-alpha.28 → 4.0.0-alpha.29

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
@@ -24,12 +24,13 @@ declare const MESSAGE_TYPES: {
24
24
  readonly SELECTABLE: "selectable";
25
25
  readonly APP_RESOLVED: "app_resolved";
26
26
  };
27
- /** The public submit settings of a configurator-type app, as the form renders
28
- * them. Never the recipient: that section is removed in SQL on the public
29
- * read and cannot ride any message. Sparse like every config section: an
30
- * absent field means the renderer's default (the backend stores the section
31
- * as written and omits empty keys), so nothing here is guaranteed present. */
32
- interface AppSubmitSettings$1 {
27
+ /** The app types this protocol version knows. The wire field stays a plain
28
+ * string on purpose: a newer viewer may report a type this bundle predates,
29
+ * and the host must receive it to fail visibly (never a quiet downgrade). */
30
+ type AppType = 'viewer' | 'configurator';
31
+ /** What a configurator app's submit form renders. Any field may be absent; an
32
+ * absent field means the form's own default. */
33
+ interface AppSubmitSettings {
33
34
  readonly fields?: string[];
34
35
  readonly ctaLabel?: string;
35
36
  readonly privacyUrl?: string;
@@ -50,7 +51,7 @@ interface AppResolvedMessage {
50
51
  * no other way to learn it, so the shell treats absence as "submit
51
52
  * unavailable" rather than the guard dropping the whole report. */
52
53
  readonly slug?: string;
53
- readonly submit?: AppSubmitSettings$1;
54
+ readonly submit?: AppSubmitSettings;
54
55
  }
55
56
  /** One mesh or material variant in a model, as reported by STATE. */
56
57
  interface ModelNodeDto {
@@ -62,11 +63,6 @@ interface ModelNodeDto {
62
63
  /**
63
64
  * State (viewer → client): the meshes and material variants of one instance,
64
65
  * with their current visibility.
65
- *
66
- * Its shape used to live in server-core, marked "server-only", while its type
67
- * value sat in the public MESSAGE_TYPES and the client read it: a public
68
- * message whose shape only one side declared, which is how two shapes of one
69
- * thing start to drift.
70
66
  */
71
67
  interface StateMessage {
72
68
  readonly type: typeof MESSAGE_TYPES.STATE;
@@ -85,16 +81,18 @@ interface MutationResult {
85
81
  readonly success: boolean;
86
82
  readonly error?: string;
87
83
  }
84
+ /** One instance's configuration: attribute name to the value selected for it. */
85
+ type InstanceAttributes = Readonly<Record<string, string>>;
88
86
  /**
89
- * The folded family state riding along on a live sync channel broadcast
90
- * (ar-session-handoff, sync-channel.md): the same shape the public
91
- * session-state read returns. Authoritative self-heal input; `mutations`
92
- * on the same message is only the delta that triggered the broadcast.
87
+ * The folded session state carried by a live sync broadcast: the same shape the
88
+ * public session-state read returns. It is authoritative, where `mutations` on
89
+ * the same message is only the change that triggered the broadcast.
93
90
  */
94
91
  interface SyncStateSnapshot {
95
92
  readonly license: string;
96
93
  readonly model: string;
97
- readonly attributes: Record<string, string>;
94
+ /** The folded configuration, keyed by instance and then by attribute name. */
95
+ readonly attributes: Readonly<Record<string, InstanceAttributes>>;
98
96
  }
99
97
  interface MutationMessage {
100
98
  readonly type: typeof MESSAGE_TYPES.MUTATION;
@@ -102,39 +100,23 @@ interface MutationMessage {
102
100
  readonly instance: string;
103
101
  readonly mutations: MutationDto[];
104
102
  readonly isInitial: boolean;
105
- /**
106
- * Live sync channel extras (ar-session-handoff, sync-channel.md), present
107
- * only on a mutation the viewer engine's sync module received over the SSE
108
- * live-state channel; a MutationMessage posted locally by the client never
109
- * carries these. `origin` is the sender's page session id: its presence
110
- * marks the mutation as remote, so the tracker must never record it again
111
- * (the no-re-track loop guard, decision 15), and the sync module drops it
112
- * when it equals the receiving engine's own session id (the echo filter).
113
- * `seq` is the sender's per-session sequence number, used by the sync
114
- * module to track how far the channel has progressed. `state` is the
115
- * authoritative folded family state: the sync module diffs it against the
116
- * channel's last-seen state and applies the difference, so a catch-up
117
- * (empty `mutations`) or a broadcast the hub dropped still converges the
118
- * subscriber. None of these fields are read by `MutationPipeline`.
119
- */
103
+ /** The sending page's session id, present when the mutation came from another device. */
120
104
  readonly origin?: string;
105
+ /** The sender's per-session sequence number. */
121
106
  readonly seq?: number;
107
+ /** The authoritative session state at the moment of the broadcast. */
122
108
  readonly state?: SyncStateSnapshot;
123
109
  /**
124
- * A drag's intermediate step (parametric dimensions): the viewer applies it
125
- * so the model follows the hand, but never tracks it, the settled value is
126
- * the one recorded choice and it arrives as an ordinary mutation without
127
- * this flag. Only the client knows when a drag settles, which is why this
128
- * travels on the message instead of being guessed viewer-side.
110
+ * A drag's intermediate step: the viewer applies it so the model follows the
111
+ * hand but does not record it. The settled value arrives afterwards as an
112
+ * ordinary mutation without this flag.
129
113
  */
130
114
  readonly transient?: boolean;
131
115
  }
132
116
  /**
133
- * Sync channel status (server → client, ar-session-handoff, sync-channel.md):
134
- * whether the viewer engine's live-state channel is currently connected to its
135
- * hand-off family. One-way, latest-wins: no history, no buffering. The public
136
- * client exposes it as `onSyncChange`; the studio composes nothing, so its
137
- * engine never sends this (it never opens the channel).
117
+ * Sync status (viewer → client): whether the live channel is connected to the
118
+ * hand-off session. One-way and latest-wins, with no history or buffering. The
119
+ * client exposes it as `onSyncChange`.
138
120
  */
139
121
  interface SyncMessage {
140
122
  readonly type: typeof MESSAGE_TYPES.SYNC;
@@ -148,17 +130,18 @@ interface MutationResultMessage {
148
130
  readonly isInitial: boolean;
149
131
  }
150
132
  /**
151
- * How a model's annotations present to the Shopper: never (hide), always
152
- * (show), or under the Shopper's own control via a viewer toggle, starting
153
- * visible (toggle-shown) or hidden (toggle-hidden). Resolved per app; the
154
- * backend's `Resolve` owns the `hide` default.
133
+ * How a model's annotations present to the visitor: never (hide), always
134
+ * (show), or under their own control via a viewer toggle, starting visible
135
+ * (toggle-shown) or hidden (toggle-hidden). Resolved per app, defaulting to
136
+ * hide.
155
137
  */
156
- type AnnotationVisibility$1 = 'hide' | 'show' | 'toggle-shown' | 'toggle-hidden';
138
+ type AnnotationVisibility = 'hide' | 'show' | 'toggle-shown' | 'toggle-hidden';
157
139
  /**
158
- * Viewer settings from model metadata (server → client via MAPPING).
159
- * Customer-configured in the portal.
140
+ * Which viewer controls the model shows, as configured for it. Sent with the
141
+ * mapping; every field is optional and an absent field means the viewer's own
142
+ * default.
160
143
  */
161
- interface ViewerSettings$1 {
144
+ interface ViewerSettings {
162
145
  readonly ar?: boolean;
163
146
  readonly fullscreen?: boolean;
164
147
  readonly screenshot?: boolean;
@@ -168,7 +151,7 @@ interface ViewerSettings$1 {
168
151
  * annotations, so a control can never render with nothing to govern;
169
152
  * absent means there is nothing to show or toggle.
170
153
  */
171
- readonly annotations?: AnnotationVisibility$1;
154
+ readonly annotations?: AnnotationVisibility;
172
155
  readonly language?: string;
173
156
  /** Where the attribute controls sit: in-frame bottom-left (default) or a docked right panel. */
174
157
  readonly configuratorLayout?: 'inframe-bottom-left' | 'sidebar-right' | 'hidden';
@@ -254,12 +237,10 @@ interface SelectableAttribute {
254
237
  readonly hidden?: boolean;
255
238
  }
256
239
  /**
257
- * Selectability snapshot (server → client): sent after the mapping and after
258
- * every choice that changes the picture. Always the complete state, never a
259
- * diff; a value not in its list is not pickable (the client greys it out).
260
- * The client only renders, it never knows the exclusion rules. Named
261
- * `selectable`, not availability: that word is taken by pricing
262
- * (AVAILABILITY, available/view_only).
240
+ * Selectability snapshot (viewer → client): which values can still be picked,
241
+ * sent after the mapping and after every choice that changes the picture.
242
+ * Always the complete state, never a difference; a value not in its list is
243
+ * not pickable and the client greys it out.
263
244
  */
264
245
  interface SelectableMessage {
265
246
  readonly type: typeof MESSAGE_TYPES.SELECTABLE;
@@ -268,25 +249,8 @@ interface SelectableMessage {
268
249
  readonly attributes: SelectableAttribute[];
269
250
  }
270
251
  /**
271
- * Loading indicator (server → client): progress percentage, phase label, or error.
272
- * Sent during model download and parse so the client loading indicator can update.
273
- *
274
- * This message concerns one model at five of its seven call sites (download
275
- * progress, a load error, the rendering phase) and is fatal with no model to
276
- * blame at the other two (no WebGL, the boot-failure path), so by the
277
- * message-scope rule it takes an instance id optionally.
278
- *
279
- * It does not carry one yet, on purpose. `publishLoadingProgress` is the only
280
- * publisher and knows no instance, so the field would be absent at all seven
281
- * sites while its documented meaning is "absent means the viewer could not
282
- * start". An integrator branching on that would read every progress update as
283
- * a viewer failure, which is worse than no field at all.
284
- *
285
- * Nothing is lost by waiting: an optional field is additive, so unlike the
286
- * required ids in this file it carries no alpha-window pressure. Composition
287
- * task 10 brings a second model and with it the choice the spec parked, thread
288
- * the id through the five model sites, or split the fatal case onto its own
289
- * message. Decide it there, with a real second instance to test against.
252
+ * Loading indicator (viewer → client): download progress, the phase it is in, or
253
+ * an error, sent while the model loads so the client's loading UI can follow.
290
254
  */
291
255
  interface LoadingIndicatorMessage {
292
256
  readonly type: typeof MESSAGE_TYPES.LOADING_INDICATOR;
@@ -310,11 +274,8 @@ interface FullscreenMessage {
310
274
  readonly type: typeof MESSAGE_TYPES.FULLSCREEN;
311
275
  }
312
276
  /**
313
- * QR shown (client → server): the desktop QR hand-off modal (ar-session-handoff)
314
- * was displayed to the shopper. The QR itself is generated client-side; this
315
- * only lets the server record the engagement (`qr_shown`, the funnel's
316
- * denominator) against the tracking session it owns. One-way, fire-and-forget,
317
- * nothing to act on, same shape as FULLSCREEN.
277
+ * QR shown (client → viewer): the desktop hand-off QR was displayed. One-way and
278
+ * fire-and-forget, with nothing to act on.
318
279
  */
319
280
  interface QrShownMessage {
320
281
  readonly type: typeof MESSAGE_TYPES.QR_SHOWN;
@@ -326,7 +287,7 @@ interface QrShownMessage {
326
287
  */
327
288
  interface ConfigMessage {
328
289
  readonly type: typeof MESSAGE_TYPES.CONFIG;
329
- readonly viewerSettings: ViewerSettings$1;
290
+ readonly viewerSettings: ViewerSettings;
330
291
  readonly cameraLimits?: CameraLimits;
331
292
  }
332
293
  interface HandshakeMessage {
@@ -377,10 +338,9 @@ declare const FOCUS_STATES: {
377
338
  type FocusAction = (typeof FOCUS_ACTIONS)[keyof typeof FOCUS_ACTIONS];
378
339
  type FocusStateValue = (typeof FOCUS_STATES)[keyof typeof FOCUS_STATES];
379
340
  /**
380
- * Focus (client → server): isolate and frame a component, or return to overview.
381
- * Programmatic — the API, the (future) component menu, and the portal editors all
382
- * send this. `component` is a name from the `components` metadata, or a raw mesh
383
- * name as a fallback (resolved server-side); required when action is `focus`.
341
+ * Focus (client → viewer): isolate and frame one component, or return to the
342
+ * overview. `component` is a name from the model's components, or a mesh name as
343
+ * a fallback, and is required when the action is `focus`.
384
344
  */
385
345
  interface FocusMessage {
386
346
  readonly type: typeof MESSAGE_TYPES.FOCUS;
@@ -403,10 +363,9 @@ interface FocusStateMessage {
403
363
  readonly attributes?: string[];
404
364
  }
405
365
  /**
406
- * What the configurator does with a matched configuration. Mirrors the backend
407
- * `availability` enum (pricing rule provenance lives on `PricingRule.source`).
366
+ * What the configurator does with a matched configuration.
408
367
  */
409
- declare const AVAILABILITY$1: {
368
+ declare const AVAILABILITY: {
410
369
  /** Price and submit button both shown (default). */
411
370
  readonly AVAILABLE: "available";
412
371
  /** Price shown, submit hidden (sold out / coming soon). */
@@ -414,12 +373,12 @@ declare const AVAILABILITY$1: {
414
373
  /** Behaves exactly like an unpriced combination: no price, no button. */
415
374
  readonly HIDDEN: "hidden";
416
375
  };
417
- type Availability$1 = (typeof AVAILABILITY$1)[keyof typeof AVAILABILITY$1];
376
+ type Availability = (typeof AVAILABILITY)[keyof typeof AVAILABILITY];
418
377
  /**
419
- * One pricing rule: a subset of attribute values mapped to a reference + price.
420
- * Matches the backend pricing read contract (`GET /api/public/models/.../pricing`).
378
+ * One pricing rule: a subset of attribute values mapped to a reference and a
379
+ * price.
421
380
  */
422
- interface PricingRule$1 {
381
+ interface PricingRule {
423
382
  /**
424
383
  * The attribute name→value pairs this rule names. `{}` is a catch-all;
425
384
  * omitted names are wildcards. A rule matches when every pair equals the
@@ -430,7 +389,7 @@ interface PricingRule$1 {
430
389
  readonly reference: string;
431
390
  /** Price in minor units (cents), exactly as the source displays it. */
432
391
  readonly priceCents: number;
433
- readonly availability: Availability$1;
392
+ readonly availability: Availability;
434
393
  /** Provenance (manual | imported); audit-only, unused by the resolver. */
435
394
  readonly source: 'manual' | 'imported';
436
395
  }
@@ -438,7 +397,7 @@ interface PricingRule$1 {
438
397
  * A model's pricing document. The viewer's fetcher merges it into the model
439
398
  * metadata and the server forwards it to the client via {@link PricingMessage}.
440
399
  */
441
- interface PricingDocument$1 {
400
+ interface PricingDocument {
442
401
  /** Optimistic-concurrency version from the editor; unused by the viewer. */
443
402
  readonly version: number;
444
403
  /** Customer-authored CTA label; null when the adapter's defaultLabel fills in. */
@@ -447,7 +406,7 @@ interface PricingDocument$1 {
447
406
  readonly currency: string;
448
407
  /** VAT label for bare embeds, e.g. "incl. btw". */
449
408
  readonly vatDisplay: string;
450
- readonly rules: PricingRule$1[];
409
+ readonly rules: PricingRule[];
451
410
  }
452
411
  /**
453
412
  * Pricing (server → client): the model's pricing document. Sent once at startup
@@ -462,7 +421,7 @@ interface PricingMessage {
462
421
  * exactly this field.
463
422
  */
464
423
  readonly instance: string;
465
- readonly pricing: PricingDocument$1;
424
+ readonly pricing: PricingDocument;
466
425
  }
467
426
  type Message = HandshakeMessage | SessionMessage | MutationMessage | MutationResultMessage | CameraCommandMessage | CameraPositionMessage | SnapshotRequestMessage | SnapshotResultMessage | MappingMessage | LoadingIndicatorMessage | ArActivateMessage | FullscreenMessage | ConfigMessage | AnnotationToggleMessage | FocusMessage | FocusStateMessage | PricingMessage | QrShownMessage | SyncMessage | SelectableMessage | AppResolvedMessage | StateMessage;
468
427
  type MessageCallback = (message: Message) => void | Promise<void>;
@@ -477,29 +436,16 @@ interface MessengerOptions {
477
436
  */
478
437
  bufferIncoming?: boolean;
479
438
  /**
480
- * Which protocol to validate incoming messages against — not whether to validate.
481
- * Both sides always reject browser-extension noise and unknown message types. The
482
- * default (`isValidMessageData`) accepts the public protocol; the studio side passes
483
- * `isValidStudioMessageData`, which accepts the public protocol plus the studio
484
- * messages (e.g. annotation placement), so the public bundle stays lean.
439
+ * Which protocol to validate incoming messages against, never whether to
440
+ * validate: both sides always reject unknown types and browser-extension
441
+ * noise. Defaults to the public protocol.
485
442
  */
486
443
  isValid?: (data: unknown) => boolean;
487
444
  }
488
445
  /**
489
- * Bidirectional iframe message port
490
- *
491
- * Handles sending and receiving messages via postMessage between
492
- * a parent page and an iframe. Both client and server use this —
493
- * the only difference is the target window.
494
- *
495
- * Features:
496
- * - Publisher queue: outgoing messages buffered until markReady()
497
- * - Consumer queue: incoming messages buffered until markConsumerReady() (opt-in)
498
- * - Handler registration: route incoming messages by type
499
- * - Echo prevention: tags outgoing messages to filter self-delivery
500
- * - Cleanup: removes listener on destroy
446
+ * A bidirectional message port between a parent page and an iframe.
501
447
  *
502
- * @example Client (parent page — publisher queue only)
448
+ * @example Client (parent page, publisher queue only)
503
449
  * ```ts
504
450
  * const messenger = new IframeMessenger(() => iframe.contentWindow);
505
451
  * messenger.on('mutation', handleMutationResult);
@@ -510,7 +456,7 @@ interface MessengerOptions {
510
456
  * messenger.send({ type: 'mutation', mutations, isInitial: true });
511
457
  * ```
512
458
  *
513
- * @example Server (inside iframe — both queues)
459
+ * @example Viewer (inside the iframe, both queues)
514
460
  * ```ts
515
461
  * const messenger = new IframeMessenger(
516
462
  * () => window.parent !== window ? window.parent : null,
@@ -549,13 +495,8 @@ declare class IframeMessenger {
549
495
  */
550
496
  send(message: Message): void;
551
497
  /**
552
- * Feed a message through the same routing path as one that just arrived via
553
- * postMessage, without an actual postMessage round-trip: still validated
554
- * against `isValid`, and still respects the consumer queue's buffering. For
555
- * a transport that delivers messages some
556
- * other way than the iframe channel this messenger already listens on (the
557
- * viewer engine's SSE-sourced live sync channel, ar-session-handoff) and
558
- * needs them to land exactly where a postMessage would.
498
+ * Route a message as if it had arrived by postMessage, without the round trip.
499
+ * Still validated against `isValid` and still subject to the consumer queue.
559
500
  */
560
501
  receive(message: Message): void;
561
502
  /**
@@ -583,18 +524,65 @@ declare class IframeMessenger {
583
524
  private routeToHandler;
584
525
  }
585
526
 
586
- type SendFn$1 = (message: Message) => void;
587
527
  /**
588
- * Annotation display controls for the viewer: show or hide existing markers.
589
- *
590
- * Placement (click-to-place for the editor) is a studio capability and lives in
591
- * `@virtualdisplay.io/client-studio`, never in the public client or its engine surface.
528
+ * The client's DOM event names, in one registry. The two selection events are
529
+ * what `client.on(...)` subscribes to; the other three bubble out of the
530
+ * container, so the host page can reach them with `addEventListener`.
531
+ */
532
+ declare const VD_EVENTS: {
533
+ /** Attribute selection requested (cancelable, does not bubble). `detail`: `{ attribute, value }`. */
534
+ readonly SELECT: "vd:select";
535
+ /** Attribute selection applied (does not bubble). `detail`: `{ attribute, value }`. */
536
+ readonly SELECTED: "vd:selected";
537
+ /** Focus changed (bubbles). `detail`: `{ component, attributes }`, or `null` for the overview. */
538
+ readonly FOCUS_CHANGE: "vd:focus-change";
539
+ /** Resolved configuration changed (bubbles). `detail`: the resolved configuration, or `null` when nothing matches. */
540
+ readonly CONFIGURATION_CHANGE: "vd:configuration-change";
541
+ /** Configuration submitted (bubbles). `detail`: the resolved configuration handed to the host. */
542
+ readonly CONFIGURATION_SUBMIT: "vd:configuration-submit";
543
+ };
544
+ type VdEvent = typeof VD_EVENTS[keyof typeof VD_EVENTS];
545
+
546
+ /** Hands a message to the viewer iframe. */
547
+ type SendFn = (message: Message) => void;
548
+ interface MutationFailedEvent {
549
+ readonly attribute: string;
550
+ readonly value: string;
551
+ }
552
+ /**
553
+ * Which app the viewer resolved, as handed to an `onAppResolved` callback.
554
+ */
555
+ interface ResolvedAppInfo {
556
+ /** Known values: `AppType`. An unknown value means a newer server; report it, never downgrade to a viewer. */
557
+ readonly appType: string;
558
+ readonly name: string;
559
+ /** The app's public slug (the address a submit POSTs to). Absent from a pre-slug server. */
560
+ readonly slug?: string;
561
+ readonly submit?: AppSubmitSettings;
562
+ }
563
+ interface VirtualdisplaySelectEvent {
564
+ readonly attribute: string;
565
+ readonly value: string;
566
+ preventDefault(): void;
567
+ }
568
+ interface VirtualdisplaySelectedEvent {
569
+ readonly attribute: string;
570
+ readonly value: string;
571
+ }
572
+ type VirtualdisplayUiEventMap = {
573
+ [VD_EVENTS.SELECT]: VirtualdisplaySelectEvent;
574
+ [VD_EVENTS.SELECTED]: VirtualdisplaySelectedEvent;
575
+ };
576
+ type VirtualdisplayUiEventType = keyof VirtualdisplayUiEventMap;
577
+
578
+ /**
579
+ * Annotation display: show or hide the model's existing markers.
592
580
  */
593
581
  declare class Annotations {
594
582
  private readonly send;
595
583
  /** Whose pins this facade toggles: a pin lives in one model's local space. */
596
584
  private readonly instance;
597
- constructor(send: SendFn$1,
585
+ constructor(send: SendFn,
598
586
  /** Whose pins this facade toggles: a pin lives in one model's local space. */
599
587
  instance: string);
600
588
  setEnabled(enabled: boolean): void;
@@ -731,15 +719,10 @@ interface SubmitOptions {
731
719
  readonly defaultLabel?: string;
732
720
  }
733
721
  /**
734
- * Configuration API — resolves the buyer's selection to a reference + price and
735
- * carries it to the host page.
736
- *
737
- * Follows the existing facade style (`client.camera`, `client.focus`). It holds
738
- * the pricing document and the latest selection, re-resolving on every change.
739
- * `onChange` fires with the resolved configuration (or `undefined` when nothing
740
- * matches); alongside it the container dispatches a bubbling `vd:configuration-change`
741
- * CustomEvent so a page reacts without registering a callback. `onSubmit` registers
742
- * the host handler that the CTA hands the configuration to.
722
+ * Resolves the current selection to a reference and a price, and carries it to
723
+ * the host page. `onChange` fires with the resolved configuration, or
724
+ * `undefined` when nothing matches; `onSubmit` registers the handler the CTA
725
+ * hands that configuration to.
743
726
  */
744
727
  declare class Configuration {
745
728
  private readonly dispatchChange;
@@ -801,7 +784,7 @@ declare class Configuration {
801
784
  */
802
785
  submit(): Promise<void>;
803
786
  /** Called by the client when a PRICING message arrives from the viewer. */
804
- handlePricingReceived(pricing: PricingDocument$1): void;
787
+ handlePricingReceived(pricing: PricingDocument): void;
805
788
  /** Called by the client after every mutation result, with the full selection. */
806
789
  handleSelectionChanged(selection: Selection): void;
807
790
  private recompute;
@@ -818,12 +801,9 @@ type FocusChange = FocusState | null;
818
801
  type FocusChangeCallback = (state: FocusChange) => void;
819
802
  type DispatchFn = (state: FocusChange) => void;
820
803
  /**
821
- * Focus control API — isolate and frame a component, or return to overview.
822
- *
823
- * Programmatic by design: `set('Seat')` needs no user selection, which is how the
824
- * (future) component menu and the portal/admin editors drive it over the same
825
- * message bus. Alongside `onChange`, the container dispatches a bubbling
826
- * `vd:focus-change` CustomEvent so a page can react without registering a callback.
804
+ * Isolate and frame one component, or return to the overview. Alongside
805
+ * `onChange` the container dispatches a bubbling `vd:focus-change` event, so a
806
+ * page can react without registering a callback.
827
807
  */
828
808
  declare class Focus {
829
809
  private readonly send;
@@ -1015,8 +995,8 @@ declare class Attribute {
1015
995
  * they normalise before the merge (`true` → `show`, `false` → `hide`), so
1016
996
  * existing mounts keep compiling while the package is in alpha.
1017
997
  */
1018
- type ViewerSettingsOverride = Omit<Partial<ViewerSettings$1>, 'annotations'> & {
1019
- readonly annotations?: boolean | AnnotationVisibility$1;
998
+ type ViewerSettingsOverride = Omit<Partial<ViewerSettings>, 'annotations'> & {
999
+ readonly annotations?: boolean | AnnotationVisibility;
1020
1000
  };
1021
1001
  /**
1022
1002
  * Options shared by both address forms.
@@ -1041,17 +1021,9 @@ interface ClientOptionsBase {
1041
1021
  readonly viewerSettings?: ViewerSettingsOverride;
1042
1022
  }
1043
1023
  /**
1044
- * How the client addresses what it shows, one of two forms:
1045
- *
1046
- * - `{ app }`: an app's public link key (the slug in its URL). The viewer
1047
- * resolves that app and renders it with its own configuration.
1048
- * - `{ license, model }`: the classic pair, which resolves to the model's
1049
- * default app. Every existing integration keeps working unchanged.
1050
- *
1051
- * Supplying both is allowed so a migration is additive (add `app`, done):
1052
- * `app` wins and a warning is logged that the pair was ignored. Supplying
1053
- * neither is a compile error, which is the guarantee this union buys over a
1054
- * bag of three optional fields.
1024
+ * What the client shows, given either way of naming it: `{ app }`, an app's
1025
+ * public link key, or `{ license, model }`, which resolves to that model's
1026
+ * default app. Given both, `app` wins and the pair is ignored with a warning.
1055
1027
  */
1056
1028
  type ClientAddress = {
1057
1029
  readonly app: string;
@@ -1067,68 +1039,7 @@ type ClientAddress = {
1067
1039
  */
1068
1040
  type ClientOptions = ClientOptionsBase & ClientAddress;
1069
1041
 
1070
- /**
1071
- * The Virtualdisplay client's DOM event names, in one registry.
1072
- *
1073
- * - `ui:*` events dispatch on the container (non-bubbling). They are the typed
1074
- * surface of `client.on(...)` — payloads in `VirtualdisplayUiEventMap` (./types).
1075
- * - `vd:*` events bubble into the customer's page, so a page can react with
1076
- * `addEventListener` without registering a client callback.
1077
- */
1078
- declare const VD_EVENTS: {
1079
- /** Attribute selection requested (cancelable). `detail`: `{ attribute, value }`. */
1080
- readonly SELECT: "ui:select";
1081
- /** Attribute selection applied. `detail`: `{ attribute, value }`. */
1082
- readonly SELECTED: "ui:selected";
1083
- /** Focus changed (bubbles). `detail`: `{ component, attributes }`, or `null` for the overview. */
1084
- readonly FOCUS_CHANGE: "vd:focus-change";
1085
- /** Resolved configuration changed (bubbles). `detail`: the resolved configuration, or `null` when nothing matches. */
1086
- readonly CONFIGURATION_CHANGE: "vd:configuration-change";
1087
- /** Configuration submitted (bubbles). `detail`: the resolved configuration handed to the host. */
1088
- readonly CONFIGURATION_SUBMIT: "vd:configuration-submit";
1089
- };
1090
- type VdEvent = typeof VD_EVENTS[keyof typeof VD_EVENTS];
1091
-
1092
- interface MutationFailedEvent {
1093
- readonly attribute: string;
1094
- readonly value: string;
1095
- }
1096
- /**
1097
- * Which app this viewer resolved (the apps spec's one-read inversion): the
1098
- * `onAppResolved` callback payload. The protocol message minus its wire
1099
- * discriminator; only the parent-safe submit settings ride along, never the
1100
- * delivery recipient (removed in SQL on the public read).
1101
- */
1102
- interface ResolvedAppInfo {
1103
- /** Known values: `AppType`. An unknown value means a newer server; report it, never downgrade to a viewer. */
1104
- readonly appType: string;
1105
- readonly name: string;
1106
- /** The app's public slug (the address a submit POSTs to). Absent from a pre-slug server. */
1107
- readonly slug?: string;
1108
- readonly submit?: AppSubmitSettings$1;
1109
- }
1110
- interface VirtualdisplaySelectEvent {
1111
- readonly attribute: string;
1112
- readonly value: string;
1113
- preventDefault(): void;
1114
- }
1115
- interface VirtualdisplaySelectedEvent {
1116
- readonly attribute: string;
1117
- readonly value: string;
1118
- }
1119
- type VirtualdisplayUiEventMap = {
1120
- [VD_EVENTS.SELECT]: VirtualdisplaySelectEvent;
1121
- [VD_EVENTS.SELECTED]: VirtualdisplaySelectedEvent;
1122
- };
1123
- type VirtualdisplayUiEventType = keyof VirtualdisplayUiEventMap;
1124
-
1125
- type SendFn = (message: Message) => void;
1126
- /**
1127
- * The seam for the AR flow behind the standard interface's AR button. The base
1128
- * only routes to it: the implementation (mobile AR_ACTIVATE, desktop hand-off
1129
- * QR) lives in the public client package (`@virtualdisplay.io/client`), which
1130
- * composes it via {@link BaseClientExtensions.createArActivation}.
1131
- */
1042
+ /** The AR flow behind the standard interface's AR button. */
1132
1043
  interface ArFlow {
1133
1044
  /** The AR button handler. */
1134
1045
  activate(): void;
@@ -1136,57 +1047,27 @@ interface ArFlow {
1136
1047
  onSession(sessionId: string): void;
1137
1048
  destroy(): void;
1138
1049
  }
1139
- /**
1140
- * The seam for the live sync channel's connection status (ar-session-handoff,
1141
- * sync-channel.md). The base only routes SYNC messages to it; the concrete
1142
- * tracker (callback list + `onSyncChange`) lives in the public client package,
1143
- * composed via {@link BaseClientExtensions.createSyncTracker}. The studio
1144
- * composes nothing, so `onSyncChange` does not exist on the studio client
1145
- * (its engine never opens the channel, so it never sends SYNC either).
1146
- */
1050
+ /** The live channel's connection status, as the client surfaces it. */
1147
1051
  interface SyncTracker {
1148
1052
  /** Latest-wins, no buffering: the channel's current connection state. */
1149
1053
  handleSync(connected: boolean): void;
1150
1054
  }
1151
- /**
1152
- * Extensions a subclass (the studio client) layers onto the base composition without
1153
- * re-implementing it: a custom incoming-message validator and extra message handlers.
1154
- */
1055
+ /** What a subclass layers onto the base composition instead of re-implementing it. */
1155
1056
  interface BaseClientExtensions {
1156
1057
  readonly messengerOptions?: MessengerOptions;
1157
1058
  readonly registerHandlers?: (messenger: IframeMessenger, send: SendFn) => void;
1158
- /**
1159
- * The server host this client's viewer iframe targets — a fixed fact per client
1160
- * type, injected like the validator, not a per-instance setting. The studio client
1161
- * fixes it to the studio viewer build; the public client leaves it unset and uses
1162
- * the default public viewer. Both clients are bundled into one portal/admin build,
1163
- * so the host cannot be a single build-time env shared by both.
1164
- */
1059
+ /** The viewer host this client's iframe targets. Unset means the default viewer. */
1165
1060
  readonly serverHost?: string;
1166
- /**
1167
- * Extra query parameters for the viewer iframe URL. An extension seam like
1168
- * `serverHost`: a client type's fixed capability flags (the studio client's
1169
- * metadata-override boot mode), never a per-instance public option.
1170
- */
1061
+ /** A client type's fixed capability flags, appended to the viewer iframe URL. */
1171
1062
  readonly iframeParams?: Readonly<Record<string, string>>;
1172
- /**
1173
- * The AR flow behind the standard interface's AR button. The public client
1174
- * composes its `ArActivation` here; the studio composes nothing (the editor
1175
- * has no AR use case), so the base wires no AR button at all.
1176
- */
1063
+ /** Builds the AR flow behind the standard interface's AR button. */
1177
1064
  readonly createArActivation?: (send: SendFn) => ArFlow;
1178
- /**
1179
- * The live sync channel's connection tracker (ar-session-handoff). The
1180
- * public client composes a tracker and exposes `onSyncChange`; the studio
1181
- * composes nothing, so SYNC messages (which its own engine never sends
1182
- * anyway) are simply dropped.
1183
- */
1065
+ /** Builds the tracker behind `onSyncChange`. */
1184
1066
  readonly createSyncTracker?: () => SyncTracker;
1185
1067
  }
1186
1068
  /**
1187
- * The shared client composition root. Wires the engine (transport, facades, UI) and exposes
1188
- * the buyer-facing API. The public `VirtualdisplayClient` is this base as-is; the studio
1189
- * client extends it with the studio facades via {@link BaseClientExtensions}.
1069
+ * The shared client composition root: wires the transport, facades and UI, and
1070
+ * exposes the integrator-facing API.
1190
1071
  */
1191
1072
  declare class BaseClient {
1192
1073
  private readonly attributes;
@@ -1204,9 +1085,9 @@ declare class BaseClient {
1204
1085
  private readonly options;
1205
1086
  private readonly serverHost;
1206
1087
  private readonly iframeParams;
1207
- /** The composed AR flow, or undefined in builds without one (the studio). */
1088
+ /** The composed AR flow, or undefined in a build that composes none. */
1208
1089
  private readonly arActivation;
1209
- /** The composed sync tracker, or undefined in builds without one (the studio). */
1090
+ /** The composed sync tracker, or undefined in a build that composes none. */
1210
1091
  private readonly syncTracker;
1211
1092
  private iframe;
1212
1093
  private container;
@@ -1220,13 +1101,6 @@ declare class BaseClient {
1220
1101
  constructor(options: ClientOptions, extensions?: BaseClientExtensions);
1221
1102
  /** The configurator UI, every callback bridging it to the facade that owns the action. */
1222
1103
  private createStandardInterface;
1223
- /**
1224
- * The visible toggle composes its message here, on the internal transport,
1225
- * because it is the one sender allowed to stamp `origin: 'control'`: the
1226
- * viewer only counts control-originated flips as Shopper use. The public
1227
- * `client.annotations` facade stays origin-less on purpose. One send per
1228
- * member instance once composition lands; today the host is the only one.
1229
- */
1230
1104
  private sendControlAnnotationToggle;
1231
1105
  /** Wire the inbound viewer protocol to the facades. */
1232
1106
  private wireIncomingMessages;
@@ -1252,11 +1126,8 @@ declare class BaseClient {
1252
1126
  private dispatchConfigurationSubmit;
1253
1127
  private handleConfig;
1254
1128
  /**
1255
- * The difference echo: the viewer already applied the app-config mode
1256
- * itself, so the client only speaks up when the merged mode (config plus
1257
- * integrator override) differs from what CONFIG announced, in either
1258
- * direction. Equal modes echo nothing, so a boot without overrides sends
1259
- * no toggle at all.
1129
+ * Tell the viewer about the annotation mode only when the merged mode differs
1130
+ * from the one it announced.
1260
1131
  */
1261
1132
  private echoAnnotationsDifference;
1262
1133
  /** Tell the viewer the user entered fullscreen, so it records the engagement. */
@@ -1272,11 +1143,9 @@ declare class BaseClient {
1272
1143
  onReady(callback: (client: this) => void): void;
1273
1144
  onMutationFailed(callback: (event: MutationFailedEvent) => void): void;
1274
1145
  /**
1275
- * Subscribe to the viewer's selectability snapshots (kiesbaarheid live):
1276
- * per attribute, which values the buyer can pick right now. Fires with the
1277
- * current snapshot immediately when one already arrived. A model without
1278
- * exclusions never sends one: everything stays selectable and this never
1279
- * fires.
1146
+ * Subscribe to which values can be picked right now, per attribute. Fires
1147
+ * immediately when a snapshot already arrived. A model with no exclusions
1148
+ * never sends one, so this never fires and everything stays selectable.
1280
1149
  */
1281
1150
  onSelectableChange(callback: (snapshot: SelectableSnapshot) => void): void;
1282
1151
  /**
@@ -1288,11 +1157,8 @@ declare class BaseClient {
1288
1157
  /** The selectable values for one attribute right now, or null when unrestricted. */
1289
1158
  getSelectable(name: string): readonly string[] | null;
1290
1159
  /**
1291
- * Trigger the composed AR flow directly (ar-session-handoff task 035): a host
1292
- * page that hides the built-in corner button (`viewerSettings.ar = false`,
1293
- * e.g. the AR landing, which owns a single custom AR affordance) still needs
1294
- * an entry point to the same flow that button would have called. A no-op
1295
- * where no AR flow is composed (the studio client has none).
1160
+ * Start the AR flow directly, as the built-in AR button would. Does nothing
1161
+ * when `viewerSettings.ar` is off or the build composes no AR flow.
1296
1162
  */
1297
1163
  activateAr(): void;
1298
1164
  on<T extends VirtualdisplayUiEventType>(type: T, callback: (event: VirtualdisplayUiEventMap[T]) => void): void;
@@ -1304,7 +1170,7 @@ declare class BaseClient {
1304
1170
  get snapshot(): Snapshot;
1305
1171
  /** The viewer's tracking session id, for a host page to correlate its own actions. */
1306
1172
  get sessionId(): string | undefined;
1307
- get viewerSettings(): ViewerSettings$1;
1173
+ get viewerSettings(): ViewerSettings;
1308
1174
  destroy(): void;
1309
1175
  }
1310
1176
 
@@ -1324,15 +1190,8 @@ declare function enableDebugMode(): void;
1324
1190
  declare function isDebugMode(): boolean;
1325
1191
 
1326
1192
  /**
1327
- * The public Virtualdisplay client: the shared base composition (transport, facades, the
1328
- * standard interface) with no studio capability. This is the buyer-facing SDK published as
1329
- * `@virtualdisplay.io/client`. The internal studio client adds the editing facades over the
1330
- * same base in `@virtualdisplay.io/client-studio`.
1331
- *
1332
- * The AR flow (mobile AR_ACTIVATE, desktop hand-off QR) is composed here: it is a shopper
1333
- * feature, so the studio client does not compose it and renders no AR button. The live sync
1334
- * channel's connection status (`onSyncChange`) is composed the same way: the studio client
1335
- * has no such method, its engine never opens the channel.
1193
+ * The Virtualdisplay client: point it at a container and an app or model, and it
1194
+ * renders the viewer with the standard interface, the configurator and AR.
1336
1195
  */
1337
1196
  declare class VirtualdisplayClient extends BaseClient {
1338
1197
  private readonly syncStatusTracker;
@@ -1341,93 +1200,5 @@ declare class VirtualdisplayClient extends BaseClient {
1341
1200
  onSyncChange(callback: (connected: boolean) => void): void;
1342
1201
  }
1343
1202
 
1344
- /** The app types this protocol version knows. The wire field stays a plain
1345
- * string on purpose: a newer viewer may report a type this bundle predates,
1346
- * and the host must receive it to fail visibly (never a quiet downgrade). */
1347
- type AppType = 'viewer' | 'configurator';
1348
- /** The public submit settings of a configurator-type app, as the form renders
1349
- * them. Never the recipient: that section is removed in SQL on the public
1350
- * read and cannot ride any message. Sparse like every config section: an
1351
- * absent field means the renderer's default (the backend stores the section
1352
- * as written and omits empty keys), so nothing here is guaranteed present. */
1353
- interface AppSubmitSettings {
1354
- readonly fields?: string[];
1355
- readonly ctaLabel?: string;
1356
- readonly privacyUrl?: string;
1357
- }
1358
- /**
1359
- * How a model's annotations present to the Shopper: never (hide), always
1360
- * (show), or under the Shopper's own control via a viewer toggle, starting
1361
- * visible (toggle-shown) or hidden (toggle-hidden). Resolved per app; the
1362
- * backend's `Resolve` owns the `hide` default.
1363
- */
1364
- type AnnotationVisibility = 'hide' | 'show' | 'toggle-shown' | 'toggle-hidden';
1365
- /**
1366
- * Viewer settings from model metadata (server → client via MAPPING).
1367
- * Customer-configured in the portal.
1368
- */
1369
- interface ViewerSettings {
1370
- readonly ar?: boolean;
1371
- readonly fullscreen?: boolean;
1372
- readonly screenshot?: boolean;
1373
- readonly cameraControls?: boolean;
1374
- /**
1375
- * The resolved per-app annotation mode. Present only when the model has
1376
- * annotations, so a control can never render with nothing to govern;
1377
- * absent means there is nothing to show or toggle.
1378
- */
1379
- readonly annotations?: AnnotationVisibility;
1380
- readonly language?: string;
1381
- /** Where the attribute controls sit: in-frame bottom-left (default) or a docked right panel. */
1382
- readonly configuratorLayout?: 'inframe-bottom-left' | 'sidebar-right' | 'hidden';
1383
- }
1384
- /**
1385
- * What the configurator does with a matched configuration. Mirrors the backend
1386
- * `availability` enum (pricing rule provenance lives on `PricingRule.source`).
1387
- */
1388
- declare const AVAILABILITY: {
1389
- /** Price and submit button both shown (default). */
1390
- readonly AVAILABLE: "available";
1391
- /** Price shown, submit hidden (sold out / coming soon). */
1392
- readonly VIEW_ONLY: "view_only";
1393
- /** Behaves exactly like an unpriced combination: no price, no button. */
1394
- readonly HIDDEN: "hidden";
1395
- };
1396
- type Availability = (typeof AVAILABILITY)[keyof typeof AVAILABILITY];
1397
- /**
1398
- * One pricing rule: a subset of attribute values mapped to a reference + price.
1399
- * Matches the backend pricing read contract (`GET /api/public/models/.../pricing`).
1400
- */
1401
- interface PricingRule {
1402
- /**
1403
- * The attribute name→value pairs this rule names. `{}` is a catch-all;
1404
- * omitted names are wildcards. A rule matches when every pair equals the
1405
- * current selection; the rule naming the most attributes wins.
1406
- */
1407
- readonly attributes: Record<string, string>;
1408
- /** The customer's own identifier (SKU, EAN, artikelnummer, name code). */
1409
- readonly reference: string;
1410
- /** Price in minor units (cents), exactly as the source displays it. */
1411
- readonly priceCents: number;
1412
- readonly availability: Availability;
1413
- /** Provenance (manual | imported); audit-only, unused by the resolver. */
1414
- readonly source: 'manual' | 'imported';
1415
- }
1416
- /**
1417
- * A model's pricing document. The viewer's fetcher merges it into the model
1418
- * metadata and the server forwards it to the client via {@link PricingMessage}.
1419
- */
1420
- interface PricingDocument {
1421
- /** Optimistic-concurrency version from the editor; unused by the viewer. */
1422
- readonly version: number;
1423
- /** Customer-authored CTA label; null when the adapter's defaultLabel fills in. */
1424
- readonly buttonLabel: string | null;
1425
- /** ISO 4217 currency code for price formatting. */
1426
- readonly currency: string;
1427
- /** VAT label for bare embeds, e.g. "incl. btw". */
1428
- readonly vatDisplay: string;
1429
- readonly rules: PricingRule[];
1430
- }
1431
-
1432
1203
  export { Annotations, Attribute, AttributeValue, Camera, Configuration, ERROR_CODES, Focus, Photo, Snapshot, VD_EVENTS, VirtualdisplayClient, VirtualdisplayError, enableDebugMode, isDebugMode, logger };
1433
1204
  export type { AppSubmitSettings, AppType, Availability, ClientOptions, ErrorCode, FocusState, MutationFailedEvent, PhotoData, PricingDocument, PricingRule, ResolvedAppInfo, ResolvedConfiguration, SubmitOptions, SubmittedConfiguration, VdEvent, ViewerSettings, VirtualdisplaySelectEvent, VirtualdisplaySelectedEvent, VirtualdisplayUiEventMap, VirtualdisplayUiEventType };