@preference-sl/pref-viewer 2.18.0 → 3.0.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.
Files changed (35) hide show
  1. package/README.md +267 -31
  2. package/index.d.ts +75 -6
  3. package/package.json +8 -2
  4. package/src/babylonjs-animation-controller.js +18 -10
  5. package/src/babylonjs-animation-opening-menu.js +80 -14
  6. package/src/babylonjs-animation-opening.js +3 -3
  7. package/src/babylonjs-ar-floor-bounds.js +169 -0
  8. package/src/babylonjs-ar-glass.js +128 -0
  9. package/src/babylonjs-ar-placement-feedback.js +449 -0
  10. package/src/babylonjs-ar-placement.js +1038 -0
  11. package/src/babylonjs-camera-wheel-motion.js +35 -2
  12. package/src/babylonjs-controller.js +542 -125
  13. package/src/babylonjs-orbital-pointers-input.js +186 -0
  14. package/src/{render-math.js → babylonjs-render-math.js} +11 -11
  15. package/src/babylonjs-selection-controller.js +4 -4
  16. package/src/babylonjs-view-cube-canvas-controller.js +1287 -0
  17. package/src/babylonjs-view-cube-canvas-orbit-math.js +220 -0
  18. package/src/babylonjs-view-cube-canvas-renderer.js +775 -0
  19. package/src/babylonjs-view-cube-controller.js +74 -1103
  20. package/src/babylonjs-view-cube-svg-controller.js +1012 -0
  21. package/src/file-storage.js +22 -4
  22. package/src/gltf-resolver.js +83 -13
  23. package/src/localization/translations.js +12 -0
  24. package/src/panzoom-controller.js +4 -4
  25. package/src/pref-viewer-2d.js +2 -2
  26. package/src/pref-viewer-3d.js +55 -9
  27. package/src/pref-viewer-controls-presentation.js +292 -0
  28. package/src/pref-viewer-dialog.js +86 -7
  29. package/src/pref-viewer-menu-3d.js +134 -17
  30. package/src/pref-viewer-style-controller.js +320 -0
  31. package/src/pref-viewer.js +133 -109
  32. package/src/quick-look-action.js +291 -0
  33. package/src/styles.js +702 -98
  34. package/src/babylonjs-view-cube-renderer.js +0 -548
  35. package/src/view-cube-orbit-math.js +0 -199
@@ -1,5 +1,7 @@
1
1
  import PrefViewerTask from "./pref-viewer-task.js";
2
- import { PREF_VIEWER_STYLES_STORAGE_KEY, PrefViewerStyles, PrefViewerStyleVariables } from "./styles.js";
2
+ import { PrefViewerStyles } from "./styles.js";
3
+ import PrefViewerStyleController from "./pref-viewer-style-controller.js";
4
+ import PrefViewerControlsPresentation from "./pref-viewer-controls-presentation.js";
3
5
  import I18n from "./localization/i18n.js";
4
6
 
5
7
  /**
@@ -7,9 +9,10 @@ import I18n from "./localization/i18n.js";
7
9
  *
8
10
  * Overview:
9
11
  * - Hosts synchronized 2D (SVG) and 3D (Babylon.js) canvases plus a render-settings menu inside a single shadow DOM wrapper.
12
+ * - Loads optional font CSS without blocking local shadow styles or the canvas's initial layout measurement.
10
13
  * - Queues mutations (config/model/scene/materials/drawing/options/culture) to guarantee deterministic execution and avoid race conditions.
11
14
  * - Normalizes remote URLs, Base64 payloads, and IndexedDB lookups before forwarding jobs to the BabylonJSController stack.
12
- * - Persists the active locale and supported style variables to localStorage, mirrors locale to PrefViewerMenu3D, and keeps the shared i18n module in sync with the chosen culture.
15
+ * - Persists locale and optional instance-owned appearance overrides; mirrors locale to the menu and shared i18n module.
13
16
  * - Surfaces a dialog API that powers built-in download flows (GLB, glTF+ZIP, USDZ) for model-only, scene-only, or combined asset exports.
14
17
  * - Emits granular lifecycle custom events so host applications can react to loading progress, drawing zoom changes, selection changes, and menu interactions.
15
18
  * - Exposes imperative methods for toggling visibility, updating options, switching modes, controlling animations, managing selection state, and requesting downloads without attribute juggling.
@@ -30,8 +33,9 @@ import I18n from "./localization/i18n.js";
30
33
  * - mode: Switches between `2d` and `3d`, automatically hiding/showing the relevant subcomponents and menu states.
31
34
  * - show-model / show-scene: Boolean flags to toggle visibility without reloading the underlying resources.
32
35
  * - show-render-menu: Boolean flag to show or hide the render-settings menu (default: true).
36
+ * - view-cube-renderer: Selects independent Babylon Canvas (default) or SVG via reflected viewCubeRenderer.
33
37
  * - show-view-cube: Enables the 3D orientation overlay unless "false"; visible cubes position the render menu below their measured height.
34
- * - styles: JSON style overrides. Currently supports `colorPrimary`, persisted in localStorage and exposed as `--pref-viewer-color-primary`.
38
+ * - styles: Validated primary/ViewCube appearance overrides; optional versioned persistence and private CSS projection.
35
39
  *
36
40
  * Public Methods (selection):
37
41
  * - loadConfig / loadModel / loadScene / loadMaterials / loadDrawing: Queue asset ingestion jobs.
@@ -67,6 +71,7 @@ import I18n from "./localization/i18n.js";
67
71
  * - Legacy `scene-*` and `drawing-*` aliases are still emitted alongside their modern equivalents for backward compatibility.
68
72
  *
69
73
  * Notes:
74
+ * - Owns one responsive control presentation shared by nested overlays; disconnect releases its observers.
70
75
  * - Stores the cube layout handler and subscribes only for active 3D cube layout; open dialogs render above viewer overlays.
71
76
  * - Internally wires PrefViewerMenu3D hover/apply events to PrefViewer3D render settings, ensuring persisted toggles stay in sync with BabylonJSController.
72
77
  * - Cleans up dialogs, listeners, and subcomponents on disconnect to prevent leaks when frameworks mount/unmount the custom element.
@@ -74,6 +79,9 @@ import I18n from "./localization/i18n.js";
74
79
  * - Designed for embeddable configurators, product showcases, and sales tooling that require responsive 2D/3D switching plus offline-friendly exports.
75
80
  */
76
81
  export default class PrefViewer extends HTMLElement {
82
+ #controlsPresentation = null;
83
+ #presentationFontLink = null;
84
+ #presentationRootStyle = null;
77
85
  // Internal state flags
78
86
  #isInitialized = false;
79
87
  #isLoaded = false;
@@ -101,10 +109,7 @@ export default class PrefViewer extends HTMLElement {
101
109
  #cultureSuppressAttribute = false;
102
110
 
103
111
  // Runtime style management
104
- #STYLES_STORAGE_KEY = PREF_VIEWER_STYLES_STORAGE_KEY;
105
- #styles = {
106
- colorPrimary: PrefViewerStyleVariables.colorPrimary,
107
- };
112
+ #styleController;
108
113
 
109
114
  // References to internal components
110
115
  #wrapper = null;
@@ -126,9 +131,23 @@ export default class PrefViewer extends HTMLElement {
126
131
  onViewCubeLayout: null,
127
132
  };
128
133
 
134
+ /** @returns {"svg"|"canvas"} Per-instance normalized renderer; missing/invalid attributes use Canvas. */
135
+ get viewCubeRenderer() {
136
+ return this.getAttribute("view-cube-renderer") === "svg" ? "svg" : "canvas";
137
+ }
138
+
139
+ /**
140
+ * Reflects the renderer preference immediately without reloading assets or changing visibility.
141
+ * @param {"svg"|"canvas"} value - SVG selects the vector widget; all other values select the dedicated Canvas 3D scene.
142
+ * @returns {void}
143
+ */
144
+ set viewCubeRenderer(value) {
145
+ this.setAttribute("view-cube-renderer", value === "svg" ? "svg" : "canvas");
146
+ }
147
+
129
148
  /**
130
149
  * Creates a new PrefViewer instance and attaches a shadow DOM.
131
- * Initializes internal state and component references.
150
+ * Initializes instance-owned styling without reading storage until insertion; other component references are created on connection.
132
151
  * @public
133
152
  */
134
153
  constructor() {
@@ -141,22 +160,24 @@ export default class PrefViewer extends HTMLElement {
141
160
  this.#culture = I18n.setLocale(this.#cultureInitial);
142
161
  }
143
162
 
144
- this.#applyStyles(this.#stylesReadPersisted(), { persist: false, applyCssVariables: false });
163
+ this.#styleController = new PrefViewerStyleController(this.ownerDocument);
145
164
  }
146
165
 
147
166
  /**
148
- * Returns the list of attributes to observe for changes.
167
+ * Returns observed data, visibility, renderer selection, styles and controls-density attributes.
149
168
  * @public
150
169
  * @returns {string[]} Array of attribute names to observe.
151
170
  */
152
171
  static get observedAttributes() {
153
- return ["config", "culture", "drawing", "materials", "mode", "model", "scene", "options", "show-model", "show-scene", "show-render-menu", "show-view-cube", "styles"];
172
+ return ["config", "culture", "drawing", "materials", "mode", "model", "scene", "options", "show-model", "show-scene", "show-render-menu", "show-view-cube", "view-cube-renderer", "styles", "controls-density"];
154
173
  }
155
174
 
156
175
  /**
157
176
  * Observes changes to specific attributes and triggers corresponding actions.
158
177
  * Loads configuration, drawing, model, scene, materials, or options when their attributes change.
159
178
  * Toggles model or scene visibility when "show-model" or "show-scene" attributes change, recreates the 3D menu when "show-render-menu" changes, and queues style overrides when "styles" changes.
179
+ * "view-cube-renderer" switches the active cube without loading assets.
180
+ * "controls-density" refreshes shared control presentation without queuing asset work.
160
181
  * "show-view-cube" synchronizes lazy cube activation and conditional layout subscription before forwarding the attribute.
161
182
  * @public
162
183
  * @param {string} name - The name of the changed attribute.
@@ -166,6 +187,12 @@ export default class PrefViewer extends HTMLElement {
166
187
  */
167
188
  attributeChangedCallback(name, _old, value) {
168
189
  switch (name) {
190
+ case "view-cube-renderer":
191
+ this.#component3D?.setAttribute("view-cube-renderer", this.viewCubeRenderer);
192
+ break;
193
+ case "controls-density":
194
+ this.#controlsPresentation?.refresh();
195
+ break;
169
196
  case "show-view-cube":
170
197
  this.#setViewCubeLayoutSubscription(this.#mode === "3d" && value !== "false");
171
198
  this.#component3D?.setAttribute("show-view-cube", value === "false" ? "false" : "true");
@@ -236,17 +263,19 @@ export default class PrefViewer extends HTMLElement {
236
263
  /**
237
264
  * Called when the element is inserted into the DOM.
238
265
  * Initializes the 3D and 2D viewer components and starts processing tasks.
239
- * Restores the primary animation-highlight color after creating the 3D component and menu.
266
+ * Restores saved styles inside a CSS inheritance boundary before creating controls and synchronizes the effective primary highlight.
240
267
  * @public
241
268
  * @returns {void|boolean} Returns false if initialization fails; otherwise void.
242
269
  */
243
270
  connectedCallback() {
244
271
  this.#createShadowDOM();
245
- this.#stylesApplyCssVariables();
272
+ this.#styleController.attach(this.#wrapper);
273
+ this.#controlsPresentation = new PrefViewerControlsPresentation(this.#wrapper,
274
+ () => this.getAttribute("controls-density"), () => this.#component3D?.refreshViewCube?.());
246
275
  this.#createComponent3D();
247
276
  this.#createComponent2D();
248
277
  this.#createMenu3D();
249
- this.#applyRenderSettings({ highlightColor: this.#styles.colorPrimary, persist: false });
278
+ this.#applyRenderSettings({ highlightColor: this.#styleController.primaryColor, persist: false });
250
279
 
251
280
  this.setMode(this.getAttribute("mode") ?? this.#mode);
252
281
 
@@ -264,11 +293,13 @@ export default class PrefViewer extends HTMLElement {
264
293
 
265
294
  /**
266
295
  * Removes stored child/menu/dialog listeners and subcomponents when detached.
267
- * Unsubscribes the cube layout handler before the 3D component and its overlay are torn down.
296
+ * Disposes responsive control observers and unsubscribes the cube layout handler before the 3D component and its overlay are torn down.
268
297
  * @public
269
298
  * @returns {void}
270
299
  */
271
300
  disconnectedCallback() {
301
+ this.#controlsPresentation?.dispose();
302
+ this.#controlsPresentation = null;
272
303
  if (this.#wrapper) {
273
304
  this.#wrapper.removeEventListener("mouseenter", this.#handlers.onViewerHoverStart);
274
305
  this.#wrapper.removeEventListener("mouseleave", this.#handlers.onViewerHoverEnd);
@@ -296,21 +327,31 @@ export default class PrefViewer extends HTMLElement {
296
327
  }
297
328
 
298
329
  /**
299
- * Creates the root shadow DOM structure and host styles.
330
+ * Replaces owned wrapper/font/style nodes on reconnect while preserving consumer-added shadow content.
331
+ * Optional font CSS starts with print media and switches on load, allowing local styles and canvas layout
332
+ * to become usable even while the external font stylesheet is pending or unavailable.
300
333
  * @private
301
334
  * @returns {void}
302
335
  */
303
336
  #createShadowDOM() {
337
+ this.#wrapper?.remove();
338
+ this.#presentationFontLink?.remove();
339
+ this.#presentationRootStyle?.remove();
304
340
  this.#wrapper = document.createElement("div");
305
341
  this.#wrapper.classList.add("pref-viewer-wrapper");
306
342
  this.shadowRoot.append(this.#wrapper);
307
343
 
308
- const link = document.createElement("link");
344
+ const link = this.#presentationFontLink = document.createElement("link");
309
345
  link.setAttribute("rel", "stylesheet");
310
346
  link.setAttribute("href", "https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap");
347
+ // Font CSS must not block local shadow styles while Babylon measures its canvas.
348
+ // Otherwise its intrinsic buffer can be mistaken for the CSS viewport and scaled again.
349
+ link.media = "print";
350
+ link.addEventListener("load", () => { link.media = "all"; }, { once: true });
311
351
  this.shadowRoot.append(link);
312
352
 
313
353
  const style = document.createElement("style");
354
+ this.#presentationRootStyle = style;
314
355
  style.textContent = PrefViewerStyles;
315
356
  this.shadowRoot.append(style);
316
357
  }
@@ -351,7 +392,7 @@ export default class PrefViewer extends HTMLElement {
351
392
  }
352
393
  /**
353
394
  * Creates and appends the 3D viewer component to the shadow DOM, initially hidden.
354
- * Forwards show-view-cube, stores its layout handler in #handlers and subscribes before activation
395
+ * Forwards renderer selection and show-view-cube, stores its layout handler in #handlers and subscribes before activation
355
396
  * so the first measured layout positions the render menu correctly.
356
397
  * @private
357
398
  * @returns {void}
@@ -360,6 +401,7 @@ export default class PrefViewer extends HTMLElement {
360
401
  this.#handlers.on3DElementClick = this.#on3DElementClick.bind(this);
361
402
  this.#component3D = document.createElement("pref-viewer-3d");
362
403
  this.#component3D.setAttribute("visible", "false");
404
+ this.#component3D.setAttribute("view-cube-renderer", this.viewCubeRenderer);
363
405
  this.#component3D.setAttribute("show-view-cube", this.getAttribute("show-view-cube") === "false" ? "false" : "true");
364
406
  this.#handlers.onViewCubeLayout = (event) => {
365
407
  if (event.detail.offset > 0) {
@@ -490,80 +532,17 @@ export default class PrefViewer extends HTMLElement {
490
532
  }
491
533
 
492
534
  /**
493
- * Reads persisted style overrides from localStorage.
494
- * @private
495
- * @returns {object} Persisted style overrides, or an empty object when unavailable.
496
- */
497
- #stylesReadPersisted() {
498
- if (typeof window === "undefined") {
499
- return {};
500
- }
501
- try {
502
- const serialized = window.localStorage?.getItem(this.#STYLES_STORAGE_KEY);
503
- return serialized ? JSON.parse(serialized) : {};
504
- } catch (_error) {
505
- return {};
506
- }
507
- }
508
-
509
- /**
510
- * Persists current style overrides in localStorage.
511
- * @private
512
- * @returns {void}
513
- */
514
- #stylesPersist() {
515
- if (typeof window === "undefined") {
516
- return;
517
- }
518
- try {
519
- window.localStorage?.setItem(this.#STYLES_STORAGE_KEY, JSON.stringify(this.#styles));
520
- } catch (_error) {}
521
- }
522
-
523
- /**
524
- * Keeps the host CSS custom properties in sync with the style variables.
525
- * @private
526
- * @returns {void}
535
+ * Validates and merges a partial appearance patch through the instance-owned controller.
536
+ * Projects styles inside the shadow boundary and persists only overrides when enabled.
537
+ * @param {object} styles - Supported primary/ViewCube leaves; null leaves or groups restore defaults.
538
+ * @returns {boolean} Whether at least one supported value or reset was accepted.
527
539
  */
528
- #stylesApplyCssVariables() {
529
- this.style.setProperty("--pref-viewer-color-primary", this.#styles.colorPrimary);
530
- }
531
-
532
- /**
533
- * Applies supported style overrides to PrefViewerStyleVariables and the host CSS variables.
534
- * @private
535
- * @param {object} styles - Style overrides.
536
- * @param {object} [options] - Apply options.
537
- * @param {boolean} [options.persist=true] - Whether to persist the resolved styles.
538
- * @param {boolean} [options.applyCssVariables=true] - Whether to update host CSS custom properties immediately.
539
- * @returns {boolean} True when at least one supported style was applied.
540
- */
541
- #applyStyles(styles, options = {}) {
542
- const { persist = true, applyCssVariables = true } = options;
543
- if (!styles || typeof styles !== "object") {
544
- return false;
545
- }
546
-
547
- const nextStyles = {};
548
- if (typeof styles.colorPrimary === "string" && styles.colorPrimary.trim().length > 0) {
549
- nextStyles.colorPrimary = styles.colorPrimary.trim();
550
- }
551
-
552
- if (!Object.keys(nextStyles).length) {
540
+ #applyStyles(styles) {
541
+ if (!this.#styleController.update(styles)) {
553
542
  return false;
554
543
  }
555
-
556
- this.#styles = {
557
- ...this.#styles,
558
- ...nextStyles,
559
- };
560
- PrefViewerStyleVariables.colorPrimary = this.#styles.colorPrimary;
561
- if (applyCssVariables) {
562
- this.#stylesApplyCssVariables();
563
- }
564
- if (persist) {
565
- this.#stylesPersist();
566
- }
544
+ this.#controlsPresentation?.refresh();
545
+ this.#component3D?.refreshViewCube?.();
567
546
  return true;
568
547
  }
569
548
 
@@ -688,7 +667,7 @@ export default class PrefViewer extends HTMLElement {
688
667
  }
689
668
 
690
669
  /**
691
- * Adds a new task to the internal queue for processing.
670
+ * Adds a task in order; every partial style patch is retained, while pending options/drawing tasks are replaced.
692
671
  * If the viewer is initialized and not currently loading, immediately processes the next task.
693
672
  * @private
694
673
  * @param {*} value - The payload or data for the task.
@@ -697,7 +676,7 @@ export default class PrefViewer extends HTMLElement {
697
676
  */
698
677
  #addTaskToQueue(value, type) {
699
678
  const task = new PrefViewerTask(value, type);
700
- if (task.type === PrefViewerTask.Types.Options || task.type === PrefViewerTask.Types.Drawing || task.type === PrefViewerTask.Types.Styles) {
679
+ if (task.type === PrefViewerTask.Types.Options || task.type === PrefViewerTask.Types.Drawing) {
701
680
  this.#taskQueue = this.#taskQueue.filter((queuedTask) => queuedTask.type !== task.type);
702
681
  }
703
682
  this.#taskQueue.push(task);
@@ -932,10 +911,14 @@ export default class PrefViewer extends HTMLElement {
932
911
  * @private
933
912
  * @param {object} config - The configuration object to process.
934
913
  * @param {boolean} [config.showRenderMenu] - Reflects render-menu visibility before loading assets; non-booleans are ignored.
914
+ * @param {"svg"|"canvas"} [config.viewCubeRenderer] - Optional per-instance renderer, applied in queue order; omission preserves it.
935
915
  * @param {boolean} [config.showViewCube] - Reflects cube activation before loading assets; non-booleans are ignored.
936
916
  * @returns {Promise<void>} Resolves after the load attempt and advances the task queue.
937
917
  */
938
918
  async #processConfig(config) {
919
+ if (Object.hasOwn(config, "viewCubeRenderer")) {
920
+ this.viewCubeRenderer = config.viewCubeRenderer;
921
+ }
939
922
  for (const [property, attribute] of [["showRenderMenu", "show-render-menu"], ["showViewCube", "show-view-cube"]]) {
940
923
  if (typeof config[property] === "boolean") {
941
924
  const value = String(config[property]);
@@ -1066,7 +1049,7 @@ export default class PrefViewer extends HTMLElement {
1066
1049
  }
1067
1050
 
1068
1051
  /**
1069
- * Applies queued styles and synchronizes animation-opening highlight color without reloading assets.
1052
+ * Applies queued appearance patches; only an accepted primary-color override synchronizes animation highlights.
1070
1053
  * Waits for render settings/menu synchronization before advancing the task queue.
1071
1054
  * @private
1072
1055
  * @param {object} styles - Style overrides to apply.
@@ -1075,7 +1058,11 @@ export default class PrefViewer extends HTMLElement {
1075
1058
  async #processStyles(styles) {
1076
1059
  try {
1077
1060
  if (this.#applyStyles(styles)) {
1078
- await this.#applyRenderSettings({ highlightColor: this.#styles.colorPrimary });
1061
+ const primary = styles.colorPrimary;
1062
+ const acceptedPrimary = primary === null || (typeof primary === "string" && primary.trim() === this.styles.colorPrimary);
1063
+ if (acceptedPrimary) {
1064
+ await this.#applyRenderSettings({ highlightColor: this.#styleController.primaryColor });
1065
+ }
1079
1066
  }
1080
1067
  } finally {
1081
1068
  this.#processNextTask();
@@ -1355,12 +1342,13 @@ export default class PrefViewer extends HTMLElement {
1355
1342
 
1356
1343
  /**
1357
1344
  * Loads a configuration object or JSON string and adds it to the task queue.
1358
- * Queues supplied styles/culture before the configuration and a supplied drawing afterward.
1345
+ * Validates/snapshots supplied style patches through setStyles(), queues styles/culture before config and drawing afterward.
1359
1346
  * Boolean showRenderMenu/showViewCube flags apply when that config task is processed, before its 3D load.
1360
1347
  * Omitted/non-boolean flags preserve current attributes; equal values do not recreate the menu.
1361
1348
  * @public
1362
1349
  * @param {object|string} config - Configuration object or JSON string; overlay flags apply in queue order.
1363
1350
  * @param {boolean} [config.showRenderMenu] - Sets show-render-menu when explicitly boolean.
1351
+ * @param {"svg"|"canvas"} [config.viewCubeRenderer] - Optional per-instance renderer, applied in queue order; omission preserves it.
1364
1352
  * @param {boolean} [config.showViewCube] - Sets show-view-cube when explicitly boolean.
1365
1353
  * @returns {false|void} Returns false for an absent/falsy payload; otherwise queues the configuration.
1366
1354
  * @throws {SyntaxError} If a supplied JSON string cannot be parsed.
@@ -1371,7 +1359,7 @@ export default class PrefViewer extends HTMLElement {
1371
1359
  return false;
1372
1360
  }
1373
1361
  if (config.styles) {
1374
- this.#addTaskToQueue(config.styles, "styles");
1362
+ this.setStyles(config.styles);
1375
1363
  }
1376
1364
  if (config.culture) {
1377
1365
  this.#addTaskToQueue(config.culture, "culture");
@@ -1503,19 +1491,49 @@ export default class PrefViewer extends HTMLElement {
1503
1491
  }
1504
1492
 
1505
1493
  /**
1506
- * Sets supported PrefViewer style variables from an object or JSON string and adds them to the task queue.
1507
- * Currently supports `colorPrimary`, which updates `PrefViewerStyleVariables.colorPrimary`, applies the host CSS custom property,
1508
- * synchronizes animation-opening highlight color (including active highlights), and persists the value in localStorage.
1494
+ * Sets instance-owned primary/ViewCube appearance from a JSON-safe object or JSON string through the task queue.
1495
+ * Synchronizes primary highlight settings and optionally persists supported overrides; external CSS is not consulted.
1509
1496
  * @public
1510
- * @param {object|string} styles - Style overrides object or JSON string.
1511
- * @returns {boolean|void} Returns false if styles are invalid; otherwise void.
1497
+ * Queues a detached partial primary/ViewCube style patch. Unknown/invalid leaves are ignored when processed.
1498
+ * @param {object|string} styles - Style tree or JSON; null leaves/groups reset customizations.
1499
+ * @returns {boolean|void} False for a non-object payload; accepted objects are queued and return void, not a Promise.
1500
+ * @throws {SyntaxError} If a JSON string is malformed.
1512
1501
  */
1513
1502
  setStyles(styles) {
1514
1503
  styles = typeof styles === "string" ? JSON.parse(styles) : styles;
1515
- if (!styles || typeof styles !== "object") {
1504
+ if (!styles || typeof styles !== "object" || Array.isArray(styles)) {
1516
1505
  return false;
1517
1506
  }
1518
- this.#addTaskToQueue(styles, "styles");
1507
+ this.#addTaskToQueue(structuredClone(styles), "styles");
1508
+ }
1509
+
1510
+ /**
1511
+ * Removes only this viewer's configured saved styles, preserving its current appearance.
1512
+ * A later accepted patch can save the current overrides again. Disabled/unavailable storage is a no-op.
1513
+ * @returns {boolean} Whether the configured entry was successfully removed.
1514
+ */
1515
+ clearPersistedStyles() {
1516
+ return this.#styleController.clearPersisted();
1517
+ }
1518
+
1519
+ /** @returns {{enabled: boolean, key: string}} Detached optional persistence configuration. */
1520
+ get stylesPersistence() {
1521
+ return this.#styleController.persistence;
1522
+ }
1523
+
1524
+ /**
1525
+ * Selects optional origin-local storage. Existing explicit patches take priority over restored values.
1526
+ * Configure before insertion to disable reads; switching keys never writes automatically.
1527
+ * @param {{enabled?: boolean, key?: string}} value - Partial configuration; enabled defaults true, key defaults pref-viewer/styles.
1528
+ * @returns {void} Refreshes mounted controls and primary highlights without reloading assets.
1529
+ */
1530
+ set stylesPersistence(value) {
1531
+ this.#styleController.setPersistence(value);
1532
+ this.#controlsPresentation?.refresh();
1533
+ this.#component3D?.refreshViewCube?.();
1534
+ if (this.#isInitialized) {
1535
+ this.#applyRenderSettings({ highlightColor: this.#styleController.primaryColor, persist: false });
1536
+ }
1519
1537
  }
1520
1538
 
1521
1539
  /**
@@ -1534,9 +1552,10 @@ export default class PrefViewer extends HTMLElement {
1534
1552
 
1535
1553
  /**
1536
1554
  * Defines the elements that the host application considers selectable.
1537
- * The normalized state is rendered by the 2D component as non-destructive yellow SVG clones.
1555
+ * The normalized state is forwarded to both renderers: SVG selection clones in 2D and material overlays in 3D.
1556
+ * Emits pref-viewer-selection-state-changed after applying the normalized state.
1538
1557
  * @public
1539
- * @param {Array<string|{id?: string, Id?: string}>} elements - Element ids or objects containing id/Id.
1558
+ * @param {Array<string|{id?: string, Id?: string}>} [elements=[]] - Element ids or objects containing id/Id.
1540
1559
  * @returns {object} Current selection state.
1541
1560
  */
1542
1561
  setSelectableElements(elements = []) {
@@ -1549,7 +1568,7 @@ export default class PrefViewer extends HTMLElement {
1549
1568
  /**
1550
1569
  * Defines the currently selected elements using the semantic PrefViewer contract.
1551
1570
  * @public
1552
- * @param {Array<string|{id?: string, Id?: string, highlights?: string[], Highlights?: string[]}>} elements - Element ids or objects containing id/Id and optional highlights/Highlights.
1571
+ * @param {Array<string|{id?: string, Id?: string, highlights?: string[], Highlights?: string[]}>} [elements=[]] - Element ids or objects containing id/Id and optional highlights/Highlights.
1553
1572
  * @returns {object} Current selection state.
1554
1573
  */
1555
1574
  setSelectedElements(elements = []) {
@@ -1564,7 +1583,7 @@ export default class PrefViewer extends HTMLElement {
1564
1583
  /**
1565
1584
  * Applies a complete host selection state in one call.
1566
1585
  * @public
1567
- * @param {{selectableIds?: Array<string|object>, selectables?: Array<string|object>, selectedItems?: Array<string|object>, selectedIds?: Array<string|object>, selected?: Array<string|object>}} state - Selection state with selectableIds/selectables and selectedItems/selectedIds/selected.
1586
+ * @param {{selectableIds?: Array<string|object>, selectables?: Array<string|object>, selectedItems?: Array<string|object>, selectedIds?: Array<string|object>, selected?: Array<string|object>}} [state={}] - Selection state with selectableIds/selectables and selectedItems/selectedIds/selected.
1568
1587
  * @returns {object} Current selection state.
1569
1588
  */
1570
1589
  applySelectionState(state = {}) {
@@ -1923,6 +1942,7 @@ export default class PrefViewer extends HTMLElement {
1923
1942
  * @public
1924
1943
  * @param {object|string} value - Configuration payload or serialized JSON.
1925
1944
  * @param {boolean} [value.showRenderMenu] - Optional render-menu visibility for an object payload.
1945
+ * @param {"svg"|"canvas"} [value.viewCubeRenderer] - Optional renderer applied with the queued configuration.
1926
1946
  * @param {boolean} [value.showViewCube] - Optional cube visibility for an object payload.
1927
1947
  * @throws {SyntaxError} If a supplied JSON string cannot be parsed.
1928
1948
  */
@@ -1984,10 +2004,14 @@ export default class PrefViewer extends HTMLElement {
1984
2004
  this.setOptions(value);
1985
2005
  }
1986
2006
 
2007
+ /** @returns {object} Detached merged style overrides including the effective primary color; missing leaves use internal defaults. */
2008
+ get styles() {
2009
+ return this.#styleController.styles;
2010
+ }
2011
+
1987
2012
  /**
1988
- * Sends style updates to `setStyles()` enabling reactive bindings.
1989
- * @public
1990
- * @param {object|string} value - Style overrides object or JSON string.
2013
+ * Queues the same validated partial appearance patch as setStyles().
2014
+ * @param {object|string} value - Override tree or JSON; null leaves/groups restore defaults.
1991
2015
  */
1992
2016
  set styles(value) {
1993
2017
  this.setStyles(value);