vidply 1.0.9 → 1.0.10
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/vidply.css +47 -14
- package/dist/vidply.esm.js +1063 -663
- package/dist/vidply.esm.js.map +3 -3
- package/dist/vidply.esm.min.js +3 -3
- package/dist/vidply.esm.min.meta.json +28 -10
- package/dist/vidply.js +1063 -663
- package/dist/vidply.js.map +3 -3
- package/dist/vidply.min.css +1 -1
- package/dist/vidply.min.js +3 -3
- package/dist/vidply.min.meta.json +27 -9
- package/package.json +1 -1
- package/src/controls/ControlBar.js +24 -14
- package/src/controls/TranscriptManager.js +344 -578
- package/src/core/Player.js +150 -276
- package/src/i18n/translations.js +50 -5
- package/src/styles/vidply.css +47 -14
- package/src/utils/DraggableResizable.js +771 -0
|
@@ -8,6 +8,7 @@ import { TimeUtils } from '../utils/TimeUtils.js';
|
|
|
8
8
|
import { createIconElement } from '../icons/Icons.js';
|
|
9
9
|
import { i18n } from '../i18n/i18n.js';
|
|
10
10
|
import { StorageManager } from '../utils/StorageManager.js';
|
|
11
|
+
import { DraggableResizable } from '../utils/DraggableResizable.js';
|
|
11
12
|
|
|
12
13
|
export class TranscriptManager {
|
|
13
14
|
constructor(player) {
|
|
@@ -21,19 +22,8 @@ export class TranscriptManager {
|
|
|
21
22
|
// Storage manager
|
|
22
23
|
this.storage = new StorageManager('vidply');
|
|
23
24
|
|
|
24
|
-
//
|
|
25
|
-
this.
|
|
26
|
-
this.dragOffsetX = 0;
|
|
27
|
-
this.dragOffsetY = 0;
|
|
28
|
-
|
|
29
|
-
// Resizing state
|
|
30
|
-
this.isResizing = false;
|
|
31
|
-
this.resizeDirection = null;
|
|
32
|
-
this.resizeStartX = 0;
|
|
33
|
-
this.resizeStartY = 0;
|
|
34
|
-
this.resizeStartWidth = 0;
|
|
35
|
-
this.resizeStartHeight = 0;
|
|
36
|
-
this.resizeEnabled = false;
|
|
25
|
+
// Draggable/Resizable utility
|
|
26
|
+
this.draggableResizable = null;
|
|
37
27
|
|
|
38
28
|
// Settings menu state
|
|
39
29
|
this.settingsMenuVisible = false;
|
|
@@ -41,8 +31,13 @@ export class TranscriptManager {
|
|
|
41
31
|
this.settingsButton = null;
|
|
42
32
|
this.settingsMenuJustOpened = false;
|
|
43
33
|
|
|
44
|
-
//
|
|
45
|
-
this.
|
|
34
|
+
// Resize mode state
|
|
35
|
+
this.resizeOptionButton = null;
|
|
36
|
+
this.resizeOptionText = null;
|
|
37
|
+
this.resizeModeIndicator = null;
|
|
38
|
+
this.resizeModeIndicatorTimeout = null;
|
|
39
|
+
this.transcriptResizeHandles = [];
|
|
40
|
+
this.liveRegion = null;
|
|
46
41
|
|
|
47
42
|
// Style dialog state
|
|
48
43
|
this.styleDialog = null;
|
|
@@ -74,13 +69,6 @@ export class TranscriptManager {
|
|
|
74
69
|
this.handlers = {
|
|
75
70
|
timeupdate: () => this.updateActiveEntry(),
|
|
76
71
|
resize: null,
|
|
77
|
-
mousemove: null,
|
|
78
|
-
mouseup: null,
|
|
79
|
-
touchmove: null,
|
|
80
|
-
touchend: null,
|
|
81
|
-
mousedown: null,
|
|
82
|
-
touchstart: null,
|
|
83
|
-
keydown: null,
|
|
84
72
|
settingsClick: null,
|
|
85
73
|
settingsKeydown: null,
|
|
86
74
|
documentClick: null,
|
|
@@ -103,8 +91,11 @@ export class TranscriptManager {
|
|
|
103
91
|
// Reposition transcript when entering/exiting fullscreen
|
|
104
92
|
this.player.on('fullscreenchange', () => {
|
|
105
93
|
if (this.isVisible) {
|
|
106
|
-
//
|
|
107
|
-
this.
|
|
94
|
+
// Only auto-position if user hasn't manually positioned it
|
|
95
|
+
if (!this.draggableResizable || !this.draggableResizable.manuallyPositioned) {
|
|
96
|
+
// Add a small delay to ensure DOM has updated after fullscreen transition
|
|
97
|
+
this.setManagedTimeout(() => this.positionTranscript(), 100);
|
|
98
|
+
}
|
|
108
99
|
}
|
|
109
100
|
});
|
|
110
101
|
}
|
|
@@ -127,11 +118,15 @@ export class TranscriptManager {
|
|
|
127
118
|
if (this.transcriptWindow) {
|
|
128
119
|
this.transcriptWindow.style.display = 'flex';
|
|
129
120
|
this.isVisible = true;
|
|
121
|
+
|
|
122
|
+
if (this.player.controlBar && typeof this.player.controlBar.updateTranscriptButton === 'function') {
|
|
123
|
+
this.player.controlBar.updateTranscriptButton();
|
|
124
|
+
}
|
|
130
125
|
|
|
131
|
-
// Focus the
|
|
126
|
+
// Focus the header for keyboard accessibility
|
|
132
127
|
this.setManagedTimeout(() => {
|
|
133
|
-
if (this.
|
|
134
|
-
this.
|
|
128
|
+
if (this.transcriptHeader) {
|
|
129
|
+
this.transcriptHeader.focus();
|
|
135
130
|
}
|
|
136
131
|
}, 150);
|
|
137
132
|
return;
|
|
@@ -144,13 +139,17 @@ export class TranscriptManager {
|
|
|
144
139
|
// Show the window
|
|
145
140
|
if (this.transcriptWindow) {
|
|
146
141
|
this.transcriptWindow.style.display = 'flex';
|
|
147
|
-
// Re-position after showing (in case window was resized while hidden)
|
|
148
|
-
this.setManagedTimeout(() => this.positionTranscript(), 0);
|
|
149
142
|
|
|
150
|
-
//
|
|
143
|
+
// Only auto-position if user hasn't manually positioned it
|
|
144
|
+
// This prevents overwriting saved positions from localStorage
|
|
145
|
+
if (!this.draggableResizable || !this.draggableResizable.manuallyPositioned) {
|
|
146
|
+
this.setManagedTimeout(() => this.positionTranscript(), 0);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Focus the header for keyboard accessibility
|
|
151
150
|
this.setManagedTimeout(() => {
|
|
152
|
-
if (this.
|
|
153
|
-
this.
|
|
151
|
+
if (this.transcriptHeader) {
|
|
152
|
+
this.transcriptHeader.focus();
|
|
154
153
|
}
|
|
155
154
|
}, 150);
|
|
156
155
|
}
|
|
@@ -160,11 +159,29 @@ export class TranscriptManager {
|
|
|
160
159
|
/**
|
|
161
160
|
* Hide transcript window
|
|
162
161
|
*/
|
|
163
|
-
hideTranscript() {
|
|
162
|
+
hideTranscript({ focusButton = false } = {}) {
|
|
164
163
|
if (this.transcriptWindow) {
|
|
165
164
|
this.transcriptWindow.style.display = 'none';
|
|
166
165
|
this.isVisible = false;
|
|
167
166
|
}
|
|
167
|
+
if (this.draggableResizable && this.draggableResizable.pointerResizeMode) {
|
|
168
|
+
this.draggableResizable.disablePointerResizeMode();
|
|
169
|
+
this.updateResizeOptionState();
|
|
170
|
+
}
|
|
171
|
+
this.hideResizeModeIndicator();
|
|
172
|
+
this.announceLive('');
|
|
173
|
+
|
|
174
|
+
// Update transcript button state in control bar
|
|
175
|
+
if (this.player.controlBar && typeof this.player.controlBar.updateTranscriptButton === 'function') {
|
|
176
|
+
this.player.controlBar.updateTranscriptButton();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (focusButton) {
|
|
180
|
+
const transcriptButton = this.player.controlBar?.controls?.transcript;
|
|
181
|
+
if (transcriptButton && typeof transcriptButton.focus === 'function') {
|
|
182
|
+
transcriptButton.focus();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
168
185
|
}
|
|
169
186
|
|
|
170
187
|
/**
|
|
@@ -184,7 +201,6 @@ export class TranscriptManager {
|
|
|
184
201
|
this.transcriptHeader = DOMUtils.createElement('div', {
|
|
185
202
|
className: `${this.player.options.classPrefix}-transcript-header`,
|
|
186
203
|
attributes: {
|
|
187
|
-
'aria-label': 'Drag to reposition transcript. Use arrow keys to move, Home to reset position, Escape to close.',
|
|
188
204
|
'tabindex': '0'
|
|
189
205
|
}
|
|
190
206
|
});
|
|
@@ -199,7 +215,7 @@ export class TranscriptManager {
|
|
|
199
215
|
className: `${this.player.options.classPrefix}-transcript-settings`,
|
|
200
216
|
attributes: {
|
|
201
217
|
'type': 'button',
|
|
202
|
-
'aria-label': i18n.t('transcript.
|
|
218
|
+
'aria-label': i18n.t('transcript.settingsMenu'),
|
|
203
219
|
'aria-expanded': 'false'
|
|
204
220
|
}
|
|
205
221
|
});
|
|
@@ -239,7 +255,7 @@ export class TranscriptManager {
|
|
|
239
255
|
this.settingsButton.addEventListener('keydown', this.handlers.settingsKeydown);
|
|
240
256
|
|
|
241
257
|
const title = DOMUtils.createElement('h3', {
|
|
242
|
-
textContent: i18n.t('transcript.title')
|
|
258
|
+
textContent: `${i18n.t('transcript.title')}. ${i18n.t('transcript.dragResizePrompt')}`
|
|
243
259
|
});
|
|
244
260
|
|
|
245
261
|
// Autoscroll checkbox
|
|
@@ -272,8 +288,8 @@ export class TranscriptManager {
|
|
|
272
288
|
this.saveAutoscrollPreference();
|
|
273
289
|
});
|
|
274
290
|
|
|
291
|
+
this.transcriptHeader.appendChild(title);
|
|
275
292
|
this.headerLeft.appendChild(this.settingsButton);
|
|
276
|
-
this.headerLeft.appendChild(title);
|
|
277
293
|
this.headerLeft.appendChild(autoscrollLabel);
|
|
278
294
|
|
|
279
295
|
// Language selector (will be populated after tracks are loaded)
|
|
@@ -294,7 +310,7 @@ export class TranscriptManager {
|
|
|
294
310
|
}
|
|
295
311
|
});
|
|
296
312
|
closeButton.appendChild(createIconElement('close'));
|
|
297
|
-
closeButton.addEventListener('click', () => this.hideTranscript());
|
|
313
|
+
closeButton.addEventListener('click', () => this.hideTranscript({ focusButton: true }));
|
|
298
314
|
|
|
299
315
|
this.transcriptHeader.appendChild(this.headerLeft);
|
|
300
316
|
this.transcriptHeader.appendChild(closeButton);
|
|
@@ -307,15 +323,30 @@ export class TranscriptManager {
|
|
|
307
323
|
this.transcriptWindow.appendChild(this.transcriptHeader);
|
|
308
324
|
this.transcriptWindow.appendChild(this.transcriptContent);
|
|
309
325
|
|
|
326
|
+
this.createResizeHandles();
|
|
327
|
+
|
|
328
|
+
// Live region for announcements (screen reader feedback)
|
|
329
|
+
this.liveRegion = DOMUtils.createElement('div', {
|
|
330
|
+
className: 'vidply-sr-only',
|
|
331
|
+
attributes: {
|
|
332
|
+
'aria-live': 'polite',
|
|
333
|
+
'aria-atomic': 'true'
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
this.transcriptWindow.appendChild(this.liveRegion);
|
|
337
|
+
|
|
310
338
|
// Append to player container
|
|
311
339
|
this.player.container.appendChild(this.transcriptWindow);
|
|
312
340
|
|
|
313
|
-
//
|
|
314
|
-
this.positionTranscript();
|
|
315
|
-
|
|
316
|
-
// Setup drag functionality
|
|
341
|
+
// Setup drag functionality FIRST (this will restore saved position if it exists)
|
|
317
342
|
this.setupDragAndDrop();
|
|
318
343
|
|
|
344
|
+
// Then position it next to the video wrapper ONLY if user hasn't manually positioned it
|
|
345
|
+
// This ensures we don't overwrite saved positions from localStorage
|
|
346
|
+
if (!this.draggableResizable || !this.draggableResizable.manuallyPositioned) {
|
|
347
|
+
this.positionTranscript();
|
|
348
|
+
}
|
|
349
|
+
|
|
319
350
|
// Setup document click handler to close settings menu and style dialog
|
|
320
351
|
// DON'T add it yet - it will be added when the menu is first opened
|
|
321
352
|
this.handlers.documentClick = (e) => {
|
|
@@ -353,23 +384,53 @@ export class TranscriptManager {
|
|
|
353
384
|
// Store flag to track if handler has been added
|
|
354
385
|
this.documentClickHandlerAdded = false;
|
|
355
386
|
|
|
356
|
-
// Re-position on window resize (debounced)
|
|
387
|
+
// Re-position on window resize (debounced) - but only if not manually positioned
|
|
357
388
|
let resizeTimeout;
|
|
358
389
|
this.handlers.resize = () => {
|
|
359
390
|
if (resizeTimeout) {
|
|
360
391
|
this.clearManagedTimeout(resizeTimeout);
|
|
361
392
|
}
|
|
362
|
-
resizeTimeout = this.setManagedTimeout(() =>
|
|
393
|
+
resizeTimeout = this.setManagedTimeout(() => {
|
|
394
|
+
// Only auto-position if user hasn't manually moved it
|
|
395
|
+
if (!this.draggableResizable || !this.draggableResizable.manuallyPositioned) {
|
|
396
|
+
this.positionTranscript();
|
|
397
|
+
}
|
|
398
|
+
}, 100);
|
|
363
399
|
};
|
|
364
400
|
window.addEventListener('resize', this.handlers.resize);
|
|
365
401
|
}
|
|
366
402
|
|
|
403
|
+
createResizeHandles() {
|
|
404
|
+
if (!this.transcriptWindow) return;
|
|
405
|
+
|
|
406
|
+
const directions = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];
|
|
407
|
+
this.transcriptResizeHandles = directions.map(direction => {
|
|
408
|
+
const handle = DOMUtils.createElement('div', {
|
|
409
|
+
className: `${this.player.options.classPrefix}-transcript-resize-handle ${this.player.options.classPrefix}-transcript-resize-${direction}`,
|
|
410
|
+
attributes: {
|
|
411
|
+
'data-direction': direction,
|
|
412
|
+
'data-vidply-managed-resize': 'true',
|
|
413
|
+
'aria-hidden': 'true'
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
handle.style.display = 'none';
|
|
418
|
+
this.transcriptWindow.appendChild(handle);
|
|
419
|
+
return handle;
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
367
423
|
/**
|
|
368
424
|
* Position transcript window next to video
|
|
369
425
|
*/
|
|
370
426
|
positionTranscript() {
|
|
371
427
|
if (!this.transcriptWindow || !this.player.videoWrapper || !this.isVisible) return;
|
|
372
428
|
|
|
429
|
+
// Don't auto-position if user has manually positioned it
|
|
430
|
+
if (this.draggableResizable && this.draggableResizable.manuallyPositioned) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
373
434
|
const isMobile = window.innerWidth < 640;
|
|
374
435
|
const videoRect = this.player.videoWrapper.getBoundingClientRect();
|
|
375
436
|
|
|
@@ -410,8 +471,12 @@ export class TranscriptManager {
|
|
|
410
471
|
this.transcriptWindow.style.top = 'auto';
|
|
411
472
|
this.transcriptWindow.style.maxHeight = 'calc(100vh - 180px)'; // Leave space for controls
|
|
412
473
|
this.transcriptWindow.style.height = 'auto';
|
|
413
|
-
|
|
414
|
-
|
|
474
|
+
const fullscreenMinWidth = 260;
|
|
475
|
+
const fullscreenAvailable = Math.max(fullscreenMinWidth, window.innerWidth - 40);
|
|
476
|
+
const fullscreenDesired = parseFloat(this.transcriptWindow.style.width) || 400;
|
|
477
|
+
const fullscreenWidth = Math.max(fullscreenMinWidth, Math.min(fullscreenDesired, fullscreenAvailable));
|
|
478
|
+
this.transcriptWindow.style.width = `${fullscreenWidth}px`;
|
|
479
|
+
this.transcriptWindow.style.maxWidth = 'none';
|
|
415
480
|
this.transcriptWindow.style.borderRadius = '8px';
|
|
416
481
|
this.transcriptWindow.style.border = '1px solid var(--vidply-border)';
|
|
417
482
|
this.transcriptWindow.style.borderTop = '';
|
|
@@ -421,16 +486,35 @@ export class TranscriptManager {
|
|
|
421
486
|
this.player.container.appendChild(this.transcriptWindow);
|
|
422
487
|
}
|
|
423
488
|
} else {
|
|
424
|
-
// Desktop mode: position
|
|
489
|
+
// Desktop mode: position in right side of viewport
|
|
490
|
+
const transcriptWidth = parseFloat(this.transcriptWindow.style.width) || 400;
|
|
491
|
+
const padding = 20;
|
|
492
|
+
const minWidth = 260;
|
|
493
|
+
const containerRect = this.player.container.getBoundingClientRect();
|
|
494
|
+
|
|
495
|
+
const ensureContainerPositioned = () => {
|
|
496
|
+
const computed = window.getComputedStyle(this.player.container);
|
|
497
|
+
if (computed.position === 'static') {
|
|
498
|
+
this.player.container.style.position = 'relative';
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
ensureContainerPositioned();
|
|
503
|
+
|
|
504
|
+
const left = (videoRect.right - containerRect.left) + padding;
|
|
505
|
+
const availableWidth = window.innerWidth - videoRect.right - padding;
|
|
506
|
+
const appliedWidth = Math.max(minWidth, Math.min(transcriptWidth, availableWidth));
|
|
507
|
+
const appliedHeight = videoRect.height;
|
|
508
|
+
|
|
425
509
|
this.transcriptWindow.style.position = 'absolute';
|
|
426
|
-
this.transcriptWindow.style.left = `${
|
|
510
|
+
this.transcriptWindow.style.left = `${left}px`;
|
|
427
511
|
this.transcriptWindow.style.right = 'auto';
|
|
428
512
|
this.transcriptWindow.style.bottom = 'auto';
|
|
429
513
|
this.transcriptWindow.style.top = '0';
|
|
430
|
-
this.transcriptWindow.style.height = `${
|
|
514
|
+
this.transcriptWindow.style.height = `${appliedHeight}px`;
|
|
431
515
|
this.transcriptWindow.style.maxHeight = 'none';
|
|
432
|
-
this.transcriptWindow.style.width =
|
|
433
|
-
this.transcriptWindow.style.maxWidth = '
|
|
516
|
+
this.transcriptWindow.style.width = `${appliedWidth}px`;
|
|
517
|
+
this.transcriptWindow.style.maxWidth = 'none';
|
|
434
518
|
this.transcriptWindow.style.borderRadius = '8px';
|
|
435
519
|
this.transcriptWindow.style.border = '1px solid var(--vidply-border)';
|
|
436
520
|
this.transcriptWindow.style.borderTop = '';
|
|
@@ -951,362 +1035,127 @@ export class TranscriptManager {
|
|
|
951
1035
|
setupDragAndDrop() {
|
|
952
1036
|
if (!this.transcriptHeader || !this.transcriptWindow) return;
|
|
953
1037
|
|
|
954
|
-
//
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
};
|
|
996
|
-
|
|
997
|
-
this.handlers.touchstart = (e) => {
|
|
998
|
-
if (e.target.closest(`.${this.player.options.classPrefix}-transcript-close`)) {
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// Don't drag if touching settings button
|
|
1003
|
-
if (e.target.closest(`.${this.player.options.classPrefix}-transcript-settings`)) {
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
// Don't drag if touching language selector
|
|
1008
|
-
if (e.target.closest(`.${this.player.options.classPrefix}-transcript-language-select`)) {
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
// Don't drag if touching settings menu
|
|
1013
|
-
if (e.target.closest(`.${this.player.options.classPrefix}-transcript-settings-menu`)) {
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// Don't drag if touching style dialog
|
|
1018
|
-
if (e.target.closest(`.${this.player.options.classPrefix}-transcript-style-dialog`)) {
|
|
1019
|
-
return;
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
const isMobile = window.innerWidth < 640;
|
|
1023
|
-
const isFullscreen = this.player.state.fullscreen;
|
|
1024
|
-
const touch = e.touches[0];
|
|
1025
|
-
|
|
1026
|
-
if (isMobile && !isFullscreen) {
|
|
1027
|
-
// Mobile (not fullscreen): No dragging/swiping, transcript is part of layout
|
|
1028
|
-
return;
|
|
1029
|
-
} else {
|
|
1030
|
-
// Desktop or fullscreen: Normal dragging
|
|
1031
|
-
this.startDragging(touch.clientX, touch.clientY);
|
|
1032
|
-
}
|
|
1033
|
-
};
|
|
1034
|
-
|
|
1035
|
-
this.handlers.touchmove = (e) => {
|
|
1036
|
-
const isMobile = window.innerWidth < 640;
|
|
1037
|
-
const isFullscreen = this.player.state.fullscreen;
|
|
1038
|
-
|
|
1039
|
-
if (isMobile && !isFullscreen) {
|
|
1040
|
-
// Mobile (not fullscreen): No dragging/swiping
|
|
1041
|
-
return;
|
|
1042
|
-
} else if (this.isDragging) {
|
|
1043
|
-
// Desktop or fullscreen: Normal drag
|
|
1044
|
-
const touch = e.touches[0];
|
|
1045
|
-
this.drag(touch.clientX, touch.clientY);
|
|
1046
|
-
e.preventDefault();
|
|
1038
|
+
// Check if we're on mobile and not in fullscreen (no dragging in this case)
|
|
1039
|
+
const isMobile = window.innerWidth < 640;
|
|
1040
|
+
const isFullscreen = this.player.state.fullscreen;
|
|
1041
|
+
|
|
1042
|
+
if (isMobile && !isFullscreen) {
|
|
1043
|
+
return; // No drag/resize on mobile (not fullscreen)
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Create DraggableResizable utility
|
|
1047
|
+
this.draggableResizable = new DraggableResizable(this.transcriptWindow, {
|
|
1048
|
+
dragHandle: this.transcriptHeader,
|
|
1049
|
+
resizeHandles: this.transcriptResizeHandles,
|
|
1050
|
+
constrainToViewport: true,
|
|
1051
|
+
classPrefix: `${this.player.options.classPrefix}-transcript`,
|
|
1052
|
+
keyboardDragKey: 'd',
|
|
1053
|
+
keyboardResizeKey: 'r',
|
|
1054
|
+
keyboardStep: 10,
|
|
1055
|
+
keyboardStepLarge: 50,
|
|
1056
|
+
minWidth: 300,
|
|
1057
|
+
minHeight: 200,
|
|
1058
|
+
maxWidth: () => Math.max(320, window.innerWidth - 40),
|
|
1059
|
+
maxHeight: () => Math.max(200, window.innerHeight - 120),
|
|
1060
|
+
pointerResizeIndicatorText: i18n.t('transcript.resizeModeHint'),
|
|
1061
|
+
onPointerResizeToggle: (enabled) => this.onPointerResizeModeChange(enabled),
|
|
1062
|
+
onDragStart: (e) => {
|
|
1063
|
+
// Don't drag if clicking on certain elements
|
|
1064
|
+
const ignoreSelectors = [
|
|
1065
|
+
`.${this.player.options.classPrefix}-transcript-close`,
|
|
1066
|
+
`.${this.player.options.classPrefix}-transcript-settings`,
|
|
1067
|
+
`.${this.player.options.classPrefix}-transcript-language-select`,
|
|
1068
|
+
`.${this.player.options.classPrefix}-transcript-settings-menu`,
|
|
1069
|
+
`.${this.player.options.classPrefix}-transcript-style-dialog`
|
|
1070
|
+
];
|
|
1071
|
+
|
|
1072
|
+
for (const selector of ignoreSelectors) {
|
|
1073
|
+
if (e.target.closest(selector)) {
|
|
1074
|
+
return false; // Prevent drag
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
return true; // Allow drag
|
|
1047
1079
|
}
|
|
1048
|
-
};
|
|
1080
|
+
});
|
|
1049
1081
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
}
|
|
1055
|
-
};
|
|
1082
|
+
// Add custom keyboard handler for special keys (Escape, Home)
|
|
1083
|
+
this.customKeyHandler = (e) => {
|
|
1084
|
+
const key = e.key.toLowerCase();
|
|
1085
|
+
const alreadyPrevented = e.defaultPrevented;
|
|
1056
1086
|
|
|
1057
|
-
|
|
1058
|
-
// Handle arrow keys only in keyboard drag mode
|
|
1059
|
-
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) {
|
|
1060
|
-
if (!this.keyboardDragMode) {
|
|
1061
|
-
// Not in drag mode, let other handlers deal with it
|
|
1062
|
-
return;
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
// In drag mode - move the window
|
|
1087
|
+
if (key === 'home') {
|
|
1066
1088
|
e.preventDefault();
|
|
1067
1089
|
e.stopPropagation();
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
const computedStyle = window.getComputedStyle(this.transcriptWindow);
|
|
1077
|
-
if (computedStyle.transform !== 'none') {
|
|
1078
|
-
const rect = this.transcriptWindow.getBoundingClientRect();
|
|
1079
|
-
currentLeft = rect.left;
|
|
1080
|
-
currentTop = rect.top;
|
|
1081
|
-
this.transcriptWindow.style.transform = 'none';
|
|
1082
|
-
this.transcriptWindow.style.left = `${currentLeft}px`;
|
|
1083
|
-
this.transcriptWindow.style.top = `${currentTop}px`;
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
// Calculate new position based on arrow key
|
|
1087
|
-
let newX = currentLeft;
|
|
1088
|
-
let newY = currentTop;
|
|
1089
|
-
|
|
1090
|
-
switch(e.key) {
|
|
1091
|
-
case 'ArrowLeft':
|
|
1092
|
-
newX -= step;
|
|
1093
|
-
break;
|
|
1094
|
-
case 'ArrowRight':
|
|
1095
|
-
newX += step;
|
|
1096
|
-
break;
|
|
1097
|
-
case 'ArrowUp':
|
|
1098
|
-
newY -= step;
|
|
1099
|
-
break;
|
|
1100
|
-
case 'ArrowDown':
|
|
1101
|
-
newY += step;
|
|
1102
|
-
break;
|
|
1090
|
+
if (this.draggableResizable) {
|
|
1091
|
+
if (this.draggableResizable.pointerResizeMode) {
|
|
1092
|
+
this.draggableResizable.disablePointerResizeMode();
|
|
1093
|
+
}
|
|
1094
|
+
this.draggableResizable.manuallyPositioned = false;
|
|
1095
|
+
this.positionTranscript();
|
|
1096
|
+
this.updateResizeOptionState();
|
|
1097
|
+
this.announceLive(i18n.t('transcript.positionReset'));
|
|
1103
1098
|
}
|
|
1104
|
-
|
|
1105
|
-
// Set new position directly
|
|
1106
|
-
this.transcriptWindow.style.left = `${newX}px`;
|
|
1107
|
-
this.transcriptWindow.style.top = `${newY}px`;
|
|
1108
1099
|
return;
|
|
1109
1100
|
}
|
|
1110
1101
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1102
|
+
if (key === 'r') {
|
|
1103
|
+
if (alreadyPrevented) {
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1113
1106
|
e.preventDefault();
|
|
1114
1107
|
e.stopPropagation();
|
|
1115
|
-
this.
|
|
1108
|
+
const enabled = this.toggleResizeMode();
|
|
1109
|
+
if (enabled) {
|
|
1110
|
+
this.transcriptWindow.focus();
|
|
1111
|
+
}
|
|
1116
1112
|
return;
|
|
1117
1113
|
}
|
|
1118
1114
|
|
|
1119
|
-
if (
|
|
1115
|
+
if (key === 'escape') {
|
|
1120
1116
|
e.preventDefault();
|
|
1121
1117
|
e.stopPropagation();
|
|
1118
|
+
if (this.draggableResizable && this.draggableResizable.pointerResizeMode) {
|
|
1119
|
+
this.draggableResizable.disablePointerResizeMode();
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
1122
|
if (this.styleDialogVisible) {
|
|
1123
|
-
// Close style dialog first
|
|
1124
1123
|
this.hideStyleDialog();
|
|
1125
|
-
} else if (this.keyboardDragMode) {
|
|
1126
|
-
|
|
1127
|
-
this.
|
|
1124
|
+
} else if (this.draggableResizable && this.draggableResizable.keyboardDragMode) {
|
|
1125
|
+
this.draggableResizable.disableKeyboardDragMode();
|
|
1126
|
+
this.announceLive(i18n.t('transcript.dragModeDisabled'));
|
|
1128
1127
|
} else if (this.settingsMenuVisible) {
|
|
1129
|
-
// Close settings menu
|
|
1130
1128
|
this.hideSettingsMenu();
|
|
1131
1129
|
} else {
|
|
1132
|
-
|
|
1133
|
-
this.hideTranscript();
|
|
1130
|
+
this.hideTranscript({ focusButton: true });
|
|
1134
1131
|
}
|
|
1135
1132
|
return;
|
|
1136
1133
|
}
|
|
1137
1134
|
};
|
|
1138
|
-
|
|
1139
|
-
// Add event listeners using stored handlers
|
|
1140
|
-
this.transcriptHeader.addEventListener('mousedown', this.handlers.mousedown);
|
|
1141
|
-
document.addEventListener('mousemove', this.handlers.mousemove);
|
|
1142
|
-
document.addEventListener('mouseup', this.handlers.mouseup);
|
|
1143
1135
|
|
|
1144
|
-
this.
|
|
1145
|
-
document.addEventListener('touchmove', this.handlers.touchmove);
|
|
1146
|
-
document.addEventListener('touchend', this.handlers.touchend);
|
|
1147
|
-
|
|
1148
|
-
this.transcriptHeader.addEventListener('keydown', this.handlers.keydown);
|
|
1136
|
+
this.transcriptWindow.addEventListener('keydown', this.customKeyHandler);
|
|
1149
1137
|
}
|
|
1150
1138
|
|
|
1151
|
-
/**
|
|
1152
|
-
* Start dragging
|
|
1153
|
-
*/
|
|
1154
|
-
startDragging(clientX, clientY) {
|
|
1155
|
-
// Get current rendered position (this is where it actually appears on screen)
|
|
1156
|
-
const rect = this.transcriptWindow.getBoundingClientRect();
|
|
1157
|
-
|
|
1158
|
-
// Get the parent container position (player container)
|
|
1159
|
-
const containerRect = this.player.container.getBoundingClientRect();
|
|
1160
|
-
|
|
1161
|
-
// Calculate position RELATIVE to container (not viewport)
|
|
1162
|
-
const relativeLeft = rect.left - containerRect.left;
|
|
1163
|
-
const relativeTop = rect.top - containerRect.top;
|
|
1164
|
-
|
|
1165
|
-
// If window is centered with transform, convert to absolute position
|
|
1166
|
-
const computedStyle = window.getComputedStyle(this.transcriptWindow);
|
|
1167
|
-
if (computedStyle.transform !== 'none') {
|
|
1168
|
-
// Remove transform and set position relative to container
|
|
1169
|
-
this.transcriptWindow.style.transform = 'none';
|
|
1170
|
-
this.transcriptWindow.style.left = `${relativeLeft}px`;
|
|
1171
|
-
this.transcriptWindow.style.top = `${relativeTop}px`;
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
// Calculate offset based on viewport coordinates (where user clicked)
|
|
1175
|
-
this.dragOffsetX = clientX - rect.left;
|
|
1176
|
-
this.dragOffsetY = clientY - rect.top;
|
|
1177
|
-
|
|
1178
|
-
this.isDragging = true;
|
|
1179
|
-
this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-dragging`);
|
|
1180
|
-
document.body.style.cursor = 'grabbing';
|
|
1181
|
-
document.body.style.userSelect = 'none';
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
/**
|
|
1185
|
-
* Perform drag
|
|
1186
|
-
*/
|
|
1187
|
-
drag(clientX, clientY) {
|
|
1188
|
-
if (!this.isDragging) return;
|
|
1189
|
-
|
|
1190
|
-
// Calculate new viewport position based on mouse position minus the offset
|
|
1191
|
-
const newViewportX = clientX - this.dragOffsetX;
|
|
1192
|
-
const newViewportY = clientY - this.dragOffsetY;
|
|
1193
|
-
|
|
1194
|
-
// Convert to position relative to container
|
|
1195
|
-
const containerRect = this.player.container.getBoundingClientRect();
|
|
1196
|
-
const newX = newViewportX - containerRect.left;
|
|
1197
|
-
const newY = newViewportY - containerRect.top;
|
|
1198
|
-
|
|
1199
|
-
// During drag, set position relative to container
|
|
1200
|
-
this.transcriptWindow.style.left = `${newX}px`;
|
|
1201
|
-
this.transcriptWindow.style.top = `${newY}px`;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
/**
|
|
1205
|
-
* Stop dragging
|
|
1206
|
-
*/
|
|
1207
|
-
stopDragging() {
|
|
1208
|
-
this.isDragging = false;
|
|
1209
|
-
this.transcriptWindow.classList.remove(`${this.player.options.classPrefix}-transcript-dragging`);
|
|
1210
|
-
document.body.style.cursor = '';
|
|
1211
|
-
document.body.style.userSelect = '';
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
/**
|
|
1215
|
-
* Set window position with boundary constraints
|
|
1216
|
-
*/
|
|
1217
|
-
setPosition(x, y) {
|
|
1218
|
-
const rect = this.transcriptWindow.getBoundingClientRect();
|
|
1219
|
-
|
|
1220
|
-
// Use document dimensions for fixed positioning
|
|
1221
|
-
const viewportWidth = document.documentElement.clientWidth;
|
|
1222
|
-
const viewportHeight = document.documentElement.clientHeight;
|
|
1223
|
-
|
|
1224
|
-
// Very relaxed boundaries - allow window to go mostly off-screen
|
|
1225
|
-
// Just keep a small part visible so user can always drag it back
|
|
1226
|
-
const minVisible = 100; // Keep at least 100px visible
|
|
1227
|
-
const minX = -(rect.width - minVisible); // Can go way off-screen to the left
|
|
1228
|
-
const minY = -(rect.height - minVisible); // Can go way off-screen to the top
|
|
1229
|
-
const maxX = viewportWidth - minVisible; // Can go way off-screen to the right
|
|
1230
|
-
const maxY = viewportHeight - minVisible; // Can go way off-screen to the bottom
|
|
1231
|
-
|
|
1232
|
-
// Clamp position to boundaries (very loose)
|
|
1233
|
-
x = Math.max(minX, Math.min(x, maxX));
|
|
1234
|
-
y = Math.max(minY, Math.min(y, maxY));
|
|
1235
|
-
|
|
1236
|
-
this.transcriptWindow.style.left = `${x}px`;
|
|
1237
|
-
this.transcriptWindow.style.top = `${y}px`;
|
|
1238
|
-
this.transcriptWindow.style.transform = 'none';
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
/**
|
|
1242
|
-
* Reset position to center
|
|
1243
|
-
*/
|
|
1244
|
-
resetPosition() {
|
|
1245
|
-
this.transcriptWindow.style.left = '50%';
|
|
1246
|
-
this.transcriptWindow.style.top = '50%';
|
|
1247
|
-
this.transcriptWindow.style.transform = 'translate(-50%, -50%)';
|
|
1248
|
-
}
|
|
1249
1139
|
|
|
1250
1140
|
/**
|
|
1251
1141
|
* Toggle keyboard drag mode
|
|
1252
1142
|
*/
|
|
1253
1143
|
toggleKeyboardDragMode() {
|
|
1254
|
-
if (this.
|
|
1255
|
-
this.
|
|
1256
|
-
|
|
1257
|
-
this.
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
if (this.settingsButton) {
|
|
1270
|
-
this.settingsButton.setAttribute('aria-label', 'Keyboard drag mode active. Use arrow keys to move window. Press D or Escape to exit.');
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
// Add visual indicator
|
|
1274
|
-
const indicator = DOMUtils.createElement('div', {
|
|
1275
|
-
className: `${this.player.options.classPrefix}-transcript-drag-indicator`,
|
|
1276
|
-
textContent: i18n.t('transcript.keyboardDragActive')
|
|
1277
|
-
});
|
|
1278
|
-
this.transcriptHeader.appendChild(indicator);
|
|
1279
|
-
|
|
1280
|
-
// Hide settings menu if open
|
|
1281
|
-
if (this.settingsMenuVisible) {
|
|
1282
|
-
this.hideSettingsMenu();
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
// Focus the header for keyboard navigation
|
|
1286
|
-
this.transcriptHeader.focus();
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
/**
|
|
1290
|
-
* Disable keyboard drag mode
|
|
1291
|
-
*/
|
|
1292
|
-
disableKeyboardDragMode() {
|
|
1293
|
-
this.keyboardDragMode = false;
|
|
1294
|
-
this.transcriptWindow.classList.remove(`${this.player.options.classPrefix}-transcript-keyboard-drag`);
|
|
1295
|
-
|
|
1296
|
-
// Update settings button aria label
|
|
1297
|
-
if (this.settingsButton) {
|
|
1298
|
-
this.settingsButton.setAttribute('aria-label', 'Transcript settings. Press Enter to open menu, or D to enable drag mode');
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
// Remove visual indicator
|
|
1302
|
-
const indicator = this.transcriptHeader.querySelector(`.${this.player.options.classPrefix}-transcript-drag-indicator`);
|
|
1303
|
-
if (indicator) {
|
|
1304
|
-
indicator.remove();
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
// Focus back to settings button
|
|
1308
|
-
if (this.settingsButton) {
|
|
1309
|
-
this.settingsButton.focus();
|
|
1144
|
+
if (this.draggableResizable) {
|
|
1145
|
+
const wasEnabled = this.draggableResizable.keyboardDragMode;
|
|
1146
|
+
this.draggableResizable.toggleKeyboardDragMode();
|
|
1147
|
+
const isEnabled = this.draggableResizable.keyboardDragMode;
|
|
1148
|
+
if (!wasEnabled && isEnabled) {
|
|
1149
|
+
this.enableMoveMode();
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// Hide settings menu if open
|
|
1153
|
+
if (this.settingsMenuVisible) {
|
|
1154
|
+
this.hideSettingsMenu();
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Focus the window for keyboard navigation
|
|
1158
|
+
this.transcriptWindow.focus();
|
|
1310
1159
|
}
|
|
1311
1160
|
}
|
|
1312
1161
|
|
|
@@ -1342,6 +1191,16 @@ export class TranscriptManager {
|
|
|
1342
1191
|
if (this.settingsMenu) {
|
|
1343
1192
|
this.settingsMenu.style.display = 'block';
|
|
1344
1193
|
this.settingsMenuVisible = true;
|
|
1194
|
+
if (this.settingsButton) {
|
|
1195
|
+
this.settingsButton.setAttribute('aria-expanded', 'true');
|
|
1196
|
+
}
|
|
1197
|
+
this.updateResizeOptionState();
|
|
1198
|
+
setTimeout(() => {
|
|
1199
|
+
const firstItem = this.settingsMenu.querySelector(`.${this.player.options.classPrefix}-transcript-settings-item`);
|
|
1200
|
+
if (firstItem) {
|
|
1201
|
+
firstItem.focus();
|
|
1202
|
+
}
|
|
1203
|
+
}, 0);
|
|
1345
1204
|
return;
|
|
1346
1205
|
}
|
|
1347
1206
|
// Create settings menu
|
|
@@ -1397,19 +1256,38 @@ export class TranscriptManager {
|
|
|
1397
1256
|
className: `${this.player.options.classPrefix}-transcript-settings-item`,
|
|
1398
1257
|
attributes: {
|
|
1399
1258
|
'type': 'button',
|
|
1400
|
-
'aria-label': i18n.t('transcript.resizeWindow')
|
|
1259
|
+
'aria-label': i18n.t('transcript.resizeWindow'),
|
|
1260
|
+
'aria-pressed': 'false'
|
|
1401
1261
|
}
|
|
1402
1262
|
});
|
|
1403
1263
|
const resizeIcon = createIconElement('resize');
|
|
1404
1264
|
const resizeText = DOMUtils.createElement('span', {
|
|
1265
|
+
className: `${this.player.options.classPrefix}-transcript-settings-text`,
|
|
1405
1266
|
textContent: i18n.t('transcript.resizeWindow')
|
|
1406
1267
|
});
|
|
1407
1268
|
resizeOption.appendChild(resizeIcon);
|
|
1408
1269
|
resizeOption.appendChild(resizeText);
|
|
1409
|
-
resizeOption.addEventListener('click', () => {
|
|
1410
|
-
|
|
1411
|
-
|
|
1270
|
+
resizeOption.addEventListener('click', (event) => {
|
|
1271
|
+
event.preventDefault();
|
|
1272
|
+
event.stopPropagation();
|
|
1273
|
+
|
|
1274
|
+
const enabled = this.toggleResizeMode({ focus: false });
|
|
1275
|
+
|
|
1276
|
+
if (enabled) {
|
|
1277
|
+
this.hideSettingsMenu({ focusButton: false });
|
|
1278
|
+
// Focus transcript window after handles appear
|
|
1279
|
+
this.setManagedTimeout(() => {
|
|
1280
|
+
if (this.transcriptWindow) {
|
|
1281
|
+
this.transcriptWindow.focus();
|
|
1282
|
+
}
|
|
1283
|
+
}, 20);
|
|
1284
|
+
} else {
|
|
1285
|
+
this.hideSettingsMenu({ focusButton: true });
|
|
1286
|
+
}
|
|
1412
1287
|
});
|
|
1288
|
+
this.resizeOptionButton = resizeOption;
|
|
1289
|
+
this.resizeOptionText = resizeText;
|
|
1290
|
+
this.updateResizeOptionState();
|
|
1413
1291
|
|
|
1414
1292
|
// Close option
|
|
1415
1293
|
const closeOption = DOMUtils.createElement('button', {
|
|
@@ -1449,6 +1327,7 @@ export class TranscriptManager {
|
|
|
1449
1327
|
if (this.settingsButton) {
|
|
1450
1328
|
this.settingsButton.setAttribute('aria-expanded', 'true');
|
|
1451
1329
|
}
|
|
1330
|
+
this.updateResizeOptionState();
|
|
1452
1331
|
|
|
1453
1332
|
// Focus first menu item
|
|
1454
1333
|
setTimeout(() => {
|
|
@@ -1462,7 +1341,7 @@ export class TranscriptManager {
|
|
|
1462
1341
|
/**
|
|
1463
1342
|
* Hide settings menu
|
|
1464
1343
|
*/
|
|
1465
|
-
hideSettingsMenu() {
|
|
1344
|
+
hideSettingsMenu({ focusButton = true } = {}) {
|
|
1466
1345
|
if (this.settingsMenu) {
|
|
1467
1346
|
this.settingsMenu.style.display = 'none';
|
|
1468
1347
|
this.settingsMenuVisible = false;
|
|
@@ -1471,8 +1350,10 @@ export class TranscriptManager {
|
|
|
1471
1350
|
// Update aria-expanded
|
|
1472
1351
|
if (this.settingsButton) {
|
|
1473
1352
|
this.settingsButton.setAttribute('aria-expanded', 'false');
|
|
1474
|
-
|
|
1475
|
-
|
|
1353
|
+
if (focusButton) {
|
|
1354
|
+
// Return focus to settings button
|
|
1355
|
+
this.settingsButton.focus();
|
|
1356
|
+
}
|
|
1476
1357
|
}
|
|
1477
1358
|
}
|
|
1478
1359
|
}
|
|
@@ -1481,6 +1362,8 @@ export class TranscriptManager {
|
|
|
1481
1362
|
* Enable move mode (gives visual feedback)
|
|
1482
1363
|
*/
|
|
1483
1364
|
enableMoveMode() {
|
|
1365
|
+
this.hideResizeModeIndicator();
|
|
1366
|
+
|
|
1484
1367
|
// Add visual feedback for move mode
|
|
1485
1368
|
this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-move-mode`);
|
|
1486
1369
|
|
|
@@ -1503,193 +1386,82 @@ export class TranscriptManager {
|
|
|
1503
1386
|
/**
|
|
1504
1387
|
* Toggle resize mode
|
|
1505
1388
|
*/
|
|
1506
|
-
toggleResizeMode() {
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
if (this.resizeEnabled) {
|
|
1510
|
-
this.enableResizeHandles();
|
|
1511
|
-
} else {
|
|
1512
|
-
this.disableResizeHandles();
|
|
1389
|
+
toggleResizeMode({ focus = true } = {}) {
|
|
1390
|
+
if (!this.draggableResizable) {
|
|
1391
|
+
return false;
|
|
1513
1392
|
}
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
/**
|
|
1517
|
-
* Enable resize handles
|
|
1518
|
-
*/
|
|
1519
|
-
enableResizeHandles() {
|
|
1520
|
-
if (!this.transcriptWindow) return;
|
|
1521
1393
|
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
const handle = DOMUtils.createElement('div', {
|
|
1527
|
-
className: `${this.player.options.classPrefix}-transcript-resize-handle ${this.player.options.classPrefix}-transcript-resize-${direction}`,
|
|
1528
|
-
attributes: {
|
|
1529
|
-
'data-direction': direction
|
|
1530
|
-
}
|
|
1531
|
-
});
|
|
1532
|
-
|
|
1533
|
-
handle.addEventListener('mousedown', (e) => this.startResize(e, direction));
|
|
1534
|
-
handle.addEventListener('touchstart', (e) => this.startResize(e.touches[0], direction));
|
|
1535
|
-
|
|
1536
|
-
this.transcriptWindow.appendChild(handle);
|
|
1537
|
-
});
|
|
1538
|
-
|
|
1539
|
-
this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-resizable`);
|
|
1540
|
-
|
|
1541
|
-
// Setup resize event handlers
|
|
1542
|
-
this.handlers.resizeMove = (e) => {
|
|
1543
|
-
if (this.isResizing) {
|
|
1544
|
-
this.performResize(e.clientX, e.clientY);
|
|
1545
|
-
}
|
|
1546
|
-
};
|
|
1547
|
-
|
|
1548
|
-
this.handlers.resizeEnd = () => {
|
|
1549
|
-
if (this.isResizing) {
|
|
1550
|
-
this.stopResize();
|
|
1551
|
-
}
|
|
1552
|
-
};
|
|
1553
|
-
|
|
1554
|
-
this.handlers.resizeTouchMove = (e) => {
|
|
1555
|
-
if (this.isResizing) {
|
|
1556
|
-
this.performResize(e.touches[0].clientX, e.touches[0].clientY);
|
|
1557
|
-
e.preventDefault();
|
|
1558
|
-
}
|
|
1559
|
-
};
|
|
1394
|
+
if (this.draggableResizable.pointerResizeMode) {
|
|
1395
|
+
this.draggableResizable.disablePointerResizeMode({ focus });
|
|
1396
|
+
return false;
|
|
1397
|
+
}
|
|
1560
1398
|
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
document.addEventListener('touchmove', this.handlers.resizeTouchMove);
|
|
1564
|
-
document.addEventListener('touchend', this.handlers.resizeEnd);
|
|
1399
|
+
this.draggableResizable.enablePointerResizeMode({ focus });
|
|
1400
|
+
return true;
|
|
1565
1401
|
}
|
|
1566
1402
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1403
|
+
updateResizeOptionState() {
|
|
1404
|
+
if (!this.resizeOptionButton) {
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
const isEnabled = !!(this.draggableResizable && this.draggableResizable.pointerResizeMode);
|
|
1409
|
+
const label = isEnabled
|
|
1410
|
+
? (i18n.t('transcript.disableResizeWindow') || 'Disable Resize Mode')
|
|
1411
|
+
: i18n.t('transcript.resizeWindow');
|
|
1576
1412
|
|
|
1577
|
-
this.
|
|
1413
|
+
this.resizeOptionButton.setAttribute('aria-pressed', isEnabled ? 'true' : 'false');
|
|
1414
|
+
this.resizeOptionButton.setAttribute('aria-label', label);
|
|
1415
|
+
this.resizeOptionButton.setAttribute('title', label);
|
|
1578
1416
|
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
document.removeEventListener('mousemove', this.handlers.resizeMove);
|
|
1582
|
-
}
|
|
1583
|
-
if (this.handlers.resizeEnd) {
|
|
1584
|
-
document.removeEventListener('mouseup', this.handlers.resizeEnd);
|
|
1585
|
-
}
|
|
1586
|
-
if (this.handlers.resizeTouchMove) {
|
|
1587
|
-
document.removeEventListener('touchmove', this.handlers.resizeTouchMove);
|
|
1417
|
+
if (this.resizeOptionText) {
|
|
1418
|
+
this.resizeOptionText.textContent = label;
|
|
1588
1419
|
}
|
|
1589
|
-
document.removeEventListener('touchend', this.handlers.resizeEnd);
|
|
1590
1420
|
}
|
|
1591
1421
|
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
e.stopPropagation();
|
|
1597
|
-
e.preventDefault();
|
|
1598
|
-
|
|
1599
|
-
this.isResizing = true;
|
|
1600
|
-
this.resizeDirection = direction;
|
|
1601
|
-
this.resizeStartX = e.clientX;
|
|
1602
|
-
this.resizeStartY = e.clientY;
|
|
1603
|
-
|
|
1604
|
-
const rect = this.transcriptWindow.getBoundingClientRect();
|
|
1605
|
-
this.resizeStartWidth = rect.width;
|
|
1606
|
-
this.resizeStartHeight = rect.height;
|
|
1607
|
-
|
|
1608
|
-
this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-resizing`);
|
|
1609
|
-
document.body.style.cursor = this.getResizeCursor(direction);
|
|
1610
|
-
document.body.style.userSelect = 'none';
|
|
1611
|
-
}
|
|
1422
|
+
showResizeModeIndicator() {
|
|
1423
|
+
if (!this.transcriptHeader) {
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1612
1426
|
|
|
1613
|
-
|
|
1614
|
-
* Perform resize
|
|
1615
|
-
*/
|
|
1616
|
-
performResize(clientX, clientY) {
|
|
1617
|
-
if (!this.isResizing) return;
|
|
1427
|
+
this.hideResizeModeIndicator();
|
|
1618
1428
|
|
|
1619
|
-
const
|
|
1620
|
-
|
|
1429
|
+
const indicator = DOMUtils.createElement('div', {
|
|
1430
|
+
className: `${this.player.options.classPrefix}-transcript-resize-tooltip`,
|
|
1431
|
+
textContent: i18n.t('transcript.resizeModeHint') || 'Resize handles enabled. Drag edges or corners to adjust. Press Esc or R to exit.'
|
|
1432
|
+
});
|
|
1621
1433
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1434
|
+
this.transcriptHeader.appendChild(indicator);
|
|
1435
|
+
this.resizeModeIndicator = indicator;
|
|
1624
1436
|
|
|
1625
|
-
|
|
1437
|
+
this.resizeModeIndicatorTimeout = this.setManagedTimeout(() => {
|
|
1438
|
+
this.hideResizeModeIndicator();
|
|
1439
|
+
}, 3000);
|
|
1440
|
+
}
|
|
1626
1441
|
|
|
1627
|
-
|
|
1628
|
-
if (
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
if (direction.includes('w')) {
|
|
1632
|
-
newWidth = this.resizeStartWidth - deltaX;
|
|
1633
|
-
}
|
|
1634
|
-
if (direction.includes('s')) {
|
|
1635
|
-
newHeight = this.resizeStartHeight + deltaY;
|
|
1636
|
-
}
|
|
1637
|
-
if (direction.includes('n')) {
|
|
1638
|
-
newHeight = this.resizeStartHeight - deltaY;
|
|
1442
|
+
hideResizeModeIndicator() {
|
|
1443
|
+
if (this.resizeModeIndicatorTimeout) {
|
|
1444
|
+
this.clearManagedTimeout(this.resizeModeIndicatorTimeout);
|
|
1445
|
+
this.resizeModeIndicatorTimeout = null;
|
|
1639
1446
|
}
|
|
1640
1447
|
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
const minHeight = 200;
|
|
1644
|
-
const maxWidth = window.innerWidth - 40;
|
|
1645
|
-
const maxHeight = window.innerHeight - 40;
|
|
1646
|
-
|
|
1647
|
-
newWidth = Math.max(minWidth, Math.min(newWidth, maxWidth));
|
|
1648
|
-
newHeight = Math.max(minHeight, Math.min(newHeight, maxHeight));
|
|
1649
|
-
|
|
1650
|
-
// Apply new dimensions
|
|
1651
|
-
this.transcriptWindow.style.width = `${newWidth}px`;
|
|
1652
|
-
this.transcriptWindow.style.height = `${newHeight}px`;
|
|
1653
|
-
this.transcriptWindow.style.maxWidth = `${newWidth}px`;
|
|
1654
|
-
this.transcriptWindow.style.maxHeight = `${newHeight}px`;
|
|
1655
|
-
|
|
1656
|
-
// Adjust position if resizing from top or left
|
|
1657
|
-
if (direction.includes('w')) {
|
|
1658
|
-
const currentLeft = parseFloat(this.transcriptWindow.style.left) || 0;
|
|
1659
|
-
this.transcriptWindow.style.left = `${currentLeft + (this.resizeStartWidth - newWidth)}px`;
|
|
1660
|
-
}
|
|
1661
|
-
if (direction.includes('n')) {
|
|
1662
|
-
const currentTop = parseFloat(this.transcriptWindow.style.top) || 0;
|
|
1663
|
-
this.transcriptWindow.style.top = `${currentTop + (this.resizeStartHeight - newHeight)}px`;
|
|
1448
|
+
if (this.resizeModeIndicator && this.resizeModeIndicator.parentNode) {
|
|
1449
|
+
this.resizeModeIndicator.remove();
|
|
1664
1450
|
}
|
|
1665
|
-
}
|
|
1666
1451
|
|
|
1667
|
-
|
|
1668
|
-
* Stop resizing
|
|
1669
|
-
*/
|
|
1670
|
-
stopResize() {
|
|
1671
|
-
this.isResizing = false;
|
|
1672
|
-
this.resizeDirection = null;
|
|
1673
|
-
this.transcriptWindow.classList.remove(`${this.player.options.classPrefix}-transcript-resizing`);
|
|
1674
|
-
document.body.style.cursor = '';
|
|
1675
|
-
document.body.style.userSelect = '';
|
|
1452
|
+
this.resizeModeIndicator = null;
|
|
1676
1453
|
}
|
|
1677
1454
|
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
'
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
'
|
|
1687
|
-
|
|
1688
|
-
'nw': 'nwse-resize',
|
|
1689
|
-
'se': 'nwse-resize',
|
|
1690
|
-
'sw': 'nesw-resize'
|
|
1691
|
-
};
|
|
1692
|
-
return cursors[direction] || 'default';
|
|
1455
|
+
onPointerResizeModeChange(enabled) {
|
|
1456
|
+
this.updateResizeOptionState();
|
|
1457
|
+
|
|
1458
|
+
if (enabled) {
|
|
1459
|
+
this.showResizeModeIndicator();
|
|
1460
|
+
this.announceLive(i18n.t('transcript.resizeModeEnabled'));
|
|
1461
|
+
} else {
|
|
1462
|
+
this.hideResizeModeIndicator();
|
|
1463
|
+
this.announceLive(i18n.t('transcript.resizeModeDisabled'));
|
|
1464
|
+
}
|
|
1693
1465
|
}
|
|
1694
1466
|
|
|
1695
1467
|
/**
|
|
@@ -2019,31 +1791,28 @@ export class TranscriptManager {
|
|
|
2019
1791
|
* Cleanup
|
|
2020
1792
|
*/
|
|
2021
1793
|
destroy() {
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
1794
|
+
this.hideResizeModeIndicator();
|
|
1795
|
+
|
|
1796
|
+
// Destroy draggableResizable utility
|
|
1797
|
+
if (this.draggableResizable) {
|
|
1798
|
+
if (this.draggableResizable.pointerResizeMode) {
|
|
1799
|
+
this.draggableResizable.disablePointerResizeMode();
|
|
1800
|
+
this.updateResizeOptionState();
|
|
1801
|
+
}
|
|
1802
|
+
this.draggableResizable.destroy();
|
|
1803
|
+
this.draggableResizable = null;
|
|
2025
1804
|
}
|
|
2026
|
-
|
|
2027
|
-
|
|
1805
|
+
|
|
1806
|
+
// Remove custom key handler
|
|
1807
|
+
if (this.transcriptWindow && this.customKeyHandler) {
|
|
1808
|
+
this.transcriptWindow.removeEventListener('keydown', this.customKeyHandler);
|
|
1809
|
+
this.customKeyHandler = null;
|
|
2028
1810
|
}
|
|
2029
1811
|
|
|
2030
1812
|
// Remove timeupdate listener from player
|
|
2031
1813
|
if (this.handlers.timeupdate) {
|
|
2032
1814
|
this.player.off('timeupdate', this.handlers.timeupdate);
|
|
2033
1815
|
}
|
|
2034
|
-
|
|
2035
|
-
// Remove drag event listeners
|
|
2036
|
-
if (this.transcriptHeader) {
|
|
2037
|
-
if (this.handlers.mousedown) {
|
|
2038
|
-
this.transcriptHeader.removeEventListener('mousedown', this.handlers.mousedown);
|
|
2039
|
-
}
|
|
2040
|
-
if (this.handlers.touchstart) {
|
|
2041
|
-
this.transcriptHeader.removeEventListener('touchstart', this.handlers.touchstart);
|
|
2042
|
-
}
|
|
2043
|
-
if (this.handlers.keydown) {
|
|
2044
|
-
this.transcriptHeader.removeEventListener('keydown', this.handlers.keydown);
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
1816
|
|
|
2048
1817
|
// Remove settings button event listeners
|
|
2049
1818
|
if (this.settingsButton) {
|
|
@@ -2060,19 +1829,7 @@ export class TranscriptManager {
|
|
|
2060
1829
|
this.styleDialog.removeEventListener('keydown', this.handlers.styleDialogKeydown);
|
|
2061
1830
|
}
|
|
2062
1831
|
|
|
2063
|
-
// Remove document
|
|
2064
|
-
if (this.handlers.mousemove) {
|
|
2065
|
-
document.removeEventListener('mousemove', this.handlers.mousemove);
|
|
2066
|
-
}
|
|
2067
|
-
if (this.handlers.mouseup) {
|
|
2068
|
-
document.removeEventListener('mouseup', this.handlers.mouseup);
|
|
2069
|
-
}
|
|
2070
|
-
if (this.handlers.touchmove) {
|
|
2071
|
-
document.removeEventListener('touchmove', this.handlers.touchmove);
|
|
2072
|
-
}
|
|
2073
|
-
if (this.handlers.touchend) {
|
|
2074
|
-
document.removeEventListener('touchend', this.handlers.touchend);
|
|
2075
|
-
}
|
|
1832
|
+
// Remove document click listener
|
|
2076
1833
|
if (this.handlers.documentClick) {
|
|
2077
1834
|
document.removeEventListener('click', this.handlers.documentClick);
|
|
2078
1835
|
}
|
|
@@ -2100,5 +1857,14 @@ export class TranscriptManager {
|
|
|
2100
1857
|
this.transcriptEntries = [];
|
|
2101
1858
|
this.settingsMenu = null;
|
|
2102
1859
|
this.styleDialog = null;
|
|
1860
|
+
this.transcriptResizeHandles = [];
|
|
1861
|
+
this.resizeOptionButton = null;
|
|
1862
|
+
this.resizeOptionText = null;
|
|
1863
|
+
this.liveRegion = null;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
announceLive(message) {
|
|
1867
|
+
if (!this.liveRegion) return;
|
|
1868
|
+
this.liveRegion.textContent = message || '';
|
|
2103
1869
|
}
|
|
2104
1870
|
}
|