@preference-sl/pref-viewer 2.11.0-beta.1 → 2.11.0-beta.10

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/src/index.js CHANGED
@@ -1,38 +1,75 @@
1
1
  import { PrefViewer2D } from "./pref-viewer-2d.js";
2
2
  import { PrefViewer3D } from "./pref-viewer-3d.js";
3
- import {PrefViewerTask} from "./pref-viewer-task.js";
3
+ import { PrefViewerDialog } from "./pref-viewer-dialog.js";
4
+ import { PrefViewerTask } from "./pref-viewer-task.js";
4
5
 
5
6
  /**
6
- * PrefViewer - Custom Web Component for rendering and managing 2D and 3D product visualizations.
7
+ * PrefViewer - Custom Web Component for advanced 2D and 3D product visualization and configuration.
7
8
  *
8
9
  * Overview:
9
- * - Encapsulates both 2D and 3D viewers using Babylon.js, supporting glTF/GLB models and environments.
10
- * - Handles loading from remote URLs, Base64 data URIs, and IndexedDB sources.
10
+ * - Encapsulates both 2D (SVG) and 3D (Babylon.js) viewers, supporting glTF/GLB models, environments, and drawings.
11
+ * - Loads assets from remote URLs, Base64 data URIs, and IndexedDB sources.
11
12
  * - Provides a unified API for loading models, scenes, drawings, materials, and configuration via attributes or methods.
12
13
  * - Manages an internal task queue for sequential processing of viewer operations.
13
14
  * - Emits custom events for loading, errors, and state changes to facilitate integration.
15
+ * - Supports downloading models and scenes in GLB and USDZ formats.
16
+ * - Automatically updates the viewer when reactive attributes change.
14
17
  *
15
18
  * Usage:
16
19
  * - Use as a custom HTML element: <pref-viewer ...>
17
- * - Configure via attributes (e.g., config, model, scene, materials, drawing, options).
18
- * - Control visibility, downloads, and viewer mode via public methods.
20
+ * - Configure via attributes (config, model, scene, materials, drawing, options, mode).
21
+ * - Control viewer mode, visibility, and downloads via public methods.
22
+ *
23
+ * Reactive Attributes:
24
+ * - config: URL or Base64 for configuration file.
25
+ * - model: URL or Base64 for 3D model (glTF/GLB).
26
+ * - scene: URL or Base64 for environment/scene (glTF/GLB).
27
+ * - materials: URL or Base64 for materials definition.
28
+ * - drawing: URL or Base64 for SVG drawing.
29
+ * - options: JSON string for viewer options.
30
+ * - mode: Viewer mode ("2d" or "3d").
19
31
  *
20
32
  * Public Methods:
21
- * - loadConfig(config), loadModel(model), loadScene(scene), loadMaterials(materials), loadDrawing(drawing)
22
- * - setOptions(options)
33
+ * - loadConfig(config): Loads a configuration object or JSON string.
34
+ * - loadModel(model): Loads a model object or JSON string.
35
+ * - loadScene(scene): Loads a scene/environment object or JSON string.
36
+ * - loadMaterials(materials): Loads materials object or JSON string.
37
+ * - loadDrawing(drawing): Loads a drawing object or JSON string.
38
+ * - setOptions(options): Sets viewer options from an object or JSON string.
23
39
  * - setMode(mode): Sets the viewer mode to "2d" or "3d" and updates component visibility.
24
- * - showModel(), hideModel(), showScene(), hideScene()
25
- * - downloadModelGLB(), downloadModelUSDZ(), downloadModelAndSceneGLB(), downloadModelAndSceneUSDZ()
40
+ * - showModel(): Shows the 3D model.
41
+ * - hideModel(): Hides the 3D model.
42
+ * - showScene(): Shows the 3D environment/scene.
43
+ * - hideScene(): Hides the 3D environment/scene.
44
+ * - zoomCenter(): Centers the 2D drawing view.
45
+ * - zoomExtentsAll(): Zooms the 2D drawing to fit all content.
46
+ * - zoomIn(): Zooms in on the 2D drawing.
47
+ * - zoomOut(): Zooms out of the 2D drawing.
48
+ * - downloadModelGLB(): Downloads the current 3D model as a GLB file.
49
+ * - downloadModelGLTF(): Downloads the current 3D model as a glTF ZIP file.
50
+ * - downloadModelUSDZ(): Downloads the current 3D model as a USDZ file.
51
+ * - downloadModelAndSceneGLB(): Downloads both the model and scene as a GLB file.
52
+ * - downloadModelAndSceneGLTF(): Downloads both the model and scene as a glTF ZIP file.
53
+ * - downloadModelAndSceneUSDZ(): Downloads both the model and scene as a USDZ file.
54
+ * - downloadSceneGLB(): Downloads the environment as a GLB file.
55
+ * - downloadSceneGLTF(): Downloads the environment as a glTF ZIP file.
56
+ * - downloadSceneUSDZ(): Downloads the environment as a USDZ file.
57
+ * - openDialog(title, content, footer): Opens a modal dialog with the specified title, content, and footer.
58
+ * - closeDialog(): Closes the currently open dialog, if any, and removes it from the DOM.
26
59
  *
27
60
  * Public Properties:
28
- * - initialized: Indicates if the viewer is initialized.
29
- * - loaded: Indicates if the viewer has finished loading.
30
- * - loading: Indicates if the viewer is currently loading.
61
+ * - isInitialized: Indicates if the viewer is initialized.
62
+ * - isLoaded: Indicates if the viewer has finished loading.
63
+ * - isLoading: Indicates if the viewer is currently loading.
64
+ * - isMode2D: Indicates if the viewer is in 2D mode.
65
+ * - isMode3D: Indicates if the viewer is in 3D mode.
31
66
  *
32
67
  * Events:
33
- * - "scene-loading": Dispatched when a loading operation starts.
34
- * - "scene-loaded": Dispatched when a loading operation completes.
35
- * - "scene-error": Dispatched when initialization fails.
68
+ * - "scene-loading": Dispatched when a 3D loading operation starts.
69
+ * - "scene-loaded": Dispatched when a 3D loading operation completes.
70
+ * - "drawing-loading": Dispatched when a 2D drawing loading operation starts.
71
+ * - "drawing-loaded": Dispatched when a 2D drawing loading operation completes.
72
+ * - "drawing-zoom-changed": Dispatched when the 2D drawing zoom/pan state changes.
36
73
  *
37
74
  * Notes:
38
75
  * - Automatically creates and manages 2D and 3D viewer components in its shadow DOM.
@@ -47,8 +84,10 @@ class PrefViewer extends HTMLElement {
47
84
 
48
85
  #taskQueue = [];
49
86
 
87
+ #wrapper = null;
50
88
  #component2D = null;
51
89
  #component3D = null;
90
+ #dialog = null;
52
91
 
53
92
  /**
54
93
  * Creates a new PrefViewer instance and attaches a shadow DOM.
@@ -125,11 +164,18 @@ class PrefViewer extends HTMLElement {
125
164
  /**
126
165
  * Called when the element is inserted into the DOM.
127
166
  * Initializes the 3D and 2D viewer components and starts processing tasks.
128
- * If the "config" attribute is missing, dispatches a "scene-error" event and stops initialization.
129
167
  * @public
130
168
  * @returns {void|boolean} Returns false if initialization fails; otherwise void.
131
169
  */
132
170
  connectedCallback() {
171
+ this.#wrapper = document.createElement("div");
172
+ this.#wrapper.classList.add("pref-viewer-wrapper");
173
+ this.shadowRoot.append(this.#wrapper);
174
+
175
+ const style = document.createElement("style");
176
+ style.textContent = `@import "/dist/css/pref-viewer.css";`;
177
+ this.shadowRoot.append(style);
178
+
133
179
  this.#createComponent3D();
134
180
  this.#createComponent2D();
135
181
 
@@ -137,20 +183,6 @@ class PrefViewer extends HTMLElement {
137
183
  this.setMode();
138
184
  }
139
185
 
140
- if (!this.hasAttribute("config")) {
141
- const error = 'PrefViewer: provide "config" as a configuration object to initialize the viewer.';
142
- console.error(error);
143
- this.dispatchEvent(
144
- new CustomEvent("scene-error", {
145
- bubbles: true,
146
- cancelable: false,
147
- composed: true,
148
- detail: { error: new Error(error) },
149
- })
150
- );
151
- return false;
152
- }
153
-
154
186
  this.#isInitialized = true;
155
187
  this.#processNextTask();
156
188
  }
@@ -164,7 +196,8 @@ class PrefViewer extends HTMLElement {
164
196
  #createComponent2D() {
165
197
  this.#component2D = document.createElement("pref-viewer-2d");
166
198
  this.#component2D.setAttribute("visible", "false");
167
- this.shadowRoot.appendChild(this.#component2D);
199
+ this.#component2D.addEventListener("drawing-zoom-changed", this.#on2DZoomChanged.bind(this));
200
+ this.#wrapper.appendChild(this.#component2D);
168
201
  }
169
202
 
170
203
  /**
@@ -176,7 +209,7 @@ class PrefViewer extends HTMLElement {
176
209
  #createComponent3D() {
177
210
  this.#component3D = document.createElement("pref-viewer-3d");
178
211
  this.#component3D.setAttribute("visible", "false");
179
- this.shadowRoot.appendChild(this.#component3D);
212
+ this.#wrapper.appendChild(this.#component3D);
180
213
  }
181
214
 
182
215
  /**
@@ -243,37 +276,38 @@ class PrefViewer extends HTMLElement {
243
276
 
244
277
  this.removeAttribute("loaded-3d");
245
278
  this.setAttribute("loading-3d", "");
246
- this.dispatchEvent(
247
- new CustomEvent("scene-loading", {
248
- bubbles: true,
249
- cancelable: false,
250
- composed: true,
251
- })
252
- );
279
+
280
+ const customEventOptions = {
281
+ bubbles: true,
282
+ cancelable: false,
283
+ composed: true,
284
+ };
285
+ this.dispatchEvent(new CustomEvent("scene-loading", customEventOptions));
253
286
  }
254
287
 
255
288
  /**
256
289
  * Handles the completion of a 3D loading operation.
257
290
  * Updates loading state, sets attributes, dispatches a "scene-loaded" event, and processes the next task.
258
291
  * @private
259
- * @param {object} [detail={}] - Optional details to include in the event.
292
+ * @param {object} [detail] - Optional details to include in the event.
260
293
  * @returns {void}
261
294
  */
262
- #on3DLoaded(detail = {}) {
263
- this.dispatchEvent(
264
- new CustomEvent("scene-loaded", {
265
- bubbles: true,
266
- cancelable: false,
267
- composed: true,
268
- detail: detail,
269
- })
270
- );
295
+ #on3DLoaded(detail) {
296
+ this.#isLoaded = true;
297
+ this.#isLoading = false;
271
298
 
272
299
  this.removeAttribute("loading-3d");
273
300
  this.setAttribute("loaded-3d", "");
274
301
 
275
- this.#isLoaded = true;
276
- this.#isLoading = false;
302
+ const customEventOptions = {
303
+ bubbles: true,
304
+ cancelable: true,
305
+ composed: true,
306
+ };
307
+ if (detail) {
308
+ customEventOptions.detail = detail;
309
+ }
310
+ this.dispatchEvent(new CustomEvent("scene-loaded", customEventOptions));
277
311
  }
278
312
 
279
313
  /**
@@ -288,35 +322,57 @@ class PrefViewer extends HTMLElement {
288
322
 
289
323
  this.removeAttribute("loaded-2d");
290
324
  this.setAttribute("loading-2d", "");
291
- this.dispatchEvent(
292
- new CustomEvent("drawing-loading", {
293
- bubbles: true,
294
- cancelable: false,
295
- composed: true,
296
- })
297
- );
325
+
326
+ const customEventOptions = {
327
+ bubbles: true,
328
+ cancelable: true,
329
+ composed: true,
330
+ };
331
+ this.dispatchEvent(new CustomEvent("drawing-loading", customEventOptions));
298
332
  }
299
333
 
300
334
  /**
301
335
  * Handles the completion of a 2D loading operation.
302
336
  * Updates loading state, sets attributes, dispatches a "drawing-loaded" event, and processes the next task.
303
337
  * @private
338
+ * @param {object} [detail] - Optional details to include in the event.
304
339
  * @returns {void}
305
340
  */
306
- #on2DLoaded() {
307
- this.dispatchEvent(
308
- new CustomEvent("drawing-loaded", {
309
- bubbles: true,
310
- cancelable: false,
311
- composed: true,
312
- })
313
- );
341
+ #on2DLoaded(detail) {
342
+ this.#isLoaded = true;
343
+ this.#isLoading = false;
314
344
 
315
345
  this.removeAttribute("loading-2d");
316
346
  this.setAttribute("loaded-2d", "");
317
347
 
318
- this.#isLoaded = true;
319
- this.#isLoading = false;
348
+ const customEventOptions = {
349
+ bubbles: true,
350
+ cancelable: true,
351
+ composed: true,
352
+ };
353
+ if (detail) {
354
+ customEventOptions.detail = detail;
355
+ }
356
+ this.dispatchEvent(new CustomEvent("drawing-loaded", customEventOptions));
357
+ }
358
+
359
+ /**
360
+ * Handles the "drawing-zoom-changed" event from the 2D viewer component.
361
+ * Dispatches a custom "drawing-zoom-changed" event from the PrefViewer element, forwarding the event detail to external listeners.
362
+ * @private
363
+ * @param {CustomEvent} event - The original zoom change event from the 2D viewer.
364
+ * @returns {void}
365
+ */
366
+ #on2DZoomChanged(event) {
367
+ event.stopPropagation();
368
+ event.preventDefault();
369
+ const customEventOptions = {
370
+ bubbles: true,
371
+ cancelable: true,
372
+ composed: true,
373
+ detail: event.detail,
374
+ };
375
+ this.dispatchEvent(new CustomEvent("drawing-zoom-changed", customEventOptions));
320
376
  }
321
377
 
322
378
  /**
@@ -351,8 +407,8 @@ class PrefViewer extends HTMLElement {
351
407
  }
352
408
 
353
409
  this.#on2DLoading();
354
- this.#component2D.load(drawing).then(() => {
355
- this.#on2DLoaded();
410
+ this.#component2D.load(drawing).then((detail) => {
411
+ this.#on2DLoaded(detail);
356
412
  this.#processNextTask();
357
413
  });
358
414
  }
@@ -438,7 +494,7 @@ class PrefViewer extends HTMLElement {
438
494
  * Public methods
439
495
  * ---------------------------
440
496
  */
441
-
497
+
442
498
  /**
443
499
  * Sets the viewer mode to "2d" or "3d" and updates component visibility accordingly.
444
500
  * @public
@@ -448,18 +504,21 @@ class PrefViewer extends HTMLElement {
448
504
  setMode(mode = this.#mode) {
449
505
  if (mode !== "2d" && mode !== "3d") {
450
506
  console.warn(`PrefViewer: invalid mode "${mode}". Allowed modes are "2d" and "3d".`);
451
- return;
507
+ mode = this.#mode;
452
508
  }
453
509
  this.#mode = mode;
454
510
  if (mode === "2d") {
455
511
  this.#component3D?.hide();
456
512
  this.#component2D?.show();
457
513
  } else {
458
- this.#component3D?.show();
459
514
  this.#component2D?.hide();
515
+ this.#component3D?.show();
460
516
  }
461
517
  if (this.getAttribute("mode") !== mode) {
462
518
  this.setAttribute("mode", mode);
519
+ if (this.#dialog) {
520
+ this.closeDialog();
521
+ }
463
522
  }
464
523
  }
465
524
 
@@ -595,6 +654,61 @@ class PrefViewer extends HTMLElement {
595
654
  this.#addTaskToQueue(config, "visibility");
596
655
  }
597
656
 
657
+ /**
658
+ * Centers the 2D drawing view in the viewer.
659
+ * @public
660
+ * @returns {void}
661
+ * @description
662
+ * Only works when the viewer is in 2D mode. Pending implementation for 3D mode when active camera is not blocked.
663
+ */
664
+ zoomCenter() {
665
+ if (!this.#component2D || this.#mode !== "2d") {
666
+ return;
667
+ }
668
+ this.#component2D.zoomCenter();
669
+ }
670
+
671
+ /**
672
+ * Zooms the 2D drawing to fit all content within the viewer.
673
+ * @public
674
+ * @returns {void}
675
+ * @description
676
+ * Only works when the viewer is in 2D mode. Pending implementation for 3D mode when active camera is not blocked.
677
+ */
678
+ zoomExtentsAll() {
679
+ if (!this.#component2D || this.#mode !== "2d") {
680
+ return;
681
+ }
682
+ this.#component2D.zoomExtentsAll();
683
+ }
684
+
685
+ /**
686
+ * Zooms in on the 2D drawing.
687
+ * @public
688
+ * @returns {void}
689
+ * @description
690
+ * Only works when the viewer is in 2D mode. Pending implementation for 3D mode when active camera is not blocked.
691
+ */
692
+ zoomIn() {
693
+ if (!this.#component2D || this.#mode !== "2d") {
694
+ return;
695
+ }
696
+ this.#component2D.zoomIn();
697
+ }
698
+
699
+ /**
700
+ * Zooms out of the 2D drawing.
701
+ * @public
702
+ * @returns {void}
703
+ * @description
704
+ * Only works when the viewer is in 2D mode. Pending implementation for 3D mode when active camera is not blocked.
705
+ */
706
+ zoomOut() {
707
+ if (!this.#component2D || this.#mode !== "2d") {
708
+ return;
709
+ }
710
+ this.#component2D.zoomOut();
711
+ }
598
712
 
599
713
  /**
600
714
  * Initiates download of the current 3D model in GLB format.
@@ -609,6 +723,19 @@ class PrefViewer extends HTMLElement {
609
723
  this.#component3D.downloadModelGLB();
610
724
  }
611
725
 
726
+ /**
727
+ * Initiates download of the current 3D model in GLTF format.
728
+ * @public
729
+ * @returns {void}
730
+ */
731
+ downloadModelGLTF() {
732
+ if (!this.#component3D) {
733
+ return;
734
+ }
735
+
736
+ this.#component3D.downloadModelGLTF();
737
+ }
738
+
612
739
  /**
613
740
  * Initiates download of the current 3D model in USDZ format.
614
741
  * @public
@@ -623,7 +750,33 @@ class PrefViewer extends HTMLElement {
623
750
  }
624
751
 
625
752
  /**
626
- * Initiates download of both the 3D model and scene in USDZ format.
753
+ * Initiates download of the current complete scene (3D model and environment) in GLB format.
754
+ * @public
755
+ * @returns {void}
756
+ */
757
+ downloadModelAndSceneGLB() {
758
+ if (!this.#component3D) {
759
+ return;
760
+ }
761
+
762
+ this.#component3D.downloadModelAndSceneGLB();
763
+ }
764
+
765
+ /**
766
+ * Initiates download of the current complete scene (3D model and environment) in GLTF format.
767
+ * @public
768
+ * @returns {void}
769
+ */
770
+ downloadModelAndSceneGLTF() {
771
+ if (!this.#component3D) {
772
+ return;
773
+ }
774
+
775
+ this.#component3D.downloadModelAndSceneGLTF();
776
+ }
777
+
778
+ /**
779
+ * Initiates download of the current complete scene (3D model and environment) in USDZ format.
627
780
  * @public
628
781
  * @returns {void}
629
782
  */
@@ -636,24 +789,88 @@ class PrefViewer extends HTMLElement {
636
789
  }
637
790
 
638
791
  /**
639
- * Initiates download of both the 3D model and scene in GLB format.
792
+ * Initiates download of the current 3D environment in GLB format.
640
793
  * @public
641
794
  * @returns {void}
642
795
  */
643
- downloadModelAndSceneGLB() {
796
+ downloadSceneGLB() {
644
797
  if (!this.#component3D) {
645
798
  return;
646
799
  }
647
800
 
648
- this.#component3D.downloadModelAndSceneGLB();
801
+ this.#component3D.downloadSceneGLB();
649
802
  }
650
803
 
804
+ /**
805
+ * Initiates download of the current 3D environment in GLTF format.
806
+ * @public
807
+ * @returns {void}
808
+ */
809
+ downloadSceneGLTF() {
810
+ if (!this.#component3D) {
811
+ return;
812
+ }
813
+
814
+ this.#component3D.downloadSceneGLTF();
815
+ }
816
+
817
+ /**
818
+ * Initiates download of the current 3D environment in USDZ format.
819
+ * @public
820
+ * @returns {void}
821
+ */
822
+ downloadSceneUSDZ() {
823
+ if (!this.#component3D) {
824
+ return;
825
+ }
826
+ this.#component3D.downloadSceneUSDZ();
827
+ }
828
+
829
+ /**
830
+ * Opens a modal dialog with the specified title, content, and footer.
831
+ * @public
832
+ * @param {string} title - The dialog title to display in the header.
833
+ * @param {string} content - The HTML content to display in the dialog body.
834
+ * @param {string} footer - The HTML content to display in the dialog footer (e.g., action buttons).
835
+ * @returns {HTMLElement} The created dialog element.
836
+ * @description
837
+ * If a dialog is already open, it is closed before opening the new one.
838
+ * The dialog is appended to the viewer's shadow DOM and returned for further manipulation.
839
+ */
840
+ openDialog(title, content, footer) {
841
+ if (this.#dialog && this.#dialog.hasAttribute("open")) {
842
+ this.#dialog.close();
843
+ }
844
+ this.#dialog = document.createElement("pref-viewer-dialog");
845
+ this.shadowRoot.querySelector(".pref-viewer-wrapper").appendChild(this.#dialog);
846
+ const opened = this.#dialog.open(title, content, footer);
847
+ return opened ? this.#dialog : null;
848
+ }
849
+
850
+ /**
851
+ * Closes the currently open dialog, if any, and removes it from the DOM.
852
+ * @public
853
+ * @returns {void}
854
+ */
855
+ closeDialog() {
856
+ if (this.#dialog) {
857
+ this.#dialog.close();
858
+ this.#dialog = null;
859
+ }
860
+ }
861
+
862
+ /**
863
+ * ---------------------------
864
+ * Public properties
865
+ * ---------------------------
866
+ */
867
+
651
868
  /**
652
869
  * Indicates whether the viewer has been initialized.
653
870
  * @public
654
871
  * @returns {boolean} True if initialized; otherwise, false.
655
872
  */
656
- get initialized() {
873
+ get isInitialized() {
657
874
  return this.#isInitialized;
658
875
  }
659
876
 
@@ -662,7 +879,7 @@ class PrefViewer extends HTMLElement {
662
879
  * @public
663
880
  * @returns {boolean} True if loaded; otherwise, false.
664
881
  */
665
- get loaded() {
882
+ get isLoaded() {
666
883
  return this.#isLoaded;
667
884
  }
668
885
 
@@ -671,11 +888,30 @@ class PrefViewer extends HTMLElement {
671
888
  * @public
672
889
  * @returns {boolean} True if loading; otherwise, false.
673
890
  */
674
- get loading() {
891
+ get isLoading() {
675
892
  return this.#isLoading;
676
893
  }
894
+
895
+ /**
896
+ * Indicates whether the viewer is currently in 2D mode.
897
+ * @public
898
+ * @returns {boolean} True if the viewer is in 2D mode; otherwise, false.
899
+ */
900
+ get isMode2D() {
901
+ return this.#mode === "2d";
902
+ }
903
+
904
+ /**
905
+ * Indicates whether the viewer is currently in 3D mode.
906
+ * @public
907
+ * @returns {boolean} True if the viewer is in 3D mode; otherwise, false.
908
+ */
909
+ get isMode3D() {
910
+ return this.#mode === "3d";
911
+ }
677
912
  }
678
913
 
679
914
  customElements.define("pref-viewer-2d", PrefViewer2D);
680
915
  customElements.define("pref-viewer-3d", PrefViewer3D);
916
+ customElements.define("pref-viewer-dialog", PrefViewerDialog);
681
917
  customElements.define("pref-viewer", PrefViewer);