@virtualdisplay.io/client 3.6.0 → 4.0.0-alpha.1

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/README.md CHANGED
@@ -3,10 +3,9 @@
3
3
  TypeScript library for embedding interactive 3D product models in web
4
4
  applications.
5
5
 
6
- **Key features:**
7
-
8
6
  - Simple attribute-based API for product configurators
9
- - Fire-and-forget messaging with automatic state synchronization
7
+ - Server-driven configuration, no client-side mapping needed
8
+ - Fire-and-forget messaging with automatic state synchronisation
10
9
  - Zero external dependencies (all bundled)
11
10
  - Works with any frontend framework or vanilla JavaScript
12
11
 
@@ -20,14 +19,6 @@ pnpm add @virtualdisplay.io/client
20
19
  yarn add @virtualdisplay.io/client
21
20
  ```
22
21
 
23
- ## What's new in v3.1
24
-
25
- - Simplified message handling with unified event system
26
- - JSON Schema validation using AJV
27
- - Cleaner, more maintainable codebase
28
- - Improved TypeScript types
29
- - Better error messages with specific error codes
30
-
31
22
  ## Quick start
32
23
 
33
24
  ### Simple product (no options)
@@ -35,7 +26,7 @@ yarn add @virtualdisplay.io/client
35
26
  ```typescript
36
27
  import { VirtualdisplayClient } from '@virtualdisplay.io/client';
37
28
 
38
- // That's it! Your 3D model is now displayed
29
+ // Your 3D model is now displayed
39
30
  const client = new VirtualdisplayClient({
40
31
  parent: '#product-container',
41
32
  license: 'your-license-key',
@@ -48,436 +39,383 @@ const client = new VirtualdisplayClient({
48
39
  ```typescript
49
40
  import { VirtualdisplayClient } from '@virtualdisplay.io/client';
50
41
 
51
- // Create client instance
52
42
  const client = new VirtualdisplayClient({
53
43
  parent: '#product-container',
54
44
  license: 'your-license-key',
55
45
  model: 'sneaker',
56
46
  });
57
47
 
58
- // Map product options to 3D parts
59
- client.setMapping({
60
- attributes: [
61
- {
62
- name: 'Color',
63
- values: [
64
- { value: 'Red', nodeIds: ['laces_red', 'sole_red'], isSelected: true },
65
- { value: 'Blue', nodeIds: ['laces_blue', 'sole_blue'] },
66
- ],
67
- },
68
- ],
69
- });
70
-
71
- // Control via product options
72
- client.getAttribute('Color')?.select('Red');
73
-
74
- // Or store the attribute for multiple operations
75
- const sizeAttribute = client.getAttribute('Size');
76
- if (sizeAttribute) {
77
- sizeAttribute.select('Large');
78
- }
48
+ // Select a product option. The 3D model updates automatically.
49
+ // Calls are buffered until the server provides the attribute mapping
50
+ client.select('Color', 'red');
79
51
  ```
80
52
 
53
+ The server defines which attributes and values are available for each model.
54
+ The client receives this configuration automatically via postMessage. No
55
+ manual mapping required.
56
+
81
57
  ## How it works
82
58
 
83
59
  The 3D server hosts the viewer in an iframe, keeping WebGL complexity isolated
84
60
  from your application. The client library handles all communication via
85
- postMessage and manages state locally using an attribute mapping system.
86
-
87
- **Key principles:**
88
-
89
- - **Iframe architecture**: 3D server loads the viewer independently
90
- - **Fire-and-forget**: Send messages without waiting for confirmation
91
- - **Attribute mapping**: Connect your product options to 3D model parts
92
- - **State synchronization**: Client and 3D server stay in sync automatically
93
- - **Domain-driven design**: Clean separation of concerns with simplified
94
- architecture
95
- - **Event-driven**: Loosely coupled components communicate via events
96
-
97
- ## Product options
98
-
99
- ### When do you need mapping
100
-
101
- Before diving in, it's important to understand when you need attribute mapping:
102
-
103
- **No mapping needed:**
61
+ postMessage.
62
+
63
+ ```mermaid
64
+ graph LR
65
+ A[Your application] -->|API calls| B[VirtualdisplayClient]
66
+ B -->|postMessage| C[3D server iframe]
67
+ C -->|Events| B
68
+ B -->|State updates| A
69
+ ```
104
70
 
105
- - Static 3D models that always look the same
106
- - Product showcases without options
107
- - Architectural visualizations
108
- - Art pieces or sculptures
109
- - Single-configuration products
71
+ On startup, the server tells the client what is available (attributes, values,
72
+ defaults). After that, the client sends commands and the server responds with
73
+ results.
74
+
75
+ ```mermaid
76
+ sequenceDiagram
77
+ participant UI as Your UI
78
+ participant Client as VirtualdisplayClient
79
+ participant Server as 3D server
80
+
81
+ UI ->> Client: new VirtualdisplayClient(options)
82
+ Client ->> Client: Create iframe
83
+ Note right of Client: Outgoing messages queued
84
+ Server ->> Client: READY
85
+ Note right of Client: Queue flushed
86
+ opt UI or camera options configured
87
+ Client ->> Server: CONFIG
88
+ end
89
+ Server ->> Client: MAPPING (attributes + values + defaults)
90
+ Client ->> Client: Store attribute definitions
91
+ Server ->> Client: MUTATION result (defaults, isInitial: true)
92
+ Client ->> UI: onReady + onChange callbacks fire
93
+ ```
110
94
 
111
- **Mapping required:**
95
+ 1. Client creates iframe; outgoing messages are queued until the server is ready.
96
+ 2. Server sends `READY` when its message listener is active; client flushes queue.
97
+ 3. `CONFIG` is only sent if UI or camera options were provided in the constructor.
98
+ 4. Server sends `MAPPING` with attribute definitions, loads the 3D model, applies defaults.
99
+ 5. `onReady` fires after the first initial mutation result arrives.
100
+
101
+ ## Product configuration
102
+
103
+ Configure product options by selecting attribute values. The server defines
104
+ which attributes exist (e.g. "Color", "Size") and which values each has
105
+ (e.g. "red", "blue"). You select values; the 3D model updates automatically.
106
+
107
+ ```mermaid
108
+ sequenceDiagram
109
+ participant UI as Your UI
110
+ participant Client as VirtualdisplayClient
111
+ participant Server as 3D server
112
+
113
+ UI ->> Client: client.select('Color', 'blue')
114
+ Client ->> Server: MUTATION
115
+ Note right of Client: Fire-and-forget
116
+ Server ->> Server: Apply 3D changes
117
+ Server ->> Client: MUTATION result
118
+ alt All succeeded
119
+ Client ->> UI: onChange callbacks fire
120
+ else Some failed
121
+ Client ->> UI: onMutationFailed callbacks fire
122
+ end
123
+ ```
112
124
 
113
- - Products where users can change colors (different textures/materials per option)
114
- - Products with size options (different 3D meshes per size)
115
- - Modular products (parts that can be added/removed)
116
- - Any product where different options show different 3D elements
125
+ - `select()` returns immediately; the result arrives asynchronously.
126
+ - Each attribute in the result has its own `success` flag and optional `error`.
127
+ - Attributes within the same component fail as a group.
117
128
 
118
- ### Attribute mapping
129
+ ### Selecting values
119
130
 
120
- The mapping system bridges your business logic with the 3D visualization. It
121
- serves multiple purposes:
131
+ ```typescript
132
+ // Select a product option. The 3D model updates automatically.
133
+ client.select('Color', 'blue');
134
+ client.select('Size', 'large');
135
+ ```
122
136
 
123
- **Why mapping is essential:**
137
+ Can be called before the server is ready. Mutations are buffered and sent when
138
+ the mapping arrives.
124
139
 
125
- - The 3D server is generic - it doesn't know your specific product options
126
- - Your business rules (stock, pricing, combinations) change independently from
127
- the 3D model
128
- - You control exactly which parts of the model are shown for each option
129
- - You can group multiple 3D nodes (meshes, materials) into logical product
130
- choices
140
+ ### Tracking changes
131
141
 
132
- **How it works:** The mapping connects your product options to specific parts of
133
- the 3D model:
142
+ Keep your UI in sync with the 3D server state:
134
143
 
135
144
  ```typescript
136
- client.setMapping({
137
- attributes: [
138
- {
139
- name: 'Color',
140
- values: [
141
- {
142
- value: 'Red',
143
- nodeIds: ['mat_red_sole', 'mat_red_laces', 'mat_red_logo'],
144
- isSelected: true,
145
- },
146
- {
147
- value: 'Blue',
148
- nodeIds: ['mat_blue_sole', 'mat_blue_laces', 'mat_blue_logo'],
149
- },
150
- ],
151
- },
152
- {
153
- name: 'Material',
154
- values: [
155
- { value: 'Leather', nodeIds: ['material_leather_upper'] },
156
- {
157
- value: 'Canvas',
158
- nodeIds: ['material_canvas_upper'],
159
- isSelected: true,
160
- },
161
- ],
162
- },
163
- ],
145
+ client.onChange('Color', (selectedValue) => {
146
+ // selectedValue.value is the selected value (e.g. 'blue')
147
+ // selectedValue.isSelected is true
148
+ updateColorButton(selectedValue.value);
164
149
  });
165
150
  ```
166
151
 
167
- **Structure explained:**
168
-
169
- - `attribute` - A product feature like Color, Size, or Material
170
- - `value` - A specific option like Red, Blue, Small, or Large
171
- - `nodeIds` - The 3D model parts that represent this option
172
- - `isSelected` - Whether this option is selected by default
173
-
174
- **Example benefits:**
152
+ ### Querying available values
175
153
 
176
154
  ```typescript
177
- // Group related 3D parts into one logical choice
178
- {
179
- value: 'Red',
180
- nodeIds: [
181
- 'mesh_sole_red', // Red sole mesh
182
- 'material_laces_red', // Red laces material
183
- 'texture_logo_red', // Red logo texture
184
- 'mesh_stitching_red' // Red stitching details
185
- ]
186
- }
155
+ // All values for an attribute
156
+ const colors = client.getValues('Color');
157
+ colors.forEach((av) => {
158
+ console.log(av.value, av.isSelected);
159
+ });
187
160
 
188
- // The server automatically handles showing Red parts
189
- // and hiding Blue/Green parts when Red is selected
161
+ // A specific value
162
+ const blue = client.getValue('Color', 'blue');
163
+ if (blue?.isSelected) {
164
+ highlightButton('blue');
165
+ }
190
166
  ```
191
167
 
192
- **Key advantages:**
193
-
194
- - Full control over what's visible without modifying the 3D model
195
- - Business logic stays in your application, not in the 3D server
196
- - Easy to update when products or availability changes
197
- - Group complex 3D structures into simple user choices
168
+ ### Direct attribute access
198
169
 
199
- **Need help with mapping?**
170
+ For advanced use cases, get the `Attribute` instance directly:
200
171
 
201
- - We help you connect your product catalog to your 3D models
202
- - Visual mapping tool coming soon to simplify this process
203
- - Contact <support@virtualdisplay.io> for mapping assistance
204
-
205
- ### Complete flow
206
-
207
- Here's how the client, mapping system, and 3D server work together:
172
+ ```typescript
173
+ const colorAttr = client.getAttribute('Color');
174
+ if (colorAttr) {
175
+ colorAttr.select('blue');
176
+ colorAttr.getValues(); // AttributeValue[]
177
+ colorAttr.getValue('blue'); // AttributeValue | undefined
178
+ colorAttr.onChange((value) => { ... });
179
+ }
180
+ ```
208
181
 
209
- **Phase by phase breakdown:**
182
+ ### Handling failed mutations
210
183
 
211
- #### Phase 0: Initialization (always happens)
184
+ ```typescript
185
+ client.onMutationFailed(({ attribute, value }) => {
186
+ console.error(`Mutation failed: ${attribute} = ${value}`);
187
+ });
188
+ ```
212
189
 
213
- - Client creates iframe and loads 3D server
214
- - Server initializes the 3D model with all nodes visible
215
- - Server sends complete node state to client
216
- - Client now knows about all available nodes in the model
190
+ ## Snapshots
191
+
192
+ Capture the current 3D view as an image. Useful for product shots, social
193
+ sharing, or saving configurations.
194
+
195
+ ```mermaid
196
+ sequenceDiagram
197
+ participant UI as Your UI
198
+ participant Client as VirtualdisplayClient
199
+ participant Server as 3D server
200
+
201
+ UI ->> Client: client.snapshot.take('photo.png')
202
+ Client ->> Server: SNAPSHOT request
203
+ alt Success
204
+ Server ->> Client: SNAPSHOT data (base64)
205
+ Client ->> UI: onDeveloped callback fires
206
+ else Failure (e.g. no canvas)
207
+ Server ->> Client: SNAPSHOT error
208
+ Client ->> UI: onError callback fires
209
+ end
210
+ ```
217
211
 
218
- **Note:** For simple models without options, this is all you need!
212
+ - Filename is validated client-side (extension, no path traversal).
213
+ - Server always responds, either with image data or an error reason.
219
214
 
220
- #### Phase 1: Configuration (only for configurable products)
215
+ ### Taking a snapshot
221
216
 
222
- - You call `setMapping()` with your product structure
223
- - Client stores the attribute mapping locally
224
- - Client sends mutations for all `isSelected: true` values
225
- - Server applies these changes and hides non-selected options
226
- - Server sends back confirmed state
227
- - Local state updates based on server response
228
- - onChange callbacks fire for initial UI synchronization
217
+ ```typescript
218
+ const photo = client.snapshot.take('product-hero-shot.png');
219
+
220
+ // Register callback for when image is ready
221
+ photo.onDeveloped((photoData) => {
222
+ // photoData.filename - The filename you provided
223
+ // photoData.data - Base64 encoded image data
224
+ const img = document.createElement('img');
225
+ img.src = photoData.data;
226
+ document.body.appendChild(img);
227
+ });
229
228
 
230
- #### Phase 2: User interaction (only for configurable products)
229
+ // Handle errors (e.g., canvas not available)
230
+ photo.onError((error) => {
231
+ console.error('Snapshot failed:', error.message);
232
+ });
233
+ ```
231
234
 
232
- - User selects a different option in your UI
233
- - You call `getAttribute('Color').select('Blue')`
234
- - Client sends mutation request to server
235
- - Your code continues immediately (doesn't wait for server response)
236
- - Server updates the 3D model
237
- - Server sends back the confirmed state
238
- - Local state updates based on server response
239
- - onChange callbacks fire with the actual state
240
- - UI stays perfectly synchronized with 3D model
235
+ Filename must end with `.png`. The server always returns PNG data.
241
236
 
242
- **Important:** The pattern is identical for both initial mapping and user
243
- interactions. In both cases:
237
+ ## Camera
244
238
 
245
- 1. Client sends mutations to server
246
- 2. Server applies changes to 3D model
247
- 3. Server sends back confirmed state
248
- 4. Client updates local state based on server response
249
- 5. onChange callbacks fire with the actual state
239
+ Control the 3D viewer's camera position programmatically. Useful for guided
240
+ product tours, preset views, or syncing camera state with your UI.
250
241
 
251
- This ensures the server remains the single source of truth for all state
252
- changes.
242
+ ```mermaid
243
+ sequenceDiagram
244
+ participant UI as Your UI
245
+ participant Client as VirtualdisplayClient
246
+ participant Server as 3D server
253
247
 
254
- ## Advanced usage
248
+ UI ->> Client: client.camera.rotate(45).tilt(30).set()
249
+ Client ->> Server: CAMERA commands
250
+ Server ->> Client: CAMERA position
251
+ Client ->> UI: camera.onChange callbacks fire
252
+ Note right of Server: Position updates also fire on user<br/>interaction with the 3D viewport
253
+ ```
255
254
 
256
- ### Live state synchronization
255
+ - Commands accumulate via chaining; `.set()` sends them all at once.
256
+ - `.reset()` sends immediately without chaining.
257
+ - Position updates are continuous. They fire on both programmatic and user-driven camera movement.
257
258
 
258
- Keep your UI in sync with the 3D server state using onChange callbacks:
259
+ ### Controlling the camera
259
260
 
260
261
  ```typescript
261
- const colorAttribute = client.getAttribute('Color');
262
+ // Chain commands, then send with .set()
263
+ client.camera.rotate(45).set();
264
+ client.camera.rotate(90).tilt(45).zoom(150).set();
262
265
 
263
- colorAttribute?.getValues().forEach((value) => {
264
- value.onChange = () => {
265
- // Update UI when state changes
266
- updateButton(value.value, value.isSelected);
267
- };
268
- });
266
+ // Reset to base position (sends immediately)
267
+ client.camera.reset();
269
268
  ```
270
269
 
271
- ### Mapping validation
270
+ ### Tracking camera position
272
271
 
273
- Validate your mapping configuration during development:
272
+ The current camera position is updated in real-time as the user orbits, zooms,
273
+ or tilts the model.
274
274
 
275
275
  ```typescript
276
- import { mappingSchema } from '@virtualdisplay.io/client';
277
- import Ajv from 'ajv';
276
+ client.cameraPosition.onChange((position) => {
277
+ console.log(position.rotation, position.tilt, position.zoom);
278
+ });
278
279
 
279
- const validate = new Ajv().compile(mappingSchema);
280
- if (!validate(myMapping)) {
281
- console.error('Invalid mapping:', validate.errors);
282
- }
280
+ // Read current values directly
281
+ console.log(client.cameraPosition.rotation);
282
+ console.log(client.cameraPosition.tilt);
283
+ console.log(client.cameraPosition.zoom);
283
284
  ```
284
285
 
285
- ## Real-world integration examples
286
+ ## UI controls
286
287
 
287
- ### CMS integration
288
+ Toggle built-in viewer elements like AR, fullscreen, and the loading indicator.
289
+ All are visible by default.
288
290
 
289
- The Virtualdisplay client is CMS-agnostic. Store the mapping configuration in
290
- your CMS alongside your product data:
291
+ ### Via constructor
291
292
 
292
293
  ```typescript
293
- // Fetch product with 3D mapping from your CMS
294
- const product = await fetch('/api/products/sneaker-pro').then((r) => r.json());
295
-
296
- // Use the stored mapping directly
297
- client.setMapping(product.server3dMapping);
294
+ const client = new VirtualdisplayClient({
295
+ parent: '#viewer',
296
+ license: 'your-license',
297
+ model: 'product',
298
+ ui: {
299
+ arEnabled: false,
300
+ loadingIndicatorEnabled: false,
301
+ },
302
+ });
298
303
  ```
299
304
 
300
- **Best practices:**
301
-
302
- - Store the complete mapping configuration with each product
303
- - Update mappings when product options or availability changes
304
- - Let your CMS handle filtering of unavailable combinations
305
- - Version your mappings when 3D models are updated
306
-
307
- ## Example
308
-
309
- See the [color configurator example](./examples/color-configurator/) for a
310
- complete working implementation that demonstrates:
311
-
312
- - Setting up the client
313
- - Defining attribute mappings
314
- - Binding UI controls to attributes
315
- - Real-time state synchronization
316
-
317
- ## API reference
318
-
319
- ### ClientOptions
305
+ ### At runtime
320
306
 
321
307
  ```typescript
322
- interface ClientOptions {
323
- parent: string | HTMLElement; // Container element or selector
324
- license: string; // Your license key
325
- model: string; // Model ID to load
326
- debug?: boolean; // Enable debug logging (default: false)
327
- language?: string; // Language for UI inside the iframe (default: 'nl', supported: 'nl', 'en', 'de')
328
- }
308
+ client.viewer.setArEnabled(false);
309
+ client.viewer.setFullscreenEnabled(true);
310
+ client.viewer.setLoadingIndicatorEnabled(false);
311
+
312
+ // Bulk operations
313
+ client.viewer.hideAllUI();
314
+ client.viewer.showAllUI();
315
+ client.viewer.updateUIConfig({ arEnabled: false, fullscreenEnabled: true });
329
316
  ```
330
317
 
331
- ### Methods
318
+ ## Lifecycle
332
319
 
333
- #### `setMapping(configuration: MappingConfiguration): void`
320
+ ### Ready state
334
321
 
335
- Configure attribute-to-node mapping for product variants. This is the primary
336
- method for setting up your product configurator. Call this once with your
337
- complete configuration - the client efficiently handles all updates.
322
+ Use `onReady` to know when the 3D model has loaded. For example, to hide
323
+ your own loading spinner. It is **not** required before calling `select()` or
324
+ `onChange()`.
338
325
 
339
326
  ```typescript
340
- client.setMapping({
341
- attributes: [
342
- {
343
- name: 'Color',
344
- values: [
345
- { value: 'Red', nodeIds: ['node1', 'node2'], isSelected: true },
346
- { value: 'Blue', nodeIds: ['node3', 'node4'] },
347
- ],
348
- },
349
- ],
327
+ client.onReady(() => {
328
+ document.getElementById('loading-spinner').hidden = true;
350
329
  });
351
330
  ```
352
331
 
353
- #### `getAttribute(name: string): AttributeSelector | undefined`
354
-
355
- Get an attribute selector for changing values. Returns undefined if the
356
- attribute doesn't exist.
332
+ ### Cleanup
357
333
 
358
334
  ```typescript
359
- const colorAttr = client.getAttribute('Color');
360
- if (colorAttr) {
361
- colorAttr.select('Blue');
362
- }
335
+ client.destroy();
363
336
  ```
364
337
 
365
- #### `destroy(): void`
366
-
367
- Clean up and remove the client connection. Always call this when unmounting your
368
- component.
338
+ ## Constructor options
369
339
 
370
340
  ```typescript
371
- // React example
372
- useEffect(() => {
373
- const client = new VirtualdisplayClient({ ... });
374
- return () => client.destroy();
375
- }, []);
376
- ```
377
-
378
- ### Types
379
-
380
- ```typescript
381
- interface MappingConfiguration {
382
- attributes: AttributeConfig[];
383
- }
384
-
385
- interface AttributeConfig {
386
- name: string; // Attribute name (e.g., 'Color')
387
- values: AttributeValueConfig[]; // Possible values
388
- }
389
-
390
- interface AttributeValueConfig {
391
- value: string; // Value name (e.g., 'Red')
392
- nodeIds: string[]; // 3D node IDs for this value
393
- isSelected?: boolean; // Default selection state
394
- }
395
-
396
- class AttributeSelector {
397
- // Properties
398
- name: string; // Attribute name (getter)
399
- currentValue: string | undefined; // Current selected value (getter)
400
- availableValues: string[]; // All possible values (getter)
401
-
402
- // Methods
403
- select(value: string): AttributeSelector; // Select a value (chainable)
404
- onChange(callback: () => void): AttributeSelector; // Register change callback (chainable)
405
- getValues(): AttributeValue[]; // Get all AttributeValue objects
406
- getValue(value: string): AttributeValue | undefined; // Get specific AttributeValue
407
- }
341
+ interface ClientOptions {
342
+ parent: string | HTMLElement; // Where to embed the 3D viewer
343
+ license: string; // License key from your Virtualdisplay account
344
+ model: string; // Which 3D model to load
345
+ debug?: boolean; // Shows console logs for troubleshooting
346
+ language?: string; // UI language: 'nl', 'en', or 'de' (default: 'nl')
347
+
348
+ // UI elements (all visible by default)
349
+ ui?: {
350
+ arEnabled?: boolean;
351
+ fullscreenEnabled?: boolean;
352
+ loadingIndicatorEnabled?: boolean;
353
+ };
408
354
 
409
- class AttributeValue {
410
- // Properties
411
- value: string; // The value name (e.g., 'Red') - getter
412
- nodeList: string[]; // Associated 3D node IDs - getter
413
- isSelected: boolean; // Current selection state - getter
414
- onChange?: () => void; // Callback when selection changes - property
355
+ // Camera behaviour
356
+ camera?: {
357
+ initialRotate?: number; // Starting view angle (-180 to 180, 0 = front)
358
+ initialTilt?: number; // Starting elevation (0 = top view, 90 = side view)
359
+ initialZoom?: number; // Starting distance (100 = default, may need adjustment)
360
+ minZoom?: number; // Minimum zoom level constraint
361
+ maxZoom?: number; // Maximum zoom level constraint
362
+ minTilt?: number; // Minimum tilt angle constraint
363
+ maxTilt?: number; // Maximum tilt angle constraint
364
+ };
415
365
  }
416
366
  ```
417
367
 
418
- #### NodeSelector
419
-
420
- The client exports `NodeSelector` for direct node manipulation. This is intended
421
- for specialized tools like mapping editors and inspectors, not for typical
422
- product configurators. For product configurators, use the attribute-based API.
423
-
424
- ### Error handling
368
+ ## Error codes
425
369
 
426
370
  The client throws `VirtualdisplayError` for known error conditions:
427
371
 
428
- ```typescript
429
- import {
430
- VirtualdisplayClient,
431
- VirtualdisplayError,
432
- ERROR_CODES,
433
- } from '@virtualdisplay.io/client';
434
-
435
- try {
436
- client.getAttribute('NonExistent').select('Value');
437
- } catch (error) {
438
- if (error instanceof VirtualdisplayError) {
439
- switch (error.code) {
440
- case ERROR_CODES.ATTRIBUTE_NOT_FOUND:
441
- console.error('Attribute does not exist');
442
- break;
443
- case ERROR_CODES.NO_MAPPING:
444
- console.error('Call setMapping() first');
445
- break;
446
- // Handle other error codes
447
- }
448
- }
449
- }
450
- ```
372
+ | Code | Meaning |
373
+ |-----------------------|--------------------------------------------------|
374
+ | `ATTRIBUTE_NOT_FOUND` | Requested attribute doesn't exist in the mapping |
375
+ | `VALUE_NOT_FOUND` | Requested value doesn't exist for attribute |
376
+ | `PARENT_NOT_FOUND` | Parent element for iframe not found |
377
+ | `INVALID_PARAMETER` | Invalid parameter (e.g. snapshot filename) |
378
+ | `SNAPSHOT_FAILED` | Server could not capture the snapshot |
451
379
 
452
- **Error codes:**
380
+ ## Real-world example
453
381
 
454
- - `NO_MAPPING` - No mapping configuration set
455
- - `ATTRIBUTE_NOT_FOUND` - Requested attribute doesn't exist
456
- - `VALUE_NOT_FOUND` - Requested value doesn't exist for attribute
457
- - `INVALID_MAPPING` - Mapping configuration is invalid
458
- - `PARENT_NOT_FOUND` - Parent element for iframe not found
382
+ ```typescript
383
+ const client = new VirtualdisplayClient({
384
+ parent: '#product-viewer',
385
+ license: 'your-license',
386
+ model: 'dining-table',
387
+ });
459
388
 
460
- ## Contributing
389
+ // Wire up your product option buttons. Selections are buffered until ready.
390
+ document.querySelectorAll('.color-option').forEach((button) => {
391
+ button.addEventListener('click', () => {
392
+ client.select('topMaterial', button.dataset.value);
393
+ });
394
+ });
461
395
 
462
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
396
+ // Keep buttons in sync with 3D state
397
+ client.onChange('topMaterial', (selectedValue) => {
398
+ document.querySelectorAll('.color-option').forEach((btn) => {
399
+ btn.classList.toggle('active', btn.dataset.value === selectedValue.value);
400
+ });
401
+ });
402
+ ```
463
403
 
464
404
  ## Troubleshooting
465
405
 
466
- ### Common issues
467
-
468
406
  **Iframe not loading:**
469
407
 
470
408
  - Check that the parent element exists in the DOM
471
409
  - Verify your license key is valid
472
- - Ensure the model ID matches your license
410
+ - Ensure the model ID matches your licence
473
411
 
474
412
  **Attributes not working:**
475
413
 
476
- - Make sure you call `setMapping()` before using `getAttribute()`
477
414
  - Verify attribute names match exactly (case-sensitive)
478
- - Check that node IDs in your mapping exist in the 3D model
415
+ - Check that the value exists for the attribute
416
+ - Use `onMutationFailed` to catch server-side errors
479
417
 
480
418
  ## Changelog
481
419
 
482
- See the [GitHub releases](https://github.com/virtualdisplay-io/client/releases)
483
- for release notes and version history.
420
+ See the [npm version history](https://www.npmjs.com/package/@virtualdisplay.io/client?activeTab=versions)
421
+ for release notes.