vidply 1.0.22 → 1.0.24
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 +184 -4
- package/dist/vidply.esm.js +411 -104
- package/dist/vidply.esm.js.map +2 -2
- package/dist/vidply.esm.min.js +11 -7
- package/dist/vidply.esm.min.meta.json +30 -25
- package/dist/vidply.js +411 -104
- package/dist/vidply.js.map +2 -2
- package/dist/vidply.min.css +1 -1
- package/dist/vidply.min.js +11 -7
- package/dist/vidply.min.meta.json +30 -25
- package/package.json +2 -2
- package/src/controls/ControlBar.js +3343 -3246
- package/src/controls/TranscriptManager.js +2296 -2271
- package/src/core/Player.js +4768 -4730
- package/src/features/PlaylistManager.js +1202 -1039
- package/src/i18n/languages/de.js +5 -1
- package/src/i18n/languages/en.js +5 -1
- package/src/i18n/languages/es.js +5 -1
- package/src/i18n/languages/fr.js +5 -1
- package/src/i18n/languages/ja.js +5 -1
- package/src/styles/vidply.css +184 -4
- package/src/utils/DOMUtils.js +67 -0
- package/src/utils/MenuUtils.js +10 -4
- package/src/utils/SettingsMenuFactory.js +8 -4
- package/src/utils/WindowComponents.js +6 -4
package/src/i18n/languages/de.js
CHANGED
|
@@ -164,7 +164,11 @@ export const de = {
|
|
|
164
164
|
nowPlaying: 'Läuft gerade: Titel {current} von {total}. {title}{artist}',
|
|
165
165
|
by: ' von ',
|
|
166
166
|
untitled: 'Ohne Titel',
|
|
167
|
-
trackUntitled: 'Titel {number}'
|
|
167
|
+
trackUntitled: 'Titel {number}',
|
|
168
|
+
currentlyPlaying: 'Wird gerade abgespielt',
|
|
169
|
+
notPlaying: 'Nicht aktiv',
|
|
170
|
+
pressEnterPlay: 'Eingabetaste zum Abspielen',
|
|
171
|
+
pressEnterRestart: 'Eingabetaste zum Neustart'
|
|
168
172
|
}
|
|
169
173
|
};
|
|
170
174
|
|
package/src/i18n/languages/en.js
CHANGED
|
@@ -164,7 +164,11 @@ export const en = {
|
|
|
164
164
|
nowPlaying: 'Now playing: Track {current} of {total}. {title}{artist}',
|
|
165
165
|
by: ' by ',
|
|
166
166
|
untitled: 'Untitled',
|
|
167
|
-
trackUntitled: 'Track {number}'
|
|
167
|
+
trackUntitled: 'Track {number}',
|
|
168
|
+
currentlyPlaying: 'Currently playing',
|
|
169
|
+
notPlaying: 'Not playing',
|
|
170
|
+
pressEnterPlay: 'Press Enter to play',
|
|
171
|
+
pressEnterRestart: 'Press Enter to restart'
|
|
168
172
|
}
|
|
169
173
|
};
|
|
170
174
|
|
package/src/i18n/languages/es.js
CHANGED
|
@@ -164,7 +164,11 @@ export const es = {
|
|
|
164
164
|
nowPlaying: 'Reproduciendo ahora: Pista {current} de {total}. {title}{artist}',
|
|
165
165
|
by: ' por ',
|
|
166
166
|
untitled: 'Sin título',
|
|
167
|
-
trackUntitled: 'Pista {number}'
|
|
167
|
+
trackUntitled: 'Pista {number}',
|
|
168
|
+
currentlyPlaying: 'Reproduciendo actualmente',
|
|
169
|
+
notPlaying: 'Sin reproducir',
|
|
170
|
+
pressEnterPlay: 'Pulsa Enter para reproducir',
|
|
171
|
+
pressEnterRestart: 'Pulsa Enter para reiniciar'
|
|
168
172
|
}
|
|
169
173
|
};
|
|
170
174
|
|
package/src/i18n/languages/fr.js
CHANGED
|
@@ -164,7 +164,11 @@ export const fr = {
|
|
|
164
164
|
nowPlaying: 'Lecture en cours : Piste {current} sur {total}. {title}{artist}',
|
|
165
165
|
by: ' par ',
|
|
166
166
|
untitled: 'Sans titre',
|
|
167
|
-
trackUntitled: 'Piste {number}'
|
|
167
|
+
trackUntitled: 'Piste {number}',
|
|
168
|
+
currentlyPlaying: 'En cours de lecture',
|
|
169
|
+
notPlaying: 'Non en lecture',
|
|
170
|
+
pressEnterPlay: 'Appuyez sur Entrée pour lire',
|
|
171
|
+
pressEnterRestart: 'Appuyez sur Entrée pour recommencer'
|
|
168
172
|
}
|
|
169
173
|
};
|
|
170
174
|
|
package/src/i18n/languages/ja.js
CHANGED
|
@@ -164,7 +164,11 @@ export const ja = {
|
|
|
164
164
|
nowPlaying: '再生中: トラック {current}/{total}. {title}{artist}',
|
|
165
165
|
by: ' - ',
|
|
166
166
|
untitled: 'タイトルなし',
|
|
167
|
-
trackUntitled: 'トラック {number}'
|
|
167
|
+
trackUntitled: 'トラック {number}',
|
|
168
|
+
currentlyPlaying: '再生中',
|
|
169
|
+
notPlaying: '停止中',
|
|
170
|
+
pressEnterPlay: 'Enterキーで再生',
|
|
171
|
+
pressEnterRestart: 'Enterキーで最初から再生'
|
|
168
172
|
}
|
|
169
173
|
};
|
|
170
174
|
|
package/src/styles/vidply.css
CHANGED
|
@@ -489,7 +489,6 @@
|
|
|
489
489
|
background: linear-gradient(135deg, var(--vidply-black) 0%, #2a2a2a 100%);
|
|
490
490
|
height: 100%;
|
|
491
491
|
order: 1; /* First in flex order */
|
|
492
|
-
overflow: hidden;
|
|
493
492
|
position: relative;
|
|
494
493
|
width: 100%;
|
|
495
494
|
z-index: 1; /* Base video layer */
|
|
@@ -580,6 +579,8 @@
|
|
|
580
579
|
transform: translate(-50%, -50%);
|
|
581
580
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
582
581
|
z-index: var(--vidply-z-overlay);
|
|
582
|
+
border: 0.125rem solid var(--vidply-primary);
|
|
583
|
+
border-radius: 50%;
|
|
583
584
|
}
|
|
584
585
|
|
|
585
586
|
.vidply-play-overlay:hover {
|
|
@@ -669,7 +670,7 @@
|
|
|
669
670
|
.vidply-progress-time-wrapper {
|
|
670
671
|
align-items: center;
|
|
671
672
|
display: flex;
|
|
672
|
-
gap:
|
|
673
|
+
gap: 1.25rem;
|
|
673
674
|
margin-bottom: var(--vidply-gap-lg);
|
|
674
675
|
width: 100%;
|
|
675
676
|
}
|
|
@@ -681,6 +682,7 @@
|
|
|
681
682
|
cursor: pointer;
|
|
682
683
|
flex: 1;
|
|
683
684
|
height: 0.5625rem;
|
|
685
|
+
margin-right: 0.5rem;
|
|
684
686
|
position: relative;
|
|
685
687
|
transition: height 0.2s ease;
|
|
686
688
|
}
|
|
@@ -802,6 +804,82 @@
|
|
|
802
804
|
opacity: 0.5;
|
|
803
805
|
}
|
|
804
806
|
|
|
807
|
+
/* Button text - hidden by default, visible when CSS is disabled */
|
|
808
|
+
.vidply-button-text {
|
|
809
|
+
display: none;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/* When CSS is disabled or unavailable, button text will be visible */
|
|
813
|
+
|
|
814
|
+
/* This ensures buttons are functional even without CSS */
|
|
815
|
+
|
|
816
|
+
/* Tooltip styles - aria-hidden popovers for sighted users */
|
|
817
|
+
.vidply-tooltip {
|
|
818
|
+
background: #e0e0e0;
|
|
819
|
+
border-radius: var(--vidply-radius-sm);
|
|
820
|
+
color: #000;
|
|
821
|
+
font-size: var(--vidply-font-xs);
|
|
822
|
+
left: 50%;
|
|
823
|
+
opacity: 0;
|
|
824
|
+
padding: 0.375rem 0.5rem;
|
|
825
|
+
pointer-events: none;
|
|
826
|
+
position: absolute;
|
|
827
|
+
top: calc(100% + 0.5rem);
|
|
828
|
+
transform: translateX(-50%) translateY(-0.25rem);
|
|
829
|
+
transition: opacity var(--vidply-transition-fast), transform var(--vidply-transition-fast);
|
|
830
|
+
white-space: nowrap;
|
|
831
|
+
z-index: calc(var(--vidply-z-menu) + 1);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.vidply-tooltip::before {
|
|
835
|
+
border-color: transparent transparent #e0e0e0;
|
|
836
|
+
border-style: solid;
|
|
837
|
+
border-width: 0 0.375rem 0.375rem;
|
|
838
|
+
content: '';
|
|
839
|
+
left: 50%;
|
|
840
|
+
position: absolute;
|
|
841
|
+
top: -0.375rem;
|
|
842
|
+
transform: translateX(-50%);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/* Show tooltip on hover/focus */
|
|
846
|
+
.vidply-tooltip-visible {
|
|
847
|
+
opacity: 1;
|
|
848
|
+
transform: translateX(-50%) translateY(0);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/* In fullscreen mode, position tooltips above buttons */
|
|
852
|
+
.vidply-player.vidply-fullscreen .vidply-tooltip,
|
|
853
|
+
.vidply-player:fullscreen .vidply-tooltip {
|
|
854
|
+
bottom: calc(100% + 0.5rem);
|
|
855
|
+
top: auto;
|
|
856
|
+
transform: translateX(-50%) translateY(0.25rem);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.vidply-player.vidply-fullscreen .vidply-tooltip-visible,
|
|
860
|
+
.vidply-player:fullscreen .vidply-tooltip-visible {
|
|
861
|
+
transform: translateX(-50%) translateY(0);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/* Adjust tooltip arrow for fullscreen (pointing down instead of up) */
|
|
865
|
+
.vidply-player.vidply-fullscreen .vidply-tooltip::before,
|
|
866
|
+
.vidply-player:fullscreen .vidply-tooltip::before {
|
|
867
|
+
border-color: #e0e0e0 transparent transparent;
|
|
868
|
+
border-width: 0.375rem 0.375rem 0;
|
|
869
|
+
bottom: -0.375rem;
|
|
870
|
+
top: auto;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/* Ensure buttons with tooltips are positioned relatively */
|
|
874
|
+
.vidply-button,
|
|
875
|
+
.vidply-icon-button,
|
|
876
|
+
.vidply-sign-language-settings,
|
|
877
|
+
.vidply-sign-language-close,
|
|
878
|
+
.vidply-transcript-settings,
|
|
879
|
+
.vidply-transcript-close {
|
|
880
|
+
position: relative;
|
|
881
|
+
}
|
|
882
|
+
|
|
805
883
|
/* Icons */
|
|
806
884
|
.vidply-icon {
|
|
807
885
|
display: inline-block;
|
|
@@ -2139,14 +2217,29 @@
|
|
|
2139
2217
|
padding: 1rem 1.25rem;
|
|
2140
2218
|
}
|
|
2141
2219
|
|
|
2220
|
+
/* Track header - contains track number and duration */
|
|
2221
|
+
.vidply-track-header {
|
|
2222
|
+
align-items: center;
|
|
2223
|
+
display: flex;
|
|
2224
|
+
gap: 0.75rem;
|
|
2225
|
+
justify-content: space-between;
|
|
2226
|
+
margin-bottom: 0.25rem;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2142
2229
|
.vidply-track-number {
|
|
2143
2230
|
color: var(--vidply-text-muted);
|
|
2144
2231
|
font-size: 0.75rem;
|
|
2145
2232
|
letter-spacing: 0.0313rem;
|
|
2146
|
-
margin-bottom: 0.25rem;
|
|
2147
2233
|
text-transform: uppercase;
|
|
2148
2234
|
}
|
|
2149
2235
|
|
|
2236
|
+
/* Duration in track info display */
|
|
2237
|
+
.vidply-track-duration {
|
|
2238
|
+
color: var(--vidply-text-muted);
|
|
2239
|
+
font-size: 0.75rem;
|
|
2240
|
+
font-variant-numeric: tabular-nums;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2150
2243
|
.vidply-track-title {
|
|
2151
2244
|
color: var(--vidply-white);
|
|
2152
2245
|
font-size: 1.125rem;
|
|
@@ -2159,6 +2252,18 @@
|
|
|
2159
2252
|
font-size: 0.875rem;
|
|
2160
2253
|
}
|
|
2161
2254
|
|
|
2255
|
+
.vidply-track-description {
|
|
2256
|
+
color: var(--vidply-white-60);
|
|
2257
|
+
font-size: 0.8125rem;
|
|
2258
|
+
line-height: 1.4;
|
|
2259
|
+
margin-top: 0.5rem;
|
|
2260
|
+
max-height: 3.5em;
|
|
2261
|
+
overflow: hidden;
|
|
2262
|
+
display: -webkit-box;
|
|
2263
|
+
-webkit-line-clamp: 2;
|
|
2264
|
+
-webkit-box-orient: vertical;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2162
2267
|
/* Playlist Panel */
|
|
2163
2268
|
.vidply-playlist-panel {
|
|
2164
2269
|
background: var(--vidply-bg-playlist);
|
|
@@ -2264,6 +2369,13 @@
|
|
|
2264
2369
|
box-shadow: 0 0.5rem 1.5rem var(--vidply-black-60);
|
|
2265
2370
|
}
|
|
2266
2371
|
|
|
2372
|
+
/* Fullscreen thumbnail container - takes full width of card */
|
|
2373
|
+
.vidply-player.vidply-fullscreen .vidply-playlist-thumbnail-container,
|
|
2374
|
+
.vidply-player:fullscreen .vidply-playlist-thumbnail-container {
|
|
2375
|
+
position: relative;
|
|
2376
|
+
width: 100%;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2267
2379
|
.vidply-player.vidply-fullscreen .vidply-playlist-thumbnail,
|
|
2268
2380
|
.vidply-player:fullscreen .vidply-playlist-thumbnail {
|
|
2269
2381
|
width: 100%;
|
|
@@ -2271,11 +2383,26 @@
|
|
|
2271
2383
|
border-radius: 0;
|
|
2272
2384
|
}
|
|
2273
2385
|
|
|
2386
|
+
/* Larger duration badge in fullscreen for better visibility */
|
|
2387
|
+
.vidply-player.vidply-fullscreen .vidply-playlist-duration-badge,
|
|
2388
|
+
.vidply-player:fullscreen .vidply-playlist-duration-badge {
|
|
2389
|
+
bottom: 0.375rem;
|
|
2390
|
+
font-size: 0.75rem;
|
|
2391
|
+
padding: 0.1875rem 0.375rem;
|
|
2392
|
+
right: 0.375rem;
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2274
2395
|
.vidply-player.vidply-fullscreen .vidply-playlist-item-info,
|
|
2275
2396
|
.vidply-player:fullscreen .vidply-playlist-item-info {
|
|
2276
2397
|
padding: 0.75rem;
|
|
2277
2398
|
}
|
|
2278
2399
|
|
|
2400
|
+
/* Hide description in fullscreen to save space */
|
|
2401
|
+
.vidply-player.vidply-fullscreen .vidply-playlist-item-description,
|
|
2402
|
+
.vidply-player:fullscreen .vidply-playlist-item-description {
|
|
2403
|
+
display: none;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2279
2406
|
.vidply-player.vidply-fullscreen .vidply-playlist-item-title,
|
|
2280
2407
|
.vidply-player:fullscreen .vidply-playlist-item-title {
|
|
2281
2408
|
font-size: 0.875rem;
|
|
@@ -2474,6 +2601,12 @@
|
|
|
2474
2601
|
outline-offset: -0.125rem;
|
|
2475
2602
|
}
|
|
2476
2603
|
|
|
2604
|
+
/* Playlist Thumbnail Container (wrapper for thumbnail + duration badge) */
|
|
2605
|
+
.vidply-playlist-thumbnail-container {
|
|
2606
|
+
flex-shrink: 0;
|
|
2607
|
+
position: relative;
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2477
2610
|
/* Playlist Thumbnail */
|
|
2478
2611
|
.vidply-playlist-thumbnail {
|
|
2479
2612
|
align-items: center;
|
|
@@ -2505,6 +2638,23 @@
|
|
|
2505
2638
|
color: var(--vidply-primary-light);
|
|
2506
2639
|
}
|
|
2507
2640
|
|
|
2641
|
+
/* Duration badge on thumbnail (YouTube-style) */
|
|
2642
|
+
.vidply-playlist-duration-badge {
|
|
2643
|
+
background: rgb(0 0 0 / 80%);
|
|
2644
|
+
border-radius: 0.1875rem;
|
|
2645
|
+
bottom: 0.125rem;
|
|
2646
|
+
color: var(--vidply-white);
|
|
2647
|
+
font-family: var(--vidply-font-family);
|
|
2648
|
+
font-size: 0.625rem;
|
|
2649
|
+
font-variant-numeric: tabular-nums;
|
|
2650
|
+
font-weight: 500;
|
|
2651
|
+
letter-spacing: 0.02em;
|
|
2652
|
+
line-height: 1;
|
|
2653
|
+
padding: 0.125rem 0.25rem;
|
|
2654
|
+
position: absolute;
|
|
2655
|
+
right: 0.125rem;
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2508
2658
|
/* Playlist Item Info */
|
|
2509
2659
|
.vidply-playlist-item-info {
|
|
2510
2660
|
display: block;
|
|
@@ -2512,12 +2662,21 @@
|
|
|
2512
2662
|
min-width: 0;
|
|
2513
2663
|
}
|
|
2514
2664
|
|
|
2665
|
+
/* Title row - contains title and optional inline duration */
|
|
2666
|
+
.vidply-playlist-item-title-row {
|
|
2667
|
+
align-items: center;
|
|
2668
|
+
display: flex;
|
|
2669
|
+
gap: 0.5rem;
|
|
2670
|
+
margin-bottom: 0.25rem;
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2515
2673
|
.vidply-playlist-item-title {
|
|
2516
2674
|
color: var(--vidply-white);
|
|
2517
2675
|
display: block;
|
|
2676
|
+
flex: 1;
|
|
2518
2677
|
font-size: 0.875rem;
|
|
2519
2678
|
font-weight: 500;
|
|
2520
|
-
|
|
2679
|
+
min-width: 0;
|
|
2521
2680
|
overflow: hidden;
|
|
2522
2681
|
text-overflow: ellipsis;
|
|
2523
2682
|
white-space: nowrap;
|
|
@@ -2527,6 +2686,14 @@
|
|
|
2527
2686
|
color: var(--vidply-primary-light);
|
|
2528
2687
|
}
|
|
2529
2688
|
|
|
2689
|
+
/* Inline duration (shown when no thumbnail) */
|
|
2690
|
+
.vidply-playlist-item-duration {
|
|
2691
|
+
color: var(--vidply-text-disabled);
|
|
2692
|
+
flex-shrink: 0;
|
|
2693
|
+
font-size: 0.6875rem;
|
|
2694
|
+
font-variant-numeric: tabular-nums;
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2530
2697
|
.vidply-playlist-item-artist {
|
|
2531
2698
|
color: var(--vidply-text-disabled);
|
|
2532
2699
|
display: block;
|
|
@@ -2536,6 +2703,19 @@
|
|
|
2536
2703
|
white-space: nowrap;
|
|
2537
2704
|
}
|
|
2538
2705
|
|
|
2706
|
+
/* Description - truncated with ellipsis */
|
|
2707
|
+
.vidply-playlist-item-description {
|
|
2708
|
+
-webkit-box-orient: vertical;
|
|
2709
|
+
color: var(--vidply-text-subtle);
|
|
2710
|
+
display: -webkit-box;
|
|
2711
|
+
font-size: 0.6875rem;
|
|
2712
|
+
-webkit-line-clamp: 2;
|
|
2713
|
+
line-height: 1.4;
|
|
2714
|
+
margin-top: 0.25rem;
|
|
2715
|
+
overflow: hidden;
|
|
2716
|
+
text-overflow: ellipsis;
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2539
2719
|
/* Playlist Item Icon */
|
|
2540
2720
|
.vidply-playlist-item-icon {
|
|
2541
2721
|
flex-shrink: 0;
|
package/src/utils/DOMUtils.js
CHANGED
|
@@ -149,6 +149,73 @@ export const DOMUtils = {
|
|
|
149
149
|
|
|
150
150
|
temp.innerHTML = safeHtml;
|
|
151
151
|
return temp.innerHTML;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Create a tooltip element that is aria-hidden (not read by screen readers)
|
|
156
|
+
* @param {string} text - Tooltip text
|
|
157
|
+
* @param {string} classPrefix - Class prefix for styling
|
|
158
|
+
* @returns {HTMLElement} Tooltip element
|
|
159
|
+
*/
|
|
160
|
+
createTooltip(text, classPrefix = 'vidply') {
|
|
161
|
+
const tooltip = this.createElement('span', {
|
|
162
|
+
className: `${classPrefix}-tooltip`,
|
|
163
|
+
textContent: text,
|
|
164
|
+
attributes: {
|
|
165
|
+
'aria-hidden': 'true'
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return tooltip;
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Attach a tooltip to an element
|
|
173
|
+
* @param {HTMLElement} element - Element to attach tooltip to
|
|
174
|
+
* @param {string} text - Tooltip text
|
|
175
|
+
* @param {string} classPrefix - Class prefix for styling
|
|
176
|
+
*/
|
|
177
|
+
attachTooltip(element, text, classPrefix = 'vidply') {
|
|
178
|
+
if (!element || !text) return;
|
|
179
|
+
|
|
180
|
+
// Remove existing tooltip if any
|
|
181
|
+
const existingTooltip = element.querySelector(`.${classPrefix}-tooltip`);
|
|
182
|
+
if (existingTooltip) {
|
|
183
|
+
existingTooltip.remove();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const tooltip = this.createTooltip(text, classPrefix);
|
|
187
|
+
element.appendChild(tooltip);
|
|
188
|
+
|
|
189
|
+
// Show tooltip on hover/focus
|
|
190
|
+
const showTooltip = () => {
|
|
191
|
+
tooltip.classList.add(`${classPrefix}-tooltip-visible`);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const hideTooltip = () => {
|
|
195
|
+
tooltip.classList.remove(`${classPrefix}-tooltip-visible`);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
element.addEventListener('mouseenter', showTooltip);
|
|
199
|
+
element.addEventListener('mouseleave', hideTooltip);
|
|
200
|
+
element.addEventListener('focus', showTooltip);
|
|
201
|
+
element.addEventListener('blur', hideTooltip);
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Create visible button text that is hidden by CSS but visible when CSS is disabled
|
|
206
|
+
* @param {string} text - Button text
|
|
207
|
+
* @param {string} classPrefix - Class prefix for styling
|
|
208
|
+
* @returns {HTMLElement} Button text element
|
|
209
|
+
*/
|
|
210
|
+
createButtonText(text, classPrefix = 'vidply') {
|
|
211
|
+
const buttonText = this.createElement('span', {
|
|
212
|
+
className: `${classPrefix}-button-text`,
|
|
213
|
+
textContent: text,
|
|
214
|
+
attributes: {
|
|
215
|
+
'aria-hidden': 'true'
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
return buttonText;
|
|
152
219
|
}
|
|
153
220
|
};
|
|
154
221
|
|
package/src/utils/MenuUtils.js
CHANGED
|
@@ -81,7 +81,8 @@ export function attachMenuKeyboardNavigation(menu, button, itemSelector, onClose
|
|
|
81
81
|
menuItems.forEach((item, idx) => {
|
|
82
82
|
item.setAttribute('tabindex', idx === nextIndex ? '0' : '-1');
|
|
83
83
|
});
|
|
84
|
-
menuItems[nextIndex].focus();
|
|
84
|
+
menuItems[nextIndex].focus({ preventScroll: false });
|
|
85
|
+
menuItems[nextIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
85
86
|
break;
|
|
86
87
|
|
|
87
88
|
case 'ArrowUp':
|
|
@@ -91,7 +92,8 @@ export function attachMenuKeyboardNavigation(menu, button, itemSelector, onClose
|
|
|
91
92
|
menuItems.forEach((item, idx) => {
|
|
92
93
|
item.setAttribute('tabindex', idx === prevIndex ? '0' : '-1');
|
|
93
94
|
});
|
|
94
|
-
menuItems[prevIndex].focus();
|
|
95
|
+
menuItems[prevIndex].focus({ preventScroll: false });
|
|
96
|
+
menuItems[prevIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
95
97
|
break;
|
|
96
98
|
|
|
97
99
|
case 'Home':
|
|
@@ -100,7 +102,8 @@ export function attachMenuKeyboardNavigation(menu, button, itemSelector, onClose
|
|
|
100
102
|
menuItems.forEach((item, idx) => {
|
|
101
103
|
item.setAttribute('tabindex', idx === 0 ? '0' : '-1');
|
|
102
104
|
});
|
|
103
|
-
menuItems[0].focus();
|
|
105
|
+
menuItems[0].focus({ preventScroll: false });
|
|
106
|
+
menuItems[0].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
104
107
|
break;
|
|
105
108
|
|
|
106
109
|
case 'End':
|
|
@@ -110,7 +113,8 @@ export function attachMenuKeyboardNavigation(menu, button, itemSelector, onClose
|
|
|
110
113
|
menuItems.forEach((item, idx) => {
|
|
111
114
|
item.setAttribute('tabindex', idx === lastIndex ? '0' : '-1');
|
|
112
115
|
});
|
|
113
|
-
menuItems[lastIndex].focus();
|
|
116
|
+
menuItems[lastIndex].focus({ preventScroll: false });
|
|
117
|
+
menuItems[lastIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
114
118
|
break;
|
|
115
119
|
|
|
116
120
|
case 'Enter':
|
|
@@ -159,6 +163,8 @@ export function focusFirstMenuItem(menu, itemSelector, delay = 0) {
|
|
|
159
163
|
item.setAttribute('tabindex', index === 0 ? '0' : '-1');
|
|
160
164
|
});
|
|
161
165
|
focusElement(menuItems[0], { delay: 0 });
|
|
166
|
+
// Scroll into view for keyboard users
|
|
167
|
+
menuItems[0].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
162
168
|
}
|
|
163
169
|
}, delay);
|
|
164
170
|
}
|
|
@@ -190,7 +190,8 @@ export function setupSettingsMenuKeyboard(menu, button, onClose) {
|
|
|
190
190
|
menuItems.forEach((item, idx) => {
|
|
191
191
|
item.setAttribute('tabindex', idx === nextIndex ? '0' : '-1');
|
|
192
192
|
});
|
|
193
|
-
menuItems[nextIndex].focus();
|
|
193
|
+
menuItems[nextIndex].focus({ preventScroll: false });
|
|
194
|
+
menuItems[nextIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
194
195
|
break;
|
|
195
196
|
|
|
196
197
|
case 'ArrowUp':
|
|
@@ -199,7 +200,8 @@ export function setupSettingsMenuKeyboard(menu, button, onClose) {
|
|
|
199
200
|
menuItems.forEach((item, idx) => {
|
|
200
201
|
item.setAttribute('tabindex', idx === prevIndex ? '0' : '-1');
|
|
201
202
|
});
|
|
202
|
-
menuItems[prevIndex].focus();
|
|
203
|
+
menuItems[prevIndex].focus({ preventScroll: false });
|
|
204
|
+
menuItems[prevIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
203
205
|
break;
|
|
204
206
|
|
|
205
207
|
case 'Home':
|
|
@@ -207,7 +209,8 @@ export function setupSettingsMenuKeyboard(menu, button, onClose) {
|
|
|
207
209
|
menuItems.forEach((item, idx) => {
|
|
208
210
|
item.setAttribute('tabindex', idx === 0 ? '0' : '-1');
|
|
209
211
|
});
|
|
210
|
-
menuItems[0].focus();
|
|
212
|
+
menuItems[0].focus({ preventScroll: false });
|
|
213
|
+
menuItems[0].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
211
214
|
break;
|
|
212
215
|
|
|
213
216
|
case 'End':
|
|
@@ -216,7 +219,8 @@ export function setupSettingsMenuKeyboard(menu, button, onClose) {
|
|
|
216
219
|
menuItems.forEach((item, idx) => {
|
|
217
220
|
item.setAttribute('tabindex', idx === lastIndex ? '0' : '-1');
|
|
218
221
|
});
|
|
219
|
-
menuItems[lastIndex].focus();
|
|
222
|
+
menuItems[lastIndex].focus({ preventScroll: false });
|
|
223
|
+
menuItems[lastIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
220
224
|
break;
|
|
221
225
|
|
|
222
226
|
case 'Escape':
|
|
@@ -47,15 +47,16 @@ export function createWindowHeader({
|
|
|
47
47
|
// Add settings button if requested
|
|
48
48
|
let settingsButton = null;
|
|
49
49
|
if (showSettings && onSettingsClick) {
|
|
50
|
+
const settingsAriaLabel = i18n.t('settings.title');
|
|
50
51
|
settingsButton = DOMUtils.createElement('button', {
|
|
51
52
|
className: `${classPrefix}-icon-button ${headerClass}-settings`,
|
|
52
53
|
attributes: {
|
|
53
54
|
'type': 'button',
|
|
54
|
-
'aria-label':
|
|
55
|
-
'title': i18n.t('settings.title')
|
|
55
|
+
'aria-label': settingsAriaLabel
|
|
56
56
|
}
|
|
57
57
|
});
|
|
58
58
|
settingsButton.appendChild(createIconElement('settings'));
|
|
59
|
+
DOMUtils.attachTooltip(settingsButton, settingsAriaLabel, classPrefix);
|
|
59
60
|
settingsButton.addEventListener('click', onSettingsClick);
|
|
60
61
|
leftSide.appendChild(settingsButton);
|
|
61
62
|
}
|
|
@@ -74,15 +75,16 @@ export function createWindowHeader({
|
|
|
74
75
|
leftSide.appendChild(title);
|
|
75
76
|
|
|
76
77
|
// Create close button
|
|
78
|
+
const closeAriaLabel = i18n.t('player.close');
|
|
77
79
|
const closeButton = DOMUtils.createElement('button', {
|
|
78
80
|
className: `${classPrefix}-icon-button ${headerClass}-close`,
|
|
79
81
|
attributes: {
|
|
80
82
|
'type': 'button',
|
|
81
|
-
'aria-label':
|
|
82
|
-
'title': i18n.t('player.close')
|
|
83
|
+
'aria-label': closeAriaLabel
|
|
83
84
|
}
|
|
84
85
|
});
|
|
85
86
|
closeButton.appendChild(createIconElement('close'));
|
|
87
|
+
DOMUtils.attachTooltip(closeButton, closeAriaLabel, classPrefix);
|
|
86
88
|
if (onClose) {
|
|
87
89
|
closeButton.addEventListener('click', onClose);
|
|
88
90
|
}
|