@preference-sl/pref-viewer 2.16.0-beta.9 → 2.16.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.
@@ -3,18 +3,12 @@ export const translations = {
3
3
  prefViewer: {
4
4
  menu3d: {
5
5
  title: "3D render settings",
6
- subtitle: "Toggle visual effects on or off.",
7
- pendingLabel: "Pending changes",
6
+ subtitle: "Modify the visual effects.",
8
7
  actions: {
9
- apply: "Apply",
10
- applying: "Applying...",
11
8
  play: "Play",
12
9
  pause: "Pause",
13
10
  },
14
- status: {
15
- error: "Couldn't apply the changes.",
16
- },
17
- switches: {
11
+ features: {
18
12
  antiAliasingEnabled: {
19
13
  label: "Antialiasing",
20
14
  helper: "Softens jagged edges and lines.",
@@ -70,18 +64,12 @@ export const translations = {
70
64
  prefViewer: {
71
65
  menu3d: {
72
66
  title: "Ajustes de render 3D",
73
- subtitle: "Activa o desactiva los efectos visuales.",
74
- pendingLabel: "Pendiente de aplicar",
67
+ subtitle: "Modifica los efectos visuales.",
75
68
  actions: {
76
- apply: "Aplicar",
77
- applying: "Aplicando...",
78
69
  play: "Reproducir",
79
70
  pause: "Pausar",
80
71
  },
81
- status: {
82
- error: "No se pudo aplicar los cambios.",
83
- },
84
- switches: {
72
+ features: {
85
73
  antiAliasingEnabled: {
86
74
  label: "Antialiasing",
87
75
  helper: "Suaviza aristas y líneas.",
@@ -100,7 +88,7 @@ export const translations = {
100
88
  },
101
89
  highlightEnabled: {
102
90
  label: "Resaltado",
103
- helper: "Resaltar elementos animados al pasar el ratón.",
91
+ helper: "Resalta elementos animados al pasar el ratón.",
104
92
  },
105
93
  highlightColor: {
106
94
  label: "Color de resaltado",
@@ -0,0 +1,72 @@
1
+ import { PBRMaterial } from "@babylonjs/core";
2
+
3
+ /**
4
+ * Utilities for classifying and tagging PBR materials by their transparency/transmission mode.
5
+ * Used by BabylonJSController at load time and by pref-viewer-3d for render-order decisions.
6
+ */
7
+
8
+ const PREF_VIEWER_SIMPLIFIED_TRANSMISSION_METADATA_KEY = "prefViewerSimplifiedTransmission";
9
+
10
+ /**
11
+ * Tags a PBR material as having been converted from physical KHR_materials_transmission to simple
12
+ * alpha blending. The tag is stored in `mat.metadata` under an internal key so that downstream
13
+ * render-order passes can still identify these materials as transparent candidates even after
14
+ * their `subSurface.isRefractionEnabled` flag has been cleared.
15
+ *
16
+ * Returns the same material instance (or the original value when `mat` is falsy) so calls can be
17
+ * chained.
18
+ * @param {import("@babylonjs/core").PBRMaterial|null|undefined} mat - Material to tag.
19
+ * @returns {import("@babylonjs/core").PBRMaterial|null|undefined} The same `mat` value that was passed in.
20
+ */
21
+ export function markPrefViewerSimplifiedTransmissionMaterial(mat) {
22
+ if (!mat) {
23
+ return mat;
24
+ }
25
+
26
+ mat.metadata = {
27
+ ...(mat.metadata ?? {}),
28
+ [PREF_VIEWER_SIMPLIFIED_TRANSMISSION_METADATA_KEY]: true,
29
+ };
30
+ return mat;
31
+ }
32
+
33
+ /**
34
+ * Returns true when the material uses Babylon.js physical glass rendering — i.e. either
35
+ * KHR_materials_transmission (refraction) or KHR_materials_translucency is active.
36
+ *
37
+ * This is intentionally narrower than `isPrefViewerTransparentRenderOrderMaterial`: it only
38
+ * matches full physical transmission, not plain alpha blending or simplified environment glass.
39
+ * @param {import("@babylonjs/core").Material|null|undefined} mat - Material to test.
40
+ * @returns {boolean}
41
+ */
42
+ export function isPrefViewerPhysicalGlassMaterial(mat) {
43
+ return Boolean(mat?.subSurface?.isRefractionEnabled || mat?.subSurface?.isTranslucencyEnabled);
44
+ }
45
+
46
+ /**
47
+ * Returns true when a material should be placed in the transparent rendering group (renderingGroupId=1).
48
+ *
49
+ * A material qualifies when any of the following is true:
50
+ * - It is a physical glass material (KHR_materials_transmission / translucency).
51
+ * - It has been tagged as simplified environment transmission via `markPrefViewerSimplifiedTransmissionMaterial`.
52
+ * - Its effective alpha is below 1, its `transparencyMode` is alpha-blend or alpha-test-and-blend,
53
+ * or it self-reports as needing blending/testing through the Babylon.js material API.
54
+ * @param {import("@babylonjs/core").Material|null|undefined} mat - Material to test.
55
+ * @returns {boolean}
56
+ */
57
+ export function isPrefViewerTransparentRenderOrderMaterial(mat) {
58
+ if (!mat) {
59
+ return false;
60
+ }
61
+ const isTransmissive = isPrefViewerPhysicalGlassMaterial(mat);
62
+ const isSimplifiedTransmission = mat.metadata?.[PREF_VIEWER_SIMPLIFIED_TRANSMISSION_METADATA_KEY] === true;
63
+ const alpha = typeof mat.alpha === "number" ? mat.alpha : 1;
64
+ const isAlphaBlended =
65
+ alpha < 1 ||
66
+ mat.transparencyMode === PBRMaterial.PBRMATERIAL_ALPHABLEND ||
67
+ mat.transparencyMode === PBRMaterial.PBRMATERIAL_ALPHATESTANDBLEND ||
68
+ mat.needAlphaBlending?.() === true ||
69
+ mat.needAlphaTesting?.() === true;
70
+
71
+ return Boolean(isTransmissive || isSimplifiedTransmission || isAlphaBlended);
72
+ }
@@ -27,6 +27,7 @@ import Panzoom from "@panzoom/panzoom/dist/panzoom.es.js";
27
27
  * - wrapper (HTMLElement): The DOM element to apply pan/zoom interactions to.
28
28
  * - options (object): Configuration options for Panzoom (e.g., minScale, maxScale, step).
29
29
  * - changedCallback (function): Optional callback invoked when the pan/zoom state changes.
30
+ * - clickCallback (function): Optional callback invoked with the original event after a gesture is classified as a click.
30
31
  *
31
32
  * Public Methods:
32
33
  * - enable(): Enables the Panzoom instance and initializes it if not already enabled.
@@ -40,7 +41,7 @@ import Panzoom from "@panzoom/panzoom/dist/panzoom.es.js";
40
41
  *
41
42
  * Getters:
42
43
  * - panzoom: Returns the current Panzoom instance or null if not initialized.
43
- * - state: Returns the current pan/zoom state (moved, scaled, maximized, minimized).
44
+ * - state: Returns the current pan/zoom transform and semantic state.
44
45
  *
45
46
  * Private Methods:
46
47
  * - #bindHandlers(): Pre-binds reusable event handlers to preserve stable references.
@@ -81,6 +82,9 @@ export default class PanzoomController {
81
82
  totalCounter: 0,
82
83
  };
83
84
  #state = {
85
+ x: 0,
86
+ y: 0,
87
+ scale: 1,
84
88
  moved: false,
85
89
  scaled: false,
86
90
  maximized: false,
@@ -89,6 +93,7 @@ export default class PanzoomController {
89
93
  #wrapper = null;
90
94
 
91
95
  #changedCallback = null;
96
+ #clickCallback = null;
92
97
  #handlers = {
93
98
  panzoomStart: null,
94
99
  panzoomChange: null,
@@ -100,10 +105,11 @@ export default class PanzoomController {
100
105
  wheel: null,
101
106
  };
102
107
 
103
- constructor(wrapper = null, options = {}, changedCallback = null) {
108
+ constructor(wrapper = null, options = {}, changedCallback = null, clickCallback = null) {
104
109
  this.#wrapper = wrapper;
105
110
  Object.assign(this.#options, options);
106
111
  this.#changedCallback = changedCallback;
112
+ this.#clickCallback = clickCallback;
107
113
  this.#bindHandlers();
108
114
  }
109
115
 
@@ -145,7 +151,7 @@ export default class PanzoomController {
145
151
  * @returns {{x:number,y:number}|null} Point or null if coordinates not present.
146
152
  */
147
153
  #getPointFromEvent(e) {
148
- if (e.clientX && e.clientY) {
154
+ if (Number.isFinite(e?.clientX) && Number.isFinite(e?.clientY)) {
149
155
  return {
150
156
  x: e.clientX,
151
157
  y: e.clientY,
@@ -155,6 +161,13 @@ export default class PanzoomController {
155
161
  }
156
162
  }
157
163
 
164
+ /** Reset transient pointer data after a completed or interrupted gesture. */
165
+ #resetPointerEvents() {
166
+ this.#pointerEvents.lastPointerDownPosition = null;
167
+ this.#pointerEvents.partialCounter = 0;
168
+ this.#pointerEvents.totalCounter = 0;
169
+ }
170
+
158
171
  /**
159
172
  * Update internal pan/zoom state and emit a custom event with current values.
160
173
  * If a changed callback is defined, it is invoked with the updated state object.
@@ -164,6 +177,9 @@ export default class PanzoomController {
164
177
  */
165
178
  #checkPanzoomState(transform) {
166
179
  this.#state = {
180
+ x: transform.x,
181
+ y: transform.y,
182
+ scale: transform.scale,
167
183
  moved: transform.x !== 0 || transform.y !== 0,
168
184
  scaled: transform.scale !== 1,
169
185
  maximized: transform.scale === this.#options.maxScale,
@@ -252,7 +268,6 @@ export default class PanzoomController {
252
268
  * @private
253
269
  * @param {CustomEvent} e - Panzoom custom event with originalEvent property.
254
270
  * @returns {void}
255
- * @todo Implement selection/click logic.
256
271
  */
257
272
  #onPanzoomEnd(e) {
258
273
  this.#pointerEvents.partialCounter--;
@@ -260,21 +275,21 @@ export default class PanzoomController {
260
275
  if (this.#pointerEvents.partialCounter > 0) {
261
276
  return;
262
277
  } else if (this.#pointerEvents.totalCounter > 1) {
263
- this.#pointerEvents.partialCounter = 0;
264
- this.#pointerEvents.totalCounter = 0;
278
+ this.#resetPointerEvents();
265
279
  return;
266
280
  }
267
281
 
268
- this.#pointerEvents.partialCounter = 0;
269
- this.#pointerEvents.totalCounter = 0;
270
-
271
- const originalEvent = e.detail.originalEvent;
282
+ const originalEvent = e?.detail?.originalEvent;
283
+ if (!originalEvent) {
284
+ this.#resetPointerEvents();
285
+ return;
286
+ }
272
287
  const point = this.#getPointFromEvent(originalEvent);
273
288
  const distance = this.#getDistanceBetweenPoints(point, this.#pointerEvents.lastPointerDownPosition);
274
289
 
275
290
  let proceedToPick = true;
276
291
 
277
- if (!point || distance > this.#interactionOptions.clickThreshold) {
292
+ if (!point || !this.#pointerEvents.lastPointerDownPosition || distance > this.#interactionOptions.clickThreshold) {
278
293
  proceedToPick = false;
279
294
  }
280
295
 
@@ -282,7 +297,7 @@ export default class PanzoomController {
282
297
  proceedToPick = false;
283
298
  }
284
299
 
285
- this.#pointerEvents.lastPointerDownPosition = null;
300
+ this.#resetPointerEvents();
286
301
 
287
302
  if (!proceedToPick) {
288
303
  return;
@@ -292,7 +307,9 @@ export default class PanzoomController {
292
307
  return;
293
308
  }
294
309
 
295
- // Selection/click logic can be implemented here.
310
+ if (typeof this.#clickCallback === "function") {
311
+ this.#clickCallback(originalEvent);
312
+ }
296
313
  }
297
314
 
298
315
  /**
@@ -302,7 +319,7 @@ export default class PanzoomController {
302
319
  * @returns {void}
303
320
  */
304
321
  #onPanzoomStart(e) {
305
- const originalEvent = e.detail.originalEvent;
322
+ const originalEvent = e?.detail?.originalEvent;
306
323
  this.#pointerEvents.partialCounter++;
307
324
  this.#pointerEvents.totalCounter++;
308
325
  this.#pointerEvents.lastPointerDownPosition = this.#getPointFromEvent(originalEvent);
@@ -421,13 +438,15 @@ export default class PanzoomController {
421
438
  if (!this.#wrapper) {
422
439
  return;
423
440
  }
441
+ this.#disableMouseWheelZoom();
442
+ this.#resetPointerEvents();
424
443
  this.#wrapper.parentElement.removeAttribute("tabindex");
425
444
  this.#wrapper.removeEventListener("panzoomstart", this.#handlers.panzoomStart);
426
445
  this.#wrapper.removeEventListener("panzoomchange", this.#handlers.panzoomChange);
427
446
  this.#wrapper.removeEventListener("panzoomend", this.#handlers.panzoomEnd);
428
447
  this.#wrapper.parentElement.removeEventListener("keyup", this.#handlers.keyUp);
429
- this.#wrapper.parentElement.removeEventListener("focus", this.#handlers.focus);
430
- this.#wrapper.parentElement.removeEventListener("blur", this.#handlers.blur);
448
+ this.#wrapper.parentElement.removeEventListener("focus", this.#handlers.focus, true);
449
+ this.#wrapper.parentElement.removeEventListener("blur", this.#handlers.blur, true);
431
450
  this.#wrapper.parentElement.removeEventListener("contextmenu", this.#handlers.contextMenu);
432
451
  }
433
452
 
@@ -523,7 +542,7 @@ export default class PanzoomController {
523
542
  /**
524
543
  * Returns the current pan/zoom state.
525
544
  * @public
526
- * @returns {object} The state object: { moved, scaled, maximized, minimized }.
545
+ * @returns {object} The state object: { x, y, scale, moved, scaled, maximized, minimized }.
527
546
  */
528
547
  get state() {
529
548
  return this.#state;