vidply 1.0.21 → 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.
@@ -406,6 +406,63 @@ var DOMUtils = {
406
406
  const safeHtml = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, "").replace(/on\w+\s*=/gi, "").replace(/javascript:/gi, "");
407
407
  temp.innerHTML = safeHtml;
408
408
  return temp.innerHTML;
409
+ },
410
+ /**
411
+ * Create a tooltip element that is aria-hidden (not read by screen readers)
412
+ * @param {string} text - Tooltip text
413
+ * @param {string} classPrefix - Class prefix for styling
414
+ * @returns {HTMLElement} Tooltip element
415
+ */
416
+ createTooltip(text, classPrefix = "vidply") {
417
+ const tooltip = this.createElement("span", {
418
+ className: `${classPrefix}-tooltip`,
419
+ textContent: text,
420
+ attributes: {
421
+ "aria-hidden": "true"
422
+ }
423
+ });
424
+ return tooltip;
425
+ },
426
+ /**
427
+ * Attach a tooltip to an element
428
+ * @param {HTMLElement} element - Element to attach tooltip to
429
+ * @param {string} text - Tooltip text
430
+ * @param {string} classPrefix - Class prefix for styling
431
+ */
432
+ attachTooltip(element, text, classPrefix = "vidply") {
433
+ if (!element || !text) return;
434
+ const existingTooltip = element.querySelector(`.${classPrefix}-tooltip`);
435
+ if (existingTooltip) {
436
+ existingTooltip.remove();
437
+ }
438
+ const tooltip = this.createTooltip(text, classPrefix);
439
+ element.appendChild(tooltip);
440
+ const showTooltip = () => {
441
+ tooltip.classList.add(`${classPrefix}-tooltip-visible`);
442
+ };
443
+ const hideTooltip = () => {
444
+ tooltip.classList.remove(`${classPrefix}-tooltip-visible`);
445
+ };
446
+ element.addEventListener("mouseenter", showTooltip);
447
+ element.addEventListener("mouseleave", hideTooltip);
448
+ element.addEventListener("focus", showTooltip);
449
+ element.addEventListener("blur", hideTooltip);
450
+ },
451
+ /**
452
+ * Create visible button text that is hidden by CSS but visible when CSS is disabled
453
+ * @param {string} text - Button text
454
+ * @param {string} classPrefix - Class prefix for styling
455
+ * @returns {HTMLElement} Button text element
456
+ */
457
+ createButtonText(text, classPrefix = "vidply") {
458
+ const buttonText = this.createElement("span", {
459
+ className: `${classPrefix}-button-text`,
460
+ textContent: text,
461
+ attributes: {
462
+ "aria-hidden": "true"
463
+ }
464
+ });
465
+ return buttonText;
409
466
  }
410
467
  };
411
468
 
@@ -572,7 +629,11 @@ var en = {
572
629
  nowPlaying: "Now playing: Track {current} of {total}. {title}{artist}",
573
630
  by: " by ",
574
631
  untitled: "Untitled",
575
- trackUntitled: "Track {number}"
632
+ trackUntitled: "Track {number}",
633
+ currentlyPlaying: "Currently playing",
634
+ notPlaying: "Not playing",
635
+ pressEnterPlay: "Press Enter to play",
636
+ pressEnterRestart: "Press Enter to restart"
576
637
  }
577
638
  };
578
639
 
@@ -739,7 +800,11 @@ var de = {
739
800
  nowPlaying: "L\xE4uft gerade: Titel {current} von {total}. {title}{artist}",
740
801
  by: " von ",
741
802
  untitled: "Ohne Titel",
742
- trackUntitled: "Titel {number}"
803
+ trackUntitled: "Titel {number}",
804
+ currentlyPlaying: "Wird gerade abgespielt",
805
+ notPlaying: "Nicht aktiv",
806
+ pressEnterPlay: "Eingabetaste zum Abspielen",
807
+ pressEnterRestart: "Eingabetaste zum Neustart"
743
808
  }
744
809
  };
745
810
 
@@ -906,7 +971,11 @@ var es = {
906
971
  nowPlaying: "Reproduciendo ahora: Pista {current} de {total}. {title}{artist}",
907
972
  by: " por ",
908
973
  untitled: "Sin t\xEDtulo",
909
- trackUntitled: "Pista {number}"
974
+ trackUntitled: "Pista {number}",
975
+ currentlyPlaying: "Reproduciendo actualmente",
976
+ notPlaying: "Sin reproducir",
977
+ pressEnterPlay: "Pulsa Enter para reproducir",
978
+ pressEnterRestart: "Pulsa Enter para reiniciar"
910
979
  }
911
980
  };
912
981
 
@@ -1073,7 +1142,11 @@ var fr = {
1073
1142
  nowPlaying: "Lecture en cours : Piste {current} sur {total}. {title}{artist}",
1074
1143
  by: " par ",
1075
1144
  untitled: "Sans titre",
1076
- trackUntitled: "Piste {number}"
1145
+ trackUntitled: "Piste {number}",
1146
+ currentlyPlaying: "En cours de lecture",
1147
+ notPlaying: "Non en lecture",
1148
+ pressEnterPlay: "Appuyez sur Entr\xE9e pour lire",
1149
+ pressEnterRestart: "Appuyez sur Entr\xE9e pour recommencer"
1077
1150
  }
1078
1151
  };
1079
1152
 
@@ -1240,7 +1313,11 @@ var ja = {
1240
1313
  nowPlaying: "\u518D\u751F\u4E2D: \u30C8\u30E9\u30C3\u30AF {current}/{total}. {title}{artist}",
1241
1314
  by: " - ",
1242
1315
  untitled: "\u30BF\u30A4\u30C8\u30EB\u306A\u3057",
1243
- trackUntitled: "\u30C8\u30E9\u30C3\u30AF {number}"
1316
+ trackUntitled: "\u30C8\u30E9\u30C3\u30AF {number}",
1317
+ currentlyPlaying: "\u518D\u751F\u4E2D",
1318
+ notPlaying: "\u505C\u6B62\u4E2D",
1319
+ pressEnterPlay: "Enter\u30AD\u30FC\u3067\u518D\u751F",
1320
+ pressEnterRestart: "Enter\u30AD\u30FC\u3067\u6700\u521D\u304B\u3089\u518D\u751F"
1244
1321
  }
1245
1322
  };
1246
1323
 
@@ -1949,13 +2026,15 @@ var ControlBar = class {
1949
2026
  e.preventDefault();
1950
2027
  e.stopPropagation();
1951
2028
  const nextIndex = (currentIndex + 1) % menuItems.length;
1952
- menuItems[nextIndex].focus();
2029
+ menuItems[nextIndex].focus({ preventScroll: false });
2030
+ menuItems[nextIndex].scrollIntoView({ behavior: "smooth", block: "nearest" });
1953
2031
  break;
1954
2032
  case "ArrowUp":
1955
2033
  e.preventDefault();
1956
2034
  e.stopPropagation();
1957
2035
  const prevIndex = (currentIndex - 1 + menuItems.length) % menuItems.length;
1958
- menuItems[prevIndex].focus();
2036
+ menuItems[prevIndex].focus({ preventScroll: false });
2037
+ menuItems[prevIndex].scrollIntoView({ behavior: "smooth", block: "nearest" });
1959
2038
  break;
1960
2039
  case "ArrowLeft":
1961
2040
  case "ArrowRight":
@@ -1965,12 +2044,14 @@ var ControlBar = class {
1965
2044
  case "Home":
1966
2045
  e.preventDefault();
1967
2046
  e.stopPropagation();
1968
- menuItems[0].focus();
2047
+ menuItems[0].focus({ preventScroll: false });
2048
+ menuItems[0].scrollIntoView({ behavior: "smooth", block: "nearest" });
1969
2049
  break;
1970
2050
  case "End":
1971
2051
  e.preventDefault();
1972
2052
  e.stopPropagation();
1973
- menuItems[menuItems.length - 1].focus();
2053
+ menuItems[menuItems.length - 1].focus({ preventScroll: false });
2054
+ menuItems[menuItems.length - 1].scrollIntoView({ behavior: "smooth", block: "nearest" });
1974
2055
  break;
1975
2056
  case "Enter":
1976
2057
  case " ":
@@ -2121,20 +2202,27 @@ var ControlBar = class {
2121
2202
  buttonContainer.appendChild(leftButtons);
2122
2203
  buttonContainer.appendChild(this.rightButtons);
2123
2204
  this.element.appendChild(buttonContainer);
2124
- this.ensureButtonTitles(buttonContainer);
2205
+ this.ensureButtonTooltips(buttonContainer);
2125
2206
  }
2126
2207
  /**
2127
2208
  * Ensure all buttons in the controls have title attributes
2128
2209
  * Uses aria-label as title if title is not present
2129
2210
  */
2130
- ensureButtonTitles(container) {
2211
+ ensureButtonTooltips(container) {
2131
2212
  const buttons = container.querySelectorAll("button");
2132
2213
  buttons.forEach((button) => {
2133
- if (!button.hasAttribute("title")) {
2134
- const ariaLabel = button.getAttribute("aria-label");
2135
- if (ariaLabel) {
2136
- button.setAttribute("title", ariaLabel);
2137
- }
2214
+ if (button.querySelector(`.${this.player.options.classPrefix}-tooltip`)) {
2215
+ return;
2216
+ }
2217
+ if (button.querySelector(`.${this.player.options.classPrefix}-button-text`)) {
2218
+ return;
2219
+ }
2220
+ if (button.getAttribute("role") === "menuitem" || button.classList.contains(`${this.player.options.classPrefix}-settings-item`) || button.classList.contains(`${this.player.options.classPrefix}-menu-item`) || button.classList.contains(`${this.player.options.classPrefix}-transcript-settings-item`) || button.classList.contains(`${this.player.options.classPrefix}-sign-language-settings-item`) || button.classList.contains(`${this.player.options.classPrefix}-popup-settings-item`)) {
2221
+ return;
2222
+ }
2223
+ const ariaLabel = button.getAttribute("aria-label");
2224
+ if (ariaLabel) {
2225
+ DOMUtils.attachTooltip(button, ariaLabel, this.player.options.classPrefix);
2138
2226
  }
2139
2227
  });
2140
2228
  }
@@ -2234,7 +2322,6 @@ var ControlBar = class {
2234
2322
  if (!this.isDraggingProgress) {
2235
2323
  const { time } = updateProgress(e.clientX);
2236
2324
  this.controls.progressTooltip.textContent = TimeUtils.formatTime(time);
2237
- this.controls.progressTooltip.setAttribute("aria-label", TimeUtils.formatDuration(time));
2238
2325
  this.controls.progressTooltip.style.left = `${e.clientX - progress.getBoundingClientRect().left}px`;
2239
2326
  this.controls.progressTooltip.style.display = "block";
2240
2327
  }
@@ -2707,7 +2794,7 @@ var ControlBar = class {
2707
2794
  setTimeout(() => {
2708
2795
  const firstItem = menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
2709
2796
  if (firstItem) {
2710
- firstItem.focus();
2797
+ firstItem.focus({ preventScroll: true });
2711
2798
  }
2712
2799
  }, 0);
2713
2800
  }
@@ -2722,16 +2809,17 @@ var ControlBar = class {
2722
2809
  this.attachMenuCloseHandler(menu, button);
2723
2810
  }
2724
2811
  createQualityButton() {
2812
+ const ariaLabel = i18n.t("player.quality");
2725
2813
  const button = DOMUtils.createElement("button", {
2726
2814
  className: `${this.player.options.classPrefix}-button ${this.player.options.classPrefix}-quality`,
2727
2815
  attributes: {
2728
2816
  "type": "button",
2729
- "aria-label": i18n.t("player.quality"),
2730
- "aria-expanded": "false",
2731
- "title": i18n.t("player.quality")
2817
+ "aria-label": ariaLabel,
2818
+ "aria-expanded": "false"
2732
2819
  }
2733
2820
  });
2734
2821
  button.appendChild(createIconElement("hd"));
2822
+ DOMUtils.attachTooltip(button, ariaLabel, this.player.options.classPrefix);
2735
2823
  const qualityText = DOMUtils.createElement("span", {
2736
2824
  className: `${this.player.options.classPrefix}-quality-text`,
2737
2825
  textContent: ""
@@ -2830,7 +2918,7 @@ var ControlBar = class {
2830
2918
  setTimeout(() => {
2831
2919
  const focusTarget = activeItem || menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
2832
2920
  if (focusTarget) {
2833
- focusTarget.focus();
2921
+ focusTarget.focus({ preventScroll: true });
2834
2922
  }
2835
2923
  }, 0);
2836
2924
  }
@@ -2852,13 +2940,13 @@ var ControlBar = class {
2852
2940
  this.attachMenuCloseHandler(menu, button);
2853
2941
  }
2854
2942
  createCaptionStyleButton() {
2943
+ const ariaLabel = i18n.t("player.captionStyling");
2855
2944
  const button = DOMUtils.createElement("button", {
2856
2945
  className: `${this.player.options.classPrefix}-button ${this.player.options.classPrefix}-caption-style`,
2857
2946
  attributes: {
2858
2947
  "type": "button",
2859
- "aria-label": i18n.t("player.captionStyling"),
2860
- "aria-expanded": "false",
2861
- "title": i18n.t("player.captionStyling")
2948
+ "aria-label": ariaLabel,
2949
+ "aria-expanded": "false"
2862
2950
  }
2863
2951
  });
2864
2952
  const textIcon = DOMUtils.createElement("span", {
@@ -2869,6 +2957,7 @@ var ControlBar = class {
2869
2957
  }
2870
2958
  });
2871
2959
  button.appendChild(textIcon);
2960
+ DOMUtils.attachTooltip(button, ariaLabel, this.player.options.classPrefix);
2872
2961
  button.addEventListener("click", () => {
2873
2962
  this.showCaptionStyleMenu(button);
2874
2963
  });
@@ -3221,7 +3310,7 @@ var ControlBar = class {
3221
3310
  setTimeout(() => {
3222
3311
  const focusTarget = activeItem || menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
3223
3312
  if (focusTarget) {
3224
- focusTarget.focus();
3313
+ focusTarget.focus({ preventScroll: true });
3225
3314
  }
3226
3315
  }, 0);
3227
3316
  }
@@ -3324,7 +3413,7 @@ var ControlBar = class {
3324
3413
  setTimeout(() => {
3325
3414
  const focusTarget = activeItem || menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
3326
3415
  if (focusTarget) {
3327
- focusTarget.focus();
3416
+ focusTarget.focus({ preventScroll: true });
3328
3417
  }
3329
3418
  }, 0);
3330
3419
  }
@@ -3359,17 +3448,18 @@ var ControlBar = class {
3359
3448
  this.controls.transcript.setAttribute("aria-expanded", isVisible ? "true" : "false");
3360
3449
  }
3361
3450
  createAudioDescriptionButton() {
3451
+ const ariaLabel = i18n.t("player.audioDescription");
3362
3452
  const button = DOMUtils.createElement("button", {
3363
3453
  className: `${this.player.options.classPrefix}-button ${this.player.options.classPrefix}-audio-description`,
3364
3454
  attributes: {
3365
3455
  "type": "button",
3366
- "aria-label": i18n.t("player.audioDescription"),
3456
+ "aria-label": ariaLabel,
3367
3457
  "role": "switch",
3368
- "aria-checked": "false",
3369
- "title": i18n.t("player.audioDescription")
3458
+ "aria-checked": "false"
3370
3459
  }
3371
3460
  });
3372
3461
  button.appendChild(createIconElement("audioDescription"));
3462
+ DOMUtils.attachTooltip(button, ariaLabel, this.player.options.classPrefix);
3373
3463
  button.addEventListener("click", async () => {
3374
3464
  await this.player.toggleAudioDescription();
3375
3465
  this.updateAudioDescriptionButton();
@@ -3385,16 +3475,17 @@ var ControlBar = class {
3385
3475
  this.controls.audioDescription.setAttribute("aria-checked", isEnabled ? "true" : "false");
3386
3476
  }
3387
3477
  createSignLanguageButton() {
3478
+ const ariaLabel = i18n.t("player.signLanguage");
3388
3479
  const button = DOMUtils.createElement("button", {
3389
3480
  className: `${this.player.options.classPrefix}-button ${this.player.options.classPrefix}-sign-language`,
3390
3481
  attributes: {
3391
3482
  "type": "button",
3392
- "aria-label": i18n.t("player.signLanguage"),
3393
- "aria-expanded": "false",
3394
- "title": i18n.t("player.signLanguage")
3483
+ "aria-label": ariaLabel,
3484
+ "aria-expanded": "false"
3395
3485
  }
3396
3486
  });
3397
3487
  button.appendChild(createIconElement("signLanguage"));
3488
+ DOMUtils.attachTooltip(button, ariaLabel, this.player.options.classPrefix);
3398
3489
  button.addEventListener("click", () => {
3399
3490
  this.player.toggleSignLanguage();
3400
3491
  this.updateSignLanguageButton();
@@ -3413,6 +3504,60 @@ var ControlBar = class {
3413
3504
  isEnabled ? i18n.t("signLanguage.hide") : i18n.t("signLanguage.show")
3414
3505
  );
3415
3506
  }
3507
+ /**
3508
+ * Update accessibility buttons visibility based on current track data.
3509
+ * Called when loading a new playlist track to show/hide buttons accordingly.
3510
+ */
3511
+ updateAccessibilityButtons() {
3512
+ const hasAudioDescription = this.hasAudioDescription();
3513
+ const hasSignLanguage = this.hasSignLanguage();
3514
+ if (hasAudioDescription) {
3515
+ if (!this.controls.audioDescription && this.player.options.audioDescriptionButton !== false) {
3516
+ const btn = this.createAudioDescriptionButton();
3517
+ btn.dataset.overflowPriority = "2";
3518
+ btn.dataset.overflowPriorityMobile = "3";
3519
+ const transcriptBtn = this.rightButtons.querySelector(`.${this.player.options.classPrefix}-transcript`);
3520
+ const playlistBtn = this.rightButtons.querySelector(`.${this.player.options.classPrefix}-playlist-toggle`);
3521
+ const insertBefore = transcriptBtn || playlistBtn || null;
3522
+ if (insertBefore) {
3523
+ this.rightButtons.insertBefore(btn, insertBefore);
3524
+ } else {
3525
+ this.rightButtons.appendChild(btn);
3526
+ }
3527
+ this.setupOverflowMenu();
3528
+ }
3529
+ if (this.controls.audioDescription) {
3530
+ this.controls.audioDescription.style.display = "";
3531
+ }
3532
+ } else {
3533
+ if (this.controls.audioDescription) {
3534
+ this.controls.audioDescription.style.display = "none";
3535
+ }
3536
+ }
3537
+ if (hasSignLanguage) {
3538
+ if (!this.controls.signLanguage && this.player.options.signLanguageButton !== false) {
3539
+ const btn = this.createSignLanguageButton();
3540
+ btn.dataset.overflowPriority = "3";
3541
+ btn.dataset.overflowPriorityMobile = "3";
3542
+ const qualityBtn = this.rightButtons.querySelector(`.${this.player.options.classPrefix}-quality`);
3543
+ const fullscreenBtn = this.rightButtons.querySelector(`.${this.player.options.classPrefix}-fullscreen`);
3544
+ const insertBefore = qualityBtn || fullscreenBtn || null;
3545
+ if (insertBefore) {
3546
+ this.rightButtons.insertBefore(btn, insertBefore);
3547
+ } else {
3548
+ this.rightButtons.appendChild(btn);
3549
+ }
3550
+ this.setupOverflowMenu();
3551
+ }
3552
+ if (this.controls.signLanguage) {
3553
+ this.controls.signLanguage.style.display = "";
3554
+ }
3555
+ } else {
3556
+ if (this.controls.signLanguage) {
3557
+ this.controls.signLanguage.style.display = "none";
3558
+ }
3559
+ }
3560
+ }
3416
3561
  createSettingsButton() {
3417
3562
  const button = DOMUtils.createElement("button", {
3418
3563
  className: `${this.player.options.classPrefix}-button ${this.player.options.classPrefix}-settings`,
@@ -3489,7 +3634,7 @@ var ControlBar = class {
3489
3634
  icon.innerHTML = isPlaying ? createIconElement("pause").innerHTML : createIconElement("play").innerHTML;
3490
3635
  const newAriaLabel = isPlaying ? i18n.t("player.pause") : i18n.t("player.play");
3491
3636
  this.controls.playPause.setAttribute("aria-label", newAriaLabel);
3492
- this.controls.playPause.setAttribute("title", newAriaLabel);
3637
+ DOMUtils.attachTooltip(this.controls.playPause, newAriaLabel, this.player.options.classPrefix);
3493
3638
  }
3494
3639
  updateProgress() {
3495
3640
  if (!this.controls.played) return;
@@ -3545,7 +3690,7 @@ var ControlBar = class {
3545
3690
  icon.innerHTML = createIconElement(iconName).innerHTML;
3546
3691
  const newMuteAriaLabel = this.player.state.muted ? i18n.t("player.unmute") : i18n.t("player.mute");
3547
3692
  this.controls.mute.setAttribute("aria-label", newMuteAriaLabel);
3548
- this.controls.mute.setAttribute("title", newMuteAriaLabel);
3693
+ DOMUtils.attachTooltip(this.controls.mute, newMuteAriaLabel, this.player.options.classPrefix);
3549
3694
  }
3550
3695
  }
3551
3696
  if (this.controls.volumeSlider) {
@@ -3657,16 +3802,17 @@ var ControlBar = class {
3657
3802
  showControls();
3658
3803
  }
3659
3804
  createOverflowMenuButton() {
3805
+ const ariaLabel = i18n.t("player.moreOptions");
3660
3806
  const button = DOMUtils.createElement("button", {
3661
3807
  className: `${this.player.options.classPrefix}-button ${this.player.options.classPrefix}-overflow-menu`,
3662
3808
  attributes: {
3663
3809
  "type": "button",
3664
- "aria-label": i18n.t("player.moreOptions"),
3665
- "aria-expanded": "false",
3666
- "title": i18n.t("player.moreOptions")
3810
+ "aria-label": ariaLabel,
3811
+ "aria-expanded": "false"
3667
3812
  }
3668
3813
  });
3669
3814
  button.appendChild(createIconElement("moreVertical"));
3815
+ DOMUtils.attachTooltip(button, ariaLabel, this.player.options.classPrefix);
3670
3816
  button.addEventListener("click", () => {
3671
3817
  this.showOverflowMenu(button);
3672
3818
  });
@@ -3748,7 +3894,7 @@ var ControlBar = class {
3748
3894
  setTimeout(() => {
3749
3895
  const firstItem = menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
3750
3896
  if (firstItem && firstItem.tagName === "BUTTON") {
3751
- firstItem.focus();
3897
+ firstItem.focus({ preventScroll: true });
3752
3898
  }
3753
3899
  }, 0);
3754
3900
  }
@@ -4679,7 +4825,8 @@ function attachMenuKeyboardNavigation(menu, button, itemSelector, onClose) {
4679
4825
  menuItems.forEach((item, idx) => {
4680
4826
  item.setAttribute("tabindex", idx === nextIndex ? "0" : "-1");
4681
4827
  });
4682
- menuItems[nextIndex].focus();
4828
+ menuItems[nextIndex].focus({ preventScroll: false });
4829
+ menuItems[nextIndex].scrollIntoView({ behavior: "smooth", block: "nearest" });
4683
4830
  break;
4684
4831
  case "ArrowUp":
4685
4832
  e.preventDefault();
@@ -4688,7 +4835,8 @@ function attachMenuKeyboardNavigation(menu, button, itemSelector, onClose) {
4688
4835
  menuItems.forEach((item, idx) => {
4689
4836
  item.setAttribute("tabindex", idx === prevIndex ? "0" : "-1");
4690
4837
  });
4691
- menuItems[prevIndex].focus();
4838
+ menuItems[prevIndex].focus({ preventScroll: false });
4839
+ menuItems[prevIndex].scrollIntoView({ behavior: "smooth", block: "nearest" });
4692
4840
  break;
4693
4841
  case "Home":
4694
4842
  e.preventDefault();
@@ -4696,7 +4844,8 @@ function attachMenuKeyboardNavigation(menu, button, itemSelector, onClose) {
4696
4844
  menuItems.forEach((item, idx) => {
4697
4845
  item.setAttribute("tabindex", idx === 0 ? "0" : "-1");
4698
4846
  });
4699
- menuItems[0].focus();
4847
+ menuItems[0].focus({ preventScroll: false });
4848
+ menuItems[0].scrollIntoView({ behavior: "smooth", block: "nearest" });
4700
4849
  break;
4701
4850
  case "End":
4702
4851
  e.preventDefault();
@@ -4705,7 +4854,8 @@ function attachMenuKeyboardNavigation(menu, button, itemSelector, onClose) {
4705
4854
  menuItems.forEach((item, idx) => {
4706
4855
  item.setAttribute("tabindex", idx === lastIndex ? "0" : "-1");
4707
4856
  });
4708
- menuItems[lastIndex].focus();
4857
+ menuItems[lastIndex].focus({ preventScroll: false });
4858
+ menuItems[lastIndex].scrollIntoView({ behavior: "smooth", block: "nearest" });
4709
4859
  break;
4710
4860
  case "Enter":
4711
4861
  case " ":
@@ -4743,6 +4893,7 @@ function focusFirstMenuItem(menu, itemSelector, delay = 0) {
4743
4893
  item.setAttribute("tabindex", index === 0 ? "0" : "-1");
4744
4894
  });
4745
4895
  focusElement(menuItems[0], { delay: 0 });
4896
+ menuItems[0].scrollIntoView({ behavior: "smooth", block: "nearest" });
4746
4897
  }
4747
4898
  }, delay);
4748
4899
  }
@@ -5535,7 +5686,7 @@ var TranscriptManager = class {
5535
5686
  if (focusButton) {
5536
5687
  const transcriptButton = (_b = (_a = this.player.controlBar) == null ? void 0 : _a.controls) == null ? void 0 : _b.transcript;
5537
5688
  if (transcriptButton && typeof transcriptButton.focus === "function") {
5538
- transcriptButton.focus();
5689
+ transcriptButton.focus({ preventScroll: true });
5539
5690
  }
5540
5691
  }
5541
5692
  }
@@ -5560,15 +5711,17 @@ var TranscriptManager = class {
5560
5711
  this.headerLeft = DOMUtils.createElement("div", {
5561
5712
  className: `${this.player.options.classPrefix}-transcript-header-left`
5562
5713
  });
5714
+ const settingsAriaLabel = i18n.t("transcript.settingsMenu");
5563
5715
  this.settingsButton = DOMUtils.createElement("button", {
5564
5716
  className: `${this.player.options.classPrefix}-transcript-settings`,
5565
5717
  attributes: {
5566
5718
  "type": "button",
5567
- "aria-label": i18n.t("transcript.settingsMenu"),
5719
+ "aria-label": settingsAriaLabel,
5568
5720
  "aria-expanded": "false"
5569
5721
  }
5570
5722
  });
5571
5723
  this.settingsButton.appendChild(createIconElement("settings"));
5724
+ DOMUtils.attachTooltip(this.settingsButton, settingsAriaLabel, this.player.options.classPrefix);
5572
5725
  this.handlers.settingsClick = (e) => {
5573
5726
  e.preventDefault();
5574
5727
  e.stopPropagation();
@@ -5602,8 +5755,7 @@ var TranscriptManager = class {
5602
5755
  const autoscrollLabel = DOMUtils.createElement("label", {
5603
5756
  className: `${this.player.options.classPrefix}-transcript-autoscroll-label`,
5604
5757
  attributes: {
5605
- "for": autoscrollId,
5606
- "title": i18n.t("transcript.autoscroll")
5758
+ "for": autoscrollId
5607
5759
  }
5608
5760
  });
5609
5761
  this.autoscrollCheckbox = DOMUtils.createElement("input", {
@@ -5652,14 +5804,16 @@ var TranscriptManager = class {
5652
5804
  this.languageSelectorWrapper = languageSelectorWrapper;
5653
5805
  preventDragOnElement(languageSelectorWrapper);
5654
5806
  this.headerLeft.appendChild(languageSelectorWrapper);
5807
+ const closeAriaLabel = i18n.t("transcript.close");
5655
5808
  const closeButton = DOMUtils.createElement("button", {
5656
5809
  className: `${this.player.options.classPrefix}-transcript-close`,
5657
5810
  attributes: {
5658
5811
  "type": "button",
5659
- "aria-label": i18n.t("transcript.close")
5812
+ "aria-label": closeAriaLabel
5660
5813
  }
5661
5814
  });
5662
5815
  closeButton.appendChild(createIconElement("close"));
5816
+ DOMUtils.attachTooltip(closeButton, closeAriaLabel, this.player.options.classPrefix);
5663
5817
  closeButton.addEventListener("click", () => this.hideTranscript({ focusButton: true }));
5664
5818
  this.transcriptHeader.appendChild(this.headerLeft);
5665
5819
  this.transcriptHeader.appendChild(closeButton);
@@ -6106,7 +6260,7 @@ var TranscriptManager = class {
6106
6260
  console.log("[VidPly Metadata] Focusing element:", targetSelector);
6107
6261
  }
6108
6262
  this.setManagedTimeout(() => {
6109
- targetElement.focus();
6263
+ targetElement.focus({ preventScroll: true });
6110
6264
  }, 10);
6111
6265
  } else if (this.player.options.debug) {
6112
6266
  console.warn("[VidPly Metadata] Element not found:", targetSelector);
@@ -6321,7 +6475,7 @@ var TranscriptManager = class {
6321
6475
  e.stopPropagation();
6322
6476
  const enabled = this.toggleResizeMode();
6323
6477
  if (enabled) {
6324
- this.transcriptWindow.focus();
6478
+ this.transcriptWindow.focus({ preventScroll: true });
6325
6479
  }
6326
6480
  return;
6327
6481
  }
@@ -6362,7 +6516,7 @@ var TranscriptManager = class {
6362
6516
  if (this.settingsMenuVisible) {
6363
6517
  this.hideSettingsMenu();
6364
6518
  }
6365
- this.transcriptWindow.focus();
6519
+ this.transcriptWindow.focus({ preventScroll: true });
6366
6520
  }
6367
6521
  }
6368
6522
  /**
@@ -6405,7 +6559,7 @@ var TranscriptManager = class {
6405
6559
  for (let i = 1; i < menuItems.length; i++) {
6406
6560
  menuItems[i].setAttribute("tabindex", "-1");
6407
6561
  }
6408
- menuItems[0].focus();
6562
+ menuItems[0].focus({ preventScroll: true });
6409
6563
  }
6410
6564
  }, 50);
6411
6565
  return;
@@ -6429,6 +6583,10 @@ var TranscriptManager = class {
6429
6583
  });
6430
6584
  keyboardDragOption.setAttribute("role", "switch");
6431
6585
  keyboardDragOption.setAttribute("aria-checked", "false");
6586
+ const dragTooltip = keyboardDragOption.querySelector(`.${this.player.options.classPrefix}-tooltip`);
6587
+ if (dragTooltip) dragTooltip.remove();
6588
+ const dragButtonText = keyboardDragOption.querySelector(`.${this.player.options.classPrefix}-button-text`);
6589
+ if (dragButtonText) dragButtonText.remove();
6432
6590
  this.dragOptionButton = keyboardDragOption;
6433
6591
  this.dragOptionText = keyboardDragOption.querySelector(`.${this.player.options.classPrefix}-settings-text`);
6434
6592
  this.updateDragOptionState();
@@ -6446,6 +6604,10 @@ var TranscriptManager = class {
6446
6604
  }, 50);
6447
6605
  }
6448
6606
  });
6607
+ const styleTooltip = styleOption.querySelector(`.${this.player.options.classPrefix}-tooltip`);
6608
+ if (styleTooltip) styleTooltip.remove();
6609
+ const styleButtonText = styleOption.querySelector(`.${this.player.options.classPrefix}-button-text`);
6610
+ if (styleButtonText) styleButtonText.remove();
6449
6611
  const resizeOption = createMenuItem({
6450
6612
  classPrefix: this.player.options.classPrefix,
6451
6613
  itemClass: `${this.player.options.classPrefix}-transcript-settings-item`,
@@ -6460,7 +6622,7 @@ var TranscriptManager = class {
6460
6622
  this.hideSettingsMenu({ focusButton: false });
6461
6623
  setTimeout(() => {
6462
6624
  if (this.transcriptWindow) {
6463
- this.transcriptWindow.focus();
6625
+ this.transcriptWindow.focus({ preventScroll: true });
6464
6626
  }
6465
6627
  }, 20);
6466
6628
  } else {
@@ -6470,6 +6632,10 @@ var TranscriptManager = class {
6470
6632
  });
6471
6633
  resizeOption.setAttribute("role", "switch");
6472
6634
  resizeOption.setAttribute("aria-checked", "false");
6635
+ const resizeTooltip = resizeOption.querySelector(`.${this.player.options.classPrefix}-tooltip`);
6636
+ if (resizeTooltip) resizeTooltip.remove();
6637
+ const resizeButtonText = resizeOption.querySelector(`.${this.player.options.classPrefix}-button-text`);
6638
+ if (resizeButtonText) resizeButtonText.remove();
6473
6639
  this.resizeOptionButton = resizeOption;
6474
6640
  this.resizeOptionText = resizeOption.querySelector(`.${this.player.options.classPrefix}-settings-text`);
6475
6641
  this.updateResizeOptionState();
@@ -6485,6 +6651,10 @@ var TranscriptManager = class {
6485
6651
  });
6486
6652
  showTimestampsOption.setAttribute("role", "switch");
6487
6653
  showTimestampsOption.setAttribute("aria-checked", this.showTimestamps ? "true" : "false");
6654
+ const timestampsTooltip = showTimestampsOption.querySelector(`.${this.player.options.classPrefix}-tooltip`);
6655
+ if (timestampsTooltip) timestampsTooltip.remove();
6656
+ const timestampsButtonText = showTimestampsOption.querySelector(`.${this.player.options.classPrefix}-button-text`);
6657
+ if (timestampsButtonText) timestampsButtonText.remove();
6488
6658
  this.showTimestampsButton = showTimestampsOption;
6489
6659
  this.showTimestampsText = showTimestampsOption.querySelector(`.${this.player.options.classPrefix}-settings-text`);
6490
6660
  this.updateShowTimestampsState();
@@ -6497,6 +6667,10 @@ var TranscriptManager = class {
6497
6667
  this.hideSettingsMenu();
6498
6668
  }
6499
6669
  });
6670
+ const closeTooltip = closeOption.querySelector(`.${this.player.options.classPrefix}-tooltip`);
6671
+ if (closeTooltip) closeTooltip.remove();
6672
+ const closeButtonText = closeOption.querySelector(`.${this.player.options.classPrefix}-button-text`);
6673
+ if (closeButtonText) closeButtonText.remove();
6500
6674
  this.settingsMenu.appendChild(keyboardDragOption);
6501
6675
  this.settingsMenu.appendChild(resizeOption);
6502
6676
  this.settingsMenu.appendChild(styleOption);
@@ -6538,7 +6712,7 @@ var TranscriptManager = class {
6538
6712
  for (let i = 1; i < menuItems.length; i++) {
6539
6713
  menuItems[i].setAttribute("tabindex", "-1");
6540
6714
  }
6541
- menuItems[0].focus();
6715
+ menuItems[0].focus({ preventScroll: true });
6542
6716
  }
6543
6717
  }, 50);
6544
6718
  }
@@ -6612,7 +6786,7 @@ var TranscriptManager = class {
6612
6786
  if (this.settingsButton) {
6613
6787
  this.settingsButton.setAttribute("aria-expanded", "false");
6614
6788
  if (focusButton) {
6615
- this.settingsButton.focus();
6789
+ this.settingsButton.focus({ preventScroll: true });
6616
6790
  }
6617
6791
  }
6618
6792
  }
@@ -6658,7 +6832,6 @@ var TranscriptManager = class {
6658
6832
  const ariaLabel = isEnabled ? i18n.t("transcript.disableDragModeAria") : i18n.t("transcript.enableDragModeAria");
6659
6833
  this.dragOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
6660
6834
  this.dragOptionButton.setAttribute("aria-label", ariaLabel);
6661
- this.dragOptionButton.setAttribute("title", text);
6662
6835
  if (this.dragOptionText) {
6663
6836
  this.dragOptionText.textContent = text;
6664
6837
  }
@@ -6672,7 +6845,6 @@ var TranscriptManager = class {
6672
6845
  const ariaLabel = isEnabled ? i18n.t("transcript.disableResizeModeAria") : i18n.t("transcript.enableResizeModeAria");
6673
6846
  this.resizeOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
6674
6847
  this.resizeOptionButton.setAttribute("aria-label", ariaLabel);
6675
- this.resizeOptionButton.setAttribute("title", text);
6676
6848
  if (this.resizeOptionText) {
6677
6849
  this.resizeOptionText.textContent = text;
6678
6850
  }
@@ -6691,7 +6863,6 @@ var TranscriptManager = class {
6691
6863
  const ariaLabel = this.showTimestamps ? i18n.t("transcript.hideTimestampsAria") : i18n.t("transcript.showTimestampsAria");
6692
6864
  this.showTimestampsButton.setAttribute("aria-checked", this.showTimestamps ? "true" : "false");
6693
6865
  this.showTimestampsButton.setAttribute("aria-label", ariaLabel);
6694
- this.showTimestampsButton.setAttribute("title", text);
6695
6866
  if (this.showTimestampsText) {
6696
6867
  this.showTimestampsText.textContent = text;
6697
6868
  }
@@ -6760,7 +6931,7 @@ var TranscriptManager = class {
6760
6931
  setTimeout(() => {
6761
6932
  const firstSelect = this.styleDialog.querySelector("select, input");
6762
6933
  if (firstSelect) {
6763
- firstSelect.focus();
6934
+ firstSelect.focus({ preventScroll: true });
6764
6935
  }
6765
6936
  }, 0);
6766
6937
  return;
@@ -6825,10 +6996,10 @@ var TranscriptManager = class {
6825
6996
  const lastElement = focusableElements[focusableElements.length - 1];
6826
6997
  if (e.shiftKey && document.activeElement === firstElement) {
6827
6998
  e.preventDefault();
6828
- lastElement.focus();
6999
+ lastElement.focus({ preventScroll: true });
6829
7000
  } else if (!e.shiftKey && document.activeElement === lastElement) {
6830
7001
  e.preventDefault();
6831
- firstElement.focus();
7002
+ firstElement.focus({ preventScroll: true });
6832
7003
  }
6833
7004
  }
6834
7005
  };
@@ -6848,7 +7019,7 @@ var TranscriptManager = class {
6848
7019
  setTimeout(() => {
6849
7020
  const firstSelect = this.styleDialog.querySelector("select, input");
6850
7021
  if (firstSelect) {
6851
- firstSelect.focus();
7022
+ firstSelect.focus({ preventScroll: true });
6852
7023
  }
6853
7024
  }, 0);
6854
7025
  }
@@ -6863,7 +7034,7 @@ var TranscriptManager = class {
6863
7034
  document.removeEventListener("keydown", this.handlers.styleDialogKeydown);
6864
7035
  }
6865
7036
  if (this.settingsButton) {
6866
- this.settingsButton.focus();
7037
+ this.settingsButton.focus({ preventScroll: true });
6867
7038
  }
6868
7039
  }
6869
7040
  }
@@ -8427,7 +8598,12 @@ var Player = class _Player extends EventEmitter {
8427
8598
  if (trackConfig.default) {
8428
8599
  track.default = true;
8429
8600
  }
8430
- this.element.appendChild(track);
8601
+ const firstChild = this.element.firstChild;
8602
+ if (firstChild && firstChild.nodeType === Node.ELEMENT_NODE && firstChild.tagName !== "TRACK") {
8603
+ this.element.insertBefore(track, firstChild);
8604
+ } else {
8605
+ this.element.appendChild(track);
8606
+ }
8431
8607
  });
8432
8608
  this.invalidateTrackCache();
8433
8609
  }
@@ -9028,6 +9204,11 @@ var Player = class _Player extends EventEmitter {
9028
9204
  });
9029
9205
  }
9030
9206
  });
9207
+ const hasSrcAttribute = this.element.hasAttribute("src");
9208
+ const srcValue = hasSrcAttribute ? this.element.getAttribute("src") : null;
9209
+ if (hasSrcAttribute) {
9210
+ this.element.removeAttribute("src");
9211
+ }
9031
9212
  allSourceElements.forEach((sourceEl) => {
9032
9213
  sourceEl.remove();
9033
9214
  });
@@ -9184,7 +9365,10 @@ var Player = class _Player extends EventEmitter {
9184
9365
  newTrackElement.setAttribute(attrName, attributes[attrName]);
9185
9366
  }
9186
9367
  });
9187
- if (nextSibling && nextSibling.parentNode) {
9368
+ const firstChild = parent.firstChild;
9369
+ if (firstChild && firstChild.nodeType === Node.ELEMENT_NODE && firstChild.tagName !== "TRACK") {
9370
+ parent.insertBefore(newTrackElement, firstChild);
9371
+ } else if (nextSibling && nextSibling.parentNode) {
9188
9372
  parent.insertBefore(newTrackElement, nextSibling);
9189
9373
  } else {
9190
9374
  parent.appendChild(newTrackElement);
@@ -9656,6 +9840,11 @@ var Player = class _Player extends EventEmitter {
9656
9840
  });
9657
9841
  }
9658
9842
  });
9843
+ const hasSrcAttribute = this.element.hasAttribute("src");
9844
+ const srcValue = hasSrcAttribute ? this.element.getAttribute("src") : null;
9845
+ if (hasSrcAttribute) {
9846
+ this.element.removeAttribute("src");
9847
+ }
9659
9848
  allSourceElements.forEach((sourceEl) => {
9660
9849
  sourceEl.remove();
9661
9850
  });
@@ -9946,15 +10135,17 @@ var Player = class _Player extends EventEmitter {
9946
10135
  const title = DOMUtils.createElement("h3", {
9947
10136
  textContent: i18n.t("player.signLanguageVideo")
9948
10137
  });
10138
+ const settingsAriaLabel = i18n.t("player.signLanguageSettings");
9949
10139
  this.signLanguageSettingsButton = DOMUtils.createElement("button", {
9950
10140
  className: `${this.options.classPrefix}-sign-language-settings`,
9951
10141
  attributes: {
9952
10142
  "type": "button",
9953
- "aria-label": i18n.t("player.signLanguageSettings"),
10143
+ "aria-label": settingsAriaLabel,
9954
10144
  "aria-expanded": "false"
9955
10145
  }
9956
10146
  });
9957
10147
  this.signLanguageSettingsButton.appendChild(createIconElement("settings"));
10148
+ DOMUtils.attachTooltip(this.signLanguageSettingsButton, settingsAriaLabel, this.options.classPrefix);
9958
10149
  this.signLanguageSettingsHandlers = {
9959
10150
  settingsClick: (e) => {
9960
10151
  e.preventDefault();
@@ -10022,19 +10213,21 @@ var Player = class _Player extends EventEmitter {
10022
10213
  headerLeft.appendChild(signLanguageSelectorWrapper);
10023
10214
  }
10024
10215
  headerLeft.appendChild(title);
10216
+ const closeAriaLabel = i18n.t("player.closeSignLanguage");
10025
10217
  const closeButton = DOMUtils.createElement("button", {
10026
10218
  className: `${this.options.classPrefix}-sign-language-close`,
10027
10219
  attributes: {
10028
10220
  "type": "button",
10029
- "aria-label": i18n.t("player.closeSignLanguage")
10221
+ "aria-label": closeAriaLabel
10030
10222
  }
10031
10223
  });
10032
10224
  closeButton.appendChild(createIconElement("close"));
10225
+ DOMUtils.attachTooltip(closeButton, closeAriaLabel, this.options.classPrefix);
10033
10226
  closeButton.addEventListener("click", () => {
10034
10227
  this.disableSignLanguage();
10035
10228
  if (this.controlBar && this.controlBar.controls && this.controlBar.controls.signLanguage) {
10036
10229
  setTimeout(() => {
10037
- this.controlBar.controls.signLanguage.focus();
10230
+ this.controlBar.controls.signLanguage.focus({ preventScroll: true });
10038
10231
  }, 0);
10039
10232
  }
10040
10233
  });
@@ -10212,7 +10405,7 @@ var Player = class _Player extends EventEmitter {
10212
10405
  e.stopPropagation();
10213
10406
  const enabled = this.toggleSignLanguageResizeMode();
10214
10407
  if (enabled) {
10215
- this.signLanguageWrapper.focus();
10408
+ this.signLanguageWrapper.focus({ preventScroll: true });
10216
10409
  }
10217
10410
  return;
10218
10411
  }
@@ -10230,7 +10423,7 @@ var Player = class _Player extends EventEmitter {
10230
10423
  this.disableSignLanguage();
10231
10424
  if (this.controlBar && this.controlBar.controls && this.controlBar.controls.signLanguage) {
10232
10425
  setTimeout(() => {
10233
- this.controlBar.controls.signLanguage.focus();
10426
+ this.controlBar.controls.signLanguage.focus({ preventScroll: true });
10234
10427
  }, 0);
10235
10428
  }
10236
10429
  return;
@@ -10365,6 +10558,10 @@ var Player = class _Player extends EventEmitter {
10365
10558
  });
10366
10559
  keyboardDragOption.setAttribute("role", "switch");
10367
10560
  keyboardDragOption.setAttribute("aria-checked", "false");
10561
+ const dragTooltip = keyboardDragOption.querySelector(`.${this.options.classPrefix}-tooltip`);
10562
+ if (dragTooltip) dragTooltip.remove();
10563
+ const dragButtonText = keyboardDragOption.querySelector(`.${this.options.classPrefix}-button-text`);
10564
+ if (dragButtonText) dragButtonText.remove();
10368
10565
  this.signLanguageDragOptionButton = keyboardDragOption;
10369
10566
  this.signLanguageDragOptionText = keyboardDragOption.querySelector(`.${this.options.classPrefix}-settings-text`);
10370
10567
  this.updateSignLanguageDragOptionState();
@@ -10382,7 +10579,7 @@ var Player = class _Player extends EventEmitter {
10382
10579
  this.hideSignLanguageSettingsMenu({ focusButton: false });
10383
10580
  setTimeout(() => {
10384
10581
  if (this.signLanguageWrapper) {
10385
- this.signLanguageWrapper.focus();
10582
+ this.signLanguageWrapper.focus({ preventScroll: true });
10386
10583
  }
10387
10584
  }, 20);
10388
10585
  } else {
@@ -10392,6 +10589,10 @@ var Player = class _Player extends EventEmitter {
10392
10589
  });
10393
10590
  resizeOption.setAttribute("role", "switch");
10394
10591
  resizeOption.setAttribute("aria-checked", "false");
10592
+ const resizeTooltip = resizeOption.querySelector(`.${this.options.classPrefix}-tooltip`);
10593
+ if (resizeTooltip) resizeTooltip.remove();
10594
+ const resizeButtonText = resizeOption.querySelector(`.${this.options.classPrefix}-button-text`);
10595
+ if (resizeButtonText) resizeButtonText.remove();
10395
10596
  this.signLanguageResizeOptionButton = resizeOption;
10396
10597
  this.signLanguageResizeOptionText = resizeOption.querySelector(`.${this.options.classPrefix}-settings-text`);
10397
10598
  this.updateSignLanguageResizeOptionState();
@@ -10404,6 +10605,10 @@ var Player = class _Player extends EventEmitter {
10404
10605
  this.hideSignLanguageSettingsMenu();
10405
10606
  }
10406
10607
  });
10608
+ const closeTooltip = closeOption.querySelector(`.${this.options.classPrefix}-tooltip`);
10609
+ if (closeTooltip) closeTooltip.remove();
10610
+ const closeButtonText = closeOption.querySelector(`.${this.options.classPrefix}-button-text`);
10611
+ if (closeButtonText) closeButtonText.remove();
10407
10612
  this.signLanguageSettingsMenu.appendChild(keyboardDragOption);
10408
10613
  this.signLanguageSettingsMenu.appendChild(resizeOption);
10409
10614
  this.signLanguageSettingsMenu.appendChild(closeOption);
@@ -10450,7 +10655,7 @@ var Player = class _Player extends EventEmitter {
10450
10655
  if (this.signLanguageSettingsButton) {
10451
10656
  this.signLanguageSettingsButton.setAttribute("aria-expanded", "false");
10452
10657
  if (focusButton) {
10453
- this.signLanguageSettingsButton.focus();
10658
+ this.signLanguageSettingsButton.focus({ preventScroll: true });
10454
10659
  }
10455
10660
  }
10456
10661
  }
@@ -10539,7 +10744,6 @@ var Player = class _Player extends EventEmitter {
10539
10744
  const ariaLabel = isEnabled ? i18n.t("player.disableSignDragModeAria") : i18n.t("player.enableSignDragModeAria");
10540
10745
  this.signLanguageDragOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
10541
10746
  this.signLanguageDragOptionButton.setAttribute("aria-label", ariaLabel);
10542
- this.signLanguageDragOptionButton.setAttribute("title", text);
10543
10747
  if (this.signLanguageDragOptionText) {
10544
10748
  this.signLanguageDragOptionText.textContent = text;
10545
10749
  }
@@ -10553,7 +10757,6 @@ var Player = class _Player extends EventEmitter {
10553
10757
  const ariaLabel = isEnabled ? i18n.t("player.disableSignResizeModeAria") : i18n.t("player.enableSignResizeModeAria");
10554
10758
  this.signLanguageResizeOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
10555
10759
  this.signLanguageResizeOptionButton.setAttribute("aria-label", ariaLabel);
10556
- this.signLanguageResizeOptionButton.setAttribute("title", text);
10557
10760
  if (this.signLanguageResizeOptionText) {
10558
10761
  this.signLanguageResizeOptionText.textContent = text;
10559
10762
  }
@@ -10987,23 +11190,23 @@ var Player = class _Player extends EventEmitter {
10987
11190
  return;
10988
11191
  }
10989
11192
  if (target === "alert" && fallbackElement) {
10990
- fallbackElement.focus();
11193
+ fallbackElement.focus({ preventScroll: true });
10991
11194
  return;
10992
11195
  }
10993
11196
  if (target === "player") {
10994
11197
  if (this.container) {
10995
- this.container.focus();
11198
+ this.container.focus({ preventScroll: true });
10996
11199
  }
10997
11200
  return;
10998
11201
  }
10999
11202
  if (target === "media") {
11000
- this.element.focus();
11203
+ this.element.focus({ preventScroll: true });
11001
11204
  return;
11002
11205
  }
11003
11206
  if (target === "playButton") {
11004
11207
  const playButton = (_b = (_a = this.controlBar) == null ? void 0 : _a.controls) == null ? void 0 : _b.playPause;
11005
11208
  if (playButton) {
11006
- playButton.focus();
11209
+ playButton.focus({ preventScroll: true });
11007
11210
  }
11008
11211
  return;
11009
11212
  }
@@ -11013,7 +11216,7 @@ var Player = class _Player extends EventEmitter {
11013
11216
  if (targetElement.tabIndex === -1 && !targetElement.hasAttribute("tabindex")) {
11014
11217
  targetElement.setAttribute("tabindex", "-1");
11015
11218
  }
11016
- targetElement.focus();
11219
+ targetElement.focus({ preventScroll: true });
11017
11220
  }
11018
11221
  }
11019
11222
  }
@@ -11057,7 +11260,7 @@ var Player = class _Player extends EventEmitter {
11057
11260
  if (element.tabIndex === -1 && !element.hasAttribute("tabindex")) {
11058
11261
  element.setAttribute("tabindex", "-1");
11059
11262
  }
11060
- element.focus();
11263
+ element.focus({ preventScroll: true });
11061
11264
  }
11062
11265
  if (shouldShow && config.autoScroll !== false && options.autoScroll !== false) {
11063
11266
  element.scrollIntoView({ behavior: "smooth", block: "nearest" });
@@ -11188,8 +11391,7 @@ var Player = class _Player extends EventEmitter {
11188
11391
  targetElement.setAttribute("tabindex", "-1");
11189
11392
  }
11190
11393
  this.setManagedTimeout(() => {
11191
- targetElement.focus();
11192
- targetElement.scrollIntoView({ behavior: "smooth", block: "nearest" });
11394
+ targetElement.focus({ preventScroll: true });
11193
11395
  }, 10);
11194
11396
  } else if (this.options.debug) {
11195
11397
  this.log("[Metadata] Element not found:", normalizedSelector || targetSelector);
@@ -11265,6 +11467,8 @@ var PlaylistManager = class {
11265
11467
  this.player.on("pause", this.handlePlaybackStateChange.bind(this));
11266
11468
  this.player.on("ended", this.handlePlaybackStateChange.bind(this));
11267
11469
  this.player.on("fullscreenchange", this.handleFullscreenChange.bind(this));
11470
+ this.player.on("audiodescriptionenabled", this.handleAudioDescriptionChange.bind(this));
11471
+ this.player.on("audiodescriptiondisabled", this.handleAudioDescriptionChange.bind(this));
11268
11472
  if (this.options.showPanel) {
11269
11473
  this.createUI();
11270
11474
  }
@@ -11492,6 +11696,52 @@ var PlaylistManager = class {
11492
11696
  this.updatePlaylistVisibilityInFullscreen();
11493
11697
  }, 50);
11494
11698
  }
11699
+ /**
11700
+ * Handle audio description state changes
11701
+ * Updates duration displays to show audio-described version duration when AD is enabled
11702
+ */
11703
+ handleAudioDescriptionChange() {
11704
+ const currentTrack = this.getCurrentTrack();
11705
+ if (!currentTrack) return;
11706
+ this.updateTrackInfo(currentTrack);
11707
+ this.updatePlaylistUI();
11708
+ this.updatePlaylistDurations();
11709
+ }
11710
+ /**
11711
+ * Update the visual duration displays in the playlist panel
11712
+ * Called when audio description state changes
11713
+ */
11714
+ updatePlaylistDurations() {
11715
+ if (!this.playlistPanel) return;
11716
+ const items = this.playlistPanel.querySelectorAll(".vidply-playlist-item");
11717
+ items.forEach((item, index) => {
11718
+ const track = this.tracks[index];
11719
+ if (!track) return;
11720
+ const effectiveDuration = this.getEffectiveDuration(track);
11721
+ const trackDuration = effectiveDuration ? TimeUtils.formatTime(effectiveDuration) : "";
11722
+ const durationBadge = item.querySelector(".vidply-playlist-duration-badge");
11723
+ if (durationBadge) {
11724
+ durationBadge.textContent = trackDuration;
11725
+ }
11726
+ const inlineDuration = item.querySelector(".vidply-playlist-item-duration");
11727
+ if (inlineDuration) {
11728
+ inlineDuration.textContent = trackDuration;
11729
+ }
11730
+ });
11731
+ }
11732
+ /**
11733
+ * Get the effective duration for a track based on audio description state
11734
+ * @param {Object} track - Track object
11735
+ * @returns {number|null} - Duration in seconds or null if not available
11736
+ */
11737
+ getEffectiveDuration(track) {
11738
+ if (!track) return null;
11739
+ const isAudioDescriptionEnabled = this.player.state.audioDescriptionEnabled;
11740
+ if (isAudioDescriptionEnabled && track.audioDescriptionDuration) {
11741
+ return track.audioDescriptionDuration;
11742
+ }
11743
+ return track.duration || null;
11744
+ }
11495
11745
  /**
11496
11746
  * Update playlist visibility based on fullscreen and playback state
11497
11747
  * In fullscreen: show when paused/not started, hide when playing
@@ -11549,9 +11799,7 @@ var PlaylistManager = class {
11549
11799
  this.trackInfoElement = DOMUtils.createElement("div", {
11550
11800
  className: "vidply-track-info",
11551
11801
  attributes: {
11552
- role: "status",
11553
- "aria-live": "polite",
11554
- "aria-atomic": "true"
11802
+ role: "status"
11555
11803
  }
11556
11804
  });
11557
11805
  this.trackInfoElement.style.display = "none";
@@ -11586,22 +11834,32 @@ var PlaylistManager = class {
11586
11834
  const totalTracks = this.tracks.length;
11587
11835
  const trackTitle = track.title || i18n.t("playlist.untitled");
11588
11836
  const trackArtist = track.artist || "";
11837
+ const effectiveDuration = this.getEffectiveDuration(track);
11838
+ const trackDuration = effectiveDuration ? TimeUtils.formatTime(effectiveDuration) : "";
11839
+ const trackDurationReadable = effectiveDuration ? TimeUtils.formatDuration(effectiveDuration) : "";
11589
11840
  const artistPart = trackArtist ? i18n.t("playlist.by") + trackArtist : "";
11841
+ const durationPart = trackDurationReadable ? `. ${trackDurationReadable}` : "";
11590
11842
  const announcement = i18n.t("playlist.nowPlaying", {
11591
11843
  current: trackNumber,
11592
11844
  total: totalTracks,
11593
11845
  title: trackTitle,
11594
11846
  artist: artistPart
11595
- });
11847
+ }) + durationPart;
11596
11848
  const trackOfText = i18n.t("playlist.trackOf", {
11597
11849
  current: trackNumber,
11598
11850
  total: totalTracks
11599
11851
  });
11852
+ const durationHtml = trackDuration ? `<span class="vidply-track-duration" aria-hidden="true">${DOMUtils.escapeHTML(trackDuration)}</span>` : "";
11853
+ const trackDescription = track.description || "";
11600
11854
  this.trackInfoElement.innerHTML = `
11601
11855
  <span class="vidply-sr-only">${DOMUtils.escapeHTML(announcement)}</span>
11602
- <div class="vidply-track-number" aria-hidden="true">${DOMUtils.escapeHTML(trackOfText)}</div>
11856
+ <div class="vidply-track-header" aria-hidden="true">
11857
+ <span class="vidply-track-number">${DOMUtils.escapeHTML(trackOfText)}</span>
11858
+ ${durationHtml}
11859
+ </div>
11603
11860
  <div class="vidply-track-title" aria-hidden="true">${DOMUtils.escapeHTML(trackTitle)}</div>
11604
11861
  ${trackArtist ? `<div class="vidply-track-artist" aria-hidden="true">${DOMUtils.escapeHTML(trackArtist)}</div>` : ""}
11862
+ ${trackDescription ? `<div class="vidply-track-description" aria-hidden="true">${DOMUtils.escapeHTML(trackDescription)}</div>` : ""}
11605
11863
  `;
11606
11864
  this.trackInfoElement.style.display = "block";
11607
11865
  this.updateTrackArtwork(track);
@@ -11643,6 +11901,7 @@ var PlaylistManager = class {
11643
11901
  const list = DOMUtils.createElement("ul", {
11644
11902
  className: "vidply-playlist-list",
11645
11903
  attributes: {
11904
+ role: "listbox",
11646
11905
  "aria-labelledby": `${this.uniqueId}-heading`,
11647
11906
  "aria-describedby": `${this.uniqueId}-keyboard-instructions`
11648
11907
  }
@@ -11666,9 +11925,14 @@ var PlaylistManager = class {
11666
11925
  });
11667
11926
  const trackTitle = track.title || i18n.t("playlist.trackUntitled", { number: index + 1 });
11668
11927
  const trackArtist = track.artist ? i18n.t("playlist.by") + track.artist : "";
11928
+ const effectiveDuration = this.getEffectiveDuration(track);
11929
+ const trackDuration = effectiveDuration ? TimeUtils.formatTime(effectiveDuration) : "";
11930
+ const trackDurationReadable = effectiveDuration ? TimeUtils.formatDuration(effectiveDuration) : "";
11669
11931
  const isActive = index === this.currentIndex;
11670
- const statusText = isActive ? "Currently playing" : "Not playing";
11671
- const actionText = isActive ? "Press Enter to restart" : "Press Enter to play";
11932
+ let ariaLabel = `${trackTitle}${trackArtist}`;
11933
+ if (trackDurationReadable) {
11934
+ ariaLabel += `. ${trackDurationReadable}`;
11935
+ }
11672
11936
  const item = DOMUtils.createElement("li", {
11673
11937
  className: isActive ? "vidply-playlist-item vidply-playlist-item-active" : "vidply-playlist-item",
11674
11938
  attributes: {
@@ -11679,23 +11943,28 @@ var PlaylistManager = class {
11679
11943
  className: "vidply-playlist-item-button",
11680
11944
  attributes: {
11681
11945
  type: "button",
11946
+ role: "option",
11682
11947
  tabIndex: index === 0 ? 0 : -1,
11683
11948
  // Only first item is in tab order initially
11684
- "aria-label": `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`,
11949
+ "aria-label": ariaLabel,
11685
11950
  "aria-posinset": index + 1,
11686
- "aria-setsize": this.tracks.length
11951
+ "aria-setsize": this.tracks.length,
11952
+ "aria-checked": isActive ? "true" : "false"
11687
11953
  }
11688
11954
  });
11689
11955
  if (isActive) {
11690
11956
  button.setAttribute("aria-current", "true");
11691
11957
  button.setAttribute("tabIndex", "0");
11692
11958
  }
11693
- const thumbnail = DOMUtils.createElement("span", {
11694
- className: "vidply-playlist-thumbnail",
11959
+ const thumbnailContainer = DOMUtils.createElement("span", {
11960
+ className: "vidply-playlist-thumbnail-container",
11695
11961
  attributes: {
11696
11962
  "aria-hidden": "true"
11697
11963
  }
11698
11964
  });
11965
+ const thumbnail = DOMUtils.createElement("span", {
11966
+ className: "vidply-playlist-thumbnail"
11967
+ });
11699
11968
  if (track.poster) {
11700
11969
  thumbnail.style.backgroundImage = `url(${track.poster})`;
11701
11970
  } else {
@@ -11703,18 +11972,37 @@ var PlaylistManager = class {
11703
11972
  icon.classList.add("vidply-playlist-thumbnail-icon");
11704
11973
  thumbnail.appendChild(icon);
11705
11974
  }
11706
- button.appendChild(thumbnail);
11975
+ thumbnailContainer.appendChild(thumbnail);
11976
+ if (trackDuration && track.poster) {
11977
+ const durationBadge = DOMUtils.createElement("span", {
11978
+ className: "vidply-playlist-duration-badge"
11979
+ });
11980
+ durationBadge.textContent = trackDuration;
11981
+ thumbnailContainer.appendChild(durationBadge);
11982
+ }
11983
+ button.appendChild(thumbnailContainer);
11707
11984
  const info = DOMUtils.createElement("span", {
11708
11985
  className: "vidply-playlist-item-info",
11709
11986
  attributes: {
11710
11987
  "aria-hidden": "true"
11711
11988
  }
11712
11989
  });
11990
+ const titleRow = DOMUtils.createElement("span", {
11991
+ className: "vidply-playlist-item-title-row"
11992
+ });
11713
11993
  const title = DOMUtils.createElement("span", {
11714
11994
  className: "vidply-playlist-item-title"
11715
11995
  });
11716
11996
  title.textContent = trackTitle;
11717
- info.appendChild(title);
11997
+ titleRow.appendChild(title);
11998
+ if (trackDuration && !track.poster) {
11999
+ const inlineDuration = DOMUtils.createElement("span", {
12000
+ className: "vidply-playlist-item-duration"
12001
+ });
12002
+ inlineDuration.textContent = trackDuration;
12003
+ titleRow.appendChild(inlineDuration);
12004
+ }
12005
+ info.appendChild(titleRow);
11718
12006
  if (track.artist) {
11719
12007
  const artist = DOMUtils.createElement("span", {
11720
12008
  className: "vidply-playlist-item-artist"
@@ -11722,6 +12010,13 @@ var PlaylistManager = class {
11722
12010
  artist.textContent = track.artist;
11723
12011
  info.appendChild(artist);
11724
12012
  }
12013
+ if (track.description) {
12014
+ const description = DOMUtils.createElement("span", {
12015
+ className: "vidply-playlist-item-description"
12016
+ });
12017
+ description.textContent = track.description;
12018
+ info.appendChild(description);
12019
+ }
11725
12020
  button.appendChild(info);
11726
12021
  const playIcon = createIconElement("play");
11727
12022
  playIcon.classList.add("vidply-playlist-item-icon");
@@ -11805,7 +12100,11 @@ var PlaylistManager = class {
11805
12100
  if (newIndex !== -1 && newIndex !== index) {
11806
12101
  buttons[index].setAttribute("tabIndex", "-1");
11807
12102
  buttons[newIndex].setAttribute("tabIndex", "0");
11808
- buttons[newIndex].focus();
12103
+ buttons[newIndex].focus({ preventScroll: false });
12104
+ const item = buttons[newIndex].closest(".vidply-playlist-item");
12105
+ if (item) {
12106
+ item.scrollIntoView({ behavior: "smooth", block: "nearest" });
12107
+ }
11809
12108
  }
11810
12109
  if (announcement && this.navigationFeedback) {
11811
12110
  this.navigationFeedback.textContent = announcement;
@@ -11833,21 +12132,29 @@ var PlaylistManager = class {
11833
12132
  });
11834
12133
  const trackTitle = track.title || i18n.t("playlist.trackUntitled", { number: index + 1 });
11835
12134
  const trackArtist = track.artist ? i18n.t("playlist.by") + track.artist : "";
12135
+ const effectiveDuration = this.getEffectiveDuration(track);
12136
+ const trackDurationReadable = effectiveDuration ? TimeUtils.formatDuration(effectiveDuration) : "";
11836
12137
  if (index === this.currentIndex) {
11837
12138
  item.classList.add("vidply-playlist-item-active");
11838
12139
  button.setAttribute("aria-current", "true");
12140
+ button.setAttribute("aria-checked", "true");
11839
12141
  button.setAttribute("tabIndex", "0");
11840
- const statusText = "Currently playing";
11841
- const actionText = "Press Enter to restart";
11842
- button.setAttribute("aria-label", `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
12142
+ let ariaLabel = `${trackTitle}${trackArtist}`;
12143
+ if (trackDurationReadable) {
12144
+ ariaLabel += `. ${trackDurationReadable}`;
12145
+ }
12146
+ button.setAttribute("aria-label", ariaLabel);
11843
12147
  item.scrollIntoView({ behavior: "smooth", block: "nearest" });
11844
12148
  } else {
11845
12149
  item.classList.remove("vidply-playlist-item-active");
11846
12150
  button.removeAttribute("aria-current");
12151
+ button.setAttribute("aria-checked", "false");
11847
12152
  button.setAttribute("tabIndex", "-1");
11848
- const statusText = "Not playing";
11849
- const actionText = "Press Enter to play";
11850
- button.setAttribute("aria-label", `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
12153
+ let ariaLabel = `${trackTitle}${trackArtist}`;
12154
+ if (trackDurationReadable) {
12155
+ ariaLabel += `. ${trackDurationReadable}`;
12156
+ }
12157
+ button.setAttribute("aria-label", ariaLabel);
11851
12158
  }
11852
12159
  });
11853
12160
  }
@@ -11946,7 +12253,7 @@ var PlaylistManager = class {
11946
12253
  setTimeout(() => {
11947
12254
  const firstItem = this.playlistPanel.querySelector('.vidply-playlist-item[tabindex="0"]');
11948
12255
  if (firstItem) {
11949
- firstItem.focus();
12256
+ firstItem.focus({ preventScroll: true });
11950
12257
  }
11951
12258
  }, 100);
11952
12259
  }
@@ -11960,7 +12267,7 @@ var PlaylistManager = class {
11960
12267
  if (this.player.controlBar && this.player.controlBar.controls.playlistToggle) {
11961
12268
  this.player.controlBar.controls.playlistToggle.setAttribute("aria-expanded", "false");
11962
12269
  this.player.controlBar.controls.playlistToggle.setAttribute("aria-pressed", "false");
11963
- this.player.controlBar.controls.playlistToggle.focus();
12270
+ this.player.controlBar.controls.playlistToggle.focus({ preventScroll: true });
11964
12271
  }
11965
12272
  }
11966
12273
  return this.isPanelVisible;