@preference-sl/pref-viewer 2.11.0-beta.9 → 2.11.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.
@@ -1,23 +1,36 @@
1
- import { AdvancedDynamicTexture, Button, Control, Image, Slider, StackPanel } from "@babylonjs/gui";
2
- import { OpeningAnimation } from "./babylonjs-animation-opening.js";
1
+ import OpeningAnimation from "./babylonjs-animation-opening.js";
2
+ import { PrefViewer3DAnimationMenuStyles } from "./styles.js";
3
3
 
4
4
  /**
5
- * OpeningAnimationMenu - Manages and renders the animation control menu for opening/closing animations in a Babylon.js scene.
5
+ * OpeningAnimationMenu - Manages and renders the interactive animation control menu for opening/closing animations in a Babylon.js scene.
6
6
  *
7
- * Responsibilities:
8
- * - Renders a GUI menu with buttons for controlling animation playback (open, close, pause, go to opened/closed, loop).
7
+ * Summary:
8
+ * Provides a floating HTML-based GUI menu with playback and loop controls for animated nodes, including buttons and a progress slider.
9
+ * Handles user interaction, updates UI state based on animation state, and invokes external callbacks for animation actions.
10
+ * Designed for integration with Babylon.js product configurators and interactive 3D applications.
11
+ *
12
+ * Key Features:
13
+ * - Renders a menu with buttons for controlling animation playback (open, close, pause, go to opened/closed, loop).
9
14
  * - Updates button states and slider position based on animation state, progress, and loop mode.
10
15
  * - Handles user interactions and invokes provided callbacks for animation actions.
11
16
  * - Synchronizes the slider value with animation progress, avoiding callback loops.
17
+ * - Provides setters to update animation state, progress, and loop mode from external controllers.
18
+ * - Disposes and cleans up all DOM resources when no longer needed.
12
19
  *
13
20
  * Public Setters:
14
21
  * - set animationState(state): Updates the animation state and button states.
15
22
  * - set animationProgress(progress): Updates the animation progress and slider value.
16
23
  * - set animationLoop(loop): Updates the loop mode and loop button states.
17
24
  *
25
+ * Public Methods:
26
+ * - dispose(): Disposes the animation menu and releases all associated DOM resources.
27
+ *
28
+ * Public Getters:
29
+ * - isVisible: Returns whether the animation menu is currently visible in the DOM.
30
+ *
18
31
  * Private Methods:
19
32
  * - #createMenu(): Initializes and renders the menu UI.
20
- * - #addButton(name, imageURL, enabled, active, visible, callback): Adds a button to the menu with specified properties.
33
+ * - #addButton(name, imageSVG, enabled, active, visible, callback): Adds a button to the menu with specified properties.
21
34
  * - #createButtons(): Creates all control buttons and sets their initial states.
22
35
  * - #getButtonByName(name): Retrieves a button control by its name.
23
36
  * - #setButtonState(name, enabled, active, visible): Updates the visual state of a button.
@@ -27,9 +40,10 @@ import { OpeningAnimation } from "./babylonjs-animation-opening.js";
27
40
  * - #setPlayerButtonsState(): Updates all playback control buttons.
28
41
  * - #setLoopButtonsState(): Updates all loop control buttons.
29
42
  * - #createSlider(): Creates and configures the animation progress slider.
43
+ * - #onSliderInput(event): Handles the slider input event for animation progress.
30
44
  *
31
45
  * Usage Example:
32
- * const menu = new OpeningAnimationMenu(adt, state, progress, loop, {
46
+ * const menu = new OpeningAnimationMenu(name, canvas, state, progress, loop, {
33
47
  * onOpen: () => { ... },
34
48
  * onClose: () => { ... },
35
49
  * onPause: () => { ... },
@@ -42,51 +56,45 @@ import { OpeningAnimation } from "./babylonjs-animation-opening.js";
42
56
  * menu.animationProgress = 0.5;
43
57
  * menu.animationLoop = true;
44
58
  */
45
- export class OpeningAnimationMenu {
59
+ export default class OpeningAnimationMenu {
46
60
  #animationState = OpeningAnimation.states.closed;
47
61
  #animationProgress = 0;
48
62
  #animationLoop = false;
49
63
  #callbacks = null;
50
64
 
51
- // GUI Elements
52
- #advancedDynamicTexture = null;
53
- #mainPanel = null;
54
- #secondaryPanel = null;
65
+ #name = "";
66
+ #canvas = null;
67
+ #containerMain = null;
68
+ #containerButtons = null;
55
69
  #slider = null;
56
70
 
57
- // Style properties
58
- #buttonSize = 28;
59
- #buttonLoopPaddingLeft = 3;
60
- #colorActive = "#6BA53A";
61
- #colorEnabled = "#333333";
62
- #colorDisabled = "#777777";
63
- #colorIcon = "#FFFFFF";
64
- #colorBorder = "#FFFFFF";
65
- #sliderThumbWidth = 20;
66
- #sliderBarOffset = 10;
67
71
  #icon = {
68
- close: `<svg id="play-backwards" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M16,18.86V4.86l-11,7,11,7Z"/></svg>`,
69
- closed: `<svg id="skip-backward" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M20,5V19L13,12M6,5V19H4V5M13,5V19L6,12"/></svg>`,
70
- open: `<svg id="play" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M8,5.14v14l11-7-11-7Z"/></svg>`,
71
- opened: `<svg id="skip-forward" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M4,5V19L11,12M18,5V19H20V5M11,5V19L18,12"/></svg>`,
72
- pause: `<svg id="pause" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M14,19H18V5H14M6,19H10V5H6V19Z"/></svg>`,
73
- repeat: `<svg id="repeat" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z"/></svg>`,
74
- repeatOff: `<svg id="repeat-off" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M2,5.27L3.28,4L20,20.72L18.73,22L15.73,19H7V22L3,18L7,14V17H13.73L7,10.27V11H5V8.27L2,5.27M17,13H19V17.18L17,15.18V13M17,5V2L21,6L17,10V7H8.82L6.82,5H17Z"/></svg>`,
72
+ close: `<svg id="play-backwards" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M16,18.86V4.86l-11,7,11,7Z"/></svg>`,
73
+ closed: `<svg id="skip-backward" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M20,5V19L13,12M6,5V19H4V5M13,5V19L6,12"/></svg>`,
74
+ open: `<svg id="play" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M8,5.14v14l11-7-11-7Z"/></svg>`,
75
+ opened: `<svg id="skip-forward" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M4,5V19L11,12M18,5V19H20V5M11,5V19L18,12"/></svg>`,
76
+ pause: `<svg id="pause" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M14,19H18V5H14M6,19H10V5H6V19Z"/></svg>`,
77
+ repeat: `<svg id="repeat" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z"/></svg>`,
78
+ repeatOff: `<svg id="repeat-off" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L15.73,19H7V22L3,18L7,14V17H13.73L7,10.27V11H5V8.27L2,5.27M17,13H19V17.18L17,15.18V13M17,5V2L21,6L17,10V7H8.82L6.82,5H17Z"/></svg>`,
75
79
  };
76
80
 
77
- #isSettingSliderValue = false;
81
+ #handler = {
82
+ onSliderInput: null,
83
+ };
78
84
 
79
85
  /**
80
86
  * Constructs the OpeningAnimationMenu and initializes the menu UI.
81
- * @param {AdvancedDynamicTexture} advancedDynamicTexture - Babylon.js GUI texture for rendering controls.
87
+ * @param {string} name - The name of the animation.
88
+ * @param {HTMLCanvasElement} canvas - The canvas element for rendering.
82
89
  * @param {number} animationState - Current animation state (enum).
83
90
  * @param {number} animationProgress - Current animation progress (0 to 1).
84
91
  * @param {boolean} animationLoop - Whether the animation is set to loop.
85
92
  * @param {object} callbacks - Callback functions for menu actions (play, pause, open, close, etc.).
86
93
  * @public
87
94
  */
88
- constructor(advancedDynamicTexture, animationState, animationProgress, animationLoop, callbacks) {
89
- this.#advancedDynamicTexture = advancedDynamicTexture;
95
+ constructor(name, canvas, animationState, animationProgress, animationLoop, callbacks) {
96
+ this.#name = name;
97
+ this.#canvas = canvas;
90
98
  this.#animationState = animationState;
91
99
  this.#animationProgress = animationProgress;
92
100
  this.#animationLoop = animationLoop;
@@ -95,29 +103,6 @@ export class OpeningAnimationMenu {
95
103
  this.#createMenu(animationState);
96
104
  }
97
105
 
98
- /**
99
- * Initializes and renders the menu UI, including buttons and slider.
100
- * @private
101
- */
102
- #createMenu() {
103
- if (!this.#advancedDynamicTexture) {
104
- return;
105
- }
106
- this.#mainPanel = new StackPanel();
107
- this.#mainPanel.isVertical = true;
108
- this.#secondaryPanel = new StackPanel();
109
- this.#secondaryPanel.isVertical = false;
110
- this.#secondaryPanel.height = `${this.#buttonSize}px`;
111
- this.#mainPanel.addControl(this.#secondaryPanel);
112
-
113
- this.#advancedDynamicTexture.addControl(this.#mainPanel);
114
- this.#mainPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
115
- this.#mainPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM;
116
-
117
- this.#createButtons();
118
- this.#createSlider();
119
- }
120
-
121
106
  /**
122
107
  * Internal helper to add a button to the menu.
123
108
  * Sets button appearance and attaches the callback for user interaction.
@@ -129,25 +114,21 @@ export class OpeningAnimationMenu {
129
114
  * @param {boolean} visible - Whether the button is visible.
130
115
  * @param {function} callback - Callback to invoke on button click.
131
116
  */
132
- #addButton(name, imageURL, enabled = true, active = false, visible = true, callback) {
133
- const buttonProps = {
134
- background: active ? this.#colorActive : enabled ? this.#colorEnabled : this.#colorDisabled,
135
- color: this.#colorBorder,
136
- cornerRadius: 0,
137
- height: `${this.#buttonSize}px`,
138
- hoverCursor: "pointer",
139
- width: `${this.#buttonSize}px`,
140
- isVisible: visible,
141
- };
142
- const button = Button.CreateImageOnlyButton(`button_animation_${name}`, imageURL);
143
- Object.assign(button, buttonProps);
144
- button.image.stretch = Image.STRETCH_UNIFORM;
145
- button.onPointerUpObservable.add(() => {
117
+ #addButton(name, imageSVG, enabled = true, active = false, visible = true, callback) {
118
+ const stateClass = active ? "active" : enabled ? "enabled" : "disabled";
119
+ const visibilityClass = visible ? "visible" : "hidden";
120
+
121
+ const button = document.createElement("button");
122
+ button.classList.add(stateClass, visibilityClass);
123
+ button.setAttribute("name", `button_animation_${name}`);
124
+ button.innerHTML = imageSVG;
125
+ button.addEventListener("click", (event) => {
146
126
  if (callback && typeof callback === "function") {
147
127
  callback();
148
128
  }
149
129
  });
150
- this.#secondaryPanel.addControl(button);
130
+
131
+ this.#containerButtons.appendChild(button);
151
132
  }
152
133
 
153
134
  /**
@@ -156,19 +137,54 @@ export class OpeningAnimationMenu {
156
137
  */
157
138
  #createButtons() {
158
139
  const buttonsState = this.#getButtonsState();
159
- this.#addButton("closed", `data:image/svg+xml,${encodeURIComponent(this.#icon.closed)}`, buttonsState.closed.enabled, buttonsState.closed.active, buttonsState.closed.visible, this.#callbacks.onGoToClosed);
160
- this.#addButton("close", `data:image/svg+xml,${encodeURIComponent(this.#icon.close)}`, buttonsState.close.enabled, buttonsState.close.active, buttonsState.close.visible, this.#callbacks.onClose);
161
- this.#addButton("pause", `data:image/svg+xml,${encodeURIComponent(this.#icon.pause)}`, buttonsState.pause.enabled, buttonsState.pause.active, buttonsState.pause.visible, this.#callbacks.onPause);
162
- this.#addButton("open", `data:image/svg+xml,${encodeURIComponent(this.#icon.open)}`, buttonsState.open.enabled, buttonsState.open.active, buttonsState.open.visible, this.#callbacks.onOpen);
163
- this.#addButton("opened", `data:image/svg+xml,${encodeURIComponent(this.#icon.opened)}`, buttonsState.opened.enabled, buttonsState.opened.active, buttonsState.opened.visible, this.#callbacks.onGoToOpened);
164
- this.#addButton("repeat", `data:image/svg+xml,${encodeURIComponent(this.#icon.repeat)}`, buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible, this.#callbacks.onToggleLoop);
165
- this.#addButton("repeatOff", `data:image/svg+xml,${encodeURIComponent(this.#icon.repeatOff)}`, buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible, this.#callbacks.onToggleLoop);
166
-
167
- // Adjust padding for loop buttons
168
- this.#getButtonByName("repeat").paddingLeft = `${this.#buttonLoopPaddingLeft}px`;
169
- this.#getButtonByName("repeat").width = `${this.#buttonSize + this.#buttonLoopPaddingLeft}px`;
170
- this.#getButtonByName("repeatOff").paddingLeft = `${this.#buttonLoopPaddingLeft}px`;
171
- this.#getButtonByName("repeatOff").width = `${this.#buttonSize + this.#buttonLoopPaddingLeft}px`;
140
+ this.#addButton("closed", this.#icon.closed, buttonsState.closed.enabled, buttonsState.closed.active, buttonsState.closed.visible, this.#callbacks.onGoToClosed);
141
+ this.#addButton("close", this.#icon.close, buttonsState.close.enabled, buttonsState.close.active, buttonsState.close.visible, this.#callbacks.onClose);
142
+ this.#addButton("pause", this.#icon.pause, buttonsState.pause.enabled, buttonsState.pause.active, buttonsState.pause.visible, this.#callbacks.onPause);
143
+ this.#addButton("open", this.#icon.open, buttonsState.open.enabled, buttonsState.open.active, buttonsState.open.visible, this.#callbacks.onOpen);
144
+ this.#addButton("opened", this.#icon.opened, buttonsState.opened.enabled, buttonsState.opened.active, buttonsState.opened.visible, this.#callbacks.onGoToOpened);
145
+ this.#addButton("repeat", this.#icon.repeat, buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible, this.#callbacks.onToggleLoop);
146
+ this.#addButton("repeatOff", this.#icon.repeatOff, buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible, this.#callbacks.onToggleLoop);
147
+ }
148
+
149
+ /**
150
+ * Creates and configures the animation progress slider.
151
+ * @private
152
+ */
153
+ #createSlider() {
154
+ this.#slider = document.createElement("input");
155
+ this.#slider.setAttribute("type", "range");
156
+ this.#slider.setAttribute("min", "0");
157
+ this.#slider.setAttribute("max", "1");
158
+ this.#slider.setAttribute("step", "0.01");
159
+ this.#slider.setAttribute("value", this.#animationProgress.toString());
160
+ this.#slider.classList.add("animation-menu-slider");
161
+ this.#handler.onSliderInput = this.#onSliderInput.bind(this);
162
+ this.#slider.addEventListener("input", this.#handler.onSliderInput);
163
+
164
+ this.#containerMain.appendChild(this.#slider);
165
+ }
166
+
167
+ /**
168
+ * Initializes and renders the menu UI, including buttons and slider.
169
+ * @private
170
+ */
171
+ #createMenu() {
172
+ this.#containerMain = document.createElement("div");
173
+ this.#containerMain.classList.add("pref-viewer-3d");
174
+ this.#containerMain.classList.add("animation-menu");
175
+ this.#containerMain.setAttribute("data-animation-name", this.#name);
176
+ this.#canvas.parentElement.prepend(this.#containerMain);
177
+
178
+ const style = document.createElement("style");
179
+ style.textContent = PrefViewer3DAnimationMenuStyles;
180
+ this.#containerMain.appendChild(style);
181
+
182
+ this.#containerButtons = document.createElement("div");
183
+ this.#containerButtons.classList.add("animation-menu-buttons");
184
+ this.#containerMain.appendChild(this.#containerButtons);
185
+
186
+ this.#createButtons();
187
+ this.#createSlider();
172
188
  }
173
189
 
174
190
  /**
@@ -178,24 +194,37 @@ export class OpeningAnimationMenu {
178
194
  * @returns {Button|null} The button control or null if not found.
179
195
  */
180
196
  #getButtonByName(name) {
181
- return this.#advancedDynamicTexture.getControlByName(`button_animation_${name}`);
197
+ return this.#containerButtons.querySelector(`button[name="button_animation_${name}"]`);
182
198
  }
183
199
 
184
200
  /**
185
- * Updates the visual state of a button (enabled, active, visible).
201
+ * Combines player and loop button states.
186
202
  * @private
187
- * @param {string} name - Button identifier.
188
- * @param {boolean} enabled
189
- * @param {boolean} active
190
- * @param {boolean} visible
203
+ * @returns {object}
191
204
  */
192
- #setButtonState(name, enabled, active, visible = true) {
193
- const button = this.#getButtonByName(name);
194
- if (!button) {
195
- return;
196
- }
197
- button.background = active ? this.#colorActive : enabled ? this.#colorEnabled : this.#colorDisabled;
198
- button.isVisible = visible;
205
+ #getButtonsState() {
206
+ return Object.assign(this.#getPlayerButtonsState(), this.#getLoopButtonsState());
207
+ }
208
+
209
+ /**
210
+ * Returns the state for loop control buttons.
211
+ * @private
212
+ * @returns {object}
213
+ */
214
+ #getLoopButtonsState() {
215
+ const buttonsState = {
216
+ repeat: {
217
+ enabled: this.#animationLoop,
218
+ active: false,
219
+ visible: this.#animationLoop,
220
+ },
221
+ repeatOff: {
222
+ enabled: !this.#animationLoop,
223
+ active: false,
224
+ visible: !this.#animationLoop,
225
+ },
226
+ };
227
+ return buttonsState;
199
228
  }
200
229
 
201
230
  /**
@@ -235,33 +264,33 @@ export class OpeningAnimationMenu {
235
264
  }
236
265
 
237
266
  /**
238
- * Returns the state for loop control buttons.
267
+ * Updates the visual state of a button (enabled, active, visible).
239
268
  * @private
240
- * @returns {object}
269
+ * @param {string} name - Button identifier.
270
+ * @param {boolean} enabled
271
+ * @param {boolean} active
272
+ * @param {boolean} visible
241
273
  */
242
- #getLoopButtonsState() {
243
- const buttonsState = {
244
- repeat: {
245
- enabled: this.#animationLoop,
246
- active: false,
247
- visible: this.#animationLoop,
248
- },
249
- repeatOff: {
250
- enabled: !this.#animationLoop,
251
- active: false,
252
- visible: !this.#animationLoop,
253
- },
254
- };
255
- return buttonsState;
274
+ #setButtonState(name, enabled, active, visible = true) {
275
+ const button = this.#getButtonByName(name);
276
+ if (!button) {
277
+ return;
278
+ }
279
+ const classToRemove = ["active", "enabled", "disabled", "hidden", "visible"];
280
+ button.classList.remove(...classToRemove);
281
+ const stateClass = active ? "active" : enabled ? "enabled" : "disabled";
282
+ const visibilityClass = visible ? "visible" : "hidden";
283
+ button.classList.add(stateClass, visibilityClass);
256
284
  }
257
285
 
258
286
  /**
259
- * Combines player and loop button states.
287
+ * Updates all loop control buttons according to current loop mode.
260
288
  * @private
261
- * @returns {object}
262
289
  */
263
- #getButtonsState() {
264
- return Object.assign(this.#getPlayerButtonsState(), this.#getLoopButtonsState());
290
+ #setLoopButtonsState() {
291
+ const buttonsState = this.#getLoopButtonsState();
292
+ this.#setButtonState("repeat", buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible);
293
+ this.#setButtonState("repeatOff", buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible);
265
294
  }
266
295
 
267
296
  /**
@@ -278,46 +307,38 @@ export class OpeningAnimationMenu {
278
307
  }
279
308
 
280
309
  /**
281
- * Updates all loop control buttons according to current loop mode.
310
+ * Handles the slider input event for animation progress.
282
311
  * @private
312
+ * @param {Event} event - The input event from the slider.
313
+ * @returns {void}
314
+ * @description
315
+ * If the slider value is being set programmatically, the event is ignored to prevent callback loops.
283
316
  */
284
- #setLoopButtonsState() {
285
- const buttonsState = this.#getLoopButtonsState();
286
- this.#setButtonState("repeat", buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible);
287
- this.#setButtonState("repeatOff", buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible);
317
+ #onSliderInput(event) {
318
+ if (this.#callbacks && typeof this.#callbacks.onSetAnimationProgress === "function") {
319
+ this.#callbacks.onSetAnimationProgress(event.target.value);
320
+ }
288
321
  }
289
322
 
290
323
  /**
291
- * Creates and configures the animation progress slider.
292
- * @private
324
+ * ---------------------------
325
+ * Public methods
326
+ * ---------------------------
293
327
  */
294
- #createSlider() {
295
- const sliderProps = {
296
- minimum: 0,
297
- maximum: 1,
298
- value: this.#animationProgress,
299
- height: `${this.#buttonSize}px`,
300
- width: `${this.#buttonSize * 7 + this.#buttonLoopPaddingLeft}px`, // Width based on number of buttons visible
301
- barOffset: `${this.#sliderBarOffset}px`,
302
- isThumbCircle: true,
303
- thumbWidth: `${this.#sliderThumbWidth}px`,
304
- background: this.#colorDisabled,
305
- color: this.#colorEnabled,
306
- borderColor: this.#colorBorder,
307
- thumbColor: this.#colorEnabled,
308
- };
309
- this.#slider = new Slider("slider_animation_progress");
310
- Object.assign(this.#slider, sliderProps);
311
- this.#slider.onValueChangedObservable.add((value) => {
312
- if (this.#isSettingSliderValue) {
313
- this.#isSettingSliderValue = false;
314
- return;
315
- }
316
- if (this.#callbacks && typeof this.#callbacks.onSetAnimationProgress === "function") {
317
- this.#callbacks.onSetAnimationProgress(value);
318
- }
319
- });
320
- this.#mainPanel.addControl(this.#slider);
328
+
329
+ /**
330
+ * Disposes the animation menu and releases all associated DOM resources.
331
+ * @public
332
+ * @returns {void}
333
+ */
334
+ dispose() {
335
+ this.#slider.removeEventListener("input", this.#handler.onSliderInput);
336
+ if (this.isVisible) {
337
+ this.#containerMain.remove();
338
+ }
339
+ this.#containerMain = null;
340
+ this.#containerButtons = null;
341
+ this.#slider = null;
321
342
  }
322
343
 
323
344
  /**
@@ -338,14 +359,12 @@ export class OpeningAnimationMenu {
338
359
 
339
360
  /**
340
361
  * Sets the animation progress value and updates the slider position.
341
- * When called, the slider value is updated programmatically without triggering the slider's value change callback.
342
362
  * @public
343
363
  * @param {number} progress - The new animation progress value (between 0 and 1).
344
364
  */
345
365
  set animationProgress(progress) {
346
366
  this.#animationProgress = progress;
347
- this.#isSettingSliderValue = true;
348
- this.#slider.value = progress;
367
+ this.#slider.value = progress.toString();
349
368
  }
350
369
 
351
370
  /**
@@ -357,4 +376,20 @@ export class OpeningAnimationMenu {
357
376
  this.#animationState = state;
358
377
  this.#setPlayerButtonsState();
359
378
  }
379
+
380
+ /**
381
+ * ---------------------------
382
+ * Public getters
383
+ * ---------------------------
384
+ */
385
+
386
+ /**
387
+ * Returns whether the animation menu is currently visible in the DOM.
388
+ * @public
389
+ * @returns {boolean} True if the menu is visible, false otherwise.
390
+ */
391
+ get isVisible() {
392
+ const menuElement = this.#canvas.parentElement.querySelector(`div.animation-menu[data-animation-name="${this.#name}"]`);
393
+ return !!menuElement;
394
+ }
360
395
  }