vidply 1.0.5 → 1.0.6

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,417 +1,417 @@
1
- /**
2
- * Settings Dialog Component
3
- */
4
-
5
- import { DOMUtils } from '../utils/DOMUtils.js';
6
- import { createIconElement } from '../icons/Icons.js';
7
- import { i18n } from '../i18n/i18n.js';
8
-
9
- export class SettingsDialog {
10
- constructor(player) {
11
- this.player = player;
12
- this.element = null;
13
- this.isOpen = false;
14
-
15
- this.init();
16
- }
17
-
18
- init() {
19
- this.createElement();
20
- }
21
-
22
- createElement() {
23
- // Create overlay
24
- this.overlay = DOMUtils.createElement('div', {
25
- className: `${this.player.options.classPrefix}-settings-overlay`,
26
- attributes: {
27
- 'role': 'dialog',
28
- 'aria-modal': 'true',
29
- 'aria-label': i18n.t('settings.title')
30
- }
31
- });
32
-
33
- this.overlay.style.display = 'none';
34
-
35
- // Create dialog
36
- this.element = DOMUtils.createElement('div', {
37
- className: `${this.player.options.classPrefix}-settings-dialog`
38
- });
39
-
40
- // Header
41
- const header = DOMUtils.createElement('div', {
42
- className: `${this.player.options.classPrefix}-settings-header`
43
- });
44
-
45
- const title = DOMUtils.createElement('h2', {
46
- textContent: i18n.t('settings.title'),
47
- attributes: {
48
- 'id': `${this.player.options.classPrefix}-settings-title`
49
- }
50
- });
51
-
52
- const closeButton = DOMUtils.createElement('button', {
53
- className: `${this.player.options.classPrefix}-button ${this.player.options.classPrefix}-settings-close`,
54
- attributes: {
55
- 'type': 'button',
56
- 'aria-label': i18n.t('settings.close')
57
- }
58
- });
59
- closeButton.appendChild(createIconElement('close'));
60
- closeButton.addEventListener('click', () => this.hide());
61
-
62
- header.appendChild(title);
63
- header.appendChild(closeButton);
64
-
65
- // Content
66
- const content = DOMUtils.createElement('div', {
67
- className: `${this.player.options.classPrefix}-settings-content`
68
- });
69
-
70
- content.appendChild(this.createSpeedSettings());
71
-
72
- if (this.player.captionManager && this.player.captionManager.tracks.length > 0) {
73
- content.appendChild(this.createCaptionSettings());
74
- }
75
-
76
- // Footer
77
- const footer = DOMUtils.createElement('div', {
78
- className: `${this.player.options.classPrefix}-settings-footer`
79
- });
80
-
81
- const resetButton = DOMUtils.createElement('button', {
82
- className: `${this.player.options.classPrefix}-button`,
83
- textContent: i18n.t('settings.reset'),
84
- attributes: {
85
- 'type': 'button'
86
- }
87
- });
88
- resetButton.addEventListener('click', () => this.resetSettings());
89
-
90
- footer.appendChild(resetButton);
91
-
92
- // Assemble dialog
93
- this.element.appendChild(header);
94
- this.element.appendChild(content);
95
- this.element.appendChild(footer);
96
-
97
- this.overlay.appendChild(this.element);
98
- this.player.container.appendChild(this.overlay);
99
-
100
- // Attach events
101
- this.overlay.addEventListener('click', (e) => {
102
- if (e.target === this.overlay) {
103
- this.hide();
104
- }
105
- });
106
-
107
- // Escape key to close
108
- document.addEventListener('keydown', (e) => {
109
- if (e.key === 'Escape' && this.isOpen) {
110
- this.hide();
111
- }
112
- });
113
- }
114
-
115
- formatSpeedLabel(speed) {
116
- // Special case: 1x is "Normal" (translated)
117
- if (speed === 1) {
118
- return i18n.t('speeds.normal');
119
- }
120
-
121
- // For other speeds, format with locale-specific decimal separator
122
- const speedStr = speed.toLocaleString(i18n.getLanguage(), {
123
- minimumFractionDigits: 0,
124
- maximumFractionDigits: 2
125
- });
126
-
127
- return `${speedStr}×`;
128
- }
129
-
130
- createSpeedSettings() {
131
- const section = DOMUtils.createElement('div', {
132
- className: `${this.player.options.classPrefix}-settings-section`
133
- });
134
-
135
- const label = DOMUtils.createElement('label', {
136
- textContent: i18n.t('settings.speed'),
137
- attributes: {
138
- 'for': `${this.player.options.classPrefix}-speed-select`
139
- }
140
- });
141
-
142
- const select = DOMUtils.createElement('select', {
143
- className: `${this.player.options.classPrefix}-settings-select`,
144
- attributes: {
145
- 'id': `${this.player.options.classPrefix}-speed-select`
146
- }
147
- });
148
-
149
- const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
150
-
151
- speeds.forEach(speed => {
152
- const option = DOMUtils.createElement('option', {
153
- textContent: this.formatSpeedLabel(speed),
154
- attributes: {
155
- 'value': String(speed)
156
- }
157
- });
158
-
159
- if (speed === this.player.state.playbackSpeed) {
160
- option.selected = true;
161
- }
162
-
163
- select.appendChild(option);
164
- });
165
-
166
- select.addEventListener('change', (e) => {
167
- this.player.setPlaybackSpeed(parseFloat(e.target.value));
168
- });
169
-
170
- section.appendChild(label);
171
- section.appendChild(select);
172
-
173
- return section;
174
- }
175
-
176
- createCaptionSettings() {
177
- const section = DOMUtils.createElement('div', {
178
- className: `${this.player.options.classPrefix}-settings-section`
179
- });
180
-
181
- const heading = DOMUtils.createElement('h3', {
182
- textContent: i18n.t('settings.captions')
183
- });
184
-
185
- section.appendChild(heading);
186
-
187
- // Caption track selection
188
- const trackLabel = DOMUtils.createElement('label', {
189
- textContent: i18n.t('captions.select'),
190
- attributes: {
191
- 'for': `${this.player.options.classPrefix}-caption-track-select`
192
- }
193
- });
194
-
195
- const trackSelect = DOMUtils.createElement('select', {
196
- className: `${this.player.options.classPrefix}-settings-select`,
197
- attributes: {
198
- 'id': `${this.player.options.classPrefix}-caption-track-select`
199
- }
200
- });
201
-
202
- // Off option
203
- const offOption = DOMUtils.createElement('option', {
204
- textContent: i18n.t('captions.off'),
205
- attributes: { 'value': '-1' }
206
- });
207
- trackSelect.appendChild(offOption);
208
-
209
- // Available tracks
210
- const tracks = this.player.captionManager.getAvailableTracks();
211
- tracks.forEach(track => {
212
- const option = DOMUtils.createElement('option', {
213
- textContent: track.label,
214
- attributes: { 'value': String(track.index) }
215
- });
216
- trackSelect.appendChild(option);
217
- });
218
-
219
- trackSelect.addEventListener('change', (e) => {
220
- const index = parseInt(e.target.value);
221
- if (index === -1) {
222
- this.player.disableCaptions();
223
- } else {
224
- this.player.captionManager.switchTrack(index);
225
- }
226
- });
227
-
228
- section.appendChild(trackLabel);
229
- section.appendChild(trackSelect);
230
-
231
- // Font size
232
- section.appendChild(this.createCaptionStyleControl('fontSize', i18n.t('captions.fontSize'), [
233
- { label: i18n.t('fontSizes.small'), value: '80%' },
234
- { label: i18n.t('fontSizes.medium'), value: '100%' },
235
- { label: i18n.t('fontSizes.large'), value: '120%' },
236
- { label: i18n.t('fontSizes.xlarge'), value: '150%' }
237
- ]));
238
-
239
- // Font family
240
- section.appendChild(this.createCaptionStyleControl('fontFamily', i18n.t('captions.fontFamily'), [
241
- { label: i18n.t('fontFamilies.sansSerif'), value: 'sans-serif' },
242
- { label: i18n.t('fontFamilies.serif'), value: 'serif' },
243
- { label: i18n.t('fontFamilies.monospace'), value: 'monospace' }
244
- ]));
245
-
246
- // Color controls
247
- section.appendChild(this.createColorControl('color', i18n.t('captions.color')));
248
- section.appendChild(this.createColorControl('backgroundColor', i18n.t('captions.backgroundColor')));
249
-
250
- // Opacity
251
- section.appendChild(this.createRangeControl('opacity', i18n.t('captions.opacity'), 0, 1, 0.1));
252
-
253
- return section;
254
- }
255
-
256
- createCaptionStyleControl(property, label, options) {
257
- const wrapper = DOMUtils.createElement('div', {
258
- className: `${this.player.options.classPrefix}-settings-control`
259
- });
260
-
261
- const labelEl = DOMUtils.createElement('label', {
262
- textContent: label,
263
- attributes: {
264
- 'for': `${this.player.options.classPrefix}-caption-${property}`
265
- }
266
- });
267
-
268
- const select = DOMUtils.createElement('select', {
269
- className: `${this.player.options.classPrefix}-settings-select`,
270
- attributes: {
271
- 'id': `${this.player.options.classPrefix}-caption-${property}`
272
- }
273
- });
274
-
275
- options.forEach(opt => {
276
- const option = DOMUtils.createElement('option', {
277
- textContent: opt.label,
278
- attributes: { 'value': opt.value }
279
- });
280
-
281
- if (opt.value === this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`]) {
282
- option.selected = true;
283
- }
284
-
285
- select.appendChild(option);
286
- });
287
-
288
- select.addEventListener('change', (e) => {
289
- this.player.captionManager.setCaptionStyle(property, e.target.value);
290
- });
291
-
292
- wrapper.appendChild(labelEl);
293
- wrapper.appendChild(select);
294
-
295
- return wrapper;
296
- }
297
-
298
- createColorControl(property, label) {
299
- const wrapper = DOMUtils.createElement('div', {
300
- className: `${this.player.options.classPrefix}-settings-control`
301
- });
302
-
303
- const labelEl = DOMUtils.createElement('label', {
304
- textContent: label,
305
- attributes: {
306
- 'for': `${this.player.options.classPrefix}-caption-${property}`
307
- }
308
- });
309
-
310
- const input = DOMUtils.createElement('input', {
311
- className: `${this.player.options.classPrefix}-settings-color`,
312
- attributes: {
313
- 'type': 'color',
314
- 'id': `${this.player.options.classPrefix}-caption-${property}`,
315
- 'value': this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`]
316
- }
317
- });
318
-
319
- input.addEventListener('change', (e) => {
320
- this.player.captionManager.setCaptionStyle(property, e.target.value);
321
- });
322
-
323
- wrapper.appendChild(labelEl);
324
- wrapper.appendChild(input);
325
-
326
- return wrapper;
327
- }
328
-
329
- createRangeControl(property, label, min, max, step) {
330
- const wrapper = DOMUtils.createElement('div', {
331
- className: `${this.player.options.classPrefix}-settings-control`
332
- });
333
-
334
- const labelEl = DOMUtils.createElement('label', {
335
- textContent: label,
336
- attributes: {
337
- 'for': `${this.player.options.classPrefix}-caption-${property}`
338
- }
339
- });
340
-
341
- const input = DOMUtils.createElement('input', {
342
- className: `${this.player.options.classPrefix}-settings-range`,
343
- attributes: {
344
- 'type': 'range',
345
- 'id': `${this.player.options.classPrefix}-caption-${property}`,
346
- 'min': String(min),
347
- 'max': String(max),
348
- 'step': String(step),
349
- 'value': String(this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`])
350
- }
351
- });
352
-
353
- const valueDisplay = DOMUtils.createElement('span', {
354
- className: `${this.player.options.classPrefix}-settings-value`,
355
- textContent: String(this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`])
356
- });
357
-
358
- input.addEventListener('input', (e) => {
359
- const value = parseFloat(e.target.value);
360
- valueDisplay.textContent = value.toFixed(1);
361
- this.player.captionManager.setCaptionStyle(property, value);
362
- });
363
-
364
- wrapper.appendChild(labelEl);
365
- wrapper.appendChild(input);
366
- wrapper.appendChild(valueDisplay);
367
-
368
- return wrapper;
369
- }
370
-
371
- resetSettings() {
372
- // Reset to default values
373
- this.player.setPlaybackSpeed(1);
374
-
375
- if (this.player.captionManager) {
376
- this.player.captionManager.setCaptionStyle('fontSize', '100%');
377
- this.player.captionManager.setCaptionStyle('fontFamily', 'sans-serif');
378
- this.player.captionManager.setCaptionStyle('color', '#FFFFFF');
379
- this.player.captionManager.setCaptionStyle('backgroundColor', '#000000');
380
- this.player.captionManager.setCaptionStyle('opacity', 0.8);
381
- }
382
-
383
- // Refresh dialog
384
- this.hide();
385
- setTimeout(() => this.show(), 100);
386
- }
387
-
388
- show() {
389
- this.overlay.style.display = 'flex';
390
- this.isOpen = true;
391
-
392
- // Focus the close button
393
- const closeButton = this.element.querySelector(`.${this.player.options.classPrefix}-settings-close`);
394
- if (closeButton) {
395
- closeButton.focus();
396
- }
397
-
398
- this.player.emit('settingsopen');
399
- }
400
-
401
- hide() {
402
- this.overlay.style.display = 'none';
403
- this.isOpen = false;
404
-
405
- // Return focus to settings button
406
- this.player.container.focus();
407
-
408
- this.player.emit('settingsclose');
409
- }
410
-
411
- destroy() {
412
- if (this.overlay && this.overlay.parentNode) {
413
- this.overlay.parentNode.removeChild(this.overlay);
414
- }
415
- }
416
- }
417
-
1
+ /**
2
+ * Settings Dialog Component
3
+ */
4
+
5
+ import { DOMUtils } from '../utils/DOMUtils.js';
6
+ import { createIconElement } from '../icons/Icons.js';
7
+ import { i18n } from '../i18n/i18n.js';
8
+
9
+ export class SettingsDialog {
10
+ constructor(player) {
11
+ this.player = player;
12
+ this.element = null;
13
+ this.isOpen = false;
14
+
15
+ this.init();
16
+ }
17
+
18
+ init() {
19
+ this.createElement();
20
+ }
21
+
22
+ createElement() {
23
+ // Create overlay
24
+ this.overlay = DOMUtils.createElement('div', {
25
+ className: `${this.player.options.classPrefix}-settings-overlay`,
26
+ attributes: {
27
+ 'role': 'dialog',
28
+ 'aria-modal': 'true',
29
+ 'aria-label': i18n.t('settings.title')
30
+ }
31
+ });
32
+
33
+ this.overlay.style.display = 'none';
34
+
35
+ // Create dialog
36
+ this.element = DOMUtils.createElement('div', {
37
+ className: `${this.player.options.classPrefix}-settings-dialog`
38
+ });
39
+
40
+ // Header
41
+ const header = DOMUtils.createElement('div', {
42
+ className: `${this.player.options.classPrefix}-settings-header`
43
+ });
44
+
45
+ const title = DOMUtils.createElement('h2', {
46
+ textContent: i18n.t('settings.title'),
47
+ attributes: {
48
+ 'id': `${this.player.options.classPrefix}-settings-title`
49
+ }
50
+ });
51
+
52
+ const closeButton = DOMUtils.createElement('button', {
53
+ className: `${this.player.options.classPrefix}-button ${this.player.options.classPrefix}-settings-close`,
54
+ attributes: {
55
+ 'type': 'button',
56
+ 'aria-label': i18n.t('settings.close')
57
+ }
58
+ });
59
+ closeButton.appendChild(createIconElement('close'));
60
+ closeButton.addEventListener('click', () => this.hide());
61
+
62
+ header.appendChild(title);
63
+ header.appendChild(closeButton);
64
+
65
+ // Content
66
+ const content = DOMUtils.createElement('div', {
67
+ className: `${this.player.options.classPrefix}-settings-content`
68
+ });
69
+
70
+ content.appendChild(this.createSpeedSettings());
71
+
72
+ if (this.player.captionManager && this.player.captionManager.tracks.length > 0) {
73
+ content.appendChild(this.createCaptionSettings());
74
+ }
75
+
76
+ // Footer
77
+ const footer = DOMUtils.createElement('div', {
78
+ className: `${this.player.options.classPrefix}-settings-footer`
79
+ });
80
+
81
+ const resetButton = DOMUtils.createElement('button', {
82
+ className: `${this.player.options.classPrefix}-button`,
83
+ textContent: i18n.t('settings.reset'),
84
+ attributes: {
85
+ 'type': 'button'
86
+ }
87
+ });
88
+ resetButton.addEventListener('click', () => this.resetSettings());
89
+
90
+ footer.appendChild(resetButton);
91
+
92
+ // Assemble dialog
93
+ this.element.appendChild(header);
94
+ this.element.appendChild(content);
95
+ this.element.appendChild(footer);
96
+
97
+ this.overlay.appendChild(this.element);
98
+ this.player.container.appendChild(this.overlay);
99
+
100
+ // Attach events
101
+ this.overlay.addEventListener('click', (e) => {
102
+ if (e.target === this.overlay) {
103
+ this.hide();
104
+ }
105
+ });
106
+
107
+ // Escape key to close
108
+ document.addEventListener('keydown', (e) => {
109
+ if (e.key === 'Escape' && this.isOpen) {
110
+ this.hide();
111
+ }
112
+ });
113
+ }
114
+
115
+ formatSpeedLabel(speed) {
116
+ // Special case: 1x is "Normal" (translated)
117
+ if (speed === 1) {
118
+ return i18n.t('speeds.normal');
119
+ }
120
+
121
+ // For other speeds, format with locale-specific decimal separator
122
+ const speedStr = speed.toLocaleString(i18n.getLanguage(), {
123
+ minimumFractionDigits: 0,
124
+ maximumFractionDigits: 2
125
+ });
126
+
127
+ return `${speedStr}×`;
128
+ }
129
+
130
+ createSpeedSettings() {
131
+ const section = DOMUtils.createElement('div', {
132
+ className: `${this.player.options.classPrefix}-settings-section`
133
+ });
134
+
135
+ const label = DOMUtils.createElement('label', {
136
+ textContent: i18n.t('settings.speed'),
137
+ attributes: {
138
+ 'for': `${this.player.options.classPrefix}-speed-select`
139
+ }
140
+ });
141
+
142
+ const select = DOMUtils.createElement('select', {
143
+ className: `${this.player.options.classPrefix}-settings-select`,
144
+ attributes: {
145
+ 'id': `${this.player.options.classPrefix}-speed-select`
146
+ }
147
+ });
148
+
149
+ const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
150
+
151
+ speeds.forEach(speed => {
152
+ const option = DOMUtils.createElement('option', {
153
+ textContent: this.formatSpeedLabel(speed),
154
+ attributes: {
155
+ 'value': String(speed)
156
+ }
157
+ });
158
+
159
+ if (speed === this.player.state.playbackSpeed) {
160
+ option.selected = true;
161
+ }
162
+
163
+ select.appendChild(option);
164
+ });
165
+
166
+ select.addEventListener('change', (e) => {
167
+ this.player.setPlaybackSpeed(parseFloat(e.target.value));
168
+ });
169
+
170
+ section.appendChild(label);
171
+ section.appendChild(select);
172
+
173
+ return section;
174
+ }
175
+
176
+ createCaptionSettings() {
177
+ const section = DOMUtils.createElement('div', {
178
+ className: `${this.player.options.classPrefix}-settings-section`
179
+ });
180
+
181
+ const heading = DOMUtils.createElement('h3', {
182
+ textContent: i18n.t('settings.captions')
183
+ });
184
+
185
+ section.appendChild(heading);
186
+
187
+ // Caption track selection
188
+ const trackLabel = DOMUtils.createElement('label', {
189
+ textContent: i18n.t('captions.select'),
190
+ attributes: {
191
+ 'for': `${this.player.options.classPrefix}-caption-track-select`
192
+ }
193
+ });
194
+
195
+ const trackSelect = DOMUtils.createElement('select', {
196
+ className: `${this.player.options.classPrefix}-settings-select`,
197
+ attributes: {
198
+ 'id': `${this.player.options.classPrefix}-caption-track-select`
199
+ }
200
+ });
201
+
202
+ // Off option
203
+ const offOption = DOMUtils.createElement('option', {
204
+ textContent: i18n.t('captions.off'),
205
+ attributes: { 'value': '-1' }
206
+ });
207
+ trackSelect.appendChild(offOption);
208
+
209
+ // Available tracks
210
+ const tracks = this.player.captionManager.getAvailableTracks();
211
+ tracks.forEach(track => {
212
+ const option = DOMUtils.createElement('option', {
213
+ textContent: track.label,
214
+ attributes: { 'value': String(track.index) }
215
+ });
216
+ trackSelect.appendChild(option);
217
+ });
218
+
219
+ trackSelect.addEventListener('change', (e) => {
220
+ const index = parseInt(e.target.value);
221
+ if (index === -1) {
222
+ this.player.disableCaptions();
223
+ } else {
224
+ this.player.captionManager.switchTrack(index);
225
+ }
226
+ });
227
+
228
+ section.appendChild(trackLabel);
229
+ section.appendChild(trackSelect);
230
+
231
+ // Font size
232
+ section.appendChild(this.createCaptionStyleControl('fontSize', i18n.t('captions.fontSize'), [
233
+ { label: i18n.t('fontSizes.small'), value: '80%' },
234
+ { label: i18n.t('fontSizes.medium'), value: '100%' },
235
+ { label: i18n.t('fontSizes.large'), value: '120%' },
236
+ { label: i18n.t('fontSizes.xlarge'), value: '150%' }
237
+ ]));
238
+
239
+ // Font family
240
+ section.appendChild(this.createCaptionStyleControl('fontFamily', i18n.t('captions.fontFamily'), [
241
+ { label: i18n.t('fontFamilies.sansSerif'), value: 'sans-serif' },
242
+ { label: i18n.t('fontFamilies.serif'), value: 'serif' },
243
+ { label: i18n.t('fontFamilies.monospace'), value: 'monospace' }
244
+ ]));
245
+
246
+ // Color controls
247
+ section.appendChild(this.createColorControl('color', i18n.t('captions.color')));
248
+ section.appendChild(this.createColorControl('backgroundColor', i18n.t('captions.backgroundColor')));
249
+
250
+ // Opacity
251
+ section.appendChild(this.createRangeControl('opacity', i18n.t('captions.opacity'), 0, 1, 0.1));
252
+
253
+ return section;
254
+ }
255
+
256
+ createCaptionStyleControl(property, label, options) {
257
+ const wrapper = DOMUtils.createElement('div', {
258
+ className: `${this.player.options.classPrefix}-settings-control`
259
+ });
260
+
261
+ const labelEl = DOMUtils.createElement('label', {
262
+ textContent: label,
263
+ attributes: {
264
+ 'for': `${this.player.options.classPrefix}-caption-${property}`
265
+ }
266
+ });
267
+
268
+ const select = DOMUtils.createElement('select', {
269
+ className: `${this.player.options.classPrefix}-settings-select`,
270
+ attributes: {
271
+ 'id': `${this.player.options.classPrefix}-caption-${property}`
272
+ }
273
+ });
274
+
275
+ options.forEach(opt => {
276
+ const option = DOMUtils.createElement('option', {
277
+ textContent: opt.label,
278
+ attributes: { 'value': opt.value }
279
+ });
280
+
281
+ if (opt.value === this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`]) {
282
+ option.selected = true;
283
+ }
284
+
285
+ select.appendChild(option);
286
+ });
287
+
288
+ select.addEventListener('change', (e) => {
289
+ this.player.captionManager.setCaptionStyle(property, e.target.value);
290
+ });
291
+
292
+ wrapper.appendChild(labelEl);
293
+ wrapper.appendChild(select);
294
+
295
+ return wrapper;
296
+ }
297
+
298
+ createColorControl(property, label) {
299
+ const wrapper = DOMUtils.createElement('div', {
300
+ className: `${this.player.options.classPrefix}-settings-control`
301
+ });
302
+
303
+ const labelEl = DOMUtils.createElement('label', {
304
+ textContent: label,
305
+ attributes: {
306
+ 'for': `${this.player.options.classPrefix}-caption-${property}`
307
+ }
308
+ });
309
+
310
+ const input = DOMUtils.createElement('input', {
311
+ className: `${this.player.options.classPrefix}-settings-color`,
312
+ attributes: {
313
+ 'type': 'color',
314
+ 'id': `${this.player.options.classPrefix}-caption-${property}`,
315
+ 'value': this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`]
316
+ }
317
+ });
318
+
319
+ input.addEventListener('change', (e) => {
320
+ this.player.captionManager.setCaptionStyle(property, e.target.value);
321
+ });
322
+
323
+ wrapper.appendChild(labelEl);
324
+ wrapper.appendChild(input);
325
+
326
+ return wrapper;
327
+ }
328
+
329
+ createRangeControl(property, label, min, max, step) {
330
+ const wrapper = DOMUtils.createElement('div', {
331
+ className: `${this.player.options.classPrefix}-settings-control`
332
+ });
333
+
334
+ const labelEl = DOMUtils.createElement('label', {
335
+ textContent: label,
336
+ attributes: {
337
+ 'for': `${this.player.options.classPrefix}-caption-${property}`
338
+ }
339
+ });
340
+
341
+ const input = DOMUtils.createElement('input', {
342
+ className: `${this.player.options.classPrefix}-settings-range`,
343
+ attributes: {
344
+ 'type': 'range',
345
+ 'id': `${this.player.options.classPrefix}-caption-${property}`,
346
+ 'min': String(min),
347
+ 'max': String(max),
348
+ 'step': String(step),
349
+ 'value': String(this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`])
350
+ }
351
+ });
352
+
353
+ const valueDisplay = DOMUtils.createElement('span', {
354
+ className: `${this.player.options.classPrefix}-settings-value`,
355
+ textContent: String(this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`])
356
+ });
357
+
358
+ input.addEventListener('input', (e) => {
359
+ const value = parseFloat(e.target.value);
360
+ valueDisplay.textContent = value.toFixed(1);
361
+ this.player.captionManager.setCaptionStyle(property, value);
362
+ });
363
+
364
+ wrapper.appendChild(labelEl);
365
+ wrapper.appendChild(input);
366
+ wrapper.appendChild(valueDisplay);
367
+
368
+ return wrapper;
369
+ }
370
+
371
+ resetSettings() {
372
+ // Reset to default values
373
+ this.player.setPlaybackSpeed(1);
374
+
375
+ if (this.player.captionManager) {
376
+ this.player.captionManager.setCaptionStyle('fontSize', '100%');
377
+ this.player.captionManager.setCaptionStyle('fontFamily', 'sans-serif');
378
+ this.player.captionManager.setCaptionStyle('color', '#FFFFFF');
379
+ this.player.captionManager.setCaptionStyle('backgroundColor', '#000000');
380
+ this.player.captionManager.setCaptionStyle('opacity', 0.8);
381
+ }
382
+
383
+ // Refresh dialog
384
+ this.hide();
385
+ setTimeout(() => this.show(), 100);
386
+ }
387
+
388
+ show() {
389
+ this.overlay.style.display = 'flex';
390
+ this.isOpen = true;
391
+
392
+ // Focus the close button
393
+ const closeButton = this.element.querySelector(`.${this.player.options.classPrefix}-settings-close`);
394
+ if (closeButton) {
395
+ closeButton.focus();
396
+ }
397
+
398
+ this.player.emit('settingsopen');
399
+ }
400
+
401
+ hide() {
402
+ this.overlay.style.display = 'none';
403
+ this.isOpen = false;
404
+
405
+ // Return focus to settings button
406
+ this.player.container.focus();
407
+
408
+ this.player.emit('settingsclose');
409
+ }
410
+
411
+ destroy() {
412
+ if (this.overlay && this.overlay.parentNode) {
413
+ this.overlay.parentNode.removeChild(this.overlay);
414
+ }
415
+ }
416
+ }
417
+