@preference-sl/pref-viewer 2.16.2 → 2.17.0-beta.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.
@@ -45,7 +45,7 @@ import { PrefViewer2DStyles } from "./styles.js";
45
45
  * - Focus is set on the component's parent to enable keyboard shortcuts without Panzoom focus issues.
46
46
  *
47
47
  * Usage examples
48
- * - load plain SVG: el.load({ storage: { url: '<svg...>' } }) or el.load('<svg...>');
48
+ * - load plain SVG: el.load({ storage: { url: '<svg...>' } }); load expects a storage descriptor, not a raw string.
49
49
  * - load from IndexedDB: el.load({ storage: { db: 'db', table: 'tbl', id: 'recId' } });
50
50
  *
51
51
  * Notes on extensibility
@@ -584,19 +584,31 @@ export default class PrefViewer2D extends HTMLElement {
584
584
  return [clone];
585
585
  }
586
586
 
587
- /** Remove all visual selection clones and cloned definitions. */
587
+ /**
588
+ * Remove all visual selection clones and cloned definitions.
589
+ * @private
590
+ * @returns {void}
591
+ */
588
592
  #clearSelectionLayers() {
589
593
  this.#selectionNodes.forEach((node) => node.parentNode?.removeChild(node));
590
594
  this.#selectionNodes = [];
591
595
  }
592
596
 
593
- /** Remove all host-provided SVG nodes and clear their registry. */
597
+ /**
598
+ * Remove all host-provided SVG nodes and clear their registry.
599
+ * @private
600
+ * @returns {void}
601
+ */
594
602
  #removeAppendedSvgNodes() {
595
603
  this.#appendedSvgElements.forEach((entry) => entry.svg?.parentNode?.removeChild(entry.svg));
596
604
  this.#appendedSvgElements = [];
597
605
  }
598
606
 
599
- /** Rebuild visual layers from the latest semantic snapshot. */
607
+ /**
608
+ * Rebuild visual layers from the latest semantic snapshot.
609
+ * @private
610
+ * @returns {void}
611
+ */
600
612
  #reconcileSelectionState() {
601
613
  this.#clearSelectionLayers();
602
614
  if (!this.#isLoaded || !this.#getSvgRoot()) {
@@ -15,6 +15,11 @@
15
15
  * - Inspect flags via `isPending`, `isSuccess`, `isVisible`, and `mustBeShown`.
16
16
  */
17
17
  export class ContainerData {
18
+ /**
19
+ * Creates container state with no loaded assets and resets its staged update.
20
+ * @public
21
+ * @param {string} [name=""] - Container identity, such as model or environment.
22
+ */
18
23
  constructor(name = "") {
19
24
  this.metadata = {};
20
25
  this.name = name;
@@ -26,6 +31,12 @@ export class ContainerData {
26
31
  // Set initial information about ongoing update
27
32
  this.reset();
28
33
  }
34
+
35
+ /**
36
+ * Clears staged cache and load flags while preserving committed container fields.
37
+ * @public
38
+ * @returns {void}
39
+ */
29
40
  reset() {
30
41
  this.update = {
31
42
  metadata: {},
@@ -37,6 +48,13 @@ export class ContainerData {
37
48
  timeStamp: null,
38
49
  };
39
50
  }
51
+
52
+ /**
53
+ * Marks the staged result; success commits visibility intent, cache metadata and a supplied source.
54
+ * @public
55
+ * @param {boolean} [success=false] - Whether the staged load succeeded.
56
+ * @returns {void}
57
+ */
40
58
  setSuccess(success = false) {
41
59
  if (success) {
42
60
  this.update.success = true;
@@ -51,6 +69,14 @@ export class ContainerData {
51
69
  this.update.success = false;
52
70
  }
53
71
  }
72
+
73
+ /**
74
+ * Resets staged state and queues a source and visibility intent for the next load.
75
+ * @public
76
+ * @param {object|null} [storage=null] - URL or IndexedDB storage descriptor.
77
+ * @param {boolean} [show] - Requested visibility; omission retains the current intent.
78
+ * @returns {void}
79
+ */
54
80
  setPending(storage = null, show) {
55
81
  this.reset();
56
82
  this.update.pending = true;
@@ -61,11 +87,26 @@ export class ContainerData {
61
87
  this.storage = storage;
62
88
  }
63
89
  }
90
+
91
+ /**
92
+ * Stages resolved source size, timestamp and a copied metadata snapshot.
93
+ * @public
94
+ * @param {number} [size=0] - Resolved source size.
95
+ * @param {string|null} [timeStamp=null] - Source modification marker.
96
+ * @param {object} [metadata={}] - Source metadata to copy.
97
+ * @returns {void}
98
+ */
64
99
  setPendingCacheData(size = 0, timeStamp = null, metadata = {}) {
65
100
  this.update.size = size;
66
101
  this.update.timeStamp = timeStamp;
67
102
  this.update.metadata = { ...(metadata ?? {}) };
68
103
  }
104
+
105
+ /**
106
+ * Queues the committed source and cache metadata again when a source is available.
107
+ * @public
108
+ * @returns {boolean}
109
+ */
69
110
  setPendingWithCurrentStorage() {
70
111
  const storedSource = this.storage ?? null;
71
112
  if (!storedSource) {
@@ -76,15 +117,39 @@ export class ContainerData {
76
117
  this.setPendingCacheData(this.size, this.timeStamp, this.metadata);
77
118
  return true;
78
119
  }
120
+
121
+ /**
122
+ * Reports whether a container load is staged.
123
+ * @public
124
+ * @returns {boolean}
125
+ */
79
126
  get isPending() {
80
127
  return this.update.pending === true;
81
128
  }
129
+
130
+ /**
131
+ * Reports whether the staged load succeeded.
132
+ * @public
133
+ * @returns {boolean}
134
+ */
82
135
  get isSuccess() {
83
136
  return this.update.success === true;
84
137
  }
138
+
139
+ /**
140
+ * Reports whether the container is currently attached as visible.
141
+ * @public
142
+ * @returns {boolean}
143
+ */
85
144
  get isVisible() {
86
145
  return this.visible === true;
87
146
  }
147
+
148
+ /**
149
+ * Resolves visibility intent from the staged value or committed show flag.
150
+ * @public
151
+ * @returns {boolean}
152
+ */
88
153
  get mustBeShown() {
89
154
  return this.update.show !== null ? this.update.show === true : this.show === true;
90
155
  }
@@ -105,6 +170,14 @@ export class ContainerData {
105
170
  * - Inspect flags via `isPending` and `isSuccess`.
106
171
  */
107
172
  export class MaterialData {
173
+ /**
174
+ * Creates material option state and stores exact-name and prefix targets.
175
+ * @public
176
+ * @param {string} [name=""] - Material option identity.
177
+ * @param {string|null} [value=null] - Selected material name.
178
+ * @param {string[]} [nodeNames=[]] - Exact mesh names to target.
179
+ * @param {string[]} [nodePrefixes=[]] - Mesh-name prefixes to target.
180
+ */
108
181
  constructor(name = "", value = null, nodeNames = [], nodePrefixes = []) {
109
182
  this.name = name;
110
183
  this.nodeNames = nodeNames;
@@ -113,6 +186,12 @@ export class MaterialData {
113
186
  // Set initial information about ongoing update
114
187
  this.reset();
115
188
  }
189
+
190
+ /**
191
+ * Clears staged material flags without changing the committed selection.
192
+ * @public
193
+ * @returns {void}
194
+ */
116
195
  reset() {
117
196
  this.update = {
118
197
  pending: false,
@@ -120,6 +199,13 @@ export class MaterialData {
120
199
  value: null,
121
200
  };
122
201
  }
202
+
203
+ /**
204
+ * Marks the result and commits the staged material value only on success.
205
+ * @public
206
+ * @param {boolean} [success=false] - Whether material application succeeded.
207
+ * @returns {void}
208
+ */
123
209
  setSuccess(success = false) {
124
210
  if (success) {
125
211
  this.update.success = true;
@@ -128,12 +214,25 @@ export class MaterialData {
128
214
  this.update.success = false;
129
215
  }
130
216
  }
217
+
218
+ /**
219
+ * Resets staged state and queues a material value for application.
220
+ * @public
221
+ * @param {string|null} [value=null] - Material name to apply.
222
+ * @returns {void}
223
+ */
131
224
  setPending(value = null) {
132
225
  this.reset();
133
226
  this.update.pending = true;
134
227
  this.update.success = false;
135
228
  this.update.value = value;
136
229
  }
230
+
231
+ /**
232
+ * Queues the committed material value unless it is undefined.
233
+ * @public
234
+ * @returns {boolean}
235
+ */
137
236
  setPendingWithCurrent() {
138
237
  if (this.value === undefined) {
139
238
  return false;
@@ -141,9 +240,21 @@ export class MaterialData {
141
240
  this.setPending(this.value);
142
241
  return true;
143
242
  }
243
+
244
+ /**
245
+ * Reports whether a material change is staged.
246
+ * @public
247
+ * @returns {boolean}
248
+ */
144
249
  get isPending() {
145
250
  return this.update.pending === true;
146
251
  }
252
+
253
+ /**
254
+ * Reports whether the staged material change succeeded.
255
+ * @public
256
+ * @returns {boolean}
257
+ */
147
258
  get isSuccess() {
148
259
  return this.update.success === true;
149
260
  }
@@ -165,6 +276,13 @@ export class MaterialData {
165
276
  */
166
277
  export class CameraData {
167
278
  defaultLocked = true;
279
+ /**
280
+ * Creates camera selection and lock state and resets its staged update.
281
+ * @public
282
+ * @param {string} [name=""] - Camera option identity.
283
+ * @param {string|null} [value=null] - Authored camera name or null for the default camera.
284
+ * @param {boolean} [locked=true] - Initial navigation lock.
285
+ */
168
286
  constructor(name = "", value = null, locked = this.defaultLocked) {
169
287
  this.name = name;
170
288
  this.value = value;
@@ -172,6 +290,12 @@ export class CameraData {
172
290
  // Set initial information about ongoing update
173
291
  this.reset();
174
292
  }
293
+
294
+ /**
295
+ * Clears staged camera flags while preserving the committed camera selection.
296
+ * @public
297
+ * @returns {void}
298
+ */
175
299
  reset() {
176
300
  this.update = {
177
301
  pending: false,
@@ -179,6 +303,13 @@ export class CameraData {
179
303
  value: null,
180
304
  };
181
305
  }
306
+
307
+ /**
308
+ * Marks the result and commits the staged camera value and lock only on success.
309
+ * @public
310
+ * @param {boolean} [success=false] - Whether camera application succeeded.
311
+ * @returns {void}
312
+ */
182
313
  setSuccess(success = false) {
183
314
  if (success) {
184
315
  this.update.success = true;
@@ -188,6 +319,14 @@ export class CameraData {
188
319
  this.update.success = false;
189
320
  }
190
321
  }
322
+
323
+ /**
324
+ * Queues a camera change; undefined clears staging and null requests an unlocked default camera.
325
+ * @public
326
+ * @param {string|null|undefined} [value] - Camera selection to stage.
327
+ * @param {boolean} [locked=true] - Navigation lock for an authored camera.
328
+ * @returns {void}
329
+ */
191
330
  setPending(value = undefined, locked = this.defaultLocked) {
192
331
  this.reset();
193
332
  if (value === undefined) {
@@ -201,14 +340,31 @@ export class CameraData {
201
340
  this.update.value = value;
202
341
  this.update.locked = locked;
203
342
  }
343
+
344
+ /**
345
+ * Queues the committed camera selection and lock again.
346
+ * @public
347
+ * @returns {boolean}
348
+ */
204
349
  setPendingWithCurrent() {
205
350
  const currentValue = this.value !== undefined ? this.value : null;
206
351
  this.setPending(currentValue, this.locked);
207
352
  return true;
208
353
  }
354
+
355
+ /**
356
+ * Reports whether a camera change is staged.
357
+ * @public
358
+ * @returns {boolean}
359
+ */
209
360
  get isPending() {
210
361
  return this.update.pending === true;
211
362
  }
363
+ /**
364
+ * Reports whether the staged camera change succeeded.
365
+ * @public
366
+ * @returns {boolean}
367
+ */
212
368
  get isSuccess() {
213
369
  return this.update.success === true;
214
370
  }
@@ -234,6 +390,16 @@ export class CameraData {
234
390
  export class IBLData {
235
391
  defaultIntensity = 1.0;
236
392
  defaultShadows = false;
393
+ /**
394
+ * Creates HDR source/cache references and effective image-based lighting settings.
395
+ * @public
396
+ * @param {string|null} [url=null] - HDR source identifier.
397
+ * @param {string|null} [cachedUrl=null] - Resolved temporary HDR URL.
398
+ * @param {number} [intensity=1] - Environment intensity.
399
+ * @param {boolean} [shadows=false] - IBL shadow toggle.
400
+ * @param {string|null} [timeStamp=null] - Source modification marker.
401
+ * @param {boolean} [valid=false] - Whether a reusable HDR texture is available.
402
+ */
237
403
  constructor(url = null, cachedUrl = null, intensity = this.defaultIntensity, shadows = this.defaultShadows, timeStamp = null, valid = false) {
238
404
  this.url = url;
239
405
  this.cachedUrl = cachedUrl;
@@ -25,6 +25,7 @@ import FileStorage from "./file-storage.js";
25
25
  * - show-model: Show or hide the 3D model ("true"/"false").
26
26
  * - show-scene: Show or hide the 3D environment ("true"/"false").
27
27
  * - visible: Show or hide the entire viewer ("true"/"false").
28
+ * - show-view-cube: Enable the orientation overlay while visible; only "false" disables it (default: enabled).
28
29
  *
29
30
  * Public Methods:
30
31
  * - show(): Shows the 3D viewer component.
@@ -39,6 +40,7 @@ import FileStorage from "./file-storage.js";
39
40
  * - hideEnvironment(): Hides the 3D environment/scene.
40
41
  * - isIBLAvailable(): Reports whether a valid IBL environment map is currently available.
41
42
  * - isAnimationMenuEnabled(): Reports whether the animation control menu is currently enabled.
43
+ * - refreshViewCube(): Internally synchronizes an existing cube after a host culture change.
42
44
  * - getDebugInfo(): Returns a diagnostic snapshot of the underlying Babylon.js runtime.
43
45
  * - getAnimations(): Returns a snapshot of all available animations and their current state.
44
46
  * - playAnimation(name, action): Controls a named animation by applying the specified action.
@@ -69,11 +71,13 @@ import FileStorage from "./file-storage.js";
69
71
  * - #onLoading/#onLoaded/#onSettingOptions/#onSetOptions(): Dispatch lifecycle events and synchronize attributes.
70
72
  *
71
73
  * Events:
74
+ * - "view-cube-layout": Internal, non-bubbling event with {offset, height} in CSS pixels for host menu layout; disposal clears both.
72
75
  * - "pref-viewer-3d-loading": Dispatched when a loading operation starts. Legacy alias: "scene-loading".
73
76
  * - "pref-viewer-3d-loaded": Dispatched when a loading operation completes. Legacy alias: "scene-loaded".
74
77
  * - "pref-viewer-3d-options-applying": Dispatched when viewer options are being set. Legacy alias: "scene-setting-options".
75
78
  * - "pref-viewer-3d-options-applied": Dispatched when viewer options have been set. Legacy alias: "scene-set-options".
76
79
  * - "pref-viewer-3d-animation-changed": Dispatched by OpeningAnimation instances via the pref-viewer-3d host when an animation state, progress, or loop value changes.
80
+ * - "pref-viewer-3d-element-click": Internal event carrying distance-ordered semantic model mesh ids.
77
81
  *
78
82
  * Notes:
79
83
  * - Internal state and heavy initialization are handled in connectedCallback.
@@ -107,7 +111,7 @@ export default class PrefViewer3D extends HTMLElement {
107
111
  * @returns {string[]} Array of attribute names that trigger attributeChangedCallback.
108
112
  */
109
113
  static get observedAttributes() {
110
- return ["show-model", "show-scene", "visible"];
114
+ return ["show-model", "show-scene", "visible", "show-view-cube"];
111
115
  }
112
116
 
113
117
  /**
@@ -120,9 +124,13 @@ export default class PrefViewer3D extends HTMLElement {
120
124
  * - "show-model": shows or hides the model according to the attribute value ("true"/"false").
121
125
  * - "show-scene": shows or hides the scene according to the attribute value ("true"/"false").
122
126
  * - "visible": shows or hides the component according to the attribute value ("true"/"false").
127
+ * - "show-view-cube": activates the cube only while visible and the attribute is not "false".
123
128
  */
124
129
  attributeChangedCallback(name, _old, value) {
125
130
  switch (name) {
131
+ case "show-view-cube":
132
+ this.#babylonJSController?.setViewCubeEnabled(this.#isVisible && value !== "false");
133
+ break;
126
134
  case "show-model":
127
135
  if (_old === value) {
128
136
  return;
@@ -237,12 +245,18 @@ export default class PrefViewer3D extends HTMLElement {
237
245
  }
238
246
 
239
247
  /**
240
- * Initializes the BabylonJSController and enables the Babylon.js engine for rendering.
248
+ * Initializes the Babylon controller with asset/option state and the visibility-aware cube contract.
249
+ * Supplies current host culture and dispatches measured view-cube-layout events for render-menu positioning.
250
+ * Starts asynchronous engine initialization without waiting for asset loading.
241
251
  * @private
242
252
  * @returns {void}
243
253
  */
244
254
  #initializeBabylonJS() {
245
- this.#babylonJSController = new BabylonJSController(this.#canvas, this.#data.containers, this.#data.options);
255
+ this.#babylonJSController = new BabylonJSController(this.#canvas, this.#data.containers, this.#data.options, {
256
+ enabled: this.#isVisible && this.getAttribute("show-view-cube") !== "false",
257
+ getCulture: () => this.getRootNode().host?.culture,
258
+ onLayout: (offset, height) => this.dispatchEvent(new CustomEvent("view-cube-layout", { detail: { offset, height } })),
259
+ });
246
260
  this.#babylonJSController.enable();
247
261
 
248
262
  }
@@ -608,22 +622,24 @@ export default class PrefViewer3D extends HTMLElement {
608
622
  */
609
623
 
610
624
  /**
611
- * Hides the 3D viewer component by updating its visibility state and DOM attribute.
625
+ * Hides the 3D viewer and releases its cube resources while retaining the requested show-view-cube attribute.
612
626
  * @public
613
627
  * @returns {void}
614
628
  */
615
629
  hide() {
616
630
  this.#isVisible = false;
631
+ this.#babylonJSController?.setViewCubeEnabled(false);
617
632
  this.setAttribute("visible", "false");
618
633
  }
619
634
 
620
635
  /**
621
- * Shows the 3D viewer component by updating its visibility state and DOM attribute.
636
+ * Shows/focuses the 3D viewer and lazily recreates its cube when show-view-cube is not "false".
622
637
  * @public
623
638
  * @returns {void}
624
639
  */
625
640
  show() {
626
641
  this.#isVisible = true;
642
+ this.#babylonJSController?.setViewCubeEnabled(this.getAttribute("show-view-cube") !== "false");
627
643
  this.setAttribute("visible", "true");
628
644
  this.#canvas.focus();
629
645
  }
@@ -779,6 +795,16 @@ export default class PrefViewer3D extends HTMLElement {
779
795
  this.#babylonJSController.setContainerVisibility("environment", false);
780
796
  }
781
797
 
798
+ /**
799
+ * Applies the host-owned semantic selection snapshot to the 3D model.
800
+ * @public
801
+ * @param {object} state - Canonical PrefViewer selection state.
802
+ * @returns {void}
803
+ */
804
+ applySelectionState(state = {}) {
805
+ this.#babylonJSController?.applySelectionState(state);
806
+ }
807
+
782
808
  /**
783
809
  * Downloads the current 3D model as a GLB file.
784
810
  * @public
@@ -900,6 +926,15 @@ export default class PrefViewer3D extends HTMLElement {
900
926
  return this.#babylonJSController.getRenderSettings();
901
927
  }
902
928
 
929
+ /**
930
+ * Forwards an internal culture/camera refresh to the existing overlay without activating a disabled cube.
931
+ * @public
932
+ * @returns {void}
933
+ */
934
+ refreshViewCube() {
935
+ this.#babylonJSController?.refreshViewCube();
936
+ }
937
+
903
938
  /**
904
939
  * Returns a snapshot of all available animations and their current state.
905
940
  * @public
@@ -44,7 +44,7 @@ export default class PrefViewerDialog extends HTMLElement {
44
44
  * Called when the element is inserted into the DOM.
45
45
  * Sets up the dialog's DOM structure and styles.
46
46
  * @returns {void}
47
- */
47
+ */
48
48
  connectedCallback() {
49
49
  this.#handles.onBackdropClick = this.#onBackdropClick.bind(this);
50
50
  this.#createDOMElements();
@@ -85,6 +85,12 @@ export default class PrefViewerDialog extends HTMLElement {
85
85
  this.#footer = this.#wrapper.querySelector(".dialog-footer");
86
86
  }
87
87
 
88
+ /**
89
+ * Closes the dialog only when its own backdrop is clicked, preserving content interactions.
90
+ * @private
91
+ * @param {MouseEvent} event - Dialog click event.
92
+ * @returns {void}
93
+ */
88
94
  #onBackdropClick(event) {
89
95
  if (event.target === this) {
90
96
  this.close();