vidply 1.0.27 → 1.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/vidply.TranscriptManager-QSF2PWUN.js +1744 -0
- package/dist/dev/vidply.TranscriptManager-QSF2PWUN.js.map +7 -0
- package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js +1744 -0
- package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js.map +7 -0
- package/dist/dev/vidply.chunk-5663PYKK.js +1631 -0
- package/dist/dev/vidply.chunk-5663PYKK.js.map +7 -0
- package/dist/dev/vidply.chunk-SRM7VNHG.js +1638 -0
- package/dist/dev/vidply.chunk-SRM7VNHG.js.map +7 -0
- package/dist/dev/vidply.de-RXAJM5QE.js +181 -0
- package/dist/dev/vidply.de-RXAJM5QE.js.map +7 -0
- package/dist/dev/vidply.de-SNL6AJ4D.js +188 -0
- package/dist/dev/vidply.de-SNL6AJ4D.js.map +7 -0
- package/dist/dev/vidply.es-2QCQKZ4U.js +188 -0
- package/dist/dev/vidply.es-2QCQKZ4U.js.map +7 -0
- package/dist/dev/vidply.es-SADVLJTQ.js +181 -0
- package/dist/dev/vidply.es-SADVLJTQ.js.map +7 -0
- package/dist/dev/vidply.esm.js +9 -9
- package/dist/dev/vidply.esm.js.map +2 -2
- package/dist/dev/vidply.fr-FJAZRL4L.js +188 -0
- package/dist/dev/vidply.fr-FJAZRL4L.js.map +7 -0
- package/dist/dev/vidply.fr-V3VAYBBT.js +181 -0
- package/dist/dev/vidply.fr-V3VAYBBT.js.map +7 -0
- package/dist/dev/vidply.ja-2XQOW53T.js +188 -0
- package/dist/dev/vidply.ja-2XQOW53T.js.map +7 -0
- package/dist/dev/vidply.ja-KL2TLZGJ.js +181 -0
- package/dist/dev/vidply.ja-KL2TLZGJ.js.map +7 -0
- package/dist/legacy/vidply.js +53 -13
- package/dist/legacy/vidply.js.map +2 -2
- package/dist/legacy/vidply.min.js +1 -1
- package/dist/legacy/vidply.min.meta.json +15 -15
- package/dist/prod/vidply.TranscriptManager-DZ2WZU3K.min.js +6 -0
- package/dist/prod/vidply.TranscriptManager-E5QHGFIR.min.js +6 -0
- package/dist/prod/vidply.chunk-5DWTMWEO.min.js +6 -0
- package/dist/prod/vidply.chunk-IBNYTGGM.min.js +6 -0
- package/dist/prod/vidply.de-FR3XX54P.min.js +6 -0
- package/dist/prod/vidply.de-HGJBCLLE.min.js +6 -0
- package/dist/prod/vidply.es-3IJCQLJ7.min.js +6 -0
- package/dist/prod/vidply.es-CZEBXCZN.min.js +6 -0
- package/dist/prod/vidply.esm.min.js +4 -4
- package/dist/prod/vidply.fr-HFOL7MWA.min.js +6 -0
- package/dist/prod/vidply.fr-NC4VEAPH.min.js +6 -0
- package/dist/prod/vidply.ja-4ZC6ZQLV.min.js +6 -0
- package/dist/prod/vidply.ja-QTVU5C25.min.js +6 -0
- package/dist/vidply.esm.min.meta.json +34 -34
- package/package.json +1 -1
- package/src/controls/TranscriptManager.js +1 -1
- package/src/features/PlaylistManager.js +12 -12
- package/src/i18n/languages/de.js +9 -1
- package/src/i18n/languages/en.js +9 -1
- package/src/i18n/languages/es.js +9 -1
- package/src/i18n/languages/fr.js +9 -1
- package/src/i18n/languages/ja.js +9 -1
|
@@ -0,0 +1,1638 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Universal, Accessible Video Player
|
|
3
|
+
* (c) 2025 Matthias Peltzer
|
|
4
|
+
* Released under GPL-2.0-or-later License
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// src/utils/DOMUtils.js
|
|
8
|
+
var DOMUtils = {
|
|
9
|
+
createElement(tag, options = {}) {
|
|
10
|
+
const element = document.createElement(tag);
|
|
11
|
+
if (options.className) {
|
|
12
|
+
element.className = options.className;
|
|
13
|
+
}
|
|
14
|
+
if (options.attributes) {
|
|
15
|
+
Object.entries(options.attributes).forEach(([key, value]) => {
|
|
16
|
+
element.setAttribute(key, value);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (options.innerHTML) {
|
|
20
|
+
element.innerHTML = options.innerHTML;
|
|
21
|
+
}
|
|
22
|
+
if (options.textContent) {
|
|
23
|
+
element.textContent = options.textContent;
|
|
24
|
+
}
|
|
25
|
+
if (options.style) {
|
|
26
|
+
Object.assign(element.style, options.style);
|
|
27
|
+
}
|
|
28
|
+
if (options.children) {
|
|
29
|
+
options.children.forEach((child) => {
|
|
30
|
+
if (child) element.appendChild(child);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return element;
|
|
34
|
+
},
|
|
35
|
+
addClass(element, className) {
|
|
36
|
+
if (element && className) {
|
|
37
|
+
element.classList.add(className);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
removeClass(element, className) {
|
|
41
|
+
if (element && className) {
|
|
42
|
+
element.classList.remove(className);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
toggleClass(element, className) {
|
|
46
|
+
if (element && className) {
|
|
47
|
+
element.classList.toggle(className);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
hasClass(element, className) {
|
|
51
|
+
return element && element.classList.contains(className);
|
|
52
|
+
},
|
|
53
|
+
show(element) {
|
|
54
|
+
if (element) {
|
|
55
|
+
element.style.display = "";
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
hide(element) {
|
|
59
|
+
if (element) {
|
|
60
|
+
element.style.display = "none";
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
fadeIn(element, duration = 300) {
|
|
64
|
+
if (!element) return;
|
|
65
|
+
element.style.opacity = "0";
|
|
66
|
+
element.style.display = "";
|
|
67
|
+
let start = null;
|
|
68
|
+
const animate = (timestamp) => {
|
|
69
|
+
if (!start) start = timestamp;
|
|
70
|
+
const progress = timestamp - start;
|
|
71
|
+
const opacity = Math.min(progress / duration, 1);
|
|
72
|
+
element.style.opacity = opacity;
|
|
73
|
+
if (progress < duration) {
|
|
74
|
+
requestAnimationFrame(animate);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
requestAnimationFrame(animate);
|
|
78
|
+
},
|
|
79
|
+
fadeOut(element, duration = 300) {
|
|
80
|
+
if (!element) return;
|
|
81
|
+
const startOpacity = parseFloat(getComputedStyle(element).opacity) || 1;
|
|
82
|
+
let start = null;
|
|
83
|
+
const animate = (timestamp) => {
|
|
84
|
+
if (!start) start = timestamp;
|
|
85
|
+
const progress = timestamp - start;
|
|
86
|
+
const opacity = Math.max(startOpacity - progress / duration, 0);
|
|
87
|
+
element.style.opacity = opacity;
|
|
88
|
+
if (progress < duration) {
|
|
89
|
+
requestAnimationFrame(animate);
|
|
90
|
+
} else {
|
|
91
|
+
element.style.display = "none";
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
requestAnimationFrame(animate);
|
|
95
|
+
},
|
|
96
|
+
offset(element) {
|
|
97
|
+
if (!element) return { top: 0, left: 0 };
|
|
98
|
+
const rect = element.getBoundingClientRect();
|
|
99
|
+
return {
|
|
100
|
+
top: rect.top + window.pageYOffset,
|
|
101
|
+
left: rect.left + window.pageXOffset,
|
|
102
|
+
width: rect.width,
|
|
103
|
+
height: rect.height
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
escapeHTML(str) {
|
|
107
|
+
const div = document.createElement("div");
|
|
108
|
+
div.textContent = str;
|
|
109
|
+
return div.innerHTML;
|
|
110
|
+
},
|
|
111
|
+
sanitizeHTML(html) {
|
|
112
|
+
const temp = document.createElement("div");
|
|
113
|
+
const safeHtml = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, "").replace(/on\w+\s*=/gi, "").replace(/javascript:/gi, "");
|
|
114
|
+
temp.innerHTML = safeHtml;
|
|
115
|
+
return temp.innerHTML;
|
|
116
|
+
},
|
|
117
|
+
/**
|
|
118
|
+
* Create a tooltip element that is aria-hidden (not read by screen readers)
|
|
119
|
+
* @param {string} text - Tooltip text
|
|
120
|
+
* @param {string} classPrefix - Class prefix for styling
|
|
121
|
+
* @returns {HTMLElement} Tooltip element
|
|
122
|
+
*/
|
|
123
|
+
createTooltip(text, classPrefix = "vidply") {
|
|
124
|
+
const tooltip = this.createElement("span", {
|
|
125
|
+
className: `${classPrefix}-tooltip`,
|
|
126
|
+
textContent: text,
|
|
127
|
+
attributes: {
|
|
128
|
+
"aria-hidden": "true"
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return tooltip;
|
|
132
|
+
},
|
|
133
|
+
/**
|
|
134
|
+
* Attach a tooltip to an element
|
|
135
|
+
* @param {HTMLElement} element - Element to attach tooltip to
|
|
136
|
+
* @param {string} text - Tooltip text
|
|
137
|
+
* @param {string} classPrefix - Class prefix for styling
|
|
138
|
+
*/
|
|
139
|
+
attachTooltip(element, text, classPrefix = "vidply") {
|
|
140
|
+
if (!element || !text) return;
|
|
141
|
+
const existingTooltip = element.querySelector(`.${classPrefix}-tooltip`);
|
|
142
|
+
if (existingTooltip) {
|
|
143
|
+
existingTooltip.remove();
|
|
144
|
+
}
|
|
145
|
+
const tooltip = this.createTooltip(text, classPrefix);
|
|
146
|
+
element.appendChild(tooltip);
|
|
147
|
+
const showTooltip = () => {
|
|
148
|
+
tooltip.classList.add(`${classPrefix}-tooltip-visible`);
|
|
149
|
+
};
|
|
150
|
+
const hideTooltip = () => {
|
|
151
|
+
tooltip.classList.remove(`${classPrefix}-tooltip-visible`);
|
|
152
|
+
};
|
|
153
|
+
element.addEventListener("mouseenter", showTooltip);
|
|
154
|
+
element.addEventListener("mouseleave", hideTooltip);
|
|
155
|
+
element.addEventListener("focus", showTooltip);
|
|
156
|
+
element.addEventListener("blur", hideTooltip);
|
|
157
|
+
},
|
|
158
|
+
/**
|
|
159
|
+
* Create visible button text that is hidden by CSS but visible when CSS is disabled
|
|
160
|
+
* @param {string} text - Button text
|
|
161
|
+
* @param {string} classPrefix - Class prefix for styling
|
|
162
|
+
* @returns {HTMLElement} Button text element
|
|
163
|
+
*/
|
|
164
|
+
createButtonText(text, classPrefix = "vidply") {
|
|
165
|
+
const buttonText = this.createElement("span", {
|
|
166
|
+
className: `${classPrefix}-button-text`,
|
|
167
|
+
textContent: text,
|
|
168
|
+
attributes: {
|
|
169
|
+
"aria-hidden": "true"
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return buttonText;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// src/i18n/languages/en.js
|
|
177
|
+
var en = {
|
|
178
|
+
player: {
|
|
179
|
+
label: "Video Player",
|
|
180
|
+
play: "Play",
|
|
181
|
+
pause: "Pause",
|
|
182
|
+
stop: "Stop",
|
|
183
|
+
restart: "Restart from beginning",
|
|
184
|
+
rewind: "Rewind",
|
|
185
|
+
forward: "Forward",
|
|
186
|
+
rewindSeconds: "Rewind {seconds} seconds",
|
|
187
|
+
forwardSeconds: "Forward {seconds} seconds",
|
|
188
|
+
previous: "Previous track",
|
|
189
|
+
next: "Next track",
|
|
190
|
+
playlist: "Toggle playlist",
|
|
191
|
+
volume: "Volume",
|
|
192
|
+
mute: "Mute",
|
|
193
|
+
unmute: "Unmute",
|
|
194
|
+
fullscreen: "Fullscreen",
|
|
195
|
+
exitFullscreen: "Exit Fullscreen",
|
|
196
|
+
captions: "Captions",
|
|
197
|
+
chapters: "Chapters",
|
|
198
|
+
quality: "Quality",
|
|
199
|
+
captionStyling: "Caption styling",
|
|
200
|
+
transcript: "Toggle transcript",
|
|
201
|
+
audioDescription: "Audio description",
|
|
202
|
+
signLanguage: "Sign language video",
|
|
203
|
+
settings: "Settings",
|
|
204
|
+
speed: "Playback Speed",
|
|
205
|
+
pip: "Picture in Picture",
|
|
206
|
+
currentTime: "Current time",
|
|
207
|
+
duration: "Duration",
|
|
208
|
+
progress: "Progress",
|
|
209
|
+
seekForward: "Seek forward {seconds} seconds",
|
|
210
|
+
seekBackward: "Seek backward {seconds} seconds",
|
|
211
|
+
volumeUp: "Volume up",
|
|
212
|
+
volumeDown: "Volume down",
|
|
213
|
+
loading: "Loading...",
|
|
214
|
+
loadingChapters: "Loading chapters...",
|
|
215
|
+
error: "Error loading media",
|
|
216
|
+
buffering: "Buffering...",
|
|
217
|
+
signLanguageVideo: "Sign Language Video",
|
|
218
|
+
closeSignLanguage: "Close sign language video",
|
|
219
|
+
signLanguageSettings: "Sign language settings",
|
|
220
|
+
noChapters: "No chapters available",
|
|
221
|
+
noCaptions: "No captions available",
|
|
222
|
+
auto: "Auto",
|
|
223
|
+
autoQuality: "Auto (no quality selection available)",
|
|
224
|
+
noQuality: "Quality selection not available",
|
|
225
|
+
signLanguageDragResize: "Sign Language Video - Press D to drag with keyboard, R to resize",
|
|
226
|
+
signLanguageDragActive: "Sign Language Video - Drag mode active. Use arrow keys to move, Escape to exit.",
|
|
227
|
+
signLanguageResizeActive: "Sign Language Video - Resize mode active. Use left/right arrow keys to resize, Escape to exit.",
|
|
228
|
+
enableSignDragMode: "Enable drag mode. Shortcut: D key",
|
|
229
|
+
disableSignDragMode: "Disable drag mode. Shortcut: D key",
|
|
230
|
+
enableSignDragModeAria: "Enable toggle keyboard drag mode with arrow keys. Shortcut: D key",
|
|
231
|
+
disableSignDragModeAria: "Disable toggle keyboard drag mode with arrow keys. Shortcut: D key",
|
|
232
|
+
enableSignResizeMode: "Enable resize mode. Shortcut: R key",
|
|
233
|
+
disableSignResizeMode: "Disable resize mode. Shortcut: R key",
|
|
234
|
+
enableSignResizeModeAria: "Enable keyboard resize mode with arrow keys. Shortcut: R key",
|
|
235
|
+
disableSignResizeModeAria: "Disable keyboard resize mode with arrow keys. Shortcut: R key",
|
|
236
|
+
resizeHandle: "Resize {direction} corner",
|
|
237
|
+
moreOptions: "More options",
|
|
238
|
+
noMoreOptions: "No additional options available"
|
|
239
|
+
},
|
|
240
|
+
captions: {
|
|
241
|
+
off: "Off",
|
|
242
|
+
select: "Select captions",
|
|
243
|
+
fontSize: "Font Size",
|
|
244
|
+
fontFamily: "Font Family",
|
|
245
|
+
color: "Text Color",
|
|
246
|
+
backgroundColor: "Background Color",
|
|
247
|
+
opacity: "Opacity"
|
|
248
|
+
},
|
|
249
|
+
fontSizes: {
|
|
250
|
+
small: "Small",
|
|
251
|
+
normal: "Normal",
|
|
252
|
+
large: "Large",
|
|
253
|
+
xlarge: "X-Large"
|
|
254
|
+
},
|
|
255
|
+
fontFamilies: {
|
|
256
|
+
sansSerif: "Sans-serif",
|
|
257
|
+
serif: "Serif",
|
|
258
|
+
monospace: "Monospace"
|
|
259
|
+
},
|
|
260
|
+
styleLabels: {
|
|
261
|
+
textColor: "Text Color",
|
|
262
|
+
background: "Background",
|
|
263
|
+
font: "Font",
|
|
264
|
+
fontSize: "Font Size",
|
|
265
|
+
opacity: "Opacity"
|
|
266
|
+
},
|
|
267
|
+
audioDescription: {
|
|
268
|
+
enable: "Enable audio description",
|
|
269
|
+
disable: "Disable audio description"
|
|
270
|
+
},
|
|
271
|
+
signLanguage: {
|
|
272
|
+
show: "Show sign language video",
|
|
273
|
+
hide: "Hide sign language video"
|
|
274
|
+
},
|
|
275
|
+
transcript: {
|
|
276
|
+
title: "Transcript",
|
|
277
|
+
ariaLabel: "Video Transcript",
|
|
278
|
+
close: "Close transcript",
|
|
279
|
+
loading: "Loading transcript...",
|
|
280
|
+
noTranscript: "No transcript available for this video.",
|
|
281
|
+
settings: "Transcript settings. Press Enter to open menu, or D to enable drag mode",
|
|
282
|
+
keyboardDragMode: "Toggle keyboard drag mode with arrow keys. Shortcut: D key",
|
|
283
|
+
keyboardDragActive: "⌨️ Keyboard Drag Mode Active (Arrow keys to move, Shift+Arrows for large steps, D or ESC to exit)",
|
|
284
|
+
dragResizePrompt: "Press D to drag or R to resize. Use Home to reset position, Esc to close.",
|
|
285
|
+
dragModeEnabled: "Keyboard drag mode enabled. Use arrow keys to move, Shift+Arrow for larger steps. Press D or Esc to exit.",
|
|
286
|
+
dragModeDisabled: "Keyboard drag mode disabled.",
|
|
287
|
+
enableDragMode: "Enable drag mode. Shortcut: D key",
|
|
288
|
+
disableDragMode: "Disable drag mode. Shortcut: D key",
|
|
289
|
+
enableDragModeAria: "Enable toggle keyboard drag mode with arrow keys. Shortcut: D key",
|
|
290
|
+
disableDragModeAria: "Disable toggle keyboard drag mode with arrow keys. Shortcut: D key",
|
|
291
|
+
resizeWindow: "Resize Window",
|
|
292
|
+
disableResizeWindow: "Disable Resize Mode",
|
|
293
|
+
enableResizeMode: "Enable resize mode. Shortcut: R key",
|
|
294
|
+
disableResizeMode: "Disable resize mode. Shortcut: R key",
|
|
295
|
+
enableResizeModeAria: "Enable keyboard resize mode with arrow keys. Shortcut: R key",
|
|
296
|
+
disableResizeModeAria: "Disable keyboard resize mode with arrow keys. Shortcut: R key",
|
|
297
|
+
resizeModeHint: "Resize handles enabled. Drag edges or corners to adjust. Press Esc or R to exit.",
|
|
298
|
+
resizeModeEnabled: "Resize mode enabled. Drag edges or corners to adjust. Press Esc or R to exit.",
|
|
299
|
+
resizeModeDisabled: "Resize mode disabled.",
|
|
300
|
+
positionReset: "Transcript position reset.",
|
|
301
|
+
styleTranscript: "Open transcript style settings",
|
|
302
|
+
closeMenu: "Close Menu",
|
|
303
|
+
styleTitle: "Transcript Style",
|
|
304
|
+
autoscroll: "Autoscroll",
|
|
305
|
+
settingsMenu: "Transcript dialog settings",
|
|
306
|
+
showTimestamps: "Show timestamps",
|
|
307
|
+
hideTimestamps: "Hide timestamps",
|
|
308
|
+
showTimestampsAria: "Show timestamps in transcript",
|
|
309
|
+
hideTimestampsAria: "Hide timestamps in transcript"
|
|
310
|
+
},
|
|
311
|
+
settings: {
|
|
312
|
+
title: "Settings",
|
|
313
|
+
quality: "Quality",
|
|
314
|
+
speed: "Speed",
|
|
315
|
+
captions: "Captions",
|
|
316
|
+
language: "Language",
|
|
317
|
+
reset: "Reset to defaults",
|
|
318
|
+
close: "Close"
|
|
319
|
+
},
|
|
320
|
+
speeds: {
|
|
321
|
+
normal: "Normal"
|
|
322
|
+
},
|
|
323
|
+
time: {
|
|
324
|
+
display: "Time display",
|
|
325
|
+
durationPrefix: "Duration: ",
|
|
326
|
+
of: "of",
|
|
327
|
+
hour: "{count} hour",
|
|
328
|
+
hours: "{count} hours",
|
|
329
|
+
minute: "{count} minute",
|
|
330
|
+
minutes: "{count} minutes",
|
|
331
|
+
second: "{count} second",
|
|
332
|
+
seconds: "{count} seconds"
|
|
333
|
+
},
|
|
334
|
+
playlist: {
|
|
335
|
+
title: "Playlist",
|
|
336
|
+
trackOf: "Track {current} of {total}",
|
|
337
|
+
nowPlaying: "Now playing: Track {current} of {total}. {title}{artist}",
|
|
338
|
+
by: " by ",
|
|
339
|
+
untitled: "Untitled",
|
|
340
|
+
trackUntitled: "Track {number}",
|
|
341
|
+
currentlyPlaying: "Currently playing",
|
|
342
|
+
notPlaying: "Not playing",
|
|
343
|
+
pressEnterPlay: "Press Enter to play",
|
|
344
|
+
pressEnterRestart: "Press Enter to restart",
|
|
345
|
+
keyboardInstructions: "Playlist navigation: Use Up and Down arrow keys to move between tracks. Press Page Up or Page Down to skip 5 tracks. Press Home to go to first track, End to go to last track. Press Enter or Space to play the selected track.",
|
|
346
|
+
endOfPlaylist: "End of playlist. {current} of {total}.",
|
|
347
|
+
beginningOfPlaylist: "Beginning of playlist. 1 of {total}.",
|
|
348
|
+
jumpedToLastTrack: "Jumped to last track. {current} of {total}.",
|
|
349
|
+
jumpedToFirstTrack: "Jumped to first track. 1 of {total}.",
|
|
350
|
+
firstTrack: "First track. 1 of {total}.",
|
|
351
|
+
lastTrack: "Last track. {current} of {total}."
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// src/i18n/translations.js
|
|
356
|
+
var builtInLanguageLoaders = {
|
|
357
|
+
de: () => import("./vidply.de-SNL6AJ4D.js"),
|
|
358
|
+
es: () => import("./vidply.es-2QCQKZ4U.js"),
|
|
359
|
+
fr: () => import("./vidply.fr-FJAZRL4L.js"),
|
|
360
|
+
ja: () => import("./vidply.ja-2XQOW53T.js")
|
|
361
|
+
};
|
|
362
|
+
function getBaseTranslations() {
|
|
363
|
+
return { en };
|
|
364
|
+
}
|
|
365
|
+
function getBuiltInLanguageLoaders() {
|
|
366
|
+
return builtInLanguageLoaders;
|
|
367
|
+
}
|
|
368
|
+
async function loadBuiltInTranslation(lang) {
|
|
369
|
+
const loader = builtInLanguageLoaders[lang];
|
|
370
|
+
if (!loader) return null;
|
|
371
|
+
const module = await loader();
|
|
372
|
+
return module[lang] || module.default || null;
|
|
373
|
+
}
|
|
374
|
+
var translations = getBaseTranslations();
|
|
375
|
+
|
|
376
|
+
// src/i18n/i18n.js
|
|
377
|
+
var I18n = class {
|
|
378
|
+
constructor() {
|
|
379
|
+
this.currentLanguage = "en";
|
|
380
|
+
this.translations = getBaseTranslations();
|
|
381
|
+
this.loadingPromises = /* @__PURE__ */ new Map();
|
|
382
|
+
this.builtInLanguageLoaders = getBuiltInLanguageLoaders();
|
|
383
|
+
}
|
|
384
|
+
setLanguage(lang) {
|
|
385
|
+
if (this.translations[lang]) {
|
|
386
|
+
this.currentLanguage = lang;
|
|
387
|
+
} else {
|
|
388
|
+
console.warn(`Language "${lang}" not found, falling back to English`);
|
|
389
|
+
this.currentLanguage = "en";
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
getLanguage() {
|
|
393
|
+
return this.currentLanguage;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Ensure a language is available, loading built-ins on demand.
|
|
397
|
+
* @param {string} lang Language code
|
|
398
|
+
* @returns {Promise<string|null>} Normalized language code if available
|
|
399
|
+
*/
|
|
400
|
+
async ensureLanguage(lang) {
|
|
401
|
+
const normalizedLang = (lang || "").toLowerCase();
|
|
402
|
+
if (!normalizedLang) return this.currentLanguage;
|
|
403
|
+
if (this.translations[normalizedLang]) {
|
|
404
|
+
return normalizedLang;
|
|
405
|
+
}
|
|
406
|
+
if (this.loadingPromises.has(normalizedLang)) {
|
|
407
|
+
await this.loadingPromises.get(normalizedLang);
|
|
408
|
+
return this.translations[normalizedLang] ? normalizedLang : null;
|
|
409
|
+
}
|
|
410
|
+
if (!this.builtInLanguageLoaders[normalizedLang]) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
const loadPromise = (async () => {
|
|
414
|
+
try {
|
|
415
|
+
const loaded = await loadBuiltInTranslation(normalizedLang);
|
|
416
|
+
if (loaded) {
|
|
417
|
+
this.translations[normalizedLang] = loaded;
|
|
418
|
+
}
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.warn(`Language "${normalizedLang}" failed to load:`, error);
|
|
421
|
+
} finally {
|
|
422
|
+
this.loadingPromises.delete(normalizedLang);
|
|
423
|
+
}
|
|
424
|
+
})();
|
|
425
|
+
this.loadingPromises.set(normalizedLang, loadPromise);
|
|
426
|
+
await loadPromise;
|
|
427
|
+
return this.translations[normalizedLang] ? normalizedLang : null;
|
|
428
|
+
}
|
|
429
|
+
t(key, replacements = {}) {
|
|
430
|
+
const keys = key.split(".");
|
|
431
|
+
let value = this.translations[this.currentLanguage];
|
|
432
|
+
for (const k of keys) {
|
|
433
|
+
if (value && typeof value === "object" && k in value) {
|
|
434
|
+
value = value[k];
|
|
435
|
+
} else {
|
|
436
|
+
value = this.translations.en;
|
|
437
|
+
for (const fallbackKey of keys) {
|
|
438
|
+
if (value && typeof value === "object" && fallbackKey in value) {
|
|
439
|
+
value = value[fallbackKey];
|
|
440
|
+
} else {
|
|
441
|
+
return key;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (typeof value === "string") {
|
|
448
|
+
Object.entries(replacements).forEach(([placeholder, replacement]) => {
|
|
449
|
+
value = value.replace(new RegExp(`{${placeholder}}`, "g"), replacement);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
return value;
|
|
453
|
+
}
|
|
454
|
+
addTranslation(lang, translations2) {
|
|
455
|
+
if (!this.translations[lang]) {
|
|
456
|
+
this.translations[lang] = {};
|
|
457
|
+
}
|
|
458
|
+
Object.assign(this.translations[lang], translations2);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Load a language file from a URL (JSON or YAML)
|
|
462
|
+
* @param {string} langCode - Language code (e.g., 'pt', 'it')
|
|
463
|
+
* @param {string} url - URL to the language file (JSON or YAML)
|
|
464
|
+
* @returns {Promise<void>}
|
|
465
|
+
*/
|
|
466
|
+
async loadLanguageFromUrl(langCode, url) {
|
|
467
|
+
if (this.loadingPromises.has(url)) {
|
|
468
|
+
return this.loadingPromises.get(url);
|
|
469
|
+
}
|
|
470
|
+
const loadPromise = (async () => {
|
|
471
|
+
try {
|
|
472
|
+
const response = await fetch(url);
|
|
473
|
+
if (!response.ok) {
|
|
474
|
+
throw new Error(`Failed to load language file: ${response.statusText}`);
|
|
475
|
+
}
|
|
476
|
+
const contentType = response.headers.get("content-type") || "";
|
|
477
|
+
let translations2;
|
|
478
|
+
const buffer = await response.arrayBuffer();
|
|
479
|
+
const utf8Text = new TextDecoder("utf-8").decode(buffer);
|
|
480
|
+
if (contentType.includes("application/json") || url.endsWith(".json")) {
|
|
481
|
+
translations2 = JSON.parse(utf8Text);
|
|
482
|
+
} else if (contentType.includes("text/yaml") || contentType.includes("application/x-yaml") || url.endsWith(".yaml") || url.endsWith(".yml")) {
|
|
483
|
+
try {
|
|
484
|
+
translations2 = JSON.parse(utf8Text);
|
|
485
|
+
} catch (e) {
|
|
486
|
+
if (typeof window !== "undefined" && window.jsyaml) {
|
|
487
|
+
translations2 = window.jsyaml.load(utf8Text);
|
|
488
|
+
} else {
|
|
489
|
+
console.warn("YAML parsing requires js-yaml library. Please include it or use JSON format.");
|
|
490
|
+
throw new Error("YAML parsing not available. Please use JSON format or include js-yaml library.");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
translations2 = JSON.parse(utf8Text);
|
|
495
|
+
}
|
|
496
|
+
this.addTranslation(langCode, translations2);
|
|
497
|
+
return translations2;
|
|
498
|
+
} catch (error) {
|
|
499
|
+
console.error(`Error loading language file from ${url}:`, error);
|
|
500
|
+
throw error;
|
|
501
|
+
} finally {
|
|
502
|
+
this.loadingPromises.delete(url);
|
|
503
|
+
}
|
|
504
|
+
})();
|
|
505
|
+
this.loadingPromises.set(url, loadPromise);
|
|
506
|
+
return loadPromise;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Load multiple language files from URLs
|
|
510
|
+
* @param {Object} languageMap - Object mapping language codes to URLs
|
|
511
|
+
* @returns {Promise<void>}
|
|
512
|
+
*/
|
|
513
|
+
async loadLanguagesFromUrls(languageMap) {
|
|
514
|
+
const promises = Object.entries(languageMap).map(
|
|
515
|
+
([langCode, url]) => this.loadLanguageFromUrl(langCode, url)
|
|
516
|
+
);
|
|
517
|
+
await Promise.all(promises);
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
var i18n = new I18n();
|
|
521
|
+
|
|
522
|
+
// src/utils/TimeUtils.js
|
|
523
|
+
var TimeUtils = {
|
|
524
|
+
/**
|
|
525
|
+
* Format seconds to time string (HH:MM:SS or MM:SS)
|
|
526
|
+
*/
|
|
527
|
+
formatTime(seconds, alwaysShowHours = false) {
|
|
528
|
+
if (!isFinite(seconds) || seconds < 0) {
|
|
529
|
+
return alwaysShowHours ? "00:00:00" : "00:00";
|
|
530
|
+
}
|
|
531
|
+
const hours = Math.floor(seconds / 3600);
|
|
532
|
+
const minutes = Math.floor(seconds % 3600 / 60);
|
|
533
|
+
const secs = Math.floor(seconds % 60);
|
|
534
|
+
const pad = (num) => String(num).padStart(2, "0");
|
|
535
|
+
if (hours > 0 || alwaysShowHours) {
|
|
536
|
+
return `${pad(hours)}:${pad(minutes)}:${pad(secs)}`;
|
|
537
|
+
}
|
|
538
|
+
return `${pad(minutes)}:${pad(secs)}`;
|
|
539
|
+
},
|
|
540
|
+
/**
|
|
541
|
+
* Parse time string to seconds
|
|
542
|
+
*/
|
|
543
|
+
parseTime(timeString) {
|
|
544
|
+
const parts = timeString.split(":").map((p) => parseInt(p, 10));
|
|
545
|
+
if (parts.length === 3) {
|
|
546
|
+
return parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
547
|
+
} else if (parts.length === 2) {
|
|
548
|
+
return parts[0] * 60 + parts[1];
|
|
549
|
+
} else if (parts.length === 1) {
|
|
550
|
+
return parts[0];
|
|
551
|
+
}
|
|
552
|
+
return 0;
|
|
553
|
+
},
|
|
554
|
+
/**
|
|
555
|
+
* Format seconds to readable duration
|
|
556
|
+
*/
|
|
557
|
+
formatDuration(seconds) {
|
|
558
|
+
if (!isFinite(seconds) || seconds < 0) {
|
|
559
|
+
return i18n.t("time.seconds", { count: 0 });
|
|
560
|
+
}
|
|
561
|
+
const hours = Math.floor(seconds / 3600);
|
|
562
|
+
const minutes = Math.floor(seconds % 3600 / 60);
|
|
563
|
+
const secs = Math.floor(seconds % 60);
|
|
564
|
+
const parts = [];
|
|
565
|
+
if (hours > 0) {
|
|
566
|
+
const key = hours === 1 ? "time.hour" : "time.hours";
|
|
567
|
+
parts.push(i18n.t(key, { count: hours }));
|
|
568
|
+
}
|
|
569
|
+
if (minutes > 0) {
|
|
570
|
+
const key = minutes === 1 ? "time.minute" : "time.minutes";
|
|
571
|
+
parts.push(i18n.t(key, { count: minutes }));
|
|
572
|
+
}
|
|
573
|
+
if (secs > 0 || parts.length === 0) {
|
|
574
|
+
const key = secs === 1 ? "time.second" : "time.seconds";
|
|
575
|
+
parts.push(i18n.t(key, { count: secs }));
|
|
576
|
+
}
|
|
577
|
+
return parts.join(", ");
|
|
578
|
+
},
|
|
579
|
+
/**
|
|
580
|
+
* Format percentage
|
|
581
|
+
*/
|
|
582
|
+
formatPercentage(value, total) {
|
|
583
|
+
if (total === 0) return 0;
|
|
584
|
+
return Math.round(value / total * 100);
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
// src/icons/Icons.js
|
|
589
|
+
var iconPaths = {
|
|
590
|
+
play: `<path d="M8 5v14l11-7z"/>`,
|
|
591
|
+
pause: `<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>`,
|
|
592
|
+
stop: `<rect x="6" y="6" width="12" height="12"/>`,
|
|
593
|
+
rewind: `<path d="M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z"/>`,
|
|
594
|
+
forward: `<path d="M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z"/>`,
|
|
595
|
+
skipPrevious: `<path d="M6 6h2v12H6V6zm3 6l8.5 6V6L9 12z"/>`,
|
|
596
|
+
skipNext: `<path d="M16 6h2v12h-2V6zM6 6l8.5 6L6 18V6z"/>`,
|
|
597
|
+
restart: `<path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/>`,
|
|
598
|
+
volumeHigh: `<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>`,
|
|
599
|
+
volumeMedium: `<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>`,
|
|
600
|
+
volumeLow: `<path d="M7 9v6h4l5 5V4l-5 5H7z"/>`,
|
|
601
|
+
volumeMuted: `<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>`,
|
|
602
|
+
fullscreen: `<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>`,
|
|
603
|
+
fullscreenExit: `<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>`,
|
|
604
|
+
settings: `<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94L14.4 2.81c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/>`,
|
|
605
|
+
captions: `<path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z"/>`,
|
|
606
|
+
captionsOff: `<path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z"/><path d="M0 0h24v24H0z" fill="none"/>`,
|
|
607
|
+
pip: `<path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98V5c0-1.1-.9-2-2-2zm0 16.01H3V4.98h18v14.03z"/>`,
|
|
608
|
+
speed: `<path d="M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44z"/><path d="M10.59 15.41a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z"/>`,
|
|
609
|
+
close: `<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>`,
|
|
610
|
+
check: `<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>`,
|
|
611
|
+
loading: `<path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/>`,
|
|
612
|
+
error: `<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>`,
|
|
613
|
+
playlist: `<path d="M15 6H3v2h12V6zm0 4H3v2h12v-2zM3 16h8v-2H3v2zM17 6v8.18c-.31-.11-.65-.18-1-.18-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3V8h3V6h-5z"/>`,
|
|
614
|
+
hd: `<path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-8 12H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm7-1c0 .55-.45 1-1 1h-.75v1.5h-1.5V15H14c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v4zm-3.5-.5h2v-3h-2v3z"/>`,
|
|
615
|
+
transcript: `<path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/>`,
|
|
616
|
+
chapters: `<path d="M3 5h2v2H3V5zm0 4h2v2H3V9zm0 4h2v2H3v-2zm0 4h2v2H3v-2zM7 5h14v2H7V5zm0 4h14v2H7V9zm0 4h14v2H7v-2zm0 4h14v2H7v-2z"/>`,
|
|
617
|
+
audioDescription: `<rect x="2" y="5" width="20" height="14" rx="2" fill="#ffffff" stroke="#ffffff" stroke-width="2"/><text x="12" y="16" font-family="Arial, sans-serif" font-size="10" font-weight="bold" text-anchor="middle" fill="#1a1a1a">AD</text>`,
|
|
618
|
+
audioDescriptionOn: `<rect x="2" y="5" width="20" height="14" rx="2" fill="none" stroke="currentColor" stroke-width="2"/><text x="12" y="16" font-family="Arial, sans-serif" font-size="10" font-weight="bold" text-anchor="middle" fill="currentColor">AD</text>`,
|
|
619
|
+
signLanguage: `<g transform="scale(1.5)"><path d="M16 11.3c-.1-.9-4.8 1.3-5.4 1.1-2.6-1 5.8-1.3 5.1-2.9s-5.1 1.5-6 1.4C6.5 9.4 16.5 9.1 13.5 8c-1.9-.6-8.8 2.9-6.8.4.7-.6.7-1.9-.7-1.7-9.7 7.2-.7 12.2 8.8 7 0-1.3-3.5.4-4.1.4-2.6 0 5.6-2 5.4-3ZM3.9 7.8c3.2-4.2 3.7 1.2 6 .1s.2-.2.2-.3c.7-2.7 2.5-7.5-1.5-1.3-1.6 0 1.1-4 1-4.6C8.9-1 7.3 4.4 7.2 4.9c-1.6.7-.9-1.4-.7-1.5 3-6-.6-3.1-.9.4-2.5 1.8 0-2.8 0-3.5C2.8-.9 4 9.4 1.1 4.9S.1 4.6 0 5c-.4 2.7 2.6 7.2 3.9 2.8Z"/></g>`,
|
|
620
|
+
signLanguageOn: `<g transform="scale(1.5)"><path d="M16 11.3c-.1-.9-4.8 1.3-5.4 1.1-2.6-1 5.8-1.3 5.1-2.9s-5.1 1.5-6 1.4C6.5 9.4 16.5 9.1 13.5 8c-1.9-.6-8.8 2.9-6.8.4.7-.6.7-1.9-.7-1.7-9.7 7.2-.7 12.2 8.8 7 0-1.3-3.5.4-4.1.4-2.6 0 5.6-2 5.4-3ZM3.9 7.8c3.2-4.2 3.7 1.2 6 .1s.2-.2.2-.3c.7-2.7 2.5-7.5-1.5-1.3-1.6 0 1.1-4 1-4.6C8.9-1 7.3 4.4 7.2 4.9c-1.6.7-.9-1.4-.7-1.5 3-6-.6-3.1-.9.4-2.5 1.8 0-2.8 0-3.5C2.8-.9 4 9.4 1.1 4.9S.1 4.6 0 5c-.4 2.7 2.6 7.2 3.9 2.8Z"/></g>`,
|
|
621
|
+
music: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7zm-1.5 16c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>`,
|
|
622
|
+
moreVertical: `<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>`,
|
|
623
|
+
move: `<path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"/>`,
|
|
624
|
+
resize: `<path d="M21.71 11.29l-9-9c-.39-.39-1.02-.39-1.41 0l-9 9c-.39.39-.39 1.02 0 1.41l9 9c.39.39 1.02.39 1.41 0l9-9c.39-.38.39-1.01 0-1.41zM14 14.5V12h-4v2.5L7 11l3-3.5V10h4V7.5l3 3.5-3 3.5z"/>`,
|
|
625
|
+
clock: `<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/><path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"/>`
|
|
626
|
+
};
|
|
627
|
+
var svgWrapper = (paths) => `<svg viewBox="0 0 24 24" fill="currentColor">${paths}</svg>`;
|
|
628
|
+
var Icons = Object.fromEntries(
|
|
629
|
+
Object.entries(iconPaths).map(([key, value]) => [key, svgWrapper(value)])
|
|
630
|
+
);
|
|
631
|
+
function getIcon(name) {
|
|
632
|
+
return Icons[name] || Icons.play;
|
|
633
|
+
}
|
|
634
|
+
function createIconElement(name, className = "") {
|
|
635
|
+
const wrapper = document.createElement("span");
|
|
636
|
+
wrapper.className = `vidply-icon ${className}`.trim();
|
|
637
|
+
wrapper.innerHTML = getIcon(name);
|
|
638
|
+
wrapper.setAttribute("aria-hidden", "true");
|
|
639
|
+
return wrapper;
|
|
640
|
+
}
|
|
641
|
+
function createPlayOverlay() {
|
|
642
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
643
|
+
svg.setAttribute("class", "vidply-play-overlay");
|
|
644
|
+
svg.setAttribute("viewBox", "0 0 80 80");
|
|
645
|
+
svg.setAttribute("width", "80");
|
|
646
|
+
svg.setAttribute("height", "80");
|
|
647
|
+
svg.setAttribute("aria-hidden", "true");
|
|
648
|
+
svg.setAttribute("role", "presentation");
|
|
649
|
+
svg.style.cursor = "pointer";
|
|
650
|
+
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
651
|
+
const filterId = `vidply-play-shadow-${Math.random().toString(36).substr(2, 9)}`;
|
|
652
|
+
const filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
|
|
653
|
+
filter.setAttribute("id", filterId);
|
|
654
|
+
filter.setAttribute("x", "-50%");
|
|
655
|
+
filter.setAttribute("y", "-50%");
|
|
656
|
+
filter.setAttribute("width", "200%");
|
|
657
|
+
filter.setAttribute("height", "200%");
|
|
658
|
+
const feGaussianBlur = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
|
|
659
|
+
feGaussianBlur.setAttribute("in", "SourceAlpha");
|
|
660
|
+
feGaussianBlur.setAttribute("stdDeviation", "3");
|
|
661
|
+
const feOffset = document.createElementNS("http://www.w3.org/2000/svg", "feOffset");
|
|
662
|
+
feOffset.setAttribute("dx", "0");
|
|
663
|
+
feOffset.setAttribute("dy", "2");
|
|
664
|
+
feOffset.setAttribute("result", "offsetblur");
|
|
665
|
+
const feComponentTransfer = document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
|
|
666
|
+
const feFuncA = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
|
|
667
|
+
feFuncA.setAttribute("type", "linear");
|
|
668
|
+
feFuncA.setAttribute("slope", "0.3");
|
|
669
|
+
feComponentTransfer.appendChild(feFuncA);
|
|
670
|
+
const feMerge = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
|
|
671
|
+
const feMergeNode1 = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
|
|
672
|
+
const feMergeNode2 = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
|
|
673
|
+
feMergeNode2.setAttribute("in", "SourceGraphic");
|
|
674
|
+
feMerge.appendChild(feMergeNode1);
|
|
675
|
+
feMerge.appendChild(feMergeNode2);
|
|
676
|
+
filter.appendChild(feGaussianBlur);
|
|
677
|
+
filter.appendChild(feOffset);
|
|
678
|
+
filter.appendChild(feComponentTransfer);
|
|
679
|
+
filter.appendChild(feMerge);
|
|
680
|
+
defs.appendChild(filter);
|
|
681
|
+
svg.appendChild(defs);
|
|
682
|
+
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
683
|
+
circle.setAttribute("cx", "40");
|
|
684
|
+
circle.setAttribute("cy", "40");
|
|
685
|
+
circle.setAttribute("r", "40");
|
|
686
|
+
circle.setAttribute("fill", "rgba(255, 255, 255, 0.95)");
|
|
687
|
+
circle.setAttribute("filter", `url(#${filterId})`);
|
|
688
|
+
circle.setAttribute("class", "vidply-play-overlay-bg");
|
|
689
|
+
svg.appendChild(circle);
|
|
690
|
+
const playTriangle = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
|
|
691
|
+
playTriangle.setAttribute("points", "32,28 32,52 54,40");
|
|
692
|
+
playTriangle.setAttribute("fill", "#0a406e");
|
|
693
|
+
playTriangle.setAttribute("class", "vidply-play-overlay-icon");
|
|
694
|
+
svg.appendChild(playTriangle);
|
|
695
|
+
return svg;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/utils/FocusUtils.js
|
|
699
|
+
function focusElement(element, { delay = 0, preventScroll = true } = {}) {
|
|
700
|
+
if (!element) return;
|
|
701
|
+
requestAnimationFrame(() => {
|
|
702
|
+
setTimeout(() => {
|
|
703
|
+
if (element && document.contains(element)) {
|
|
704
|
+
element.focus({ preventScroll });
|
|
705
|
+
}
|
|
706
|
+
}, delay);
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
function focusFirstElement(container, selector, options = {}) {
|
|
710
|
+
if (!container) return;
|
|
711
|
+
const element = container.querySelector(selector);
|
|
712
|
+
if (element) {
|
|
713
|
+
focusElement(element, options);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/utils/StorageManager.js
|
|
718
|
+
var StorageManager = class {
|
|
719
|
+
constructor(namespace = "vidply") {
|
|
720
|
+
this.namespace = namespace;
|
|
721
|
+
this.storage = this.isStorageAvailable() ? localStorage : null;
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Check if localStorage is available
|
|
725
|
+
*/
|
|
726
|
+
isStorageAvailable() {
|
|
727
|
+
try {
|
|
728
|
+
const test = "__storage_test__";
|
|
729
|
+
localStorage.setItem(test, test);
|
|
730
|
+
localStorage.removeItem(test);
|
|
731
|
+
return true;
|
|
732
|
+
} catch (e) {
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Get a namespaced key
|
|
738
|
+
*/
|
|
739
|
+
getKey(key) {
|
|
740
|
+
return `${this.namespace}_${key}`;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Save a value to storage
|
|
744
|
+
*/
|
|
745
|
+
set(key, value) {
|
|
746
|
+
if (!this.storage) return false;
|
|
747
|
+
try {
|
|
748
|
+
const namespacedKey = this.getKey(key);
|
|
749
|
+
this.storage.setItem(namespacedKey, JSON.stringify(value));
|
|
750
|
+
return true;
|
|
751
|
+
} catch (e) {
|
|
752
|
+
console.warn("Failed to save to localStorage:", e);
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Get a value from storage
|
|
758
|
+
*/
|
|
759
|
+
get(key, defaultValue = null) {
|
|
760
|
+
if (!this.storage) return defaultValue;
|
|
761
|
+
try {
|
|
762
|
+
const namespacedKey = this.getKey(key);
|
|
763
|
+
const value = this.storage.getItem(namespacedKey);
|
|
764
|
+
return value ? JSON.parse(value) : defaultValue;
|
|
765
|
+
} catch (e) {
|
|
766
|
+
console.warn("Failed to read from localStorage:", e);
|
|
767
|
+
return defaultValue;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Remove a value from storage
|
|
772
|
+
*/
|
|
773
|
+
remove(key) {
|
|
774
|
+
if (!this.storage) return false;
|
|
775
|
+
try {
|
|
776
|
+
const namespacedKey = this.getKey(key);
|
|
777
|
+
this.storage.removeItem(namespacedKey);
|
|
778
|
+
return true;
|
|
779
|
+
} catch (e) {
|
|
780
|
+
console.warn("Failed to remove from localStorage:", e);
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Clear all namespaced values
|
|
786
|
+
*/
|
|
787
|
+
clear() {
|
|
788
|
+
if (!this.storage) return false;
|
|
789
|
+
try {
|
|
790
|
+
const keys = Object.keys(this.storage);
|
|
791
|
+
keys.forEach((key) => {
|
|
792
|
+
if (key.startsWith(this.namespace)) {
|
|
793
|
+
this.storage.removeItem(key);
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
return true;
|
|
797
|
+
} catch (e) {
|
|
798
|
+
console.warn("Failed to clear localStorage:", e);
|
|
799
|
+
return false;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Save transcript preferences
|
|
804
|
+
*/
|
|
805
|
+
saveTranscriptPreferences(preferences) {
|
|
806
|
+
return this.set("transcript_preferences", preferences);
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Get transcript preferences
|
|
810
|
+
*/
|
|
811
|
+
getTranscriptPreferences() {
|
|
812
|
+
return this.get("transcript_preferences", null);
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Save caption preferences
|
|
816
|
+
*/
|
|
817
|
+
saveCaptionPreferences(preferences) {
|
|
818
|
+
return this.set("caption_preferences", preferences);
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Get caption preferences
|
|
822
|
+
*/
|
|
823
|
+
getCaptionPreferences() {
|
|
824
|
+
return this.get("caption_preferences", null);
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Save player preferences (volume, speed, etc.)
|
|
828
|
+
*/
|
|
829
|
+
savePlayerPreferences(preferences) {
|
|
830
|
+
return this.set("player_preferences", preferences);
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Get player preferences
|
|
834
|
+
*/
|
|
835
|
+
getPlayerPreferences() {
|
|
836
|
+
return this.get("player_preferences", null);
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Save sign language preferences (position and size)
|
|
840
|
+
*/
|
|
841
|
+
saveSignLanguagePreferences(preferences) {
|
|
842
|
+
return this.set("sign_language_preferences", preferences);
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Get sign language preferences
|
|
846
|
+
*/
|
|
847
|
+
getSignLanguagePreferences() {
|
|
848
|
+
return this.get("sign_language_preferences", null);
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
// src/utils/DraggableResizable.js
|
|
853
|
+
var DraggableResizable = class {
|
|
854
|
+
constructor(element, options = {}) {
|
|
855
|
+
this.element = element;
|
|
856
|
+
this.options = {
|
|
857
|
+
dragHandle: null,
|
|
858
|
+
// Element to use as drag handle (defaults to element itself)
|
|
859
|
+
resizeHandles: [],
|
|
860
|
+
// Array of resize handle elements
|
|
861
|
+
onDragStart: null,
|
|
862
|
+
onDrag: null,
|
|
863
|
+
onDragEnd: null,
|
|
864
|
+
onResizeStart: null,
|
|
865
|
+
onResize: null,
|
|
866
|
+
onResizeEnd: null,
|
|
867
|
+
constrainToViewport: true,
|
|
868
|
+
// Allow movement outside viewport?
|
|
869
|
+
minWidth: 150,
|
|
870
|
+
minHeight: 100,
|
|
871
|
+
maintainAspectRatio: false,
|
|
872
|
+
keyboardDragKey: "d",
|
|
873
|
+
keyboardResizeKey: "r",
|
|
874
|
+
keyboardStep: 5,
|
|
875
|
+
keyboardStepLarge: 10,
|
|
876
|
+
maxWidth: null,
|
|
877
|
+
maxHeight: null,
|
|
878
|
+
pointerResizeIndicatorText: null,
|
|
879
|
+
onPointerResizeToggle: null,
|
|
880
|
+
classPrefix: "draggable",
|
|
881
|
+
storage: null,
|
|
882
|
+
// StorageManager instance for saving position/size
|
|
883
|
+
storageKey: null,
|
|
884
|
+
// Key for localStorage (if storage is provided)
|
|
885
|
+
...options
|
|
886
|
+
};
|
|
887
|
+
this.isDragging = false;
|
|
888
|
+
this.isResizing = false;
|
|
889
|
+
this.resizeDirection = null;
|
|
890
|
+
this.dragOffsetX = 0;
|
|
891
|
+
this.dragOffsetY = 0;
|
|
892
|
+
this.positionOffsetX = 0;
|
|
893
|
+
this.positionOffsetY = 0;
|
|
894
|
+
this.initialMouseX = 0;
|
|
895
|
+
this.initialMouseY = 0;
|
|
896
|
+
this.needsPositionConversion = false;
|
|
897
|
+
this.resizeStartX = 0;
|
|
898
|
+
this.resizeStartY = 0;
|
|
899
|
+
this.resizeStartWidth = 0;
|
|
900
|
+
this.resizeStartHeight = 0;
|
|
901
|
+
this.resizeStartLeft = 0;
|
|
902
|
+
this.resizeStartTop = 0;
|
|
903
|
+
this.keyboardDragMode = false;
|
|
904
|
+
this.keyboardResizeMode = false;
|
|
905
|
+
this.pointerResizeMode = false;
|
|
906
|
+
this.manuallyPositioned = false;
|
|
907
|
+
this.resizeHandlesManaged = /* @__PURE__ */ new Map();
|
|
908
|
+
this.resizeIndicatorElement = null;
|
|
909
|
+
this.handlers = {
|
|
910
|
+
mousedown: this.onMouseDown.bind(this),
|
|
911
|
+
mousemove: this.onMouseMove.bind(this),
|
|
912
|
+
mouseup: this.onMouseUp.bind(this),
|
|
913
|
+
touchstart: this.onTouchStart.bind(this),
|
|
914
|
+
touchmove: this.onTouchMove.bind(this),
|
|
915
|
+
touchend: this.onTouchEnd.bind(this),
|
|
916
|
+
keydown: this.onKeyDown.bind(this),
|
|
917
|
+
resizeHandleMousedown: this.onResizeHandleMouseDown.bind(this)
|
|
918
|
+
};
|
|
919
|
+
this.init();
|
|
920
|
+
}
|
|
921
|
+
hasManagedResizeHandles() {
|
|
922
|
+
return Array.from(this.resizeHandlesManaged.values()).some(Boolean);
|
|
923
|
+
}
|
|
924
|
+
storeOriginalHandleDisplay(handle) {
|
|
925
|
+
if (!handle.dataset.originalDisplay) {
|
|
926
|
+
handle.dataset.originalDisplay = handle.style.display || "";
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
hideResizeHandle(handle) {
|
|
930
|
+
handle.style.display = "none";
|
|
931
|
+
handle.setAttribute("aria-hidden", "true");
|
|
932
|
+
}
|
|
933
|
+
showResizeHandle(handle) {
|
|
934
|
+
const original = handle.dataset.originalDisplay !== void 0 ? handle.dataset.originalDisplay : "";
|
|
935
|
+
handle.style.display = original;
|
|
936
|
+
handle.removeAttribute("aria-hidden");
|
|
937
|
+
}
|
|
938
|
+
setManagedHandlesVisible(visible) {
|
|
939
|
+
if (!this.options.resizeHandles || this.options.resizeHandles.length === 0) {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
this.options.resizeHandles.forEach((handle) => {
|
|
943
|
+
if (!this.resizeHandlesManaged.get(handle)) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
if (visible) {
|
|
947
|
+
this.showResizeHandle(handle);
|
|
948
|
+
} else {
|
|
949
|
+
this.hideResizeHandle(handle);
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
init() {
|
|
954
|
+
const dragHandle = this.options.dragHandle || this.element;
|
|
955
|
+
dragHandle.addEventListener("mousedown", this.handlers.mousedown);
|
|
956
|
+
dragHandle.addEventListener("touchstart", this.handlers.touchstart);
|
|
957
|
+
document.addEventListener("mousemove", this.handlers.mousemove);
|
|
958
|
+
document.addEventListener("mouseup", this.handlers.mouseup);
|
|
959
|
+
document.addEventListener("touchmove", this.handlers.touchmove, { passive: false });
|
|
960
|
+
document.addEventListener("touchend", this.handlers.touchend);
|
|
961
|
+
this.element.addEventListener("keydown", this.handlers.keydown);
|
|
962
|
+
if (this.options.resizeHandles && this.options.resizeHandles.length > 0) {
|
|
963
|
+
this.options.resizeHandles.forEach((handle) => {
|
|
964
|
+
handle.addEventListener("mousedown", this.handlers.resizeHandleMousedown);
|
|
965
|
+
handle.addEventListener("touchstart", this.handlers.resizeHandleMousedown);
|
|
966
|
+
const managed = handle.dataset.vidplyManagedResize === "true";
|
|
967
|
+
this.resizeHandlesManaged.set(handle, managed);
|
|
968
|
+
if (managed) {
|
|
969
|
+
this.storeOriginalHandleDisplay(handle);
|
|
970
|
+
this.hideResizeHandle(handle);
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
onMouseDown(e) {
|
|
976
|
+
if (e.target.classList.contains(`${this.options.classPrefix}-resize-handle`)) {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
if (this.options.onDragStart && !this.options.onDragStart(e)) {
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
this.startDragging(e.clientX, e.clientY);
|
|
983
|
+
e.preventDefault();
|
|
984
|
+
}
|
|
985
|
+
onTouchStart(e) {
|
|
986
|
+
if (e.target.classList.contains(`${this.options.classPrefix}-resize-handle`)) {
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
if (this.options.onDragStart && !this.options.onDragStart(e)) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
const touch = e.touches[0];
|
|
993
|
+
this.startDragging(touch.clientX, touch.clientY);
|
|
994
|
+
}
|
|
995
|
+
onResizeHandleMouseDown(e) {
|
|
996
|
+
e.preventDefault();
|
|
997
|
+
e.stopPropagation();
|
|
998
|
+
const handle = e.target;
|
|
999
|
+
this.resizeDirection = handle.getAttribute("data-direction");
|
|
1000
|
+
const clientX = e.clientX || e.touches?.[0]?.clientX;
|
|
1001
|
+
const clientY = e.clientY || e.touches?.[0]?.clientY;
|
|
1002
|
+
this.startResizing(clientX, clientY);
|
|
1003
|
+
}
|
|
1004
|
+
onMouseMove(e) {
|
|
1005
|
+
if (this.isDragging) {
|
|
1006
|
+
this.drag(e.clientX, e.clientY);
|
|
1007
|
+
e.preventDefault();
|
|
1008
|
+
} else if (this.isResizing) {
|
|
1009
|
+
this.resize(e.clientX, e.clientY);
|
|
1010
|
+
e.preventDefault();
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
onTouchMove(e) {
|
|
1014
|
+
if (this.isDragging || this.isResizing) {
|
|
1015
|
+
const touch = e.touches[0];
|
|
1016
|
+
if (this.isDragging) {
|
|
1017
|
+
this.drag(touch.clientX, touch.clientY);
|
|
1018
|
+
} else {
|
|
1019
|
+
this.resize(touch.clientX, touch.clientY);
|
|
1020
|
+
}
|
|
1021
|
+
e.preventDefault();
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
onMouseUp() {
|
|
1025
|
+
if (this.isDragging) {
|
|
1026
|
+
this.stopDragging();
|
|
1027
|
+
} else if (this.isResizing) {
|
|
1028
|
+
this.stopResizing();
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
onTouchEnd() {
|
|
1032
|
+
if (this.isDragging) {
|
|
1033
|
+
this.stopDragging();
|
|
1034
|
+
} else if (this.isResizing) {
|
|
1035
|
+
this.stopResizing();
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
onKeyDown(e) {
|
|
1039
|
+
if (e.key.toLowerCase() === this.options.keyboardDragKey.toLowerCase()) {
|
|
1040
|
+
e.preventDefault();
|
|
1041
|
+
this.toggleKeyboardDragMode();
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
if (e.key.toLowerCase() === this.options.keyboardResizeKey.toLowerCase()) {
|
|
1045
|
+
e.preventDefault();
|
|
1046
|
+
if (this.hasManagedResizeHandles()) {
|
|
1047
|
+
this.togglePointerResizeMode();
|
|
1048
|
+
} else {
|
|
1049
|
+
this.toggleKeyboardResizeMode();
|
|
1050
|
+
}
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
if (e.key === "Escape") {
|
|
1054
|
+
if (this.pointerResizeMode) {
|
|
1055
|
+
e.preventDefault();
|
|
1056
|
+
this.disablePointerResizeMode();
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
if (this.keyboardDragMode || this.keyboardResizeMode) {
|
|
1060
|
+
e.preventDefault();
|
|
1061
|
+
this.disableKeyboardDragMode();
|
|
1062
|
+
this.disableKeyboardResizeMode();
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(e.key)) {
|
|
1067
|
+
if (this.keyboardDragMode) {
|
|
1068
|
+
e.preventDefault();
|
|
1069
|
+
e.stopPropagation();
|
|
1070
|
+
this.keyboardDrag(e.key, e.shiftKey);
|
|
1071
|
+
} else if (this.keyboardResizeMode) {
|
|
1072
|
+
e.preventDefault();
|
|
1073
|
+
e.stopPropagation();
|
|
1074
|
+
this.keyboardResize(e.key, e.shiftKey);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
if (e.key === "Home" && (this.keyboardDragMode || this.keyboardResizeMode)) {
|
|
1078
|
+
e.preventDefault();
|
|
1079
|
+
this.resetPosition();
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
startDragging(clientX, clientY) {
|
|
1083
|
+
const rect = this.element.getBoundingClientRect();
|
|
1084
|
+
const computedStyle = window.getComputedStyle(this.element);
|
|
1085
|
+
const needsConversion = computedStyle.right !== "auto" || computedStyle.bottom !== "auto" || computedStyle.transform !== "none";
|
|
1086
|
+
this.positionOffsetX = 0;
|
|
1087
|
+
this.positionOffsetY = 0;
|
|
1088
|
+
if (needsConversion) {
|
|
1089
|
+
let targetLeft, targetTop;
|
|
1090
|
+
if (computedStyle.position === "absolute") {
|
|
1091
|
+
const offsetParent = this.element.offsetParent || document.body;
|
|
1092
|
+
const parentRect = offsetParent.getBoundingClientRect();
|
|
1093
|
+
targetLeft = rect.left - parentRect.left;
|
|
1094
|
+
targetTop = rect.top - parentRect.top;
|
|
1095
|
+
this.positionOffsetX = parentRect.left;
|
|
1096
|
+
this.positionOffsetY = parentRect.top;
|
|
1097
|
+
} else if (computedStyle.position === "fixed") {
|
|
1098
|
+
const parsedLeft = parseFloat(computedStyle.left);
|
|
1099
|
+
const parsedTop = parseFloat(computedStyle.top);
|
|
1100
|
+
const hasLeft = Number.isFinite(parsedLeft);
|
|
1101
|
+
const hasTop = Number.isFinite(parsedTop);
|
|
1102
|
+
targetLeft = hasLeft ? parsedLeft : rect.left;
|
|
1103
|
+
targetTop = hasTop ? parsedTop : rect.top;
|
|
1104
|
+
this.positionOffsetX = rect.left - targetLeft;
|
|
1105
|
+
this.positionOffsetY = rect.top - targetTop;
|
|
1106
|
+
} else {
|
|
1107
|
+
targetLeft = rect.left;
|
|
1108
|
+
targetTop = rect.top;
|
|
1109
|
+
this.positionOffsetX = rect.left - targetLeft;
|
|
1110
|
+
this.positionOffsetY = rect.top - targetTop;
|
|
1111
|
+
}
|
|
1112
|
+
const currentCssText = this.element.style.cssText;
|
|
1113
|
+
let newCssText = currentCssText.split(";").filter((rule) => {
|
|
1114
|
+
const trimmed = rule.trim();
|
|
1115
|
+
if (!trimmed) return false;
|
|
1116
|
+
const colonIndex = trimmed.indexOf(":");
|
|
1117
|
+
if (colonIndex === -1) return false;
|
|
1118
|
+
const property = trimmed.substring(0, colonIndex).trim();
|
|
1119
|
+
const value = trimmed.substring(colonIndex + 1).trim();
|
|
1120
|
+
if (!value || value === "") return false;
|
|
1121
|
+
if (property === "right" || property === "bottom" || property === "transform" || property === "left" || property === "top" || property === "inset") {
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
if (property.startsWith("border-image")) {
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
return true;
|
|
1128
|
+
}).join("; ");
|
|
1129
|
+
if (newCssText) newCssText += "; ";
|
|
1130
|
+
newCssText += `left: ${targetLeft}px; top: ${targetTop}px; right: auto; bottom: auto; transform: none`;
|
|
1131
|
+
this.element.style.cssText = newCssText;
|
|
1132
|
+
}
|
|
1133
|
+
const finalRect = this.element.getBoundingClientRect();
|
|
1134
|
+
this.dragOffsetX = clientX - finalRect.left;
|
|
1135
|
+
this.dragOffsetY = clientY - finalRect.top;
|
|
1136
|
+
this.isDragging = true;
|
|
1137
|
+
this.element.classList.add(`${this.options.classPrefix}-dragging`);
|
|
1138
|
+
document.body.style.cursor = "grabbing";
|
|
1139
|
+
document.body.style.userSelect = "none";
|
|
1140
|
+
}
|
|
1141
|
+
drag(clientX, clientY) {
|
|
1142
|
+
if (!this.isDragging) return;
|
|
1143
|
+
let newX = clientX - this.dragOffsetX - this.positionOffsetX;
|
|
1144
|
+
let newY = clientY - this.dragOffsetY - this.positionOffsetY;
|
|
1145
|
+
if (this.options.constrainToViewport) {
|
|
1146
|
+
const rect = this.element.getBoundingClientRect();
|
|
1147
|
+
const viewportWidth = document.documentElement.clientWidth;
|
|
1148
|
+
const viewportHeight = document.documentElement.clientHeight;
|
|
1149
|
+
const minVisible = 100;
|
|
1150
|
+
const minX = -(rect.width - minVisible);
|
|
1151
|
+
const minY = -(rect.height - minVisible);
|
|
1152
|
+
const maxX = viewportWidth - minVisible;
|
|
1153
|
+
const maxY = viewportHeight - minVisible;
|
|
1154
|
+
newX = Math.max(minX, Math.min(newX, maxX));
|
|
1155
|
+
newY = Math.max(minY, Math.min(newY, maxY));
|
|
1156
|
+
}
|
|
1157
|
+
this.element.style.left = `${newX}px`;
|
|
1158
|
+
this.element.style.top = `${newY}px`;
|
|
1159
|
+
if (this.options.onDrag) {
|
|
1160
|
+
this.options.onDrag({ x: newX, y: newY });
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
stopDragging() {
|
|
1164
|
+
this.isDragging = false;
|
|
1165
|
+
this.element.classList.remove(`${this.options.classPrefix}-dragging`);
|
|
1166
|
+
document.body.style.cursor = "";
|
|
1167
|
+
document.body.style.userSelect = "";
|
|
1168
|
+
this.manuallyPositioned = true;
|
|
1169
|
+
if (this.options.onDragEnd) {
|
|
1170
|
+
this.options.onDragEnd();
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
startResizing(clientX, clientY) {
|
|
1174
|
+
this.isResizing = true;
|
|
1175
|
+
this.resizeStartX = clientX;
|
|
1176
|
+
this.resizeStartY = clientY;
|
|
1177
|
+
const rect = this.element.getBoundingClientRect();
|
|
1178
|
+
this.resizeStartWidth = rect.width;
|
|
1179
|
+
this.resizeStartHeight = rect.height;
|
|
1180
|
+
this.resizeStartLeft = rect.left;
|
|
1181
|
+
this.resizeStartTop = rect.top;
|
|
1182
|
+
this.element.classList.add(`${this.options.classPrefix}-resizing`);
|
|
1183
|
+
document.body.style.userSelect = "none";
|
|
1184
|
+
if (this.options.onResizeStart) {
|
|
1185
|
+
this.options.onResizeStart();
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
resize(clientX, clientY) {
|
|
1189
|
+
if (!this.isResizing) return;
|
|
1190
|
+
const deltaX = clientX - this.resizeStartX;
|
|
1191
|
+
const deltaY = clientY - this.resizeStartY;
|
|
1192
|
+
let newWidth = this.resizeStartWidth;
|
|
1193
|
+
let newHeight = this.resizeStartHeight;
|
|
1194
|
+
let newLeft = this.resizeStartLeft;
|
|
1195
|
+
let newTop = this.resizeStartTop;
|
|
1196
|
+
if (this.resizeDirection.includes("e")) {
|
|
1197
|
+
newWidth = Math.max(this.options.minWidth, this.resizeStartWidth + deltaX);
|
|
1198
|
+
}
|
|
1199
|
+
if (this.resizeDirection.includes("w")) {
|
|
1200
|
+
const proposedWidth = Math.max(this.options.minWidth, this.resizeStartWidth - deltaX);
|
|
1201
|
+
newLeft = this.resizeStartLeft + (this.resizeStartWidth - proposedWidth);
|
|
1202
|
+
newWidth = proposedWidth;
|
|
1203
|
+
}
|
|
1204
|
+
const maxWidthOption = typeof this.options.maxWidth === "function" ? this.options.maxWidth() : this.options.maxWidth;
|
|
1205
|
+
if (Number.isFinite(maxWidthOption)) {
|
|
1206
|
+
const clampedWidth = Math.min(newWidth, maxWidthOption);
|
|
1207
|
+
if (clampedWidth !== newWidth && this.resizeDirection.includes("w")) {
|
|
1208
|
+
newLeft += newWidth - clampedWidth;
|
|
1209
|
+
}
|
|
1210
|
+
newWidth = clampedWidth;
|
|
1211
|
+
}
|
|
1212
|
+
if (!this.options.maintainAspectRatio) {
|
|
1213
|
+
if (this.resizeDirection.includes("s")) {
|
|
1214
|
+
newHeight = Math.max(this.options.minHeight, this.resizeStartHeight + deltaY);
|
|
1215
|
+
}
|
|
1216
|
+
if (this.resizeDirection.includes("n")) {
|
|
1217
|
+
const proposedHeight = Math.max(this.options.minHeight, this.resizeStartHeight - deltaY);
|
|
1218
|
+
newTop = this.resizeStartTop + (this.resizeStartHeight - proposedHeight);
|
|
1219
|
+
newHeight = proposedHeight;
|
|
1220
|
+
}
|
|
1221
|
+
const maxHeightOption = typeof this.options.maxHeight === "function" ? this.options.maxHeight() : this.options.maxHeight;
|
|
1222
|
+
if (Number.isFinite(maxHeightOption)) {
|
|
1223
|
+
const clampedHeight = Math.min(newHeight, maxHeightOption);
|
|
1224
|
+
if (clampedHeight !== newHeight && this.resizeDirection.includes("n")) {
|
|
1225
|
+
newTop += newHeight - clampedHeight;
|
|
1226
|
+
}
|
|
1227
|
+
newHeight = clampedHeight;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
this.element.style.width = `${newWidth}px`;
|
|
1231
|
+
if (!this.options.maintainAspectRatio) {
|
|
1232
|
+
this.element.style.height = `${newHeight}px`;
|
|
1233
|
+
} else {
|
|
1234
|
+
this.element.style.height = "auto";
|
|
1235
|
+
}
|
|
1236
|
+
if (this.resizeDirection.includes("w")) {
|
|
1237
|
+
this.element.style.left = `${newLeft}px`;
|
|
1238
|
+
}
|
|
1239
|
+
if (this.resizeDirection.includes("n") && !this.options.maintainAspectRatio) {
|
|
1240
|
+
this.element.style.top = `${newTop}px`;
|
|
1241
|
+
}
|
|
1242
|
+
if (this.options.onResize) {
|
|
1243
|
+
this.options.onResize({ width: newWidth, height: newHeight, left: newLeft, top: newTop });
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
stopResizing() {
|
|
1247
|
+
this.isResizing = false;
|
|
1248
|
+
this.resizeDirection = null;
|
|
1249
|
+
this.element.classList.remove(`${this.options.classPrefix}-resizing`);
|
|
1250
|
+
document.body.style.userSelect = "";
|
|
1251
|
+
this.manuallyPositioned = true;
|
|
1252
|
+
if (this.options.onResizeEnd) {
|
|
1253
|
+
this.options.onResizeEnd();
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
toggleKeyboardDragMode() {
|
|
1257
|
+
if (this.keyboardDragMode) {
|
|
1258
|
+
this.disableKeyboardDragMode();
|
|
1259
|
+
} else {
|
|
1260
|
+
this.enableKeyboardDragMode();
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
enableKeyboardDragMode() {
|
|
1264
|
+
this.keyboardDragMode = true;
|
|
1265
|
+
this.keyboardResizeMode = false;
|
|
1266
|
+
this.element.classList.add(`${this.options.classPrefix}-keyboard-drag`);
|
|
1267
|
+
this.element.classList.remove(`${this.options.classPrefix}-keyboard-resize`);
|
|
1268
|
+
this.focusElement();
|
|
1269
|
+
}
|
|
1270
|
+
disableKeyboardDragMode() {
|
|
1271
|
+
this.keyboardDragMode = false;
|
|
1272
|
+
this.element.classList.remove(`${this.options.classPrefix}-keyboard-drag`);
|
|
1273
|
+
}
|
|
1274
|
+
toggleKeyboardResizeMode() {
|
|
1275
|
+
if (this.keyboardResizeMode) {
|
|
1276
|
+
this.disableKeyboardResizeMode();
|
|
1277
|
+
} else {
|
|
1278
|
+
this.enableKeyboardResizeMode();
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
enableKeyboardResizeMode() {
|
|
1282
|
+
this.keyboardResizeMode = true;
|
|
1283
|
+
this.keyboardDragMode = false;
|
|
1284
|
+
this.element.classList.add(`${this.options.classPrefix}-keyboard-resize`);
|
|
1285
|
+
this.element.classList.remove(`${this.options.classPrefix}-keyboard-drag`);
|
|
1286
|
+
this.focusElement();
|
|
1287
|
+
}
|
|
1288
|
+
disableKeyboardResizeMode() {
|
|
1289
|
+
this.keyboardResizeMode = false;
|
|
1290
|
+
this.element.classList.remove(`${this.options.classPrefix}-keyboard-resize`);
|
|
1291
|
+
}
|
|
1292
|
+
enablePointerResizeMode({ focus = true } = {}) {
|
|
1293
|
+
if (!this.hasManagedResizeHandles()) {
|
|
1294
|
+
this.enableKeyboardResizeMode();
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
if (this.pointerResizeMode) {
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
this.pointerResizeMode = true;
|
|
1301
|
+
this.setManagedHandlesVisible(true);
|
|
1302
|
+
this.element.classList.add(`${this.options.classPrefix}-resizable`);
|
|
1303
|
+
this.enableKeyboardResizeMode();
|
|
1304
|
+
if (focus) {
|
|
1305
|
+
this.focusElement();
|
|
1306
|
+
}
|
|
1307
|
+
if (typeof this.options.onPointerResizeToggle === "function") {
|
|
1308
|
+
this.options.onPointerResizeToggle(true);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
disablePointerResizeMode({ focus = false } = {}) {
|
|
1312
|
+
if (!this.pointerResizeMode) {
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
this.pointerResizeMode = false;
|
|
1316
|
+
this.setManagedHandlesVisible(false);
|
|
1317
|
+
this.element.classList.remove(`${this.options.classPrefix}-resizable`);
|
|
1318
|
+
this.disableKeyboardResizeMode();
|
|
1319
|
+
if (focus) {
|
|
1320
|
+
this.focusElement();
|
|
1321
|
+
}
|
|
1322
|
+
if (typeof this.options.onPointerResizeToggle === "function") {
|
|
1323
|
+
this.options.onPointerResizeToggle(false);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
togglePointerResizeMode() {
|
|
1327
|
+
if (this.pointerResizeMode) {
|
|
1328
|
+
this.disablePointerResizeMode();
|
|
1329
|
+
} else {
|
|
1330
|
+
this.enablePointerResizeMode();
|
|
1331
|
+
}
|
|
1332
|
+
return this.pointerResizeMode;
|
|
1333
|
+
}
|
|
1334
|
+
focusElement() {
|
|
1335
|
+
if (typeof this.element.focus === "function") {
|
|
1336
|
+
try {
|
|
1337
|
+
this.element.focus({ preventScroll: true });
|
|
1338
|
+
} catch (e) {
|
|
1339
|
+
this.element.focus();
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
keyboardDrag(key, shiftKey) {
|
|
1344
|
+
const step = shiftKey ? this.options.keyboardStepLarge : this.options.keyboardStep;
|
|
1345
|
+
let currentLeft = parseFloat(this.element.style.left) || 0;
|
|
1346
|
+
let currentTop = parseFloat(this.element.style.top) || 0;
|
|
1347
|
+
const computedStyle = window.getComputedStyle(this.element);
|
|
1348
|
+
if (computedStyle.transform !== "none") {
|
|
1349
|
+
const rect = this.element.getBoundingClientRect();
|
|
1350
|
+
currentLeft = rect.left;
|
|
1351
|
+
currentTop = rect.top;
|
|
1352
|
+
this.element.style.transform = "none";
|
|
1353
|
+
this.element.style.left = `${currentLeft}px`;
|
|
1354
|
+
this.element.style.top = `${currentTop}px`;
|
|
1355
|
+
}
|
|
1356
|
+
let newX = currentLeft;
|
|
1357
|
+
let newY = currentTop;
|
|
1358
|
+
switch (key) {
|
|
1359
|
+
case "ArrowLeft":
|
|
1360
|
+
newX -= step;
|
|
1361
|
+
break;
|
|
1362
|
+
case "ArrowRight":
|
|
1363
|
+
newX += step;
|
|
1364
|
+
break;
|
|
1365
|
+
case "ArrowUp":
|
|
1366
|
+
newY -= step;
|
|
1367
|
+
break;
|
|
1368
|
+
case "ArrowDown":
|
|
1369
|
+
newY += step;
|
|
1370
|
+
break;
|
|
1371
|
+
}
|
|
1372
|
+
this.element.style.left = `${newX}px`;
|
|
1373
|
+
this.element.style.top = `${newY}px`;
|
|
1374
|
+
if (this.options.onDrag) {
|
|
1375
|
+
this.options.onDrag({ x: newX, y: newY });
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
keyboardResize(key, shiftKey) {
|
|
1379
|
+
const step = shiftKey ? this.options.keyboardStepLarge : this.options.keyboardStep;
|
|
1380
|
+
const rect = this.element.getBoundingClientRect();
|
|
1381
|
+
let width = rect.width;
|
|
1382
|
+
let height = rect.height;
|
|
1383
|
+
switch (key) {
|
|
1384
|
+
case "ArrowLeft":
|
|
1385
|
+
width -= step;
|
|
1386
|
+
break;
|
|
1387
|
+
case "ArrowRight":
|
|
1388
|
+
width += step;
|
|
1389
|
+
break;
|
|
1390
|
+
case "ArrowUp":
|
|
1391
|
+
if (this.options.maintainAspectRatio) {
|
|
1392
|
+
width += step;
|
|
1393
|
+
} else {
|
|
1394
|
+
height -= step;
|
|
1395
|
+
}
|
|
1396
|
+
break;
|
|
1397
|
+
case "ArrowDown":
|
|
1398
|
+
if (this.options.maintainAspectRatio) {
|
|
1399
|
+
width -= step;
|
|
1400
|
+
} else {
|
|
1401
|
+
height += step;
|
|
1402
|
+
}
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
width = Math.max(this.options.minWidth, width);
|
|
1406
|
+
height = Math.max(this.options.minHeight, height);
|
|
1407
|
+
this.element.style.width = `${width}px`;
|
|
1408
|
+
if (!this.options.maintainAspectRatio) {
|
|
1409
|
+
this.element.style.height = `${height}px`;
|
|
1410
|
+
} else {
|
|
1411
|
+
this.element.style.height = "auto";
|
|
1412
|
+
}
|
|
1413
|
+
if (this.options.onResize) {
|
|
1414
|
+
this.options.onResize({ width, height });
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
resetPosition() {
|
|
1418
|
+
this.element.style.left = "50%";
|
|
1419
|
+
this.element.style.top = "50%";
|
|
1420
|
+
this.element.style.transform = "translate(-50%, -50%)";
|
|
1421
|
+
this.element.style.right = "";
|
|
1422
|
+
this.element.style.bottom = "";
|
|
1423
|
+
this.manuallyPositioned = false;
|
|
1424
|
+
if (this.options.onDrag) {
|
|
1425
|
+
this.options.onDrag({ centered: true });
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
destroy() {
|
|
1429
|
+
const dragHandle = this.options.dragHandle || this.element;
|
|
1430
|
+
this.disablePointerResizeMode();
|
|
1431
|
+
dragHandle.removeEventListener("mousedown", this.handlers.mousedown);
|
|
1432
|
+
dragHandle.removeEventListener("touchstart", this.handlers.touchstart);
|
|
1433
|
+
document.removeEventListener("mousemove", this.handlers.mousemove);
|
|
1434
|
+
document.removeEventListener("mouseup", this.handlers.mouseup);
|
|
1435
|
+
document.removeEventListener("touchmove", this.handlers.touchmove);
|
|
1436
|
+
document.removeEventListener("touchend", this.handlers.touchend);
|
|
1437
|
+
this.element.removeEventListener("keydown", this.handlers.keydown);
|
|
1438
|
+
if (this.options.resizeHandles && this.options.resizeHandles.length > 0) {
|
|
1439
|
+
this.options.resizeHandles.forEach((handle) => {
|
|
1440
|
+
handle.removeEventListener("mousedown", this.handlers.resizeHandleMousedown);
|
|
1441
|
+
handle.removeEventListener("touchstart", this.handlers.resizeHandleMousedown);
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
this.element.classList.remove(
|
|
1445
|
+
`${this.options.classPrefix}-dragging`,
|
|
1446
|
+
`${this.options.classPrefix}-resizing`,
|
|
1447
|
+
`${this.options.classPrefix}-keyboard-drag`,
|
|
1448
|
+
`${this.options.classPrefix}-keyboard-resize`
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
|
|
1453
|
+
// src/utils/MenuUtils.js
|
|
1454
|
+
function createMenuItem({ classPrefix, itemClass, icon, label, ariaLabel, onClick, hasTextClass = false }) {
|
|
1455
|
+
const isI18nKeyForAria = typeof label === "string" && (label.startsWith("transcript.") || label.startsWith("player.") || label.startsWith("settings."));
|
|
1456
|
+
const ariaLabelText = ariaLabel || (isI18nKeyForAria ? i18n.t(label) || label : label);
|
|
1457
|
+
const button = DOMUtils.createElement("button", {
|
|
1458
|
+
className: itemClass,
|
|
1459
|
+
attributes: {
|
|
1460
|
+
"type": "button",
|
|
1461
|
+
"aria-label": ariaLabelText,
|
|
1462
|
+
"tabindex": "-1"
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
if (icon) {
|
|
1466
|
+
button.appendChild(createIconElement(icon));
|
|
1467
|
+
}
|
|
1468
|
+
const isI18nKey = typeof label === "string" && (label.startsWith("transcript.") || label.startsWith("player.") || label.startsWith("settings."));
|
|
1469
|
+
const textContent = isI18nKey ? i18n.t(label) || label : label;
|
|
1470
|
+
const text = DOMUtils.createElement("span", {
|
|
1471
|
+
textContent,
|
|
1472
|
+
className: hasTextClass ? `${classPrefix}-settings-text` : void 0,
|
|
1473
|
+
attributes: {
|
|
1474
|
+
"aria-hidden": "true"
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
button.appendChild(text);
|
|
1478
|
+
if (onClick) {
|
|
1479
|
+
button.addEventListener("click", onClick);
|
|
1480
|
+
}
|
|
1481
|
+
return button;
|
|
1482
|
+
}
|
|
1483
|
+
function attachMenuKeyboardNavigation(menu, button, itemSelector, onClose) {
|
|
1484
|
+
if (!menu) return;
|
|
1485
|
+
const menuItems = Array.from(menu.querySelectorAll(itemSelector));
|
|
1486
|
+
if (menuItems.length === 0) return;
|
|
1487
|
+
const handleKeyDown = (e) => {
|
|
1488
|
+
const currentIndex = menuItems.indexOf(document.activeElement);
|
|
1489
|
+
switch (e.key) {
|
|
1490
|
+
case "ArrowDown":
|
|
1491
|
+
e.preventDefault();
|
|
1492
|
+
e.stopPropagation();
|
|
1493
|
+
const nextIndex = (currentIndex + 1) % menuItems.length;
|
|
1494
|
+
menuItems.forEach((item, idx) => {
|
|
1495
|
+
item.setAttribute("tabindex", idx === nextIndex ? "0" : "-1");
|
|
1496
|
+
});
|
|
1497
|
+
menuItems[nextIndex].focus({ preventScroll: false });
|
|
1498
|
+
menuItems[nextIndex].scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
1499
|
+
break;
|
|
1500
|
+
case "ArrowUp":
|
|
1501
|
+
e.preventDefault();
|
|
1502
|
+
e.stopPropagation();
|
|
1503
|
+
const prevIndex = (currentIndex - 1 + menuItems.length) % menuItems.length;
|
|
1504
|
+
menuItems.forEach((item, idx) => {
|
|
1505
|
+
item.setAttribute("tabindex", idx === prevIndex ? "0" : "-1");
|
|
1506
|
+
});
|
|
1507
|
+
menuItems[prevIndex].focus({ preventScroll: false });
|
|
1508
|
+
menuItems[prevIndex].scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
1509
|
+
break;
|
|
1510
|
+
case "Home":
|
|
1511
|
+
e.preventDefault();
|
|
1512
|
+
e.stopPropagation();
|
|
1513
|
+
menuItems.forEach((item, idx) => {
|
|
1514
|
+
item.setAttribute("tabindex", idx === 0 ? "0" : "-1");
|
|
1515
|
+
});
|
|
1516
|
+
menuItems[0].focus({ preventScroll: false });
|
|
1517
|
+
menuItems[0].scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
1518
|
+
break;
|
|
1519
|
+
case "End":
|
|
1520
|
+
e.preventDefault();
|
|
1521
|
+
e.stopPropagation();
|
|
1522
|
+
const lastIndex = menuItems.length - 1;
|
|
1523
|
+
menuItems.forEach((item, idx) => {
|
|
1524
|
+
item.setAttribute("tabindex", idx === lastIndex ? "0" : "-1");
|
|
1525
|
+
});
|
|
1526
|
+
menuItems[lastIndex].focus({ preventScroll: false });
|
|
1527
|
+
menuItems[lastIndex].scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
1528
|
+
break;
|
|
1529
|
+
case "Enter":
|
|
1530
|
+
case " ":
|
|
1531
|
+
e.preventDefault();
|
|
1532
|
+
e.stopPropagation();
|
|
1533
|
+
if (document.activeElement && menuItems.includes(document.activeElement)) {
|
|
1534
|
+
document.activeElement.click();
|
|
1535
|
+
if (onClose) {
|
|
1536
|
+
setTimeout(() => {
|
|
1537
|
+
if (button && document.contains(button)) {
|
|
1538
|
+
button.focus();
|
|
1539
|
+
}
|
|
1540
|
+
}, 0);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
break;
|
|
1544
|
+
case "Escape":
|
|
1545
|
+
e.preventDefault();
|
|
1546
|
+
e.stopPropagation();
|
|
1547
|
+
if (onClose) {
|
|
1548
|
+
onClose();
|
|
1549
|
+
}
|
|
1550
|
+
break;
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
menu.addEventListener("keydown", handleKeyDown, true);
|
|
1554
|
+
return handleKeyDown;
|
|
1555
|
+
}
|
|
1556
|
+
function focusFirstMenuItem(menu, itemSelector, delay = 0) {
|
|
1557
|
+
if (!menu) return;
|
|
1558
|
+
setTimeout(() => {
|
|
1559
|
+
const menuItems = Array.from(menu.querySelectorAll(itemSelector));
|
|
1560
|
+
if (menuItems.length > 0) {
|
|
1561
|
+
menuItems.forEach((item, index) => {
|
|
1562
|
+
item.setAttribute("tabindex", index === 0 ? "0" : "-1");
|
|
1563
|
+
});
|
|
1564
|
+
focusElement(menuItems[0], { delay: 0 });
|
|
1565
|
+
menuItems[0].scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
1566
|
+
}
|
|
1567
|
+
}, delay);
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// src/utils/FormUtils.js
|
|
1571
|
+
function createLabeledSelect({
|
|
1572
|
+
classPrefix,
|
|
1573
|
+
labelClass,
|
|
1574
|
+
selectClass,
|
|
1575
|
+
labelText,
|
|
1576
|
+
selectId,
|
|
1577
|
+
hidden = false,
|
|
1578
|
+
onChange = null,
|
|
1579
|
+
options = []
|
|
1580
|
+
}) {
|
|
1581
|
+
const isI18nKey = typeof labelText === "string" && (labelText.startsWith("transcript.") || labelText.startsWith("player.") || labelText.startsWith("settings.") || labelText.startsWith("captions."));
|
|
1582
|
+
const labelTextContent = isI18nKey ? i18n.t(labelText) || labelText : labelText;
|
|
1583
|
+
const label = DOMUtils.createElement("label", {
|
|
1584
|
+
className: labelClass,
|
|
1585
|
+
textContent: labelTextContent,
|
|
1586
|
+
attributes: {
|
|
1587
|
+
"for": selectId,
|
|
1588
|
+
"style": hidden ? "display: none;" : void 0
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
const select = DOMUtils.createElement("select", {
|
|
1592
|
+
className: selectClass,
|
|
1593
|
+
attributes: {
|
|
1594
|
+
"id": selectId,
|
|
1595
|
+
"style": hidden ? "display: none;" : void 0
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
options.forEach((opt) => {
|
|
1599
|
+
const option = DOMUtils.createElement("option", {
|
|
1600
|
+
textContent: opt.text,
|
|
1601
|
+
attributes: {
|
|
1602
|
+
"value": opt.value,
|
|
1603
|
+
"selected": opt.selected ? "selected" : void 0
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
1606
|
+
select.appendChild(option);
|
|
1607
|
+
});
|
|
1608
|
+
if (onChange) {
|
|
1609
|
+
select.addEventListener("change", onChange);
|
|
1610
|
+
}
|
|
1611
|
+
return { label, select };
|
|
1612
|
+
}
|
|
1613
|
+
function preventDragOnElement(element) {
|
|
1614
|
+
if (!element) return;
|
|
1615
|
+
["mousedown", "click"].forEach((eventType) => {
|
|
1616
|
+
element.addEventListener(eventType, (e) => {
|
|
1617
|
+
e.stopPropagation();
|
|
1618
|
+
});
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
export {
|
|
1623
|
+
DOMUtils,
|
|
1624
|
+
i18n,
|
|
1625
|
+
TimeUtils,
|
|
1626
|
+
createIconElement,
|
|
1627
|
+
createPlayOverlay,
|
|
1628
|
+
focusElement,
|
|
1629
|
+
focusFirstElement,
|
|
1630
|
+
StorageManager,
|
|
1631
|
+
DraggableResizable,
|
|
1632
|
+
createMenuItem,
|
|
1633
|
+
attachMenuKeyboardNavigation,
|
|
1634
|
+
focusFirstMenuItem,
|
|
1635
|
+
createLabeledSelect,
|
|
1636
|
+
preventDragOnElement
|
|
1637
|
+
};
|
|
1638
|
+
//# sourceMappingURL=vidply.chunk-SRM7VNHG.js.map
|