@virtualdisplay.io/client 4.0.0-alpha.25 → 4.0.0-alpha.26

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.0-alpha.26
4
+
5
+ ### Minor Changes
6
+
7
+ Add `app` option to `ClientOptions` as an alternative to the `license` and `model` pair, mounting one specific app by its link key. The pair keeps working unchanged; when both are supplied, `app` wins and a warning is logged that the pair was ignored.
8
+
9
+ Add `AppType` with the known app types. `ResolvedAppInfo.appType` is typed as a plain string so an app type newer than your bundle still reaches `onAppResolved` for explicit handling.
10
+
11
+ Add a measurement slider for size attributes. A model that offers a size (width, depth, diameter) now renders it as a slider with the formatted value and unit beside it, and the model follows the drag live while only the released value counts as the chosen size.
12
+
13
+ Add `slug` to the `onAppResolved` callback payload: the resolved app's public slug, so a host page can address the app it embeds without knowing the slug in advance.
14
+
15
+ Add `onAppResolved(callback)`. The viewer reports which app it resolved (`appType`, `name`, and public submit settings as `ResolvedAppInfo`), and the callback fires immediately when the report already arrived.
16
+
3
17
  ## 4.0.0-alpha.25
4
18
 
5
19
  ### Minor Changes
package/README.md CHANGED
@@ -55,6 +55,26 @@ The server defines which attributes and values are available for each model.
55
55
  The client receives this configuration automatically via postMessage. No
56
56
  manual mapping required.
57
57
 
58
+ ### Mounting a specific app
59
+
60
+ A model can have several apps (a plain viewer, a configurator), each with its
61
+ own link and its own configuration. Pass the app's link key as `app` to mount
62
+ that exact app:
63
+
64
+ ```typescript
65
+ import { VirtualdisplayClient } from '@virtualdisplay.io/client';
66
+
67
+ const client = new VirtualdisplayClient({
68
+ parent: '#product-container',
69
+ app: 'sofa-deluxe-k3f9dm',
70
+ });
71
+ ```
72
+
73
+ `license` + `model` keeps working unchanged and shows the model's default
74
+ viewer. If both forms are supplied, `app` wins and a warning is logged that the
75
+ pair was ignored, so adding `app` to an existing integration is additive. One
76
+ of the two forms is required; supplying neither is a compile error.
77
+
58
78
  ## How it works
59
79
 
60
80
  The 3D server hosts the viewer in an iframe, keeping WebGL complexity isolated
@@ -229,7 +249,7 @@ exposes an API to toggle their visibility.
229
249
 
230
250
  ```typescript
231
251
  client.annotations.setEnabled(false); // Hide all markers
232
- client.annotations.setEnabled(true); // Show them again
252
+ client.annotations.setEnabled(true); // Show them again
233
253
  ```
234
254
 
235
255
  Visibility can also be controlled via `viewerSettings.annotations` (see below).
@@ -243,15 +263,15 @@ settings configured in the Virtualdisplay portal.
243
263
 
244
264
  ### Available settings
245
265
 
246
- | Field | Type | Effect when `false` |
247
- | ------------------- | --------- | ---------------------------------------------------------------------------------------------- |
248
- | `ar` | `boolean` | Hides the AR button |
249
- | `fullscreen` | `boolean` | Hides the fullscreen button |
250
- | `screenshot` | `boolean` | Hides the screenshot button |
251
- | `cameraControls` | `boolean` | Hides the camera control buttons |
252
- | `annotations` | `boolean` | Hides all annotation markers |
253
- | `language` | `string` | Sets the viewer UI language (BCP 47, e.g. `'nl'`, `'en'`, `'de'`) |
254
- | `configuratorLayout`| `string` | Where the attribute controls sit, or whether they show: `'inframe-bottom-left'` (default), `'sidebar-right'` (a docked, scrollable right-hand panel), or `'hidden'` (no built-in selector, `client.select()` and `client.onChange()` keep working). |
266
+ | Field | Type | Effect when `false` |
267
+ | -------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
268
+ | `ar` | `boolean` | Hides the AR button |
269
+ | `fullscreen` | `boolean` | Hides the fullscreen button |
270
+ | `screenshot` | `boolean` | Hides the screenshot button |
271
+ | `cameraControls` | `boolean` | Hides the camera control buttons |
272
+ | `annotations` | `boolean` | Hides all annotation markers |
273
+ | `language` | `string` | Sets the viewer UI language (BCP 47, e.g. `'nl'`, `'en'`, `'de'`) |
274
+ | `configuratorLayout` | `string` | Where the attribute controls sit, or whether they show: `'inframe-bottom-left'` (default), `'sidebar-right'` (a docked, scrollable right-hand panel), or `'hidden'` (no built-in selector, `client.select()` and `client.onChange()` keep working). |
255
275
 
256
276
  ### Overriding settings
257
277
 
@@ -290,7 +310,10 @@ automatically.
290
310
  ```typescript
291
311
  client.onReady(() => {
292
312
  const values = client.getValues('Color');
293
- console.log('Available colours:', values.map(v => v.value));
313
+ console.log(
314
+ 'Available colours:',
315
+ values.map((v) => v.value)
316
+ );
294
317
  });
295
318
  ```
296
319
 
@@ -306,15 +329,26 @@ client.destroy();
306
329
  import type { ViewerSettings } from '@virtualdisplay.io/client';
307
330
 
308
331
  interface ClientOptions {
309
- parent: string | HTMLElement; // Where to embed the 3D viewer
310
- license: string; // License key from your Virtualdisplay account
311
- model: string; // Which 3D model to load
312
- debug?: boolean; // Shows console logs for troubleshooting
313
- language?: string; // Viewer language: 'nl', 'en', or 'de'
332
+ parent: string | HTMLElement; // Where to embed the 3D viewer
333
+ app?: string; // An app's link key: the alternative to license + model (see Mounting a specific app)
334
+ license: string; // License key from your Virtualdisplay account; with model, the classic address form
335
+ model: string; // Which 3D model to load
336
+ debug?: boolean; // Shows console logs for troubleshooting
337
+ language?: string; // Viewer language: 'nl', 'en', or 'de'
314
338
  viewerSettings?: Partial<ViewerSettings>; // Override fields from model metadata (see Standard interface)
315
339
  }
316
340
  ```
317
341
 
342
+ One of the two address forms is required: `app`, or `license` + `model`. The
343
+ real type is a union that enforces this at compile time; the block above is the
344
+ flattened reference.
345
+
346
+ ```typescript
347
+ type ClientAddress =
348
+ | { app: string } // an app's link key
349
+ | { license: string; model: string }; // the classic address form
350
+ ```
351
+
318
352
  ## Error codes
319
353
 
320
354
  The client throws `VirtualdisplayError` for known error conditions:
package/dist/index.d.ts CHANGED
@@ -22,7 +22,36 @@ declare const MESSAGE_TYPES: {
22
22
  readonly QR_SHOWN: "qr_shown";
23
23
  readonly SYNC: "sync";
24
24
  readonly SELECTABLE: "selectable";
25
+ readonly APP_RESOLVED: "app_resolved";
25
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 {
33
+ readonly fields?: string[];
34
+ readonly ctaLabel?: string;
35
+ readonly privacyUrl?: string;
36
+ }
37
+ /**
38
+ * Viewer -> client: which app this viewer resolved, reported up so the parent
39
+ * (the embed host) can load the matching shell without its own resolve (the
40
+ * apps spec's one-read inversion). Sent once, after the resolve, before or
41
+ * with MAPPING.
42
+ */
43
+ interface AppResolvedMessage {
44
+ readonly type: typeof MESSAGE_TYPES.APP_RESOLVED;
45
+ /** Known values: {@link AppType}. An unknown value means a newer server; the host reports it, never downgrades. */
46
+ readonly appType: string;
47
+ readonly name: string;
48
+ /** The app's public slug, the address a submit POSTs to. Optional on the
49
+ * wire: a pre-task-60 server does not send it, and the pair-form host has
50
+ * no other way to learn it, so the shell treats absence as "submit
51
+ * unavailable" rather than the guard dropping the whole report. */
52
+ readonly slug?: string;
53
+ readonly submit?: AppSubmitSettings$1;
54
+ }
26
55
  interface MutationDto {
27
56
  readonly name: string;
28
57
  readonly value: string;
@@ -61,11 +90,19 @@ interface MutationMessage {
61
90
  * authoritative folded family state: the sync module diffs it against the
62
91
  * channel's last-seen state and applies the difference, so a catch-up
63
92
  * (empty `mutations`) or a broadcast the hub dropped still converges the
64
- * subscriber. None of these fields are read by `MutationRouter`.
93
+ * subscriber. None of these fields are read by `MutationPipeline`.
65
94
  */
66
95
  readonly origin?: string;
67
96
  readonly seq?: number;
68
97
  readonly state?: SyncStateSnapshot;
98
+ /**
99
+ * A drag's intermediate step (parametric dimensions): the viewer applies it
100
+ * so the model follows the hand, but never tracks it, the settled value is
101
+ * the one recorded choice and it arrives as an ordinary mutation without
102
+ * this flag. Only the client knows when a drag settles, which is why this
103
+ * travels on the message instead of being guessed viewer-side.
104
+ */
105
+ readonly transient?: boolean;
69
106
  }
70
107
  /**
71
108
  * Sync channel status (server → client, ar-session-handoff, sync-channel.md):
@@ -136,10 +173,24 @@ interface SnapshotResultMessage {
136
173
  readonly data?: string;
137
174
  readonly error?: string;
138
175
  }
176
+ /**
177
+ * Marks an attribute as a measurement (a parametric dimension): the values
178
+ * are millimetre integer strings, and this carries what a client needs to
179
+ * render a measurement control and format the unit. Absent for ordinary
180
+ * attributes; a client that ignores it still renders the value list.
181
+ */
182
+ interface DimensionDescriptor {
183
+ readonly displayUnit: 'mm' | 'cm' | 'm' | 'in';
184
+ readonly minMm: number;
185
+ readonly maxMm: number;
186
+ readonly stepMm: number;
187
+ }
139
188
  interface AttributeDefinition {
140
189
  readonly name: string;
141
190
  readonly values: string[];
142
191
  readonly defaultValue?: string;
192
+ /** Present when this attribute is a measurement; see DimensionDescriptor. */
193
+ readonly dimension?: DimensionDescriptor;
143
194
  }
144
195
  interface MappingMessage {
145
196
  readonly type: typeof MESSAGE_TYPES.MAPPING;
@@ -149,6 +200,13 @@ interface MappingMessage {
149
200
  interface SelectableAttribute {
150
201
  readonly name: string;
151
202
  readonly selectable: string[];
203
+ /**
204
+ * The control should not render at all: a dimension whose component is
205
+ * currently hidden by its show-when conditions (parametric dimensions).
206
+ * Different from an empty `selectable`, which greys values out but keeps
207
+ * the control visible. Absent means visible.
208
+ */
209
+ readonly hidden?: boolean;
152
210
  }
153
211
  /**
154
212
  * Selectability snapshot (server → client): sent after the mapping and after
@@ -238,8 +296,8 @@ declare const FOCUS_STATES: {
238
296
  readonly FOCUSED: "focused";
239
297
  readonly UNFOCUSED: "unfocused";
240
298
  };
241
- type FocusAction = typeof FOCUS_ACTIONS[keyof typeof FOCUS_ACTIONS];
242
- type FocusStateValue = typeof FOCUS_STATES[keyof typeof FOCUS_STATES];
299
+ type FocusAction = (typeof FOCUS_ACTIONS)[keyof typeof FOCUS_ACTIONS];
300
+ type FocusStateValue = (typeof FOCUS_STATES)[keyof typeof FOCUS_STATES];
243
301
  /**
244
302
  * Focus (client → server): isolate and frame a component, or return to overview.
245
303
  * Programmatic — the API, the (future) component menu, and the portal editors all
@@ -274,7 +332,7 @@ declare const AVAILABILITY$1: {
274
332
  /** Behaves exactly like an unpriced combination: no price, no button. */
275
333
  readonly HIDDEN: "hidden";
276
334
  };
277
- type Availability$1 = typeof AVAILABILITY$1[keyof typeof AVAILABILITY$1];
335
+ type Availability$1 = (typeof AVAILABILITY$1)[keyof typeof AVAILABILITY$1];
278
336
  /**
279
337
  * One pricing rule: a subset of attribute values mapped to a reference + price.
280
338
  * Matches the backend pricing read contract (`GET /api/public/models/.../pricing`).
@@ -317,7 +375,7 @@ interface PricingMessage {
317
375
  readonly type: typeof MESSAGE_TYPES.PRICING;
318
376
  readonly pricing: PricingDocument$1;
319
377
  }
320
- type Message = HandshakeMessage | SessionMessage | MutationMessage | MutationResultMessage | CameraCommandMessage | CameraPositionMessage | SnapshotRequestMessage | SnapshotResultMessage | MappingMessage | LoadingIndicatorMessage | ArActivateMessage | FullscreenMessage | ConfigMessage | AnnotationToggleMessage | FocusMessage | FocusStateMessage | PricingMessage | QrShownMessage | SyncMessage | SelectableMessage;
378
+ type Message = HandshakeMessage | SessionMessage | MutationMessage | MutationResultMessage | CameraCommandMessage | CameraPositionMessage | SnapshotRequestMessage | SnapshotResultMessage | MappingMessage | LoadingIndicatorMessage | ArActivateMessage | FullscreenMessage | ConfigMessage | AnnotationToggleMessage | FocusMessage | FocusStateMessage | PricingMessage | QrShownMessage | SyncMessage | SelectableMessage | AppResolvedMessage;
321
379
  type MessageCallback = (message: Message) => void | Promise<void>;
322
380
 
323
381
  /**
@@ -863,12 +921,10 @@ declare class Attribute {
863
921
  }
864
922
 
865
923
  /**
866
- * Configuration options for VirtualdisplayClient
924
+ * Options shared by both address forms.
867
925
  */
868
- interface ClientOptions {
926
+ interface ClientOptionsBase {
869
927
  readonly parent: string | HTMLElement;
870
- readonly model: string;
871
- readonly license: string;
872
928
  readonly debug?: boolean | undefined;
873
929
  readonly language?: string | undefined;
874
930
  /** W3C traceparent string to propagate to the viewer iframe for end-to-end tracing. */
@@ -886,6 +942,32 @@ interface ClientOptions {
886
942
  */
887
943
  readonly viewerSettings?: Partial<ViewerSettings$1>;
888
944
  }
945
+ /**
946
+ * How the client addresses what it shows, one of two forms:
947
+ *
948
+ * - `{ app }`: an app's public link key (the slug in its URL). The viewer
949
+ * resolves that app and renders it with its own configuration.
950
+ * - `{ license, model }`: the classic pair, which resolves to the model's
951
+ * default app. Every existing integration keeps working unchanged.
952
+ *
953
+ * Supplying both is allowed so a migration is additive (add `app`, done):
954
+ * `app` wins and a warning is logged that the pair was ignored. Supplying
955
+ * neither is a compile error, which is the guarantee this union buys over a
956
+ * bag of three optional fields.
957
+ */
958
+ type ClientAddress = {
959
+ readonly app: string;
960
+ readonly license?: string | undefined;
961
+ readonly model?: string | undefined;
962
+ } | {
963
+ readonly license: string;
964
+ readonly model: string;
965
+ readonly app?: undefined;
966
+ };
967
+ /**
968
+ * Configuration options for VirtualdisplayClient
969
+ */
970
+ type ClientOptions = ClientOptionsBase & ClientAddress;
889
971
 
890
972
  /**
891
973
  * The Virtualdisplay client's DOM event names, in one registry.
@@ -913,6 +995,20 @@ interface MutationFailedEvent {
913
995
  readonly attribute: string;
914
996
  readonly value: string;
915
997
  }
998
+ /**
999
+ * Which app this viewer resolved (the apps spec's one-read inversion): the
1000
+ * `onAppResolved` callback payload. The protocol message minus its wire
1001
+ * discriminator; only the parent-safe submit settings ride along, never the
1002
+ * delivery recipient (removed in SQL on the public read).
1003
+ */
1004
+ interface ResolvedAppInfo {
1005
+ /** Known values: `AppType`. An unknown value means a newer server; report it, never downgrade to a viewer. */
1006
+ readonly appType: string;
1007
+ readonly name: string;
1008
+ /** The app's public slug (the address a submit POSTs to). Absent from a pre-slug server. */
1009
+ readonly slug?: string;
1010
+ readonly submit?: AppSubmitSettings$1;
1011
+ }
916
1012
  interface VirtualdisplaySelectEvent {
917
1013
  readonly attribute: string;
918
1014
  readonly value: string;
@@ -1020,9 +1116,16 @@ declare class BaseClient {
1020
1116
  private readonly mutationFailedCallbacks;
1021
1117
  /** The viewer's tracking session id, once the server announces it (SESSION). */
1022
1118
  private viewerSessionId;
1119
+ /** Which app the viewer resolved, once reported (APP_RESOLVED). */
1120
+ private resolvedApp;
1121
+ private readonly appResolvedCallbacks;
1023
1122
  constructor(options: ClientOptions, extensions?: BaseClientExtensions);
1123
+ /** The configurator UI, every callback bridging it to the facade that owns the action. */
1124
+ private createStandardInterface;
1024
1125
  /** Wire the inbound viewer protocol to the facades. */
1025
1126
  private wireIncomingMessages;
1127
+ /** Store the resolved app and notify every registered `onAppResolved` callback. */
1128
+ private handleAppResolved;
1026
1129
  /** Store the tracking session id and hand it to the AR flow, which waits on it for the QR. */
1027
1130
  private handleSession;
1028
1131
  /** Forward a failed mutation to every registered `onMutationFailed` callback. */
@@ -1062,6 +1165,12 @@ declare class BaseClient {
1062
1165
  * fires.
1063
1166
  */
1064
1167
  onSelectableChange(callback: (snapshot: SelectableSnapshot) => void): void;
1168
+ /**
1169
+ * Subscribe to which app this viewer resolved (type, name, public submit
1170
+ * settings). Reported once by the viewer after its resolve; fires immediately
1171
+ * when the report already arrived, so registration order does not matter.
1172
+ */
1173
+ onAppResolved(callback: (app: ResolvedAppInfo) => void): void;
1065
1174
  /** The selectable values for one attribute right now, or null when unrestricted. */
1066
1175
  getSelectable(name: string): readonly string[] | null;
1067
1176
  /**
@@ -1086,12 +1195,17 @@ declare class BaseClient {
1086
1195
  }
1087
1196
 
1088
1197
  type Attrs = Record<string, unknown> | unknown;
1089
- declare let logger: {
1198
+ /**
1199
+ * The logger the client exposes: the same four levels as the base logger, but
1200
+ * accepting any value as attributes so callers can pass an Error or a bare value.
1201
+ */
1202
+ interface ClientLogger {
1090
1203
  debug: (msg: string, attrs?: Attrs) => void;
1091
1204
  info: (msg: string, attrs?: Attrs) => void;
1092
1205
  warn: (msg: string, attrs?: Attrs) => void;
1093
1206
  error: (msg: string, attrs?: Attrs) => void;
1094
- };
1207
+ }
1208
+ declare let logger: ClientLogger;
1095
1209
  declare function enableDebugMode(): void;
1096
1210
  declare function isDebugMode(): boolean;
1097
1211
 
@@ -1113,6 +1227,20 @@ declare class VirtualdisplayClient extends BaseClient {
1113
1227
  onSyncChange(callback: (connected: boolean) => void): void;
1114
1228
  }
1115
1229
 
1230
+ /** The app types this protocol version knows. The wire field stays a plain
1231
+ * string on purpose: a newer viewer may report a type this bundle predates,
1232
+ * and the host must receive it to fail visibly (never a quiet downgrade). */
1233
+ type AppType = 'viewer' | 'configurator';
1234
+ /** The public submit settings of a configurator-type app, as the form renders
1235
+ * them. Never the recipient: that section is removed in SQL on the public
1236
+ * read and cannot ride any message. Sparse like every config section: an
1237
+ * absent field means the renderer's default (the backend stores the section
1238
+ * as written and omits empty keys), so nothing here is guaranteed present. */
1239
+ interface AppSubmitSettings {
1240
+ readonly fields?: string[];
1241
+ readonly ctaLabel?: string;
1242
+ readonly privacyUrl?: string;
1243
+ }
1116
1244
  /**
1117
1245
  * Viewer settings from model metadata (server → client via MAPPING).
1118
1246
  * Customer-configured in the portal.
@@ -1139,7 +1267,7 @@ declare const AVAILABILITY: {
1139
1267
  /** Behaves exactly like an unpriced combination: no price, no button. */
1140
1268
  readonly HIDDEN: "hidden";
1141
1269
  };
1142
- type Availability = typeof AVAILABILITY[keyof typeof AVAILABILITY];
1270
+ type Availability = (typeof AVAILABILITY)[keyof typeof AVAILABILITY];
1143
1271
  /**
1144
1272
  * One pricing rule: a subset of attribute values mapped to a reference + price.
1145
1273
  * Matches the backend pricing read contract (`GET /api/public/models/.../pricing`).
@@ -1176,4 +1304,4 @@ interface PricingDocument {
1176
1304
  }
1177
1305
 
1178
1306
  export { Annotations, Attribute, AttributeValue, Camera, Configuration, ERROR_CODES, Focus, Photo, Snapshot, VD_EVENTS, VirtualdisplayClient, VirtualdisplayError, enableDebugMode, isDebugMode, logger };
1179
- export type { Availability, ClientOptions, ErrorCode, FocusState, MutationFailedEvent, PhotoData, PricingDocument, PricingRule, ResolvedConfiguration, SubmitOptions, SubmittedConfiguration, VdEvent, ViewerSettings, VirtualdisplaySelectEvent, VirtualdisplaySelectedEvent, VirtualdisplayUiEventMap, VirtualdisplayUiEventType };
1307
+ export type { AppSubmitSettings, AppType, Availability, ClientOptions, ErrorCode, FocusState, MutationFailedEvent, PhotoData, PricingDocument, PricingRule, ResolvedAppInfo, ResolvedConfiguration, SubmitOptions, SubmittedConfiguration, VdEvent, ViewerSettings, VirtualdisplaySelectEvent, VirtualdisplaySelectedEvent, VirtualdisplayUiEventMap, VirtualdisplayUiEventType };