@virtualdisplay.io/client 4.0.0-alpha.27 → 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 {
@@ -356,6 +317,13 @@ interface AnnotationToggleMessage {
356
317
  */
357
318
  readonly instance: string;
358
319
  readonly enabled: boolean;
320
+ /**
321
+ * Who sent it: `control` marks the client's own visible toggle button, the
322
+ * only sender that counts as Shopper use in the viewer statistics. Absent
323
+ * means programmatic (the boot-time config echo, an integrator's
324
+ * `client.annotations` call), which must never be measured as interest.
325
+ */
326
+ readonly origin?: 'control';
359
327
  }
360
328
  /** Focus actions (client → server). */
361
329
  declare const FOCUS_ACTIONS: {
@@ -370,10 +338,9 @@ declare const FOCUS_STATES: {
370
338
  type FocusAction = (typeof FOCUS_ACTIONS)[keyof typeof FOCUS_ACTIONS];
371
339
  type FocusStateValue = (typeof FOCUS_STATES)[keyof typeof FOCUS_STATES];
372
340
  /**
373
- * Focus (client → server): isolate and frame a component, or return to overview.
374
- * Programmatic — the API, the (future) component menu, and the portal editors all
375
- * send this. `component` is a name from the `components` metadata, or a raw mesh
376
- * 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`.
377
344
  */
378
345
  interface FocusMessage {
379
346
  readonly type: typeof MESSAGE_TYPES.FOCUS;
@@ -396,10 +363,9 @@ interface FocusStateMessage {
396
363
  readonly attributes?: string[];
397
364
  }
398
365
  /**
399
- * What the configurator does with a matched configuration. Mirrors the backend
400
- * `availability` enum (pricing rule provenance lives on `PricingRule.source`).
366
+ * What the configurator does with a matched configuration.
401
367
  */
402
- declare const AVAILABILITY$1: {
368
+ declare const AVAILABILITY: {
403
369
  /** Price and submit button both shown (default). */
404
370
  readonly AVAILABLE: "available";
405
371
  /** Price shown, submit hidden (sold out / coming soon). */
@@ -407,12 +373,12 @@ declare const AVAILABILITY$1: {
407
373
  /** Behaves exactly like an unpriced combination: no price, no button. */
408
374
  readonly HIDDEN: "hidden";
409
375
  };
410
- type Availability$1 = (typeof AVAILABILITY$1)[keyof typeof AVAILABILITY$1];
376
+ type Availability = (typeof AVAILABILITY)[keyof typeof AVAILABILITY];
411
377
  /**
412
- * One pricing rule: a subset of attribute values mapped to a reference + price.
413
- * 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.
414
380
  */
415
- interface PricingRule$1 {
381
+ interface PricingRule {
416
382
  /**
417
383
  * The attribute name→value pairs this rule names. `{}` is a catch-all;
418
384
  * omitted names are wildcards. A rule matches when every pair equals the
@@ -423,7 +389,7 @@ interface PricingRule$1 {
423
389
  readonly reference: string;
424
390
  /** Price in minor units (cents), exactly as the source displays it. */
425
391
  readonly priceCents: number;
426
- readonly availability: Availability$1;
392
+ readonly availability: Availability;
427
393
  /** Provenance (manual | imported); audit-only, unused by the resolver. */
428
394
  readonly source: 'manual' | 'imported';
429
395
  }
@@ -431,7 +397,7 @@ interface PricingRule$1 {
431
397
  * A model's pricing document. The viewer's fetcher merges it into the model
432
398
  * metadata and the server forwards it to the client via {@link PricingMessage}.
433
399
  */
434
- interface PricingDocument$1 {
400
+ interface PricingDocument {
435
401
  /** Optimistic-concurrency version from the editor; unused by the viewer. */
436
402
  readonly version: number;
437
403
  /** Customer-authored CTA label; null when the adapter's defaultLabel fills in. */
@@ -440,7 +406,7 @@ interface PricingDocument$1 {
440
406
  readonly currency: string;
441
407
  /** VAT label for bare embeds, e.g. "incl. btw". */
442
408
  readonly vatDisplay: string;
443
- readonly rules: PricingRule$1[];
409
+ readonly rules: PricingRule[];
444
410
  }
445
411
  /**
446
412
  * Pricing (server → client): the model's pricing document. Sent once at startup
@@ -455,7 +421,7 @@ interface PricingMessage {
455
421
  * exactly this field.
456
422
  */
457
423
  readonly instance: string;
458
- readonly pricing: PricingDocument$1;
424
+ readonly pricing: PricingDocument;
459
425
  }
460
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;
461
427
  type MessageCallback = (message: Message) => void | Promise<void>;
@@ -470,29 +436,16 @@ interface MessengerOptions {
470
436
  */
471
437
  bufferIncoming?: boolean;
472
438
  /**
473
- * Which protocol to validate incoming messages against — not whether to validate.
474
- * Both sides always reject browser-extension noise and unknown message types. The
475
- * default (`isValidMessageData`) accepts the public protocol; the studio side passes
476
- * `isValidStudioMessageData`, which accepts the public protocol plus the studio
477
- * 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.
478
442
  */
479
443
  isValid?: (data: unknown) => boolean;
480
444
  }
481
445
  /**
482
- * Bidirectional iframe message port
446
+ * A bidirectional message port between a parent page and an iframe.
483
447
  *
484
- * Handles sending and receiving messages via postMessage between
485
- * a parent page and an iframe. Both client and server use this —
486
- * the only difference is the target window.
487
- *
488
- * Features:
489
- * - Publisher queue: outgoing messages buffered until markReady()
490
- * - Consumer queue: incoming messages buffered until markConsumerReady() (opt-in)
491
- * - Handler registration: route incoming messages by type
492
- * - Echo prevention: tags outgoing messages to filter self-delivery
493
- * - Cleanup: removes listener on destroy
494
- *
495
- * @example Client (parent page — publisher queue only)
448
+ * @example Client (parent page, publisher queue only)
496
449
  * ```ts
497
450
  * const messenger = new IframeMessenger(() => iframe.contentWindow);
498
451
  * messenger.on('mutation', handleMutationResult);
@@ -503,7 +456,7 @@ interface MessengerOptions {
503
456
  * messenger.send({ type: 'mutation', mutations, isInitial: true });
504
457
  * ```
505
458
  *
506
- * @example Server (inside iframe — both queues)
459
+ * @example Viewer (inside the iframe, both queues)
507
460
  * ```ts
508
461
  * const messenger = new IframeMessenger(
509
462
  * () => window.parent !== window ? window.parent : null,
@@ -542,13 +495,8 @@ declare class IframeMessenger {
542
495
  */
543
496
  send(message: Message): void;
544
497
  /**
545
- * Feed a message through the same routing path as one that just arrived via
546
- * postMessage, without an actual postMessage round-trip: still validated
547
- * against `isValid`, and still respects the consumer queue's buffering. For
548
- * a transport that delivers messages some
549
- * other way than the iframe channel this messenger already listens on (the
550
- * viewer engine's SSE-sourced live sync channel, ar-session-handoff) and
551
- * 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.
552
500
  */
553
501
  receive(message: Message): void;
554
502
  /**
@@ -576,18 +524,65 @@ declare class IframeMessenger {
576
524
  private routeToHandler;
577
525
  }
578
526
 
579
- type SendFn$1 = (message: Message) => void;
580
527
  /**
581
- * Annotation display controls for the viewer: show or hide existing markers.
582
- *
583
- * Placement (click-to-place for the editor) is a studio capability and lives in
584
- * `@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.
585
580
  */
586
581
  declare class Annotations {
587
582
  private readonly send;
588
583
  /** Whose pins this facade toggles: a pin lives in one model's local space. */
589
584
  private readonly instance;
590
- constructor(send: SendFn$1,
585
+ constructor(send: SendFn,
591
586
  /** Whose pins this facade toggles: a pin lives in one model's local space. */
592
587
  instance: string);
593
588
  setEnabled(enabled: boolean): void;
@@ -724,15 +719,10 @@ interface SubmitOptions {
724
719
  readonly defaultLabel?: string;
725
720
  }
726
721
  /**
727
- * Configuration API — resolves the buyer's selection to a reference + price and
728
- * carries it to the host page.
729
- *
730
- * Follows the existing facade style (`client.camera`, `client.focus`). It holds
731
- * the pricing document and the latest selection, re-resolving on every change.
732
- * `onChange` fires with the resolved configuration (or `undefined` when nothing
733
- * matches); alongside it the container dispatches a bubbling `vd:configuration-change`
734
- * CustomEvent so a page reacts without registering a callback. `onSubmit` registers
735
- * 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.
736
726
  */
737
727
  declare class Configuration {
738
728
  private readonly dispatchChange;
@@ -794,7 +784,7 @@ declare class Configuration {
794
784
  */
795
785
  submit(): Promise<void>;
796
786
  /** Called by the client when a PRICING message arrives from the viewer. */
797
- handlePricingReceived(pricing: PricingDocument$1): void;
787
+ handlePricingReceived(pricing: PricingDocument): void;
798
788
  /** Called by the client after every mutation result, with the full selection. */
799
789
  handleSelectionChanged(selection: Selection): void;
800
790
  private recompute;
@@ -811,12 +801,9 @@ type FocusChange = FocusState | null;
811
801
  type FocusChangeCallback = (state: FocusChange) => void;
812
802
  type DispatchFn = (state: FocusChange) => void;
813
803
  /**
814
- * Focus control API — isolate and frame a component, or return to overview.
815
- *
816
- * Programmatic by design: `set('Seat')` needs no user selection, which is how the
817
- * (future) component menu and the portal/admin editors drive it over the same
818
- * message bus. Alongside `onChange`, the container dispatches a bubbling
819
- * `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.
820
807
  */
821
808
  declare class Focus {
822
809
  private readonly send;
@@ -1008,8 +995,8 @@ declare class Attribute {
1008
995
  * they normalise before the merge (`true` → `show`, `false` → `hide`), so
1009
996
  * existing mounts keep compiling while the package is in alpha.
1010
997
  */
1011
- type ViewerSettingsOverride = Omit<Partial<ViewerSettings$1>, 'annotations'> & {
1012
- readonly annotations?: boolean | AnnotationVisibility$1;
998
+ type ViewerSettingsOverride = Omit<Partial<ViewerSettings>, 'annotations'> & {
999
+ readonly annotations?: boolean | AnnotationVisibility;
1013
1000
  };
1014
1001
  /**
1015
1002
  * Options shared by both address forms.
@@ -1034,17 +1021,9 @@ interface ClientOptionsBase {
1034
1021
  readonly viewerSettings?: ViewerSettingsOverride;
1035
1022
  }
1036
1023
  /**
1037
- * How the client addresses what it shows, one of two forms:
1038
- *
1039
- * - `{ app }`: an app's public link key (the slug in its URL). The viewer
1040
- * resolves that app and renders it with its own configuration.
1041
- * - `{ license, model }`: the classic pair, which resolves to the model's
1042
- * default app. Every existing integration keeps working unchanged.
1043
- *
1044
- * Supplying both is allowed so a migration is additive (add `app`, done):
1045
- * `app` wins and a warning is logged that the pair was ignored. Supplying
1046
- * neither is a compile error, which is the guarantee this union buys over a
1047
- * 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.
1048
1027
  */
1049
1028
  type ClientAddress = {
1050
1029
  readonly app: string;
@@ -1060,68 +1039,7 @@ type ClientAddress = {
1060
1039
  */
1061
1040
  type ClientOptions = ClientOptionsBase & ClientAddress;
1062
1041
 
1063
- /**
1064
- * The Virtualdisplay client's DOM event names, in one registry.
1065
- *
1066
- * - `ui:*` events dispatch on the container (non-bubbling). They are the typed
1067
- * surface of `client.on(...)` — payloads in `VirtualdisplayUiEventMap` (./types).
1068
- * - `vd:*` events bubble into the customer's page, so a page can react with
1069
- * `addEventListener` without registering a client callback.
1070
- */
1071
- declare const VD_EVENTS: {
1072
- /** Attribute selection requested (cancelable). `detail`: `{ attribute, value }`. */
1073
- readonly SELECT: "ui:select";
1074
- /** Attribute selection applied. `detail`: `{ attribute, value }`. */
1075
- readonly SELECTED: "ui:selected";
1076
- /** Focus changed (bubbles). `detail`: `{ component, attributes }`, or `null` for the overview. */
1077
- readonly FOCUS_CHANGE: "vd:focus-change";
1078
- /** Resolved configuration changed (bubbles). `detail`: the resolved configuration, or `null` when nothing matches. */
1079
- readonly CONFIGURATION_CHANGE: "vd:configuration-change";
1080
- /** Configuration submitted (bubbles). `detail`: the resolved configuration handed to the host. */
1081
- readonly CONFIGURATION_SUBMIT: "vd:configuration-submit";
1082
- };
1083
- type VdEvent = typeof VD_EVENTS[keyof typeof VD_EVENTS];
1084
-
1085
- interface MutationFailedEvent {
1086
- readonly attribute: string;
1087
- readonly value: string;
1088
- }
1089
- /**
1090
- * Which app this viewer resolved (the apps spec's one-read inversion): the
1091
- * `onAppResolved` callback payload. The protocol message minus its wire
1092
- * discriminator; only the parent-safe submit settings ride along, never the
1093
- * delivery recipient (removed in SQL on the public read).
1094
- */
1095
- interface ResolvedAppInfo {
1096
- /** Known values: `AppType`. An unknown value means a newer server; report it, never downgrade to a viewer. */
1097
- readonly appType: string;
1098
- readonly name: string;
1099
- /** The app's public slug (the address a submit POSTs to). Absent from a pre-slug server. */
1100
- readonly slug?: string;
1101
- readonly submit?: AppSubmitSettings$1;
1102
- }
1103
- interface VirtualdisplaySelectEvent {
1104
- readonly attribute: string;
1105
- readonly value: string;
1106
- preventDefault(): void;
1107
- }
1108
- interface VirtualdisplaySelectedEvent {
1109
- readonly attribute: string;
1110
- readonly value: string;
1111
- }
1112
- type VirtualdisplayUiEventMap = {
1113
- [VD_EVENTS.SELECT]: VirtualdisplaySelectEvent;
1114
- [VD_EVENTS.SELECTED]: VirtualdisplaySelectedEvent;
1115
- };
1116
- type VirtualdisplayUiEventType = keyof VirtualdisplayUiEventMap;
1117
-
1118
- type SendFn = (message: Message) => void;
1119
- /**
1120
- * The seam for the AR flow behind the standard interface's AR button. The base
1121
- * only routes to it: the implementation (mobile AR_ACTIVATE, desktop hand-off
1122
- * QR) lives in the public client package (`@virtualdisplay.io/client`), which
1123
- * composes it via {@link BaseClientExtensions.createArActivation}.
1124
- */
1042
+ /** The AR flow behind the standard interface's AR button. */
1125
1043
  interface ArFlow {
1126
1044
  /** The AR button handler. */
1127
1045
  activate(): void;
@@ -1129,57 +1047,27 @@ interface ArFlow {
1129
1047
  onSession(sessionId: string): void;
1130
1048
  destroy(): void;
1131
1049
  }
1132
- /**
1133
- * The seam for the live sync channel's connection status (ar-session-handoff,
1134
- * sync-channel.md). The base only routes SYNC messages to it; the concrete
1135
- * tracker (callback list + `onSyncChange`) lives in the public client package,
1136
- * composed via {@link BaseClientExtensions.createSyncTracker}. The studio
1137
- * composes nothing, so `onSyncChange` does not exist on the studio client
1138
- * (its engine never opens the channel, so it never sends SYNC either).
1139
- */
1050
+ /** The live channel's connection status, as the client surfaces it. */
1140
1051
  interface SyncTracker {
1141
1052
  /** Latest-wins, no buffering: the channel's current connection state. */
1142
1053
  handleSync(connected: boolean): void;
1143
1054
  }
1144
- /**
1145
- * Extensions a subclass (the studio client) layers onto the base composition without
1146
- * re-implementing it: a custom incoming-message validator and extra message handlers.
1147
- */
1055
+ /** What a subclass layers onto the base composition instead of re-implementing it. */
1148
1056
  interface BaseClientExtensions {
1149
1057
  readonly messengerOptions?: MessengerOptions;
1150
1058
  readonly registerHandlers?: (messenger: IframeMessenger, send: SendFn) => void;
1151
- /**
1152
- * The server host this client's viewer iframe targets — a fixed fact per client
1153
- * type, injected like the validator, not a per-instance setting. The studio client
1154
- * fixes it to the studio viewer build; the public client leaves it unset and uses
1155
- * the default public viewer. Both clients are bundled into one portal/admin build,
1156
- * so the host cannot be a single build-time env shared by both.
1157
- */
1059
+ /** The viewer host this client's iframe targets. Unset means the default viewer. */
1158
1060
  readonly serverHost?: string;
1159
- /**
1160
- * Extra query parameters for the viewer iframe URL. An extension seam like
1161
- * `serverHost`: a client type's fixed capability flags (the studio client's
1162
- * metadata-override boot mode), never a per-instance public option.
1163
- */
1061
+ /** A client type's fixed capability flags, appended to the viewer iframe URL. */
1164
1062
  readonly iframeParams?: Readonly<Record<string, string>>;
1165
- /**
1166
- * The AR flow behind the standard interface's AR button. The public client
1167
- * composes its `ArActivation` here; the studio composes nothing (the editor
1168
- * has no AR use case), so the base wires no AR button at all.
1169
- */
1063
+ /** Builds the AR flow behind the standard interface's AR button. */
1170
1064
  readonly createArActivation?: (send: SendFn) => ArFlow;
1171
- /**
1172
- * The live sync channel's connection tracker (ar-session-handoff). The
1173
- * public client composes a tracker and exposes `onSyncChange`; the studio
1174
- * composes nothing, so SYNC messages (which its own engine never sends
1175
- * anyway) are simply dropped.
1176
- */
1065
+ /** Builds the tracker behind `onSyncChange`. */
1177
1066
  readonly createSyncTracker?: () => SyncTracker;
1178
1067
  }
1179
1068
  /**
1180
- * The shared client composition root. Wires the engine (transport, facades, UI) and exposes
1181
- * the buyer-facing API. The public `VirtualdisplayClient` is this base as-is; the studio
1182
- * 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.
1183
1071
  */
1184
1072
  declare class BaseClient {
1185
1073
  private readonly attributes;
@@ -1197,9 +1085,9 @@ declare class BaseClient {
1197
1085
  private readonly options;
1198
1086
  private readonly serverHost;
1199
1087
  private readonly iframeParams;
1200
- /** 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. */
1201
1089
  private readonly arActivation;
1202
- /** 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. */
1203
1091
  private readonly syncTracker;
1204
1092
  private iframe;
1205
1093
  private container;
@@ -1213,6 +1101,7 @@ declare class BaseClient {
1213
1101
  constructor(options: ClientOptions, extensions?: BaseClientExtensions);
1214
1102
  /** The configurator UI, every callback bridging it to the facade that owns the action. */
1215
1103
  private createStandardInterface;
1104
+ private sendControlAnnotationToggle;
1216
1105
  /** Wire the inbound viewer protocol to the facades. */
1217
1106
  private wireIncomingMessages;
1218
1107
  /** Store the resolved app and notify every registered `onAppResolved` callback. */
@@ -1237,11 +1126,8 @@ declare class BaseClient {
1237
1126
  private dispatchConfigurationSubmit;
1238
1127
  private handleConfig;
1239
1128
  /**
1240
- * The difference echo: the viewer already applied the app-config mode
1241
- * itself, so the client only speaks up when the merged mode (config plus
1242
- * integrator override) differs from what CONFIG announced, in either
1243
- * direction. Equal modes echo nothing, so a boot without overrides sends
1244
- * no toggle at all.
1129
+ * Tell the viewer about the annotation mode only when the merged mode differs
1130
+ * from the one it announced.
1245
1131
  */
1246
1132
  private echoAnnotationsDifference;
1247
1133
  /** Tell the viewer the user entered fullscreen, so it records the engagement. */
@@ -1257,11 +1143,9 @@ declare class BaseClient {
1257
1143
  onReady(callback: (client: this) => void): void;
1258
1144
  onMutationFailed(callback: (event: MutationFailedEvent) => void): void;
1259
1145
  /**
1260
- * Subscribe to the viewer's selectability snapshots (kiesbaarheid live):
1261
- * per attribute, which values the buyer can pick right now. Fires with the
1262
- * current snapshot immediately when one already arrived. A model without
1263
- * exclusions never sends one: everything stays selectable and this never
1264
- * 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.
1265
1149
  */
1266
1150
  onSelectableChange(callback: (snapshot: SelectableSnapshot) => void): void;
1267
1151
  /**
@@ -1273,11 +1157,8 @@ declare class BaseClient {
1273
1157
  /** The selectable values for one attribute right now, or null when unrestricted. */
1274
1158
  getSelectable(name: string): readonly string[] | null;
1275
1159
  /**
1276
- * Trigger the composed AR flow directly (ar-session-handoff task 035): a host
1277
- * page that hides the built-in corner button (`viewerSettings.ar = false`,
1278
- * e.g. the AR landing, which owns a single custom AR affordance) still needs
1279
- * an entry point to the same flow that button would have called. A no-op
1280
- * 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.
1281
1162
  */
1282
1163
  activateAr(): void;
1283
1164
  on<T extends VirtualdisplayUiEventType>(type: T, callback: (event: VirtualdisplayUiEventMap[T]) => void): void;
@@ -1289,7 +1170,7 @@ declare class BaseClient {
1289
1170
  get snapshot(): Snapshot;
1290
1171
  /** The viewer's tracking session id, for a host page to correlate its own actions. */
1291
1172
  get sessionId(): string | undefined;
1292
- get viewerSettings(): ViewerSettings$1;
1173
+ get viewerSettings(): ViewerSettings;
1293
1174
  destroy(): void;
1294
1175
  }
1295
1176
 
@@ -1309,15 +1190,8 @@ declare function enableDebugMode(): void;
1309
1190
  declare function isDebugMode(): boolean;
1310
1191
 
1311
1192
  /**
1312
- * The public Virtualdisplay client: the shared base composition (transport, facades, the
1313
- * standard interface) with no studio capability. This is the buyer-facing SDK published as
1314
- * `@virtualdisplay.io/client`. The internal studio client adds the editing facades over the
1315
- * same base in `@virtualdisplay.io/client-studio`.
1316
- *
1317
- * The AR flow (mobile AR_ACTIVATE, desktop hand-off QR) is composed here: it is a shopper
1318
- * feature, so the studio client does not compose it and renders no AR button. The live sync
1319
- * channel's connection status (`onSyncChange`) is composed the same way: the studio client
1320
- * 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.
1321
1195
  */
1322
1196
  declare class VirtualdisplayClient extends BaseClient {
1323
1197
  private readonly syncStatusTracker;
@@ -1326,93 +1200,5 @@ declare class VirtualdisplayClient extends BaseClient {
1326
1200
  onSyncChange(callback: (connected: boolean) => void): void;
1327
1201
  }
1328
1202
 
1329
- /** The app types this protocol version knows. The wire field stays a plain
1330
- * string on purpose: a newer viewer may report a type this bundle predates,
1331
- * and the host must receive it to fail visibly (never a quiet downgrade). */
1332
- type AppType = 'viewer' | 'configurator';
1333
- /** The public submit settings of a configurator-type app, as the form renders
1334
- * them. Never the recipient: that section is removed in SQL on the public
1335
- * read and cannot ride any message. Sparse like every config section: an
1336
- * absent field means the renderer's default (the backend stores the section
1337
- * as written and omits empty keys), so nothing here is guaranteed present. */
1338
- interface AppSubmitSettings {
1339
- readonly fields?: string[];
1340
- readonly ctaLabel?: string;
1341
- readonly privacyUrl?: string;
1342
- }
1343
- /**
1344
- * How a model's annotations present to the Shopper: never (hide), always
1345
- * (show), or under the Shopper's own control via a viewer toggle, starting
1346
- * visible (toggle-shown) or hidden (toggle-hidden). Resolved per app; the
1347
- * backend's `Resolve` owns the `hide` default.
1348
- */
1349
- type AnnotationVisibility = 'hide' | 'show' | 'toggle-shown' | 'toggle-hidden';
1350
- /**
1351
- * Viewer settings from model metadata (server → client via MAPPING).
1352
- * Customer-configured in the portal.
1353
- */
1354
- interface ViewerSettings {
1355
- readonly ar?: boolean;
1356
- readonly fullscreen?: boolean;
1357
- readonly screenshot?: boolean;
1358
- readonly cameraControls?: boolean;
1359
- /**
1360
- * The resolved per-app annotation mode. Present only when the model has
1361
- * annotations, so a control can never render with nothing to govern;
1362
- * absent means there is nothing to show or toggle.
1363
- */
1364
- readonly annotations?: AnnotationVisibility;
1365
- readonly language?: string;
1366
- /** Where the attribute controls sit: in-frame bottom-left (default) or a docked right panel. */
1367
- readonly configuratorLayout?: 'inframe-bottom-left' | 'sidebar-right' | 'hidden';
1368
- }
1369
- /**
1370
- * What the configurator does with a matched configuration. Mirrors the backend
1371
- * `availability` enum (pricing rule provenance lives on `PricingRule.source`).
1372
- */
1373
- declare const AVAILABILITY: {
1374
- /** Price and submit button both shown (default). */
1375
- readonly AVAILABLE: "available";
1376
- /** Price shown, submit hidden (sold out / coming soon). */
1377
- readonly VIEW_ONLY: "view_only";
1378
- /** Behaves exactly like an unpriced combination: no price, no button. */
1379
- readonly HIDDEN: "hidden";
1380
- };
1381
- type Availability = (typeof AVAILABILITY)[keyof typeof AVAILABILITY];
1382
- /**
1383
- * One pricing rule: a subset of attribute values mapped to a reference + price.
1384
- * Matches the backend pricing read contract (`GET /api/public/models/.../pricing`).
1385
- */
1386
- interface PricingRule {
1387
- /**
1388
- * The attribute name→value pairs this rule names. `{}` is a catch-all;
1389
- * omitted names are wildcards. A rule matches when every pair equals the
1390
- * current selection; the rule naming the most attributes wins.
1391
- */
1392
- readonly attributes: Record<string, string>;
1393
- /** The customer's own identifier (SKU, EAN, artikelnummer, name code). */
1394
- readonly reference: string;
1395
- /** Price in minor units (cents), exactly as the source displays it. */
1396
- readonly priceCents: number;
1397
- readonly availability: Availability;
1398
- /** Provenance (manual | imported); audit-only, unused by the resolver. */
1399
- readonly source: 'manual' | 'imported';
1400
- }
1401
- /**
1402
- * A model's pricing document. The viewer's fetcher merges it into the model
1403
- * metadata and the server forwards it to the client via {@link PricingMessage}.
1404
- */
1405
- interface PricingDocument {
1406
- /** Optimistic-concurrency version from the editor; unused by the viewer. */
1407
- readonly version: number;
1408
- /** Customer-authored CTA label; null when the adapter's defaultLabel fills in. */
1409
- readonly buttonLabel: string | null;
1410
- /** ISO 4217 currency code for price formatting. */
1411
- readonly currency: string;
1412
- /** VAT label for bare embeds, e.g. "incl. btw". */
1413
- readonly vatDisplay: string;
1414
- readonly rules: PricingRule[];
1415
- }
1416
-
1417
1203
  export { Annotations, Attribute, AttributeValue, Camera, Configuration, ERROR_CODES, Focus, Photo, Snapshot, VD_EVENTS, VirtualdisplayClient, VirtualdisplayError, enableDebugMode, isDebugMode, logger };
1418
1204
  export type { AppSubmitSettings, AppType, Availability, ClientOptions, ErrorCode, FocusState, MutationFailedEvent, PhotoData, PricingDocument, PricingRule, ResolvedAppInfo, ResolvedConfiguration, SubmitOptions, SubmittedConfiguration, VdEvent, ViewerSettings, VirtualdisplaySelectEvent, VirtualdisplaySelectedEvent, VirtualdisplayUiEventMap, VirtualdisplayUiEventType };