@virtualdisplay.io/client 3.6.0 → 3.7.0

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.npm.md CHANGED
@@ -1,14 +1,6 @@
1
1
  # Virtualdisplay client
2
2
 
3
- TypeScript library for embedding interactive 3D product models in web
4
- applications.
5
-
6
- **Key features:**
7
-
8
- - Simple attribute-based API for product configurators
9
- - Fire-and-forget messaging with automatic state synchronization
10
- - Zero external dependencies (all bundled)
11
- - Works with any frontend framework or vanilla JavaScript
3
+ TypeScript library for embedding interactive 3D product models in web applications.
12
4
 
13
5
  ## Installation
14
6
 
@@ -20,14 +12,6 @@ pnpm add @virtualdisplay.io/client
20
12
  yarn add @virtualdisplay.io/client
21
13
  ```
22
14
 
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
15
  ## Quick start
32
16
 
33
17
  ### Simple product (no options)
@@ -70,414 +54,25 @@ client.setMapping({
70
54
 
71
55
  // Control via product options
72
56
  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
- }
79
- ```
80
-
81
- ## How it works
82
-
83
- The 3D server hosts the viewer in an iframe, keeping WebGL complexity isolated
84
- 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:**
104
-
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
110
-
111
- **Mapping required:**
112
-
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
117
-
118
- ### Attribute mapping
119
-
120
- The mapping system bridges your business logic with the 3D visualization. It
121
- serves multiple purposes:
122
-
123
- **Why mapping is essential:**
124
-
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
131
-
132
- **How it works:** The mapping connects your product options to specific parts of
133
- the 3D model:
134
-
135
- ```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
- ],
164
- });
165
- ```
166
-
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:**
175
-
176
- ```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
- }
187
-
188
- // The server automatically handles showing Red parts
189
- // and hiding Blue/Green parts when Red is selected
190
- ```
191
-
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
198
-
199
- **Need help with mapping?**
200
-
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:
208
-
209
- **Phase by phase breakdown:**
210
-
211
- #### Phase 0: Initialization (always happens)
212
-
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
217
-
218
- **Note:** For simple models without options, this is all you need!
219
-
220
- #### Phase 1: Configuration (only for configurable products)
221
-
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
229
-
230
- #### Phase 2: User interaction (only for configurable products)
231
-
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
241
-
242
- **Important:** The pattern is identical for both initial mapping and user
243
- interactions. In both cases:
244
-
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
250
-
251
- This ensures the server remains the single source of truth for all state
252
- changes.
253
-
254
- ## Advanced usage
255
-
256
- ### Live state synchronization
257
-
258
- Keep your UI in sync with the 3D server state using onChange callbacks:
259
-
260
- ```typescript
261
- const colorAttribute = client.getAttribute('Color');
262
-
263
- colorAttribute?.getValues().forEach((value) => {
264
- value.onChange = () => {
265
- // Update UI when state changes
266
- updateButton(value.value, value.isSelected);
267
- };
268
- });
269
- ```
270
-
271
- ### Mapping validation
272
-
273
- Validate your mapping configuration during development:
274
-
275
- ```typescript
276
- import { mappingSchema } from '@virtualdisplay.io/client';
277
- import Ajv from 'ajv';
278
-
279
- const validate = new Ajv().compile(mappingSchema);
280
- if (!validate(myMapping)) {
281
- console.error('Invalid mapping:', validate.errors);
282
- }
283
- ```
284
-
285
- ## Real-world integration examples
286
-
287
- ### CMS integration
288
-
289
- The Virtualdisplay client is CMS-agnostic. Store the mapping configuration in
290
- your CMS alongside your product data:
291
-
292
- ```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);
298
- ```
299
-
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
320
-
321
- ```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
- }
329
- ```
330
-
331
- ### Methods
332
-
333
- #### `setMapping(configuration: MappingConfiguration): void`
334
-
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.
338
-
339
- ```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
- ],
350
- });
351
- ```
352
-
353
- #### `getAttribute(name: string): AttributeSelector | undefined`
354
-
355
- Get an attribute selector for changing values. Returns undefined if the
356
- attribute doesn't exist.
357
-
358
- ```typescript
359
- const colorAttr = client.getAttribute('Color');
360
- if (colorAttr) {
361
- colorAttr.select('Blue');
362
- }
363
- ```
364
-
365
- #### `destroy(): void`
366
-
367
- Clean up and remove the client connection. Always call this when unmounting your
368
- component.
369
-
370
- ```typescript
371
- // React example
372
- useEffect(() => {
373
- const client = new VirtualdisplayClient({ ... });
374
- return () => client.destroy();
375
- }, []);
376
57
  ```
377
58
 
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
- }
408
-
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
415
- }
416
- ```
417
-
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
425
-
426
- The client throws `VirtualdisplayError` for known error conditions:
427
-
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
- ```
451
-
452
- **Error codes:**
453
-
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
459
-
460
- ## Contributing
461
-
462
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
463
-
464
- ## Troubleshooting
465
-
466
- ### Common issues
467
-
468
- **Iframe not loading:**
59
+ ## Key features
469
60
 
470
- - Check that the parent element exists in the DOM
471
- - Verify your license key is valid
472
- - Ensure the model ID matches your license
61
+ - **Simple API**: Attribute-based product configuration
62
+ - **Fire-and-forget**: Send commands without waiting for responses
63
+ - **State sync**: Automatic synchronization between client and 3D viewer
64
+ - **Framework-agnostic**: Works with React, Vue, Angular, or vanilla JS
65
+ - **TypeScript first**: Full type safety and IntelliSense support
66
+ - **Minimal dependencies**: Only essential validation and logging included
473
67
 
474
- **Attributes not working:**
68
+ ## Documentation
475
69
 
476
- - Make sure you call `setMapping()` before using `getAttribute()`
477
- - Verify attribute names match exactly (case-sensitive)
478
- - Check that node IDs in your mapping exist in the 3D model
70
+ For complete documentation including:
479
71
 
480
- ## Changelog
72
+ - API reference
73
+ - Advanced examples
74
+ - Integration guides
75
+ - Architecture diagrams
76
+ - Troubleshooting
481
77
 
482
- See the [GitHub releases](https://github.com/virtualdisplay-io/client/releases)
483
- for release notes and version history.
78
+ Visit our [GitHub repository](https://github.com/virtualdisplay-io/client)
package/dist/index.d.ts CHANGED
@@ -9,11 +9,6 @@ declare class Attribute {
9
9
  private readonly values;
10
10
  constructor(name: string);
11
11
  addValue(attributeValue: AttributeValue): void;
12
- /**
13
- * Select a value - returns mutations needed for the change
14
- * Pure function - no side effects
15
- */
16
- select(value: string): Mutation[];
17
12
  getDefaultMutations(): Mutation[];
18
13
  getCurrentValue(): AttributeValue | undefined;
19
14
  getValue(valueName: string): AttributeValue | undefined;
@@ -21,6 +16,7 @@ declare class Attribute {
21
16
  hasValue(valueName: string): boolean;
22
17
  get name(): string;
23
18
  get currentSelection(): string | undefined;
19
+ clear(): void;
24
20
  }
25
21
 
26
22
  /**
@@ -28,7 +24,7 @@ declare class Attribute {
28
24
  */
29
25
  export declare interface AttributeConfig {
30
26
  readonly name: string;
31
- readonly values: AttributeValueConfig[];
27
+ readonly values: DynamicValue<AttributeValueConfig[]>;
32
28
  }
33
29
 
34
30
  /**
@@ -75,9 +71,12 @@ export declare class AttributeSelector {
75
71
  * Handles business logic and coordinates with other domains via events
76
72
  */
77
73
  declare class AttributeService {
78
- private readonly attributes;
74
+ private attributes;
79
75
  private readonly eventBus;
80
76
  private isInitialized;
77
+ private mappingConfig;
78
+ private readonly selections;
79
+ private readonly currentState;
81
80
  constructor(eventBus: EventBus);
82
81
  /**
83
82
  * Load mapping configuration and apply defaults
@@ -89,8 +88,48 @@ declare class AttributeService {
89
88
  getAllAttributes(): Attribute[];
90
89
  get initialized(): boolean;
91
90
  private syncStateWithAttributes;
92
- private getAttributeValueForNode;
93
91
  clear(): void;
92
+ /**
93
+ * Create context for dynamic evaluation
94
+ */
95
+ private createContext;
96
+ /**
97
+ * Evaluate a property that might be static or dynamic
98
+ */
99
+ private evaluateProperty;
100
+ /**
101
+ * Evaluate the entire mapping with current context
102
+ */
103
+ private evaluateMapping;
104
+ /**
105
+ * Create a single attribute from configuration
106
+ */
107
+ private createAttributeFromConfig;
108
+ /**
109
+ * Initialize selections from default values
110
+ */
111
+ private initializeSelections;
112
+ /**
113
+ * Check if the mapping configuration contains dynamic elements
114
+ */
115
+ private hasDynamicMapping;
116
+ /**
117
+ * Update current state map with visible nodes per attribute
118
+ */
119
+ private updateCurrentState;
120
+ private getVisibleNodesForAttribute;
121
+ /**
122
+ * Get initial mutations for default selections
123
+ */
124
+ private getInitialMutations;
125
+ /**
126
+ * Calculate mutations by comparing old and new state
127
+ */
128
+ private calculateMutations;
129
+ /**
130
+ * Check if a node is visible in the given state
131
+ */
132
+ private isNodeVisible;
94
133
  }
95
134
 
96
135
  /**
@@ -114,11 +153,12 @@ declare class AttributeValue {
114
153
 
115
154
  /**
116
155
  * Configuration for a single attribute value
156
+ * All properties can be static or dynamic
117
157
  */
118
158
  export declare interface AttributeValueConfig {
119
- readonly value: string;
120
- readonly nodeIds: string[];
121
- readonly isSelected?: boolean;
159
+ readonly value: DynamicValue<string>;
160
+ readonly nodeIds: DynamicValue<string[]>;
161
+ readonly isSelected?: DynamicValue<boolean>;
122
162
  }
123
163
 
124
164
  /**
@@ -228,6 +268,11 @@ export declare interface DomainEvents extends Record<string, unknown> {
228
268
  [EVENT_NAMES.INITIAL_STATE_RECEIVED]: VoidEvent;
229
269
  }
230
270
 
271
+ /**
272
+ * A value that can be static or dynamically computed
273
+ */
274
+ declare type DynamicValue<T> = T | ((context: MappingContext) => T);
275
+
231
276
  export declare function enableDebugMode(): void;
232
277
 
233
278
  /**
@@ -257,7 +302,6 @@ export declare const EVENT_NAMES: {
257
302
  readonly STATE_CHANGED: "state:changed";
258
303
  readonly INITIAL_STATE_RECEIVED: "state:initial-received";
259
304
  readonly SNAPSHOT_MESSAGE: "snapshot:take";
260
- readonly SNAPSHOT_DEVELOPED: "snapshot:developed";
261
305
  };
262
306
 
263
307
  /**
@@ -293,6 +337,14 @@ export declare interface MappingConfiguration {
293
337
  readonly attributes: AttributeConfig[];
294
338
  }
295
339
 
340
+ /**
341
+ * Context provided to dynamic value functions
342
+ */
343
+ declare interface MappingContext {
344
+ getValue: (attributeName: string) => string | undefined;
345
+ getAllValues: () => Map<string, string>;
346
+ }
347
+
296
348
  export declare const mappingSchema: {
297
349
  "$schema": "http://json-schema.org/draft-07/schema#",
298
350
  "title": "Virtualdisplay mapping configuration",
@@ -371,6 +423,14 @@ export declare class MappingValidator {
371
423
  * @throws {VirtualdisplayError} When mapping configuration is invalid
372
424
  */
373
425
  static validate(config: MappingConfiguration): void;
426
+ /**
427
+ * Check if configuration contains dynamic values (functions)
428
+ */
429
+ static containsDynamicValues(config: MappingConfiguration): boolean;
430
+ /**
431
+ * Basic structure validation for dynamic mappings
432
+ */
433
+ private static validateBasicStructure;
374
434
  }
375
435
 
376
436
  /**