vidply 1.2.0 → 1.2.3

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.
Files changed (167) hide show
  1. package/README.md +974 -946
  2. package/dist/dev/{vidply.AudioDescriptionManager-R47Q4YR4.js → vidply.AudioDescriptionManager-JYYLK5KB.js} +275 -19
  3. package/dist/dev/vidply.AudioDescriptionManager-JYYLK5KB.js.map +7 -0
  4. package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js → vidply.CaptionStyleMenu-47RCKQF5.js} +4 -4
  5. package/dist/dev/{vidply.DASHRenderer-PWEMC3BT.js → vidply.DASHRenderer-VEJ4HDHO.js} +3 -3
  6. package/dist/dev/vidply.DASHRenderer-VEJ4HDHO.js.map +7 -0
  7. package/dist/dev/{vidply.FloatingPlayerManager-PKNNNAS2.js → vidply.FloatingPlayerManager-C2UVRPZY.js} +5 -5
  8. package/dist/dev/vidply.FloatingPlayerManager-C2UVRPZY.js.map +7 -0
  9. package/dist/dev/{vidply.HLSRenderer-E7RQ74HI.js → vidply.HLSRenderer-ZVBK2TNQ.js} +4 -4
  10. package/dist/dev/vidply.HLSRenderer-ZVBK2TNQ.js.map +7 -0
  11. package/dist/dev/vidply.HTML5Renderer-HLRQUOCU.js +12 -0
  12. package/dist/dev/{vidply.SignLanguageManager-FET5FQZ7.js → vidply.SignLanguageManager-FLUCBW5H.js} +8 -8
  13. package/dist/dev/vidply.SignLanguageManager-FLUCBW5H.js.map +7 -0
  14. package/dist/dev/{vidply.SoundCloudRenderer-RVSJZODO.js → vidply.SoundCloudRenderer-MGMAJ7NK.js} +3 -3
  15. package/dist/dev/vidply.SoundCloudRenderer-MGMAJ7NK.js.map +7 -0
  16. package/dist/dev/{vidply.TranscriptManager-5GXOQCLX.js → vidply.TranscriptManager-Y2RYXHJW.js} +10 -10
  17. package/dist/dev/vidply.TranscriptManager-Y2RYXHJW.js.map +7 -0
  18. package/dist/dev/{vidply.VimeoRenderer-ER2LSBBW.js → vidply.VimeoRenderer-RQFQD7OE.js} +3 -3
  19. package/dist/dev/vidply.VimeoRenderer-RQFQD7OE.js.map +7 -0
  20. package/dist/dev/{vidply.YouTubeRenderer-NFTOWJAB.js → vidply.YouTubeRenderer-EU2BARZF.js} +3 -3
  21. package/dist/dev/vidply.YouTubeRenderer-EU2BARZF.js.map +7 -0
  22. package/dist/dev/{vidply.chunk-BNFIUL2Z.js → vidply.chunk-6LYZYI5G.js} +3 -3
  23. package/dist/dev/vidply.chunk-6LYZYI5G.js.map +7 -0
  24. package/dist/dev/{vidply.chunk-NQPURMQY.js → vidply.chunk-A4ESEOQ2.js} +6 -6
  25. package/dist/dev/vidply.chunk-A4ESEOQ2.js.map +7 -0
  26. package/dist/dev/{vidply.chunk-W25HUCHS.js → vidply.chunk-CGYIED2I.js} +2 -2
  27. package/dist/dev/vidply.chunk-CGYIED2I.js.map +7 -0
  28. package/dist/dev/{vidply.chunk-SK4V2DAU.js → vidply.chunk-HZ7UOKOU.js} +3 -3
  29. package/dist/dev/{vidply.chunk-RZWZGDV7.js → vidply.chunk-I6EDRYGJ.js} +33 -6
  30. package/dist/dev/vidply.chunk-I6EDRYGJ.js.map +7 -0
  31. package/dist/dev/{vidply.chunk-64WXMTGL.js → vidply.chunk-JKMQGLCJ.js} +5 -5
  32. package/dist/dev/vidply.chunk-JKMQGLCJ.js.map +7 -0
  33. package/dist/dev/{vidply.chunk-DZSDO3B7.js → vidply.chunk-JZNZX32Z.js} +2 -2
  34. package/dist/dev/{vidply.chunk-VMSQAZM7.js → vidply.chunk-P25MRJ6O.js} +2 -2
  35. package/dist/dev/{vidply.chunk-6RQA4ROD.js → vidply.chunk-PRFNESYA.js} +2 -2
  36. package/dist/dev/{vidply.chunk-KI7C3QVL.js → vidply.chunk-UI3RYCVF.js} +4 -3
  37. package/dist/dev/vidply.chunk-UI3RYCVF.js.map +7 -0
  38. package/dist/dev/{vidply.chunk-JJ47NO24.js → vidply.chunk-UNFGMABP.js} +2 -2
  39. package/dist/dev/vidply.chunk-UNFGMABP.js.map +7 -0
  40. package/dist/dev/{vidply.de-4YAT7FFM.js → vidply.de-VAUVSQXC.js} +29 -2
  41. package/dist/dev/vidply.de-VAUVSQXC.js.map +7 -0
  42. package/dist/dev/{vidply.es-LEWIRUBH.js → vidply.es-G65HFWO6.js} +29 -2
  43. package/dist/dev/vidply.es-G65HFWO6.js.map +7 -0
  44. package/dist/dev/vidply.esm.js +648 -35
  45. package/dist/dev/vidply.esm.js.map +3 -3
  46. package/dist/dev/{vidply.fr-Q4GV2NOE.js → vidply.fr-MJNOZELM.js} +29 -2
  47. package/dist/dev/vidply.fr-MJNOZELM.js.map +7 -0
  48. package/dist/dev/{vidply.ja-S6ITA6WU.js → vidply.ja-INMDHIYJ.js} +29 -2
  49. package/dist/dev/vidply.ja-INMDHIYJ.js.map +7 -0
  50. package/dist/legacy/vidply.js +1062 -34
  51. package/dist/legacy/vidply.js.map +3 -3
  52. package/dist/legacy/vidply.min.js +2 -2
  53. package/dist/legacy/vidply.min.meta.json +127 -55
  54. package/dist/prod/vidply.AudioDescriptionManager-EKWUNOPE.min.js +6 -0
  55. package/dist/prod/{vidply.CaptionStyleMenu-M3KVQWB5.min.js → vidply.CaptionStyleMenu-JTWSNHRQ.min.js} +2 -2
  56. package/dist/prod/{vidply.DASHRenderer-DN3S27AV.min.js → vidply.DASHRenderer-CIDAHFWM.min.js} +2 -2
  57. package/dist/prod/{vidply.FloatingPlayerManager-SKAZ4J43.min.js → vidply.FloatingPlayerManager-VZXHPCPM.min.js} +2 -2
  58. package/dist/prod/{vidply.HLSRenderer-62HH4YTK.min.js → vidply.HLSRenderer-Q6EFYKID.min.js} +2 -2
  59. package/dist/prod/vidply.HTML5Renderer-XRDJUTOO.min.js +6 -0
  60. package/dist/prod/{vidply.SignLanguageManager-V3OBTSSF.min.js → vidply.SignLanguageManager-JUOSA5AM.min.js} +2 -2
  61. package/dist/prod/{vidply.SoundCloudRenderer-CXRVZIAJ.min.js → vidply.SoundCloudRenderer-7ODD46PO.min.js} +2 -2
  62. package/dist/prod/{vidply.TranscriptManager-SYM3DFUY.min.js → vidply.TranscriptManager-4B2QKYGQ.min.js} +2 -2
  63. package/dist/prod/{vidply.VimeoRenderer-72TWRYFQ.min.js → vidply.VimeoRenderer-MR3UYLY7.min.js} +2 -2
  64. package/dist/prod/{vidply.YouTubeRenderer-UMHEV3KD.min.js → vidply.YouTubeRenderer-ITRTC7NR.min.js} +2 -2
  65. package/dist/prod/{vidply.chunk-YEENJS3Q.min.js → vidply.chunk-3EEDXU4T.min.js} +2 -2
  66. package/dist/prod/{vidply.chunk-P4UXOHWA.min.js → vidply.chunk-5HTX5QXW.min.js} +1 -1
  67. package/dist/prod/{vidply.chunk-ABPXVDLZ.min.js → vidply.chunk-6OGHHIYP.min.js} +2 -2
  68. package/dist/prod/{vidply.chunk-UK7YGOB5.min.js → vidply.chunk-GUPGMFUZ.min.js} +2 -2
  69. package/dist/prod/{vidply.chunk-WX3MVEAL.min.js → vidply.chunk-MWEBITCP.min.js} +1 -1
  70. package/dist/prod/{vidply.chunk-2TB6MUHO.min.js → vidply.chunk-PL3P3JHR.min.js} +1 -1
  71. package/dist/prod/{vidply.chunk-EKEYJWMG.min.js → vidply.chunk-QQZGV7TD.min.js} +2 -2
  72. package/dist/prod/{vidply.chunk-Z3YDSA7O.min.js → vidply.chunk-U5IRRYG5.min.js} +1 -1
  73. package/dist/prod/{vidply.chunk-TM6AXJVD.min.js → vidply.chunk-VKHV4QHA.min.js} +2 -2
  74. package/dist/prod/{vidply.chunk-P3PQBY7N.min.js → vidply.chunk-XA4M574A.min.js} +1 -1
  75. package/dist/prod/{vidply.chunk-RI3X7WHK.min.js → vidply.chunk-ZPTV4JEP.min.js} +2 -2
  76. package/dist/prod/{vidply.de-BCHXRQ53.min.js → vidply.de-UZHFCEZU.min.js} +2 -2
  77. package/dist/prod/{vidply.es-PICAKMZD.min.js → vidply.es-BOISUV2S.min.js} +2 -2
  78. package/dist/prod/vidply.esm.min.js +2 -2
  79. package/dist/prod/{vidply.fr-ZILI7CDR.min.js → vidply.fr-ICSMKPFM.min.js} +2 -2
  80. package/dist/prod/{vidply.ja-5WQ4OK4J.min.js → vidply.ja-VQ2HKGAY.min.js} +2 -2
  81. package/dist/vidply.css +5852 -5796
  82. package/dist/vidply.esm.min.meta.json +204 -149
  83. package/dist/vidply.min.css +1 -1
  84. package/package.json +11 -11
  85. package/src/controls/CaptionManager.ts +598 -598
  86. package/src/controls/ControlBar.ts +4191 -4159
  87. package/src/controls/KeyboardHelp.ts +326 -0
  88. package/src/controls/KeyboardManager.ts +387 -383
  89. package/src/controls/SettingsDialog.ts +488 -488
  90. package/src/controls/TranscriptManager.ts +2426 -2426
  91. package/src/core/AudioDescriptionManager.ts +863 -774
  92. package/src/core/DescriptionSpeechManager.ts +232 -0
  93. package/src/core/FloatingPlayerManager.ts +674 -674
  94. package/src/core/LazyInit.ts +80 -80
  95. package/src/core/MediaSessionManager.ts +341 -0
  96. package/src/core/MetadataAlertsManager.ts +514 -514
  97. package/src/core/Player.ts +2973 -2872
  98. package/src/core/PosterManager.ts +169 -169
  99. package/src/core/PseudoFullscreen.ts +179 -179
  100. package/src/core/ResponsiveManager.ts +231 -231
  101. package/src/core/ResumeManager.ts +236 -236
  102. package/src/core/SignLanguageManager.ts +1308 -1308
  103. package/src/core/ThemeManager.ts +157 -157
  104. package/src/features/PlaylistManager.ts +1838 -1827
  105. package/src/i18n/i18n.ts +223 -223
  106. package/src/i18n/languages/de.ts +244 -217
  107. package/src/i18n/languages/en.ts +245 -218
  108. package/src/i18n/languages/es.ts +241 -214
  109. package/src/i18n/languages/fr.ts +241 -214
  110. package/src/i18n/languages/ja.ts +241 -214
  111. package/src/icons/Icons.ts +168 -167
  112. package/src/index.ts +196 -193
  113. package/src/renderers/DASHRenderer.ts +1077 -1077
  114. package/src/renderers/HLSRenderer.ts +944 -944
  115. package/src/renderers/HTML5Renderer.ts +389 -389
  116. package/src/renderers/SoundCloudRenderer.ts +437 -437
  117. package/src/renderers/VimeoRenderer.ts +298 -298
  118. package/src/renderers/YouTubeRenderer.ts +290 -290
  119. package/src/styles/vidply.css +5852 -5796
  120. package/src/types/events.ts +128 -122
  121. package/src/types/globals.d.ts +313 -313
  122. package/src/types/options.ts +199 -182
  123. package/src/types/renderer.ts +58 -58
  124. package/src/utils/DOMUtils.ts +281 -281
  125. package/src/utils/DownloadInfo.ts +171 -171
  126. package/src/utils/DraggablePanel.ts +539 -539
  127. package/src/utils/DraggablePanelMenu.ts +195 -195
  128. package/src/utils/DraggableResizable.ts +997 -997
  129. package/src/utils/EventEmitter.ts +59 -59
  130. package/src/utils/FormUtils.ts +95 -95
  131. package/src/utils/MenuFactory.ts +483 -483
  132. package/src/utils/Sanitize.ts +70 -70
  133. package/src/utils/SettingsMenuFactory.ts +289 -289
  134. package/src/utils/StorageManager.ts +287 -287
  135. package/src/utils/TrackLabelUtils.ts +52 -52
  136. package/src/utils/UrlSafe.ts +69 -69
  137. package/src/utils/VideoFrameCapture.ts +114 -114
  138. package/src/utils/WindowComponents.ts +250 -250
  139. package/dist/dev/vidply.AudioDescriptionManager-R47Q4YR4.js.map +0 -7
  140. package/dist/dev/vidply.DASHRenderer-PWEMC3BT.js.map +0 -7
  141. package/dist/dev/vidply.FloatingPlayerManager-PKNNNAS2.js.map +0 -7
  142. package/dist/dev/vidply.HLSRenderer-E7RQ74HI.js.map +0 -7
  143. package/dist/dev/vidply.HTML5Renderer-BU4QJJJY.js +0 -12
  144. package/dist/dev/vidply.SignLanguageManager-FET5FQZ7.js.map +0 -7
  145. package/dist/dev/vidply.SoundCloudRenderer-RVSJZODO.js.map +0 -7
  146. package/dist/dev/vidply.TranscriptManager-5GXOQCLX.js.map +0 -7
  147. package/dist/dev/vidply.VimeoRenderer-ER2LSBBW.js.map +0 -7
  148. package/dist/dev/vidply.YouTubeRenderer-NFTOWJAB.js.map +0 -7
  149. package/dist/dev/vidply.chunk-64WXMTGL.js.map +0 -7
  150. package/dist/dev/vidply.chunk-BNFIUL2Z.js.map +0 -7
  151. package/dist/dev/vidply.chunk-JJ47NO24.js.map +0 -7
  152. package/dist/dev/vidply.chunk-KI7C3QVL.js.map +0 -7
  153. package/dist/dev/vidply.chunk-NQPURMQY.js.map +0 -7
  154. package/dist/dev/vidply.chunk-RZWZGDV7.js.map +0 -7
  155. package/dist/dev/vidply.chunk-W25HUCHS.js.map +0 -7
  156. package/dist/dev/vidply.de-4YAT7FFM.js.map +0 -7
  157. package/dist/dev/vidply.es-LEWIRUBH.js.map +0 -7
  158. package/dist/dev/vidply.fr-Q4GV2NOE.js.map +0 -7
  159. package/dist/dev/vidply.ja-S6ITA6WU.js.map +0 -7
  160. package/dist/prod/vidply.AudioDescriptionManager-RHLIJW5C.min.js +0 -6
  161. package/dist/prod/vidply.HTML5Renderer-IU5T3WEW.min.js +0 -6
  162. /package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js.map → vidply.CaptionStyleMenu-47RCKQF5.js.map} +0 -0
  163. /package/dist/dev/{vidply.HTML5Renderer-BU4QJJJY.js.map → vidply.HTML5Renderer-HLRQUOCU.js.map} +0 -0
  164. /package/dist/dev/{vidply.chunk-SK4V2DAU.js.map → vidply.chunk-HZ7UOKOU.js.map} +0 -0
  165. /package/dist/dev/{vidply.chunk-DZSDO3B7.js.map → vidply.chunk-JZNZX32Z.js.map} +0 -0
  166. /package/dist/dev/{vidply.chunk-VMSQAZM7.js.map → vidply.chunk-P25MRJ6O.js.map} +0 -0
  167. /package/dist/dev/{vidply.chunk-6RQA4ROD.js.map → vidply.chunk-PRFNESYA.js.map} +0 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/FormUtils.ts", "../../src/utils/MenuUtils.ts", "../../src/utils/DraggablePanelMenu.ts", "../../src/utils/DraggablePanel.ts"],
4
+ "sourcesContent": ["import { DOMUtils } from './DOMUtils.js';\nimport { i18n } from '../i18n/i18n.js';\n\nexport interface SelectOption {\n value: string;\n text: string;\n selected?: boolean;\n}\n\nexport interface LabeledSelectOptions {\n classPrefix: string;\n labelClass: string;\n selectClass: string;\n labelText: string;\n selectId: string;\n hidden?: boolean;\n onChange?: (e: Event) => void;\n options?: SelectOption[];\n}\n\nexport function createLabeledSelect({\n classPrefix: _classPrefix,\n labelClass,\n selectClass,\n labelText,\n selectId,\n hidden = false,\n onChange = undefined,\n options = []\n}: LabeledSelectOptions): { label: HTMLLabelElement; select: HTMLSelectElement } {\n const isI18nKey =\n typeof labelText === 'string' &&\n (labelText.startsWith('transcript.') ||\n labelText.startsWith('player.') ||\n labelText.startsWith('settings.') ||\n labelText.startsWith('captions.'));\n const labelTextContent = isI18nKey ? i18n.t(labelText) || labelText : labelText;\n\n const label = DOMUtils.createElement('label', {\n className: labelClass,\n textContent: labelTextContent,\n attributes: {\n for: selectId,\n style: hidden ? 'display: none;' : undefined\n }\n });\n\n const select = DOMUtils.createElement('select', {\n className: selectClass,\n attributes: {\n id: selectId,\n style: hidden ? 'display: none;' : undefined\n }\n });\n\n options.forEach(opt => {\n const option = DOMUtils.createElement('option', {\n textContent: opt.text,\n attributes: {\n value: opt.value,\n selected: opt.selected ? 'selected' : undefined\n }\n });\n select.appendChild(option);\n });\n\n if (onChange) {\n select.addEventListener('change', onChange);\n }\n\n return { label, select };\n}\n\nexport function toggleLabeledSelect(\n label: HTMLElement | null,\n select: HTMLElement | null,\n show: boolean\n): void {\n if (label) {\n label.style.display = show ? 'block' : 'none';\n }\n if (select) {\n select.style.display = show ? 'block' : 'none';\n }\n}\n\nexport function preventDragOnElement(element: HTMLElement | null): void {\n if (!element) return;\n\n (['pointerdown', 'mousedown', 'click'] as const).forEach(eventType => {\n element.addEventListener(eventType, (e: Event) => {\n e.stopPropagation();\n });\n });\n}\n", "import { DOMUtils } from './DOMUtils.js';\nimport { createIconElement } from '../icons/Icons.js';\nimport { i18n } from '../i18n/i18n.js';\nimport { focusElement } from './FocusUtils.js';\nimport { reducedMotionScrollOptions } from './PerformanceUtils.js';\n\nexport interface MenuItemOptions {\n classPrefix: string;\n itemClass: string;\n icon?: string;\n label: string;\n ariaLabel?: string;\n onClick?: (e: Event) => void;\n hasTextClass?: boolean;\n}\n\nexport function createMenuItem({\n classPrefix,\n itemClass,\n icon,\n label,\n ariaLabel,\n onClick,\n hasTextClass = false\n}: MenuItemOptions): HTMLElement {\n const isI18nKeyForAria =\n typeof label === 'string' &&\n (label.startsWith('transcript.') || label.startsWith('player.') || label.startsWith('settings.'));\n const ariaLabelText = ariaLabel || (isI18nKeyForAria ? i18n.t(label) || label : label);\n\n const button = DOMUtils.createElement('button', {\n className: itemClass,\n attributes: {\n type: 'button',\n 'aria-label': ariaLabelText,\n tabindex: '-1'\n }\n });\n\n if (icon) {\n button.appendChild(createIconElement(icon));\n }\n\n const isI18nKey =\n typeof label === 'string' &&\n (label.startsWith('transcript.') || label.startsWith('player.') || label.startsWith('settings.'));\n const textContent = isI18nKey ? i18n.t(label) || label : label;\n\n const text = DOMUtils.createElement('span', {\n textContent,\n className: hasTextClass ? `${classPrefix}-settings-text` : undefined,\n attributes: { 'aria-hidden': 'true' }\n });\n button.appendChild(text);\n\n if (onClick) {\n button.addEventListener('click', onClick);\n }\n\n return button;\n}\n\nexport function attachMenuKeyboardNavigation(\n menu: HTMLElement | null,\n button: HTMLElement | null,\n itemSelector: string,\n onClose?: () => void\n): ((e: KeyboardEvent) => void) | undefined {\n if (!menu) return undefined;\n\n // Only enabled items participate in roving navigation. aria-disabled\n // empty-state entries must never receive focus (WCAG 2.1.1, 4.1.2).\n const menuItems = Array.from(menu.querySelectorAll<HTMLElement>(itemSelector))\n .filter((item) => item.getAttribute('aria-disabled') !== 'true');\n if (menuItems.length === 0) return undefined;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n const currentIndex = menuItems.indexOf(document.activeElement as HTMLElement);\n\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n e.stopPropagation();\n const nextIndex = (currentIndex + 1) % menuItems.length;\n menuItems.forEach((item, idx) => {\n item.setAttribute('tabindex', idx === nextIndex ? '0' : '-1');\n });\n const next = menuItems[nextIndex];\n if (next) {\n next.focus({ preventScroll: false });\n next.scrollIntoView(reducedMotionScrollOptions('nearest'));\n }\n break;\n }\n\n case 'ArrowUp': {\n e.preventDefault();\n e.stopPropagation();\n const prevIndex = (currentIndex - 1 + menuItems.length) % menuItems.length;\n menuItems.forEach((item, idx) => {\n item.setAttribute('tabindex', idx === prevIndex ? '0' : '-1');\n });\n const prev = menuItems[prevIndex];\n if (prev) {\n prev.focus({ preventScroll: false });\n prev.scrollIntoView(reducedMotionScrollOptions('nearest'));\n }\n break;\n }\n\n case 'Home': {\n e.preventDefault();\n e.stopPropagation();\n menuItems.forEach((item, idx) => {\n item.setAttribute('tabindex', idx === 0 ? '0' : '-1');\n });\n const firstItem = menuItems[0];\n if (firstItem) {\n firstItem.focus({ preventScroll: false });\n firstItem.scrollIntoView(reducedMotionScrollOptions('nearest'));\n }\n break;\n }\n\n case 'End': {\n e.preventDefault();\n e.stopPropagation();\n const lastIndex = menuItems.length - 1;\n menuItems.forEach((item, idx) => {\n item.setAttribute('tabindex', idx === lastIndex ? '0' : '-1');\n });\n const lastItem = menuItems[lastIndex];\n if (lastItem) {\n lastItem.focus({ preventScroll: false });\n lastItem.scrollIntoView(reducedMotionScrollOptions('nearest'));\n }\n break;\n }\n\n case 'Enter':\n case ' ': {\n e.preventDefault();\n e.stopPropagation();\n if (document.activeElement && menuItems.includes(document.activeElement as HTMLElement)) {\n (document.activeElement as HTMLElement).click();\n if (onClose) {\n setTimeout(() => {\n if (button && document.contains(button)) {\n button.focus();\n }\n }, 0);\n }\n }\n break;\n }\n\n case 'Escape':\n e.preventDefault();\n e.stopPropagation();\n if (onClose) {\n onClose();\n }\n break;\n }\n };\n\n menu.addEventListener('keydown', handleKeyDown as EventListener, true);\n return handleKeyDown;\n}\n\nexport function focusFirstMenuItem(\n menu: HTMLElement | null,\n itemSelector: string,\n delay = 0\n): void {\n if (!menu) return;\n\n setTimeout(() => {\n const menuItems = Array.from(menu.querySelectorAll<HTMLElement>(itemSelector))\n .filter((item) => item.getAttribute('aria-disabled') !== 'true');\n const firstItem = menuItems[0];\n if (firstItem) {\n menuItems.forEach((item, index) => {\n item.setAttribute('tabindex', index === 0 ? '0' : '-1');\n });\n focusElement(firstItem, { delay: 0 });\n firstItem.scrollIntoView(reducedMotionScrollOptions('nearest'));\n }\n }, delay);\n}\n", "/**\n * Shared helpers for the settings-menu items that toggle drag / resize\n * mode on a `DraggableResizable`-powered panel.\n *\n * Previously `SignLanguageManager` and `TranscriptManager` both carried\n * near-identical copies of:\n * - `updateDragOptionState` / `updateResizeOptionState`\n * (button `aria-checked` + label + i18n-driven text sync)\n * - `positionSettingsMenuImmediate`\n * (place the menu relative to the settings button, with above/below\n * flipping and viewport-edge clamping)\n *\n * This module owns those pieces so a future manager (or a refactor of\n * an existing one) doesn't have to re-derive the rules from scratch.\n *\n * NOTE: the full \"DraggablePanel\" abstraction that also owns the menu\n * *contents* (language selector, style dialog, pinned state, etc.) is\n * deliberately NOT attempted here — the per-manager specifics are\n * significant and an attempt to unify them would churn security-\n * sensitive code without meaningfully reducing LOC.\n */\n\n/** Visual state of a two-state (\"currently enabled / currently disabled\")\n * toggle menu item. */\nexport interface ToggleMenuItemState {\n /** Whether the underlying mode is currently active. */\n enabled: boolean;\n /** Label shown when the mode is active (i.e. the label invites the\n * user to disable it). */\n enabledText: string;\n /** Label shown when the mode is inactive. */\n disabledText: string;\n /** `aria-label` for screen readers when the mode is active. */\n enabledAria: string;\n /** `aria-label` for screen readers when the mode is inactive. */\n disabledAria: string;\n}\n\n/**\n * Sync a settings-menu toggle button's `aria-checked`, `aria-label`,\n * and visible text node with the current mode state. Both properties\n * are updated unconditionally so the element stays in sync even if a\n * previous render left them stale.\n *\n * The caller owns the DOM nodes; this helper only mutates known-safe\n * properties (no innerHTML), so a translated label with angle brackets\n * or ampersands is stored as text, not parsed as markup.\n */\nexport function updateToggleMenuItem(\n button: HTMLElement | null | undefined,\n textElement: Element | null | undefined,\n state: ToggleMenuItemState\n): void {\n if (!button) return;\n\n button.setAttribute('aria-checked', state.enabled ? 'true' : 'false');\n button.setAttribute('aria-label', state.enabled ? state.enabledAria : state.disabledAria);\n\n if (textElement) {\n // `textContent` exists on `Node`, so we can accept the broader\n // `Element` type here and let callers pass either an `HTMLElement`\n // (TranscriptManager keeps the tighter type for the sake of its\n // `.style` access elsewhere) or a plain `Element` returned by\n // `querySelector`.\n textElement.textContent = state.enabled ? state.enabledText : state.disabledText;\n }\n}\n\nexport interface PositionSettingsMenuOptions {\n /**\n * Horizontal anchoring:\n * - `'left'` : align the menu's left edge with the button's left edge\n * (the TranscriptManager convention — simple panels).\n * - `'center'` : centre the menu horizontally on the button, with\n * viewport-edge clamping (the SignLanguageManager\n * convention — small floating panel that can end up\n * near the viewport edges).\n */\n align?: 'left' | 'center';\n /** Vertical gap (px) between button and menu. */\n gap?: number;\n /** Extra breathing room (px) before we decide to flip above. */\n spaceReserve?: number;\n}\n\n/**\n * Position a settings menu relative to its button, using offsets that\n * are relative to the button's offset parent (so transforms on ancestor\n * elements don't throw the layout off).\n *\n * When there isn't enough room below the button but there is room\n * above, the menu flips upward and picks up a `vidply-menu-above` CSS\n * class so arrows / drop-shadows can be mirrored via stylesheet.\n */\nexport function positionSettingsMenu(\n menu: HTMLElement | null | undefined,\n button: HTMLElement | null | undefined,\n opts: PositionSettingsMenuOptions = {}\n): void {\n if (!menu || !button) return;\n const { align = 'left', gap = 4, spaceReserve = 20 } = opts;\n\n const parentContainer = button.parentElement;\n if (!parentContainer) return;\n\n const buttonRect = button.getBoundingClientRect();\n const parentRect = parentContainer.getBoundingClientRect();\n const menuRect = menu.getBoundingClientRect();\n const viewportHeight = window.innerHeight;\n const viewportWidth = window.innerWidth;\n\n const buttonBottom = buttonRect.bottom - parentRect.top;\n const buttonTop = buttonRect.top - parentRect.top;\n const buttonLeftOffset = buttonRect.left - parentRect.left;\n const buttonCenterX = buttonLeftOffset + buttonRect.width / 2;\n\n const spaceAbove = buttonRect.top;\n const spaceBelow = viewportHeight - buttonRect.bottom;\n\n // Vertical placement: default below, flip above when tight.\n let menuTop: number | null = buttonBottom + gap;\n let menuBottomPx: number | null = null;\n if (spaceBelow < menuRect.height + spaceReserve && spaceAbove > spaceBelow) {\n if (align === 'center') {\n // Measure from the bottom edge of the parent so the menu sits\n // directly above the button without being pushed off-screen.\n const parentHeight = parentRect.bottom - parentRect.top;\n menuBottomPx = parentHeight - buttonTop + gap;\n menuTop = null;\n } else {\n menuTop = buttonTop - menuRect.height - gap;\n }\n menu.classList.add('vidply-menu-above');\n } else {\n menu.classList.remove('vidply-menu-above');\n }\n\n // Horizontal placement: simple left-align vs viewport-clamped centre.\n let leftValue: string;\n let rightValue: string;\n let transform: string;\n\n if (align === 'center') {\n const menuLeftAbsolute = buttonRect.left + buttonRect.width / 2 - menuRect.width / 2;\n if (menuLeftAbsolute < 10) {\n leftValue = '0';\n rightValue = 'auto';\n transform = 'translateX(0)';\n } else if (menuLeftAbsolute + menuRect.width > viewportWidth - 10) {\n leftValue = 'auto';\n rightValue = '0';\n transform = 'translateX(0)';\n } else {\n leftValue = `${buttonCenterX}px`;\n rightValue = 'auto';\n transform = 'translateX(-50%)';\n }\n } else {\n leftValue = `${buttonLeftOffset}px`;\n rightValue = 'auto';\n transform = 'translateX(0)';\n }\n\n if (menuTop !== null) {\n menu.style.top = `${menuTop}px`;\n menu.style.bottom = 'auto';\n } else if (menuBottomPx !== null) {\n menu.style.top = 'auto';\n menu.style.bottom = `${menuBottomPx}px`;\n }\n\n menu.style.left = leftValue;\n menu.style.right = rightValue;\n menu.style.transform = transform;\n}\n\n/**\n * RAF-deferred wrapper around {@link positionSettingsMenu}. Some\n * callers need to re-position after layout has settled (e.g. when the\n * panel size just changed and `getBoundingClientRect` would still\n * return the pre-change measurements on this frame).\n */\nexport function positionSettingsMenuDeferred(\n menu: HTMLElement | null | undefined,\n button: HTMLElement | null | undefined,\n opts: PositionSettingsMenuOptions = {}\n): void {\n requestAnimationFrame(() => {\n // The extra setTimeout is intentional: older WebKit still reports\n // stale geometry during the first RAF callback following a DOM\n // mutation. A 10ms trailing microtask gives layout a chance to\n // settle without being user-perceptible.\n setTimeout(() => positionSettingsMenu(menu, button, opts), 10);\n });\n}\n", "/**\n * Owns the settings-menu lifecycle for a `DraggableResizable`-powered\n * floating panel: create, show, hide, outside-click dismissal, keyboard\n * navigation, viewport-aware positioning, and the drag-mode / resize-\n * mode toggle items.\n *\n * `SignLanguageManager` and `TranscriptManager` both present the same\n * pattern: a settings button that opens a menu containing a \"Keyboard\n * drag mode\" toggle, a \"Resize mode\" toggle, manager-specific extras,\n * and a close item. This class captures that pattern so each manager\n * only has to describe:\n * - how to resolve its `DraggableResizable` (for state readback),\n * - which i18n keys to use on the toggle items,\n * - where to place the menu in its own layout (via `getMenuParent`),\n * - how to toggle each mode (`onDragItemClick` / `onResizeItemClick`\n * run the manager's side effects — announcements, badges, focus),\n * - any extra menu items (`buildExtraItems`, e.g. style dialog,\n * timestamp toggle for TranscriptManager).\n *\n * The manager's own public API (`showSettingsMenu`, `toggleKeyboardDragMode`,\n * `hideSettingsMenu`, ...) is preserved by forwarding to this panel.\n * External code that reads `settingsMenuVisible` or the option-button\n * elements keeps working via getters on the manager that proxy the\n * panel's state.\n *\n * Event listeners attached to `document` are tied to the player's\n * `lifecycleSignal` so they are torn down automatically when the\n * player is destroyed.\n */\n\nimport { DOMUtils } from './DOMUtils.js';\nimport { i18n } from '../i18n/i18n.js';\nimport {\n attachMenuKeyboardNavigation,\n createMenuItem,\n focusFirstMenuItem,\n} from './MenuUtils.js';\nimport {\n positionSettingsMenu,\n positionSettingsMenuDeferred,\n updateToggleMenuItem,\n} from './DraggablePanelMenu.js';\n\nimport type { Player } from '../core/Player.js';\nimport type { DraggableResizable } from './DraggableResizable.js';\n\n/**\n * i18n keys used for the drag-mode and resize-mode toggle items and\n * the \"Close\" item. All values must be translation keys (passed\n * through `i18n.t()` by `createMenuItem`/`updateToggleMenuItem`).\n */\nexport interface DraggablePanelI18nKeys {\n enableDrag: string;\n disableDrag: string;\n enableDragAria: string;\n disableDragAria: string;\n enableResize: string;\n disableResize: string;\n enableResizeAria: string;\n disableResizeAria: string;\n closeMenu: string;\n}\n\nexport interface DraggablePanelExtrasContext {\n menu: HTMLElement;\n itemClass: string;\n classPrefix: string;\n /**\n * Strip the tooltip and duplicate `button-text` nodes from a menu\n * item. `createMenuItem` is shared with toolbar buttons that want a\n * hover tooltip; settings-menu rows already show the same text\n * inline, so the tooltip would cause screen readers to read the\n * label twice. Exposed here so manager-provided extras can reuse\n * the same stripping logic the panel applies to its own items.\n */\n stripInlineTooltip: (item: HTMLElement) => void;\n}\n\nexport interface DraggablePanelOptions {\n player: Player;\n /**\n * Namespace for CSS classes, e.g. `'sign-language'` or `'transcript'`.\n * The final menu class is `{classPrefix}-{namespace}-settings-menu`\n * and item class is `{classPrefix}-{namespace}-settings-item`.\n */\n namespace: string;\n /** The button that opens the menu. */\n settingsButton: HTMLElement;\n /** Lazy draggable lookup (it may be re-created by the manager). */\n getDraggable: () => DraggableResizable | null;\n i18nKeys: DraggablePanelI18nKeys;\n /**\n * Horizontal anchoring of the menu relative to the settings button.\n * - `'left'` : align left edge (Transcript — header-row button).\n * - `'center'` : centre horizontally with viewport clamping (Sign\n * language — floating near viewport edges).\n */\n menuAlign: 'left' | 'center';\n /** Where to attach the menu node when first created. */\n getMenuParent: () => HTMLElement | null;\n /**\n * Called when the drag-mode item is clicked. The manager is\n * expected to toggle drag mode and run any side effects\n * (announcements, badges, focus). After this runs the panel will\n * refresh menu item state.\n */\n onDragItemClick: (panel: DraggablePanel) => void;\n /** Same for the resize-mode item. */\n onResizeItemClick: (panel: DraggablePanel) => void;\n /**\n * Insert manager-specific items (style dialog, timestamps, ...)\n * between the resize item and the close item.\n */\n buildExtraItems?: (ctx: DraggablePanelExtrasContext) => void;\n /** Optional: vertical gap (px) between button and menu. */\n menuGap?: number;\n /** Optional: breathing room (px) before the menu flips above. */\n menuSpaceReserve?: number;\n /**\n * Where to attach the mode-feedback badge while keyboard drag /\n * pointer resize mode is active. If omitted, {@link DraggablePanel.showBadge}\n * is a no-op (the manager effectively opts out of the badge).\n */\n getBadgeHost?: () => HTMLElement | null;\n /**\n * CSS class for the badge element. Defaults to\n * `{classPrefix}-{namespace}-mode-badge`. Sign-language passes a\n * shorter class (`vidply-sign-mode-badge`) to preserve existing\n * CSS. Transcript uses the default.\n */\n badgeClass?: string;\n}\n\nexport class DraggablePanel {\n private readonly opts: DraggablePanelOptions;\n\n /** Populated lazily on first `show()`. */\n settingsMenu: HTMLElement | null = null;\n settingsMenuVisible = false;\n dragOptionButton: HTMLElement | null = null;\n dragOptionText: Element | null = null;\n resizeOptionButton: HTMLElement | null = null;\n resizeOptionText: Element | null = null;\n\n private _justOpened = false;\n private _justOpenedTimer: ReturnType<typeof setTimeout> | null = null;\n private _keyHandler: ((e: KeyboardEvent) => void) | null = null;\n private _documentClick: ((e: MouseEvent) => void) | null = null;\n private _documentClickAdded = false;\n private _modeBadge: HTMLElement | null = null;\n\n constructor(opts: DraggablePanelOptions) {\n this.opts = opts;\n }\n\n /** True while the just-opened debounce window (prevents the same\n * click that opened the menu from also closing it via document\n * `mousedown` / `click`). */\n get justOpened(): boolean {\n return this._justOpened;\n }\n\n get classPrefix(): string {\n return this.opts.player.options.classPrefix;\n }\n\n get menuClass(): string {\n return `${this.classPrefix}-${this.opts.namespace}-settings-menu`;\n }\n\n get itemClass(): string {\n return `${this.classPrefix}-${this.opts.namespace}-settings-item`;\n }\n\n /**\n * Show the menu. First call creates the DOM; subsequent calls reuse\n * it. Refreshes menu item state from the current draggable.\n */\n show(): void {\n this._markJustOpened(350);\n this._ensureDocumentClickHandler();\n\n if (this.settingsMenu) {\n this.settingsMenu.style.display = 'block';\n this.settingsMenuVisible = true;\n this.opts.settingsButton.setAttribute('aria-expanded', 'true');\n this._attachKeyboardNavigation();\n this._positionImmediate();\n this.refreshState();\n focusFirstMenuItem(this.settingsMenu, `.${this.itemClass}`);\n return;\n }\n\n this._createMenu();\n }\n\n /**\n * Hide the menu. By default returns focus to the settings button;\n * callers can opt out when the next interaction should land\n * elsewhere (e.g. on the wrapper after enabling drag mode).\n */\n hide({ focusButton = true }: { focusButton?: boolean } = {}): void {\n if (!this.settingsMenu) return;\n\n this.settingsMenu.style.display = 'none';\n this.settingsMenuVisible = false;\n this._clearJustOpened();\n\n if (this._keyHandler) {\n this.settingsMenu.removeEventListener('keydown', this._keyHandler, true);\n this._keyHandler = null;\n }\n\n const items = this.settingsMenu.querySelectorAll<HTMLElement>(`.${this.itemClass}`);\n items.forEach((item) => item.setAttribute('tabindex', '-1'));\n\n const { settingsButton } = this.opts;\n settingsButton.setAttribute('aria-expanded', 'false');\n if (focusButton) {\n settingsButton.focus({ preventScroll: true });\n }\n }\n\n toggle(): void {\n if (this.settingsMenuVisible) {\n this.hide();\n } else {\n this.show();\n }\n }\n\n /** Set a short \"just opened\" guard so the document-click handler\n * attached for outside-dismissal ignores the originating click. */\n markJustOpenedForClick(): void {\n if (this._documentClick) {\n this._markJustOpened(100);\n }\n }\n\n /** Refresh the drag and resize toggle item state from the draggable. */\n refreshState(): void {\n this.refreshDragState();\n this.refreshResizeState();\n }\n\n refreshDragState(): void {\n const draggable = this.opts.getDraggable();\n updateToggleMenuItem(this.dragOptionButton, this.dragOptionText, {\n enabled: Boolean(draggable?.keyboardDragMode),\n enabledText: i18n.t(this.opts.i18nKeys.disableDrag),\n disabledText: i18n.t(this.opts.i18nKeys.enableDrag),\n enabledAria: i18n.t(this.opts.i18nKeys.disableDragAria),\n disabledAria: i18n.t(this.opts.i18nKeys.enableDragAria),\n });\n }\n\n refreshResizeState(): void {\n const draggable = this.opts.getDraggable();\n updateToggleMenuItem(this.resizeOptionButton, this.resizeOptionText, {\n enabled: Boolean(draggable?.pointerResizeMode),\n enabledText: i18n.t(this.opts.i18nKeys.disableResize),\n disabledText: i18n.t(this.opts.i18nKeys.enableResize),\n enabledAria: i18n.t(this.opts.i18nKeys.disableResizeAria),\n disabledAria: i18n.t(this.opts.i18nKeys.enableResizeAria),\n });\n }\n\n /**\n * Show a persistent mode-feedback badge (e.g. \"Drag mode: arrow\n * keys to move, Esc to exit\") anchored to the host element\n * returned by `getBadgeHost`. Replaces any previous badge. The\n * badge is a real DOM element (not a CSS pseudo-element) so its\n * text is translatable, selectable, visible under high-contrast\n * themes, and reflected in browser translation overlays.\n *\n * Marked `aria-hidden` because the accompanying live-region\n * announcement (the manager's responsibility) already conveys the\n * state change to assistive tech.\n */\n showBadge(text: string): void {\n const host = this.opts.getBadgeHost?.();\n if (!host) return;\n this.hideBadge();\n\n const className =\n this.opts.badgeClass ??\n `${this.classPrefix}-${this.opts.namespace}-mode-badge`;\n const badge = DOMUtils.createElement('span', {\n className,\n textContent: text,\n attributes: { 'aria-hidden': 'true' },\n });\n host.appendChild(badge);\n this._modeBadge = badge;\n }\n\n /** Remove the mode-feedback badge if one is showing. */\n hideBadge(): void {\n if (this._modeBadge && this._modeBadge.parentNode) {\n this._modeBadge.remove();\n }\n this._modeBadge = null;\n }\n\n /** RAF-deferred reposition (e.g. after a panel resize). */\n reposition(): void {\n positionSettingsMenuDeferred(this.settingsMenu, this.opts.settingsButton, {\n align: this.opts.menuAlign,\n gap: this.opts.menuGap ?? 4,\n spaceReserve: this.opts.menuSpaceReserve ?? 20,\n });\n }\n\n /**\n * Tear down any DOM/listeners owned by this panel. Safe to call\n * multiple times. Callers must still drop their own references.\n */\n destroy(): void {\n if (this._justOpenedTimer) {\n clearTimeout(this._justOpenedTimer);\n this._justOpenedTimer = null;\n }\n this._justOpened = false;\n\n this.hideBadge();\n\n if (this.settingsMenu) {\n if (this._keyHandler) {\n this.settingsMenu.removeEventListener('keydown', this._keyHandler, true);\n }\n this.settingsMenu.remove();\n this.settingsMenu = null;\n }\n\n this._keyHandler = null;\n this.settingsMenuVisible = false;\n this.dragOptionButton = null;\n this.dragOptionText = null;\n this.resizeOptionButton = null;\n this.resizeOptionText = null;\n\n // The document-click listener was attached with the player's\n // lifecycleSignal, so it is cleaned up automatically. We clear\n // the local reference so a later show() re-installs.\n this._documentClick = null;\n this._documentClickAdded = false;\n }\n\n private _createMenu(): void {\n const { player, settingsButton, i18nKeys } = this.opts;\n\n const menu = DOMUtils.createElement('div', {\n className: this.menuClass,\n attributes: { role: 'menu' },\n });\n this.settingsMenu = menu;\n\n const dragOption = createMenuItem({\n classPrefix: this.classPrefix,\n itemClass: this.itemClass,\n icon: 'move',\n label: i18nKeys.enableDrag,\n hasTextClass: true,\n onClick: (event) => {\n event.preventDefault();\n event.stopPropagation();\n this.opts.onDragItemClick(this);\n this.refreshState();\n },\n });\n dragOption.setAttribute('role', 'switch');\n dragOption.setAttribute('aria-checked', 'false');\n dragOption.setAttribute('data-setting', 'keyboard-drag');\n this._stripInlineTooltip(dragOption);\n this.dragOptionButton = dragOption;\n this.dragOptionText = dragOption.querySelector(`.${this.classPrefix}-settings-text`);\n\n const resizeOption = createMenuItem({\n classPrefix: this.classPrefix,\n itemClass: this.itemClass,\n icon: 'resize',\n label: i18nKeys.enableResize,\n hasTextClass: true,\n onClick: (event) => {\n event.preventDefault();\n event.stopPropagation();\n this.opts.onResizeItemClick(this);\n this.refreshState();\n },\n });\n resizeOption.setAttribute('role', 'switch');\n resizeOption.setAttribute('aria-checked', 'false');\n this._stripInlineTooltip(resizeOption);\n this.resizeOptionButton = resizeOption;\n this.resizeOptionText = resizeOption.querySelector(`.${this.classPrefix}-settings-text`);\n\n menu.appendChild(dragOption);\n menu.appendChild(resizeOption);\n\n if (this.opts.buildExtraItems) {\n this.opts.buildExtraItems({\n menu,\n itemClass: this.itemClass,\n classPrefix: this.classPrefix,\n stripInlineTooltip: (item) => this._stripInlineTooltip(item),\n });\n }\n\n const closeOption = createMenuItem({\n classPrefix: this.classPrefix,\n itemClass: this.itemClass,\n icon: 'close',\n label: i18nKeys.closeMenu,\n onClick: () => this.hide(),\n });\n this._stripInlineTooltip(closeOption);\n menu.appendChild(closeOption);\n\n menu.style.visibility = 'hidden';\n menu.style.display = 'block';\n\n const parent = this.opts.getMenuParent();\n if (settingsButton.parentNode) {\n settingsButton.insertAdjacentElement('afterend', menu);\n } else if (parent) {\n parent.appendChild(menu);\n }\n\n this._positionImmediate();\n\n requestAnimationFrame(() => {\n if (this.settingsMenu) {\n this.settingsMenu.style.visibility = 'visible';\n }\n });\n\n this._attachKeyboardNavigation();\n\n this.settingsMenuVisible = true;\n settingsButton.setAttribute('aria-expanded', 'true');\n this.refreshState();\n\n focusFirstMenuItem(menu, `.${this.itemClass}`);\n\n // Don't reveal the fact that we created the menu to callers that\n // didn't trigger show() explicitly — but this is always driven\n // by a show() so the `player` reference is just for\n // documentation clarity here.\n void player;\n }\n\n private _attachKeyboardNavigation(): void {\n const menu = this.settingsMenu;\n if (!menu) return;\n if (this._keyHandler) {\n menu.removeEventListener('keydown', this._keyHandler, true);\n }\n const handler = attachMenuKeyboardNavigation(\n menu,\n this.opts.settingsButton,\n `.${this.itemClass}`,\n () => this.hide({ focusButton: true })\n );\n this._keyHandler = handler ?? null;\n }\n\n private _positionImmediate(): void {\n positionSettingsMenu(this.settingsMenu, this.opts.settingsButton, {\n align: this.opts.menuAlign,\n gap: this.opts.menuGap ?? 4,\n spaceReserve: this.opts.menuSpaceReserve ?? 20,\n });\n }\n\n /**\n * Remove tooltip and duplicate button-text nodes from a menu item.\n * `createMenuItem` is used both for toolbar buttons (which want a\n * tooltip) and for settings-menu rows (which show the same text\n * inline). This strips the duplicated pieces so screen readers\n * don't read the label twice.\n */\n private _stripInlineTooltip(item: HTMLElement): void {\n const tooltip = item.querySelector(`.${this.classPrefix}-tooltip`);\n if (tooltip) tooltip.remove();\n const buttonText = item.querySelector(`.${this.classPrefix}-button-text`);\n if (buttonText) buttonText.remove();\n }\n\n private _markJustOpened(durationMs: number): void {\n this._justOpened = true;\n if (this._justOpenedTimer) {\n clearTimeout(this._justOpenedTimer);\n }\n this._justOpenedTimer = setTimeout(() => {\n this._justOpened = false;\n this._justOpenedTimer = null;\n }, durationMs);\n }\n\n private _clearJustOpened(): void {\n this._justOpened = false;\n if (this._justOpenedTimer) {\n clearTimeout(this._justOpenedTimer);\n this._justOpenedTimer = null;\n }\n }\n\n private _ensureDocumentClickHandler(): void {\n if (this._documentClickAdded) return;\n\n this._documentClick = (event: MouseEvent) => {\n if (this._justOpened) return;\n const target = event.target as Node | null;\n const { settingsButton } = this.opts;\n\n if (settingsButton === target || (target && settingsButton.contains(target))) {\n return;\n }\n if (this.settingsMenu && target && this.settingsMenu.contains(target)) {\n return;\n }\n if (this.settingsMenuVisible) {\n this.hide();\n }\n };\n\n // Delay attach so the click that opened the menu doesn't instantly\n // close it even if the justOpened flag somehow races.\n setTimeout(() => {\n const handler = this._documentClick;\n if (!handler) return;\n document.addEventListener('mousedown', handler, {\n capture: true,\n signal: this.opts.player.lifecycleSignal,\n });\n this._documentClickAdded = true;\n }, 300);\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * VidPly v1.2.0 - Universal, Accessible Video Player
2
+ * VidPly v1.2.3 - Universal, Accessible Video Player
3
3
  * (c) 2026 Matthias Peltzer
4
4
  * Released under GPL-2.0-or-later License
5
5
  */
@@ -297,4 +297,4 @@ var HTML5Renderer = class {
297
297
  export {
298
298
  HTML5Renderer
299
299
  };
300
- //# sourceMappingURL=vidply.chunk-W25HUCHS.js.map
300
+ //# sourceMappingURL=vidply.chunk-CGYIED2I.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/renderers/HTML5Renderer.ts"],
4
+ "sourcesContent": ["import type { Renderer } from '../types/renderer.js';\nimport type { Player } from '../core/Player.js';\n\nexport class HTML5Renderer implements Renderer {\n player: Player;\n media: HTMLMediaElement;\n _didDeferredLoad: boolean;\n // All media listeners are registered with this controller's signal so a\n // single abort() in destroy() detaches every one of them. Avoids the leak\n // of removeEventListener being called with fresh, non-matching callbacks.\n private _listenerController: AbortController;\n\n constructor(player: Player) {\n this.player = player;\n this.media = player.element;\n this._didDeferredLoad = false;\n this._listenerController = new AbortController();\n }\n\n async init() {\n // Hide native controls\n this.media.controls = false;\n this.media.removeAttribute('controls');\n \n this.attachEvents();\n \n // Set preload + optionally defer network loading until user play\n if (this.player.options.deferLoad) {\n // Allow metadata preload while still avoiding full media download.\n // Note: browsers may still fetch metadata automatically when preload=\"metadata\".\n this.media.preload = this.player.options.preload || 'none';\n \n // Firefox requires an explicit load() call to fetch metadata even with preload=\"metadata\".\n // Only call load() if preload is set to \"metadata\" to ensure duration is available.\n if (this.player.options.preload === 'metadata') {\n this.media.load();\n }\n } else {\n this.media.preload = this.player.options.preload;\n // Load media (eager)\n this.media.load();\n }\n \n // Show VidPly controls (remove external controls class if present)\n if (this.player.container) {\n this.player.container.classList.remove('vidply-external-controls');\n }\n }\n\n attachEvents() {\n const { signal } = this._listenerController;\n\n // Playback events\n this.media.addEventListener('loadedmetadata', () => {\n this.player.state.duration = this.media.duration;\n this.player.emit('loadedmetadata');\n \n // Auto-generate poster if none exists (for HTML5 video only)\n if (this.media.tagName === 'VIDEO') {\n this.player.autoGeneratePoster().catch((error: unknown) => {\n this.player.log('Failed to auto-generate poster:', error, 'warn');\n });\n }\n }, { signal });\n\n this.media.addEventListener('durationchange', () => {\n const duration = this.media.duration;\n if (duration && isFinite(duration) && duration > 0) {\n this.player.state.duration = duration;\n this.player.emit('durationchange', duration);\n }\n }, { signal });\n\n this.media.addEventListener('play', () => {\n this.player.state.playing = true;\n this.player.state.paused = false;\n this.player.state.ended = false;\n this.player.emit('play');\n \n if (this.player.options.onPlay) {\n this.player.options.onPlay.call(this.player);\n }\n \n // Pause other players if enabled\n if (this.player.options.pauseOthersOnPlay) {\n this.pauseOtherPlayers();\n }\n }, { signal });\n\n this.media.addEventListener('pause', () => {\n this.player.state.playing = false;\n this.player.state.paused = true;\n this.player.emit('pause');\n \n if (this.player.options.onPause) {\n this.player.options.onPause.call(this.player);\n }\n }, { signal });\n\n this.media.addEventListener('ended', () => {\n this.player.state.playing = false;\n this.player.state.paused = true;\n this.player.state.ended = true;\n this.player.emit('ended');\n \n if (this.player.options.onEnded) {\n this.player.options.onEnded.call(this.player);\n }\n \n // Handle loop\n if (this.player.options.loop) {\n this.player.seek(0);\n this.player.play();\n }\n }, { signal });\n\n this.media.addEventListener('timeupdate', () => {\n this.player.state.currentTime = this.media.currentTime;\n this.player.emit('timeupdate', this.media.currentTime);\n \n if (this.player.options.onTimeUpdate) {\n this.player.options.onTimeUpdate.call(this.player, this.media.currentTime);\n }\n }, { signal });\n\n this.media.addEventListener('volumechange', () => {\n this.player.state.volume = this.media.volume;\n this.player.state.muted = this.media.muted;\n this.player.emit('volumechange', this.media.volume);\n \n if (this.player.options.onVolumeChange) {\n this.player.options.onVolumeChange.call(this.player, this.media.volume);\n }\n }, { signal });\n\n this.media.addEventListener('seeking', () => {\n this.player.state.seeking = true;\n this.player.emit('seeking');\n }, { signal });\n\n this.media.addEventListener('seeked', () => {\n this.player.state.seeking = false;\n this.player.emit('seeked');\n }, { signal });\n\n this.media.addEventListener('waiting', () => {\n this.player.state.buffering = true;\n this.player.emit('waiting');\n }, { signal });\n\n this.media.addEventListener('canplay', () => {\n this.player.state.buffering = false;\n this.player.emit('canplay');\n }, { signal });\n\n this.media.addEventListener('progress', () => {\n if (this.media.buffered.length > 0) {\n const buffered = this.media.buffered.end(this.media.buffered.length - 1);\n this.player.emit('progress', buffered);\n }\n }, { signal });\n\n this.media.addEventListener('error', (_e: Event) => {\n this.player.handleError(this.media.error);\n }, { signal });\n\n this.media.addEventListener('ratechange', () => {\n this.player.state.playbackSpeed = this.media.playbackRate;\n this.player.emit('ratechange', this.media.playbackRate);\n }, { signal });\n }\n\n pauseOtherPlayers() {\n // Pause other VidPly instances\n const allPlayers = document.querySelectorAll('.vidply-player');\n allPlayers.forEach(playerEl => {\n if (playerEl !== this.player.container) {\n const video = playerEl.querySelector('video, audio') as HTMLMediaElement | null;\n if (video && !video.paused) {\n video.pause();\n }\n }\n });\n }\n\n play() {\n // Save scroll position to prevent browser from scrolling to video\n const scrollX = window.scrollX;\n const scrollY = window.scrollY;\n\n // If deferLoad is enabled, trigger load only on the first user play request.\n if (this.player.options.deferLoad && !this._didDeferredLoad) {\n try {\n // Only call load() if browser hasn't loaded anything yet.\n if (this.media.readyState === 0) {\n this.media.load();\n }\n } catch {\n // ignore\n }\n this._didDeferredLoad = true;\n }\n \n const promise = this.media.play();\n \n // Restore scroll position immediately to prevent auto-scroll\n window.scrollTo(scrollX, scrollY);\n \n if (promise !== undefined) {\n promise.catch(error => {\n this.player.log('Play failed:', error, 'warn');\n \n // If autoplay failed, try muted autoplay\n if (this.player.options.autoplay && !this.player.state.muted) {\n this.player.log('Retrying play with muted audio', 'info');\n this.media.muted = true;\n \n // Save scroll position again for retry\n const retryScrollX = window.scrollX;\n const retryScrollY = window.scrollY;\n this.media.play().then(() => {\n window.scrollTo(retryScrollX, retryScrollY);\n }).catch(err => {\n this.player.handleError(err);\n });\n }\n });\n return promise;\n }\n return Promise.resolve();\n }\n\n /**\n * Ensure the media element has been loaded at least once (metadata/initial state)\n * without starting playback. Useful for playlists to behave like single videos.\n */\n ensureLoaded() {\n if (!this.player.options.deferLoad || this._didDeferredLoad) {\n return;\n }\n\n try {\n if (this.media.readyState === 0) {\n this.media.load();\n }\n } catch {\n // ignore\n }\n this._didDeferredLoad = true;\n }\n\n pause() {\n this.media.pause();\n }\n\n seek(time: number) {\n this.media.currentTime = time;\n }\n\n setVolume(volume: number) {\n this.media.volume = volume;\n }\n\n setMuted(muted: boolean) {\n this.media.muted = muted;\n }\n\n setPlaybackSpeed(speed: number) {\n this.media.playbackRate = speed;\n }\n\n /**\n * Get available quality levels from source elements\n * @returns {Array} Array of quality objects with index, height, width, and src\n */\n getQualities() {\n const sources = Array.from(this.media.querySelectorAll('source'));\n \n if (sources.length <= 1) {\n return [];\n }\n\n return sources.map((source, index) => {\n // Extract quality from data-quality attribute (preferred, HTML5-valid)\n // Fallback to deprecated 'label' attribute for backward compatibility\n // Note: 'label' is not a valid HTML attribute on <source> elements\n const label = source.getAttribute('data-quality') || source.getAttribute('data-label') || source.getAttribute('label') || '';\n const height = source.getAttribute('data-height') || String(this.extractHeightFromLabel(label));\n const width = source.getAttribute('data-width') || '';\n \n return {\n index,\n height: height ? parseInt(height) : 0,\n width: width ? parseInt(width) : 0,\n src: source.src,\n type: source.type,\n name: label || (height ? `${height}p` : `Quality ${index + 1}`)\n };\n }).filter(q => q.height > 0); // Only return qualities with valid height\n }\n\n /**\n * Extract height from quality label (e.g., \"1080p\" -> 1080)\n * @param {string} label \n * @returns {number}\n */\n extractHeightFromLabel(label: string) {\n const match = label.match(/(\\d+)p/i);\n return match && match[1] ? parseInt(match[1], 10) : 0;\n }\n\n /**\n * Switch to a specific quality level\n * @param {number} qualityIndex - Index of the quality level (-1 for auto, not applicable for HTML5)\n */\n switchQuality(qualityIndex: number) {\n const qualities = this.getQualities();\n \n if (qualityIndex < 0 || qualityIndex >= qualities.length) {\n this.player.log('Invalid quality index', 'warn');\n return;\n }\n\n const quality = qualities[qualityIndex];\n if (!quality || !quality.src) {\n return;\n }\n const currentTime = this.media.currentTime;\n const wasPlaying = !this.media.paused;\n\n // Store the current source for comparison\n const currentSrc = this.media.currentSrc;\n \n // Don't switch if already at this quality\n if (currentSrc === quality.src) {\n this.player.log('Already at this quality level', 'info');\n return;\n }\n\n this.player.log(`Switching to quality: ${quality.name}`, 'info');\n\n // Update the src\n this.media.src = quality.src;\n \n // Wait for the new source to load, then restore playback state\n const onLoadedMetadata = () => {\n this.media.removeEventListener('loadedmetadata', onLoadedMetadata);\n \n // Restore playback position\n this.media.currentTime = currentTime;\n \n // Resume playback if it was playing\n if (wasPlaying) {\n this.media.play().catch(() => {\n this.player.log('Failed to resume playback after quality switch', 'warn');\n });\n }\n \n // Emit quality change event\n this.player.emit('qualitychange', { quality: quality.name, index: qualityIndex });\n };\n\n this.media.addEventListener('loadedmetadata', onLoadedMetadata);\n this.media.load();\n }\n\n /**\n * Get current quality index\n * @returns {number}\n */\n getCurrentQuality() {\n const qualities = this.getQualities();\n const currentSrc = this.media.currentSrc;\n \n for (let i = 0; i < qualities.length; i++) {\n if (qualities[i]?.src === currentSrc) {\n return i;\n }\n }\n \n return 0; // Default to first quality if not found\n }\n\n destroy() {\n // Detach every media listener registered in attachEvents() in one shot.\n this._listenerController.abort();\n }\n}\n\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,11 +1,11 @@
1
1
  /*!
2
- * VidPly v1.2.0 - Universal, Accessible Video Player
2
+ * VidPly v1.2.3 - Universal, Accessible Video Player
3
3
  * (c) 2026 Matthias Peltzer
4
4
  * Released under GPL-2.0-or-later License
5
5
  */
6
6
  import {
7
7
  i18n
8
- } from "./vidply.chunk-RZWZGDV7.js";
8
+ } from "./vidply.chunk-I6EDRYGJ.js";
9
9
 
10
10
  // src/utils/TimeUtils.ts
11
11
  var TimeUtils = {
@@ -64,4 +64,4 @@ var TimeUtils = {
64
64
  export {
65
65
  TimeUtils
66
66
  };
67
- //# sourceMappingURL=vidply.chunk-SK4V2DAU.js.map
67
+ //# sourceMappingURL=vidply.chunk-HZ7UOKOU.js.map
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * VidPly v1.2.0 - Universal, Accessible Video Player
2
+ * VidPly v1.2.3 - Universal, Accessible Video Player
3
3
  * (c) 2026 Matthias Peltzer
4
4
  * Released under GPL-2.0-or-later License
5
5
  */
@@ -429,6 +429,33 @@ var en = {
429
429
  reset: "Reset to defaults",
430
430
  close: "Close"
431
431
  },
432
+ help: {
433
+ title: "Keyboard shortcuts",
434
+ button: "Keyboard shortcuts",
435
+ close: "Close",
436
+ or: "or",
437
+ keys: {
438
+ space: "Space"
439
+ },
440
+ actions: {
441
+ "play-pause": "Play / Pause",
442
+ "seek-backward": "Seek backward",
443
+ "seek-forward": "Seek forward",
444
+ "volume-up": "Volume up",
445
+ "volume-down": "Volume down",
446
+ "mute": "Mute / Unmute",
447
+ "captions": "Toggle captions",
448
+ "caption-style-menu": "Caption styling",
449
+ "speed-down": "Decrease speed",
450
+ "speed-up": "Increase speed",
451
+ "speed-menu": "Playback speed",
452
+ "quality-menu": "Quality",
453
+ "chapters-menu": "Chapters",
454
+ "transcript-toggle": "Toggle transcript",
455
+ "fullscreen": "Toggle fullscreen",
456
+ "help": "Keyboard shortcuts"
457
+ }
458
+ },
432
459
  speeds: {
433
460
  normal: "Normal"
434
461
  },
@@ -471,10 +498,10 @@ var en = {
471
498
 
472
499
  // src/i18n/translations.ts
473
500
  var builtInLanguageLoaders = {
474
- de: () => import("./vidply.de-4YAT7FFM.js"),
475
- es: () => import("./vidply.es-LEWIRUBH.js"),
476
- fr: () => import("./vidply.fr-Q4GV2NOE.js"),
477
- ja: () => import("./vidply.ja-S6ITA6WU.js")
501
+ de: () => import("./vidply.de-VAUVSQXC.js"),
502
+ es: () => import("./vidply.es-G65HFWO6.js"),
503
+ fr: () => import("./vidply.fr-MJNOZELM.js"),
504
+ ja: () => import("./vidply.ja-INMDHIYJ.js")
478
505
  };
479
506
  function getBaseTranslations() {
480
507
  return { en };
@@ -658,4 +685,4 @@ export {
658
685
  shallowSanitize,
659
686
  i18n
660
687
  };
661
- //# sourceMappingURL=vidply.chunk-RZWZGDV7.js.map
688
+ //# sourceMappingURL=vidply.chunk-I6EDRYGJ.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/DOMUtils.ts", "../../src/utils/Sanitize.ts", "../../src/i18n/languages/en.ts", "../../src/i18n/translations.ts", "../../src/i18n/i18n.ts"],
4
+ "sourcesContent": ["/**\n * Options accepted by {@link DOMUtils.createElement}.\n *\n * `innerHTML` is deliberately **not** exposed here. Code that needs to\n * inject raw markup must do so explicitly at the call site where the\n * source of the markup is auditable; DOMUtils is the \"safe by default\"\n * surface.\n */\nexport interface CreateElementOptions {\n className?: string;\n attributes?: Record<string, string | undefined>;\n textContent?: string;\n style?: Partial<CSSStyleDeclaration>;\n children?: (Node | null | undefined)[];\n}\n\nfunction createElementImpl<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n options?: CreateElementOptions\n): HTMLElementTagNameMap[K];\nfunction createElementImpl(tag: string, options?: CreateElementOptions): HTMLElement;\nfunction createElementImpl(tag: string, options: CreateElementOptions = {}): HTMLElement {\n const element = document.createElement(tag);\n\n if (options.className) {\n element.className = options.className;\n }\n\n if (options.attributes) {\n for (const [key, value] of Object.entries(options.attributes)) {\n if (value !== undefined) {\n element.setAttribute(key, value);\n }\n }\n }\n\n if (options.textContent) {\n element.textContent = options.textContent;\n }\n\n if (options.style) {\n Object.assign(element.style, options.style);\n }\n\n if (options.children) {\n for (const child of options.children) {\n if (child) element.appendChild(child);\n }\n }\n\n return element;\n}\n\nexport const DOMUtils = {\n createElement: createElementImpl,\n\n show(element: HTMLElement | null | undefined): void {\n if (element?.style) {\n element.style.display = '';\n }\n },\n\n hide(element: HTMLElement | null | undefined): void {\n if (element?.style) {\n element.style.display = 'none';\n }\n },\n\n fadeIn(element: HTMLElement | null, duration = 300, onComplete?: () => void): void {\n if (!element) return;\n\n element.style.opacity = '0';\n element.style.display = '';\n element.style.transition = `opacity ${duration}ms ease`;\n\n // Force reflow to ensure transition works\n void element.offsetHeight;\n\n element.style.opacity = '1';\n\n if (onComplete) {\n let called = false;\n const cleanup = () => {\n if (called) return;\n called = true;\n element.removeEventListener('transitionend', cleanup);\n onComplete();\n };\n element.addEventListener('transitionend', cleanup, { once: true });\n setTimeout(cleanup, duration + 50);\n }\n },\n\n fadeOut(element: HTMLElement | null, duration = 300, onComplete?: () => void): void {\n if (!element) return;\n\n element.style.transition = `opacity ${duration}ms ease`;\n element.style.opacity = '0';\n\n let called = false;\n const cleanup = () => {\n if (called) return;\n called = true;\n element.removeEventListener('transitionend', cleanup);\n element.style.display = 'none';\n if (onComplete) onComplete();\n };\n\n element.addEventListener('transitionend', cleanup, { once: true });\n setTimeout(cleanup, duration + 50);\n },\n\n offset(element: HTMLElement | null): { top: number; left: number; width: number; height: number } {\n if (!element) return { top: 0, left: 0, width: 0, height: 0 };\n\n const rect = element.getBoundingClientRect();\n return {\n top: rect.top + window.scrollY,\n left: rect.left + window.scrollX,\n width: rect.width,\n height: rect.height\n };\n },\n\n escapeHTML(str: string): string {\n const escapeMap: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#x27;'\n };\n return str.replace(/[&<>\"']/g, char => escapeMap[char] ?? char);\n },\n\n /**\n * Render a WebVTT cue's text safely.\n *\n * The previous implementation ran a regex-based blacklist over the cue\n * string and assigned the result to `innerHTML`, which is a known-unsafe\n * pattern (mutation-XSS bypasses, attribute-name tricks, etc.). Caption\n * text on most sites is fetched verbatim from external `.vtt` files that\n * the embedding page has no control over (third-party HLS/DASH manifests,\n * user-supplied playlists, ...) so this code path is reachable by\n * untrusted authors.\n *\n * The new implementation tokenizes only the WebVTT inline tags allowed by\n * the spec (`<b>`, `<i>`, `<u>`, `<c[.class]>`, `<v authorName>`) and\n * builds the resulting DOM via `document.createElement` /\n * `document.createTextNode`. Anything else (script, iframe, attributes,\n * URL schemes, character refs, ...) is rendered as literal text.\n *\n * Cue input is hard-capped at 10,000 characters before parsing to\n * eliminate ReDoS and runaway-DOM concerns.\n */\n renderVTTToDOM(text: string): DocumentFragment {\n const MAX_CUE_LENGTH = 10_000;\n const safeInput = text.length > MAX_CUE_LENGTH ? text.slice(0, MAX_CUE_LENGTH) : text;\n const fragment = document.createDocumentFragment();\n const stack: HTMLElement[] = [];\n\n const append = (node: Node): void => {\n const target = stack[stack.length - 1] ?? fragment;\n target.appendChild(node);\n };\n\n const tagPattern = /<(\\/)?([a-z])(?:\\.([\\w.-]{1,200}))?(?:\\s+([^<>]{0,500}))?>/i;\n let cursor = 0;\n\n while (cursor < safeInput.length) {\n const remaining = safeInput.slice(cursor);\n const match = tagPattern.exec(remaining);\n if (!match || match.index === undefined) {\n append(document.createTextNode(remaining));\n break;\n }\n\n if (match.index > 0) {\n append(document.createTextNode(remaining.slice(0, match.index)));\n }\n\n const [, closing, tagLetter, classList, voiceName] = match;\n const tag = (tagLetter || '').toLowerCase();\n\n if (closing) {\n const top = stack[stack.length - 1];\n if (top && top.dataset.vttTag === tag) {\n stack.pop();\n }\n } else if (tag === 'b' || tag === 'i' || tag === 'u') {\n const elementTag = tag === 'b' ? 'strong' : tag === 'i' ? 'em' : 'u';\n const node = document.createElement(elementTag);\n node.dataset.vttTag = tag;\n append(node);\n stack.push(node);\n } else if (tag === 'c') {\n const span = document.createElement('span');\n span.dataset.vttTag = tag;\n span.classList.add('caption-class');\n if (classList) {\n for (const cls of classList.split('.').filter(Boolean)) {\n if (/^[\\w-]+$/.test(cls)) {\n span.classList.add(`caption-class-${cls}`);\n }\n }\n }\n append(span);\n stack.push(span);\n } else if (tag === 'v') {\n const span = document.createElement('span');\n span.dataset.vttTag = tag;\n span.classList.add('caption-voice');\n if (voiceName) {\n // Voice name is text; never an attribute. data-voice mirrors the\n // historical hook used by `caption-voice` styles.\n span.dataset.voice = voiceName.trim().slice(0, 200);\n }\n append(span);\n stack.push(span);\n } else {\n // Unknown tag — render literally as escaped text so authors can see\n // the bug without it becoming an injection vector.\n append(document.createTextNode(match[0]));\n }\n\n cursor += match.index + match[0].length;\n }\n\n return fragment;\n },\n\n createTooltip(text: string, classPrefix = 'vidply'): HTMLElement {\n return this.createElement('span', {\n className: `${classPrefix}-tooltip`,\n textContent: text,\n attributes: { 'aria-hidden': 'true' }\n });\n },\n\n attachTooltip(element: HTMLElement | null, text: string, classPrefix = 'vidply'): void {\n if (!element || !text) return;\n\n element.querySelector(`.${classPrefix}-tooltip`)?.remove();\n\n const tooltip = this.createTooltip(text, classPrefix);\n element.appendChild(tooltip);\n\n const visibleClass = `${classPrefix}-tooltip-visible`;\n const show = () => tooltip.classList.add(visibleClass);\n const hide = () => tooltip.classList.remove(visibleClass);\n\n element.addEventListener('mouseenter', show);\n element.addEventListener('mouseleave', hide);\n element.addEventListener('focus', show);\n element.addEventListener('blur', hide);\n },\n\n createButtonText(text: string, classPrefix = 'vidply'): HTMLElement {\n return this.createElement('span', {\n className: `${classPrefix}-button-text`,\n textContent: text,\n attributes: { 'aria-hidden': 'true' }\n });\n },\n\n addClass(element: HTMLElement | null | undefined, className: string): void {\n element?.classList?.add(className);\n },\n\n removeClass(element: HTMLElement | null | undefined, className: string): void {\n element?.classList?.remove(className);\n },\n\n toggleClass(element: HTMLElement | null | undefined, className: string): void {\n element?.classList?.toggle(className);\n },\n\n hasClass(element: HTMLElement | null | undefined, className: string): boolean {\n return element?.classList?.contains(className) ?? false;\n }\n};\n", "/**\n * Prototype-pollution defence helpers.\n *\n * Every time the player mixes attacker-influenced data (JSON from\n * `data-vidply-options`, persisted values from `localStorage`,\n * translation catalogues fetched over HTTP, theme-variable overrides)\n * into an object with a normal prototype, the keys\n * `__proto__`, `prototype`, and `constructor` are stripped on the way in.\n *\n * This module centralises that logic so the three separate copies that\n * used to live in `core/Player.ts`, `utils/StorageManager.ts`,\n * `i18n/i18n.ts`, and `index.ts` cannot drift apart silently.\n */\n\n/** Keys that are forbidden in attacker-influenced input. Frozen so no\n * caller can accidentally mutate the shared set at runtime. */\nexport const PROTO_FORBIDDEN_KEYS: ReadonlySet<string> = Object.freeze(\n new Set(['__proto__', 'prototype', 'constructor'])\n);\n\n/** Return `true` if the key would risk prototype pollution. */\nexport function isForbiddenKey(key: PropertyKey): boolean {\n return PROTO_FORBIDDEN_KEYS.has(String(key));\n}\n\n/**\n * One-level copy of `input` that drops forbidden keys. The result has\n * a null prototype so further property access can't accidentally hit\n * `Object.prototype`. The generic `T` cast matches the ergonomic\n * pattern `shallowSanitize<MyShape>(dirty)` used by callers that know\n * the expected shape.\n */\nexport function shallowSanitize<T extends Record<string, unknown>>(input: T): T {\n const out: Record<string, unknown> = Object.create(null);\n for (const [key, value] of Object.entries(input)) {\n if (isForbiddenKey(key)) continue;\n out[key] = value;\n }\n return out as T;\n}\n\n/**\n * Recursive, plain-object-only sanitiser. Arrays and primitives are\n * passed through unchanged; plain objects are copied onto a null\n * prototype with forbidden keys filtered.\n *\n * This is the shape needed by the i18n layer (nested translation\n * catalogues) and by any code that merges deeply nested options.\n */\nexport function deepSanitize<T = unknown>(input: unknown): T {\n if (!input || typeof input !== 'object' || Array.isArray(input)) {\n // For non-objects we still want a null-prototype fallback in the\n // \"expected an object but got garbage\" case — matches the previous\n // i18n behaviour so catalogue loaders stay defensive.\n if (input && typeof input === 'object') {\n return input as T;\n }\n return Object.create(null) as T;\n }\n const out: Record<string, unknown> = Object.create(null);\n for (const [key, value] of Object.entries(input as Record<string, unknown>)) {\n if (isForbiddenKey(key)) continue;\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n out[key] = deepSanitize(value);\n } else {\n out[key] = value;\n }\n }\n return out as T;\n}\n", "/**\n * English translations for VidPly\n */\n\nexport const en = {\n player: {\n label: 'Video Player',\n play: 'Play',\n pause: 'Pause',\n stop: 'Stop',\n restart: 'Restart from beginning',\n rewind: 'Rewind',\n forward: 'Forward',\n rewindSeconds: 'Rewind {seconds} seconds',\n forwardSeconds: 'Forward {seconds} seconds',\n previous: 'Previous track',\n next: 'Next track',\n playlist: 'Toggle playlist',\n volume: 'Volume',\n mute: 'Mute',\n unmute: 'Unmute',\n fullscreen: 'Fullscreen',\n exitFullscreen: 'Exit Fullscreen',\n captions: 'Captions',\n chapters: 'Chapters',\n quality: 'Quality',\n captionStyling: 'Caption styling',\n transcript: 'Toggle transcript',\n audioDescription: 'Audio description',\n signLanguage: 'Sign language video',\n settings: 'Settings',\n speed: 'Playback Speed',\n pip: 'Picture in Picture',\n floatingPlayer: 'Floating player',\n floatingPlayerClose: 'Close floating player',\n floatingPlayerEnter: 'Pop out video',\n floatingPlayerExit: 'Dock video',\n floatingPlayerDialog: 'Floating video player. Press D to drag, R to resize, Escape to close.',\n download: 'Download',\n downloadWithFormat: 'Download {format}',\n downloadWithSize: 'Download ({size})',\n downloadWithFormatSize: 'Download {format} ({size})',\n currentTime: 'Current time',\n duration: 'Duration',\n progress: 'Progress',\n seekForward: 'Seek forward {seconds} seconds',\n seekBackward: 'Seek backward {seconds} seconds',\n volumeUp: 'Volume up',\n volumeDown: 'Volume down',\n loading: 'Loading...',\n loadingChapters: 'Loading chapters...',\n error: 'Error loading media',\n buffering: 'Buffering...',\n signLanguageVideo: 'Sign Language Video',\n closeSignLanguage: 'Close sign language video',\n signLanguageSettings: 'Sign language settings',\n startPlaybackFirst: 'Please start playback first.',\n startPlaybackForAudioDescription: 'Please start playback first to use audio description.',\n startPlaybackForSignLanguage: 'Please start playback first to use sign language video.',\n noChapters: 'No chapters available',\n noCaptions: 'No captions available',\n auto: 'Auto',\n autoQuality: 'Auto (no quality selection available)',\n noQuality: 'Quality selection not available',\n signLanguageDragResize: 'Sign Language Video - Press D to drag with keyboard, R to resize',\n signLanguageDragActive: 'Sign Language Video - Drag mode active. Use arrow keys to move, Escape to exit.',\n signLanguageResizeActive: 'Sign Language Video - Resize mode active. Use left/right arrow keys to resize, Escape to exit.',\n enableSignDragMode: 'Enable drag mode. Shortcut: D key',\n disableSignDragMode: 'Disable drag mode. Shortcut: D key',\n enableSignDragModeAria: 'Enable toggle keyboard drag mode with arrow keys. Shortcut: D key',\n disableSignDragModeAria: 'Disable toggle keyboard drag mode with arrow keys. Shortcut: D key',\n enableSignResizeMode: 'Enable resize mode. Shortcut: R key',\n disableSignResizeMode: 'Disable resize mode. Shortcut: R key',\n enableSignResizeModeAria: 'Enable keyboard resize mode with arrow keys. Shortcut: R key',\n disableSignResizeModeAria: 'Disable keyboard resize mode with arrow keys. Shortcut: R key',\n signDragModeHint: 'Drag mode: arrow keys to move, Esc to exit',\n signResizeModeHint: 'Resize mode: arrow keys to resize, Esc to exit',\n signDragModeDisabled: 'Sign language drag mode disabled.',\n signResizeModeDisabled: 'Sign language resize mode disabled.',\n resizeHandle: 'Resize {direction} corner',\n moreOptions: 'More options',\n noMoreOptions: 'No additional options available',\n // Screen-reader announcements (used by KeyboardManager.announceAction)\n playing: 'Playing',\n paused: 'Paused',\n muted: 'Muted',\n unmuted: 'Unmuted',\n captionsOn: 'Captions on',\n captionsOff: 'Captions off',\n volumePercent: 'Volume {percent} percent',\n volumePercentMuted: 'Muted',\n speedRate: 'Speed {rate}x'\n },\n captions: {\n off: 'Off',\n select: 'Select captions',\n fontSize: 'Font Size',\n fontFamily: 'Font Family',\n color: 'Text Color',\n backgroundColor: 'Background Color',\n opacity: 'Opacity'\n },\n fontSizes: {\n small: 'Small',\n normal: 'Normal',\n large: 'Large',\n xlarge: 'X-Large'\n },\n fontFamilies: {\n sansSerif: 'Sans-serif',\n serif: 'Serif',\n monospace: 'Monospace'\n },\n styleLabels: {\n textColor: 'Text Color',\n background: 'Background',\n font: 'Font',\n fontSize: 'Font Size',\n opacity: 'Opacity'\n },\n audioDescription: {\n enable: 'Enable audio description',\n disable: 'Disable audio description'\n },\n signLanguage: {\n show: 'Show sign language video',\n hide: 'Hide sign language video',\n showInMainView: 'Show sign language in main video',\n hideInMainView: 'Hide sign language from main video'\n },\n transcript: {\n title: 'Transcript',\n ariaLabel: 'Video Transcript',\n close: 'Close transcript',\n loading: 'Loading transcript...',\n noTranscript: 'No transcript available for this video.',\n settings: 'Transcript settings. Press Enter to open menu, or D to enable drag mode',\n keyboardDragMode: 'Toggle keyboard drag mode with arrow keys. Shortcut: D key',\n keyboardDragActive: '⌨️ Keyboard Drag Mode Active (Arrow keys to move, Shift+Arrows for large steps, D or ESC to exit)',\n dragResizePrompt: 'Press D to drag or R to resize. Use Home to reset position, Esc to close.',\n dragModeEnabled: 'Keyboard drag mode enabled. Use arrow keys to move, Shift+Arrow for larger steps. Press D or Esc to exit.',\n dragModeDisabled: 'Keyboard drag mode disabled.',\n enableDragMode: 'Enable drag mode. Shortcut: D key',\n disableDragMode: 'Disable drag mode. Shortcut: D key',\n enableDragModeAria: 'Enable toggle keyboard drag mode with arrow keys. Shortcut: D key',\n disableDragModeAria: 'Disable toggle keyboard drag mode with arrow keys. Shortcut: D key',\n resizeWindow: 'Resize Window',\n disableResizeWindow: 'Disable Resize Mode',\n enableResizeMode: 'Enable resize mode. Shortcut: R key',\n disableResizeMode: 'Disable resize mode. Shortcut: R key',\n enableResizeModeAria: 'Enable keyboard resize mode with arrow keys. Shortcut: R key',\n disableResizeModeAria: 'Disable keyboard resize mode with arrow keys. Shortcut: R key',\n resizeModeHint: 'Resize handles enabled. Drag edges or corners to adjust. Press Esc or R to exit.',\n resizeModeEnabled: 'Resize mode enabled. Drag edges or corners to adjust. Press Esc or R to exit.',\n resizeModeDisabled: 'Resize mode disabled.',\n dragModeBadge: 'Drag mode: arrow keys to move, Esc to exit',\n resizeModeBadge: 'Resize mode: drag edges or corners, Esc to exit',\n positionReset: 'Transcript position reset.',\n styleTranscript: 'Open transcript style settings',\n closeMenu: 'Close Menu',\n styleTitle: 'Transcript Style',\n autoscroll: 'Autoscroll',\n settingsMenu: 'Transcript dialog settings',\n showTimestamps: 'Show timestamps',\n hideTimestamps: 'Hide timestamps',\n showTimestampsAria: 'Show timestamps in transcript',\n hideTimestampsAria: 'Hide timestamps in transcript',\n seekTo: 'Seek to {time}'\n },\n settings: {\n title: 'Settings',\n quality: 'Quality',\n speed: 'Speed',\n captions: 'Captions',\n language: 'Language',\n reset: 'Reset to defaults',\n close: 'Close'\n },\n help: {\n title: 'Keyboard shortcuts',\n button: 'Keyboard shortcuts',\n close: 'Close',\n or: 'or',\n keys: {\n space: 'Space'\n },\n actions: {\n 'play-pause': 'Play / Pause',\n 'seek-backward': 'Seek backward',\n 'seek-forward': 'Seek forward',\n 'volume-up': 'Volume up',\n 'volume-down': 'Volume down',\n 'mute': 'Mute / Unmute',\n 'captions': 'Toggle captions',\n 'caption-style-menu': 'Caption styling',\n 'speed-down': 'Decrease speed',\n 'speed-up': 'Increase speed',\n 'speed-menu': 'Playback speed',\n 'quality-menu': 'Quality',\n 'chapters-menu': 'Chapters',\n 'transcript-toggle': 'Toggle transcript',\n 'fullscreen': 'Toggle fullscreen',\n 'help': 'Keyboard shortcuts'\n }\n },\n speeds: {\n normal: 'Normal'\n },\n time: {\n display: 'Time display',\n durationPrefix: 'Duration: ',\n of: 'of',\n hour: '{count} hour',\n hours: '{count} hours',\n minute: '{count} minute',\n minutes: '{count} minutes',\n second: '{count} second',\n seconds: '{count} seconds'\n },\n resume: {\n prompt: 'Resume from {time}?',\n resume: 'Resume',\n startOver: 'Start Over'\n },\n playlist: {\n title: 'Playlist',\n trackOf: 'Track {current} of {total}',\n nowPlaying: 'Now playing: Track {current} of {total}. {title}{artist}',\n by: ' by ',\n untitled: 'Untitled',\n trackUntitled: 'Track {number}',\n currentlyPlaying: 'Currently playing',\n notPlaying: 'Not playing',\n pressEnterPlay: 'Press Enter to play',\n pressEnterRestart: 'Press Enter to restart',\n keyboardInstructions: 'Playlist navigation: Use Up and Down arrow keys to move between tracks. Press Page Up or Page Down to skip 5 tracks. Press Home to go to first track, End to go to last track. Press Enter or Space to play the selected track.',\n endOfPlaylist: 'End of playlist. {current} of {total}.',\n beginningOfPlaylist: 'Beginning of playlist. 1 of {total}.',\n jumpedToLastTrack: 'Jumped to last track. {current} of {total}.',\n jumpedToFirstTrack: 'Jumped to first track. 1 of {total}.',\n firstTrack: 'First track. 1 of {total}.',\n lastTrack: 'Last track. {current} of {total}.'\n }\n};\n\n", "import { en } from './languages/en.js';\n\nexport type TranslationData = Record<string, unknown>;\n\nconst builtInLanguageLoaders: Record<string, () => Promise<Record<string, TranslationData>>> = {\n de: () => import('./languages/de.js') as Promise<Record<string, TranslationData>>,\n es: () => import('./languages/es.js') as Promise<Record<string, TranslationData>>,\n fr: () => import('./languages/fr.js') as Promise<Record<string, TranslationData>>,\n ja: () => import('./languages/ja.js') as Promise<Record<string, TranslationData>>\n};\n\nexport function getBaseTranslations(): Record<string, TranslationData> {\n return { en };\n}\n\nexport function getBuiltInLanguageLoaders(): Record<string, () => Promise<Record<string, TranslationData>>> {\n return builtInLanguageLoaders;\n}\n\nexport async function loadBuiltInTranslation(lang: string): Promise<TranslationData | null> {\n const loader = builtInLanguageLoaders[lang];\n if (!loader) return null;\n\n const module = await loader();\n return (module[lang] || (module as Record<string, unknown>).default || null) as TranslationData | null;\n}\n\nexport const translations = getBaseTranslations();\n", "import { getBaseTranslations, getBuiltInLanguageLoaders, loadBuiltInTranslation, type TranslationData } from './translations.js';\nimport { deepSanitize, isForbiddenKey } from '../utils/Sanitize.js';\n\ndeclare global {\n interface Window {\n jsyaml?: { load(input: string): unknown };\n }\n}\n\n/**\n * Escape RegExp meta-characters so a placeholder name like\n * `{name.first}` does not turn into a wildcard or backreference when\n * compiled into a `RegExp`.\n */\nfunction escapeRegExp(input: string): string {\n return input.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Recursively copy a translation object onto a null-prototype map and\n * drop forbidden keys (`__proto__`, `prototype`, `constructor`) so a\n * malicious translation file cannot pollute Object.prototype on its way\n * through `Object.assign`.\n */\nfunction deepSanitizeTranslations(input: unknown): TranslationData {\n return deepSanitize<TranslationData>(input);\n}\n\nclass I18n {\n currentLanguage: string;\n translations: Record<string, TranslationData>;\n private loadingPromises: Map<string, Promise<unknown>>;\n private builtInLanguageLoaders: Record<string, () => Promise<Record<string, TranslationData>>>;\n\n constructor() {\n this.currentLanguage = 'en';\n this.translations = Object.create(null) as Record<string, TranslationData>;\n Object.assign(this.translations, getBaseTranslations());\n this.loadingPromises = new Map();\n this.builtInLanguageLoaders = getBuiltInLanguageLoaders();\n }\n\n setLanguage(lang: string): void {\n if (this.translations[lang]) {\n this.currentLanguage = lang;\n } else {\n console.warn(`Language \"${lang}\" not found, falling back to English`);\n this.currentLanguage = 'en';\n }\n }\n\n getLanguage(): string {\n return this.currentLanguage;\n }\n\n async ensureLanguage(lang: string): Promise<string | null> {\n const normalizedLang = (lang || '').toLowerCase();\n if (!normalizedLang) return this.currentLanguage;\n\n if (this.translations[normalizedLang]) {\n return normalizedLang;\n }\n\n if (this.loadingPromises.has(normalizedLang)) {\n await this.loadingPromises.get(normalizedLang);\n return this.translations[normalizedLang] ? normalizedLang : null;\n }\n\n if (!this.builtInLanguageLoaders[normalizedLang]) {\n return null;\n }\n\n const loadPromise = (async () => {\n try {\n const loaded = await loadBuiltInTranslation(normalizedLang);\n if (loaded) {\n this.translations[normalizedLang] = deepSanitizeTranslations(loaded);\n }\n } catch (error) {\n console.warn(`Language \"${normalizedLang}\" failed to load:`, error);\n } finally {\n this.loadingPromises.delete(normalizedLang);\n }\n })();\n\n this.loadingPromises.set(normalizedLang, loadPromise);\n await loadPromise;\n\n return this.translations[normalizedLang] ? normalizedLang : null;\n }\n\n t(key: string, replacements: Record<string, string | number> = {}): string {\n const keys = key.split('.');\n let value: unknown = this.translations[this.currentLanguage];\n\n for (const k of keys) {\n if (value && typeof value === 'object' && k in (value as Record<string, unknown>)) {\n value = (value as Record<string, unknown>)[k];\n } else {\n value = this.translations.en;\n for (const fallbackKey of keys) {\n if (value && typeof value === 'object' && fallbackKey in (value as Record<string, unknown>)) {\n value = (value as Record<string, unknown>)[fallbackKey];\n } else {\n return key;\n }\n }\n break;\n }\n }\n\n if (typeof value === 'string') {\n let result = value;\n for (const [placeholder, replacement] of Object.entries(replacements)) {\n if (isForbiddenKey(placeholder)) continue;\n const safe = escapeRegExp(placeholder);\n result = result.replace(new RegExp(`\\\\{${safe}\\\\}`, 'g'), String(replacement));\n }\n return result;\n }\n\n return typeof value === 'string' ? value : key;\n }\n\n addTranslation(lang: string, newTranslations: TranslationData): void {\n if (isForbiddenKey(lang)) {\n console.warn(`[VidPly] Refusing to register language with forbidden name \"${lang}\"`);\n return;\n }\n if (!this.translations[lang]) {\n this.translations[lang] = Object.create(null) as TranslationData;\n }\n const sanitized = deepSanitizeTranslations(newTranslations);\n Object.assign(this.translations[lang], sanitized);\n }\n\n /**\n * Load a translation file from a URL. Bounded by an `AbortSignal.timeout`\n * (default 8s) plus an optional caller-supplied signal — typically the\n * Player's lifecycle controller — so a torn-down player does not keep\n * the request alive.\n */\n async loadLanguageFromUrl(\n langCode: string,\n url: string,\n options: { signal?: AbortSignal; timeoutMs?: number } = {}\n ): Promise<unknown> {\n if (this.loadingPromises.has(url)) {\n return this.loadingPromises.get(url);\n }\n\n const loadPromise = (async () => {\n const signals: AbortSignal[] = [];\n if (options.signal) signals.push(options.signal);\n if (typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function') {\n signals.push(AbortSignal.timeout(options.timeoutMs ?? 8000));\n }\n const signal = signals.length === 0\n ? undefined\n : signals.length === 1\n ? signals[0]\n : (AbortSignal as { any?: (sigs: AbortSignal[]) => AbortSignal }).any?.(signals) ?? signals[0];\n\n try {\n const response = await fetch(url, { signal });\n if (!response.ok) {\n throw new Error(`Failed to load language file: ${response.statusText}`);\n }\n\n const contentType = response.headers.get('content-type') || '';\n let loadedTranslations: TranslationData;\n\n const buffer = await response.arrayBuffer();\n const utf8Text = new TextDecoder('utf-8').decode(buffer);\n\n if (contentType.includes('application/json') || url.endsWith('.json')) {\n loadedTranslations = JSON.parse(utf8Text);\n } else if (\n contentType.includes('text/yaml') ||\n contentType.includes('application/x-yaml') ||\n url.endsWith('.yaml') ||\n url.endsWith('.yml')\n ) {\n try {\n loadedTranslations = JSON.parse(utf8Text);\n } catch {\n if (typeof window !== 'undefined' && window.jsyaml) {\n loadedTranslations = window.jsyaml.load(utf8Text) as TranslationData;\n } else {\n console.warn('YAML parsing requires js-yaml library. Please include it or use JSON format.');\n throw new Error('YAML parsing not available. Please use JSON format or include js-yaml library.');\n }\n }\n } else {\n loadedTranslations = JSON.parse(utf8Text);\n }\n\n this.addTranslation(langCode, loadedTranslations);\n return loadedTranslations;\n } catch (error) {\n console.error(`Error loading language file from ${url}:`, error);\n throw error;\n } finally {\n this.loadingPromises.delete(url);\n }\n })();\n\n this.loadingPromises.set(url, loadPromise);\n return loadPromise;\n }\n\n async loadLanguagesFromUrls(\n languageMap: Record<string, string>,\n options: { signal?: AbortSignal; timeoutMs?: number } = {}\n ): Promise<void> {\n const promises = Object.entries(languageMap).map(([langCode, url]) =>\n this.loadLanguageFromUrl(langCode, url, options)\n );\n await Promise.all(promises);\n }\n}\n\nexport const i18n = new I18n();\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,22 +1,22 @@
1
1
  /*!
2
- * VidPly v1.2.0 - Universal, Accessible Video Player
2
+ * VidPly v1.2.3 - Universal, Accessible Video Player
3
3
  * (c) 2026 Matthias Peltzer
4
4
  * Released under GPL-2.0-or-later License
5
5
  */
6
6
  import {
7
7
  StorageManager,
8
8
  deriveTrackLabel
9
- } from "./vidply.chunk-BNFIUL2Z.js";
9
+ } from "./vidply.chunk-6LYZYI5G.js";
10
10
  import {
11
11
  debounce,
12
12
  isMobile,
13
13
  rafWithTimeout,
14
14
  reducedMotionScrollOptions
15
- } from "./vidply.chunk-6RQA4ROD.js";
15
+ } from "./vidply.chunk-PRFNESYA.js";
16
16
  import {
17
17
  DOMUtils,
18
18
  i18n
19
- } from "./vidply.chunk-RZWZGDV7.js";
19
+ } from "./vidply.chunk-I6EDRYGJ.js";
20
20
 
21
21
  // src/controls/CaptionManager.ts
22
22
  var CaptionManager = class {
@@ -450,4 +450,4 @@ var CaptionManager = class {
450
450
  export {
451
451
  CaptionManager
452
452
  };
453
- //# sourceMappingURL=vidply.chunk-64WXMTGL.js.map
453
+ //# sourceMappingURL=vidply.chunk-JKMQGLCJ.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/controls/CaptionManager.ts"],
4
+ "sourcesContent": ["/**\n * Caption/Subtitle Manager\n */\n\nimport {DOMUtils} from '../utils/DOMUtils.js';\nimport {i18n} from '../i18n/i18n.js';\nimport {StorageManager} from '../utils/StorageManager.js';\nimport {debounce, isMobile, rafWithTimeout, reducedMotionScrollOptions} from '../utils/PerformanceUtils.js';\nimport {deriveTrackLabel} from '../utils/TrackLabelUtils.js';\nimport type { Player } from '../core/Player.js';\n\n/**\n * Deduplicated subtitle/captions entry shown in the captions menu. Native HLS\n * on Safari can expose the same language as both `SUBTITLES` (WebVTT sidecar)\n * and `CLOSED-CAPTIONS` (inband CEA-608) groups; the duplicates are kept in\n * `alternatives` so we can listen on all of them and use whichever delivers\n * cues first.\n */\nexport type CaptionTrackEntry = {\n track: TextTrack;\n language: string;\n label: string;\n kind: string;\n index: number;\n isDefault: boolean;\n alternatives: TextTrack[];\n};\n\ntype CueChangeHandler = () => void;\n\nexport class CaptionManager {\n player: Player;\n _altCueChangeHandler: CueChangeHandler | null = null;\n cueChangeHandler: CueChangeHandler | null = null;\n currentCue: VTTCue | null;\n currentTrack: CaptionTrackEntry | null;\n debouncedPositionCaptions!: () => void;\n element!: HTMLElement;\n storage: StorageManager;\n tracks: CaptionTrackEntry[];\n\n constructor(player: Player) {\n this.player = player;\n this.tracks = [];\n this.currentTrack = null;\n this.currentCue = null;\n\n // Storage manager\n this.storage = new StorageManager('vidply');\n\n // Load saved preferences\n this.loadSavedPreferences();\n\n this.init();\n }\n \n loadSavedPreferences() {\n const saved = this.storage.getCaptionPreferences();\n if (saved) {\n if (typeof saved.fontSize === 'string') this.player.options.captionsFontSize = saved.fontSize;\n if (typeof saved.fontFamily === 'string') this.player.options.captionsFontFamily = saved.fontFamily;\n if (typeof saved.color === 'string') this.player.options.captionsColor = saved.color;\n if (typeof saved.backgroundColor === 'string') this.player.options.captionsBackgroundColor = saved.backgroundColor;\n if (typeof saved.opacity === 'number') this.player.options.captionsOpacity = saved.opacity;\n }\n }\n \n saveCaptionPreferences() {\n this.storage.saveCaptionPreferences({\n fontSize: this.player.options.captionsFontSize,\n fontFamily: this.player.options.captionsFontFamily,\n color: this.player.options.captionsColor,\n backgroundColor: this.player.options.captionsBackgroundColor,\n opacity: this.player.options.captionsOpacity\n });\n }\n\n init() {\n this.createElement();\n this.loadTracks();\n this.attachEvents();\n\n // Only enable captions via captionsDefault option if no default track was found\n // (loadTracks() already enables tracks with the default attribute)\n if (this.player.options.captionsDefault && this.tracks.length > 0 && !this.currentTrack) {\n this.enable();\n }\n }\n\n createElement() {\n // The native <track> is rendered hidden and captions are drawn into\n // this overlay, so assistive tech cannot read cues from the track\n // itself. aria-live=\"polite\" makes each cue change announceable for\n // screen-reader users (notably deaf-blind/braille users) without\n // interrupting other speech (WCAG 1.2.2, 4.1.3).\n this.element = DOMUtils.createElement('div', {\n className: `${this.player.options.classPrefix}-captions`,\n attributes: {\n 'role': 'region',\n 'aria-label': i18n.t('player.captions'),\n 'aria-live': 'polite'\n }\n });\n\n // Apply caption styles\n this.updateStyles();\n\n // Append to videoWrapper if it exists, otherwise to container\n const target = this.player.videoWrapper || this.player.container;\n target.appendChild(this.element);\n }\n\n loadTracks() {\n const textTracks = this.player.element.textTracks;\n let defaultTrackIndex = -1;\n\n // Safari's native HLS can expose both SUBTITLES (WebVTT sidecar) and\n // CLOSED-CAPTIONS (inband CEA-608) groups as separate TextTrack objects\n // with the same language and label. Deduplicate to one menu entry per\n // language+label, storing the extras as alternatives. enable() will\n // listen on all of them and use whichever delivers cues first.\n const seen = new Map();\n\n for (let i = 0; i < textTracks.length; i++) {\n const track = textTracks[i];\n if (!track) continue;\n\n if ((track.kind === 'subtitles' || track.kind === 'captions') && !track._vidplyStale) {\n track.mode = 'hidden';\n\n const dedupeKey = `${track.language}|${track.label}`;\n const existing = seen.get(dedupeKey);\n\n if (existing) {\n existing.alternatives.push(track);\n continue;\n }\n\n const trackElement = this.player.findTrackElement(track) as Element | undefined;\n const isDefault = trackElement ? trackElement.hasAttribute('default') : false;\n const entry: CaptionTrackEntry = {\n track,\n language: track.language,\n label: deriveTrackLabel(track.label, track.language),\n kind: track.kind,\n index: i,\n isDefault,\n alternatives: []\n };\n\n this.tracks.push(entry);\n seen.set(dedupeKey, entry);\n\n if (isDefault) {\n defaultTrackIndex = this.tracks.length - 1;\n }\n }\n }\n \n if (defaultTrackIndex >= 0) {\n requestAnimationFrame(() => {\n this.enable(defaultTrackIndex);\n });\n }\n }\n\n /**\n * Sync hls.js subtitle rendition to match the given language.\n * Matches by lang, language, or falls back to name/label.\n */\n private _syncHlsSubtitleTrack(targetLang: string, targetLabel?: string) {\n const renderer = this.player.renderer;\n if (!renderer?.hls || !renderer.hls.subtitleTracks?.length) return;\n\n const tracks = renderer.hls.subtitleTracks as Array<{ lang?: string; name?: string }>;\n let hlsIndex = tracks.findIndex(\n (t) => t.lang === targetLang\n );\n // Fallback: match by name/label\n if (hlsIndex < 0 && targetLabel) {\n hlsIndex = tracks.findIndex(\n (t) => t.name === targetLabel\n );\n }\n if (hlsIndex >= 0 && renderer.hls.subtitleTrack !== hlsIndex) {\n renderer.hls.subtitleTrack = hlsIndex;\n this.player.log(`HLS subtitle track set to index ${hlsIndex} (${targetLang})`, 'info');\n }\n }\n\n attachEvents() {\n this.player.on('timeupdate', () => {\n this.updateCaptions();\n });\n\n // When hls.js finishes loading new subtitle cues, refresh captions\n this.player.on('textcuesupdate', () => {\n this.updateCaptions();\n });\n\n this.player.on('captionschange', () => {\n this.updateStyles();\n });\n \n // Debounced resize handler to avoid excessive recalculations\n this.debouncedPositionCaptions = debounce(() => {\n this.positionCaptionsOnMobile();\n }, 150);\n \n window.addEventListener('resize', this.debouncedPositionCaptions, {\n signal: this.player.lifecycleSignal\n });\n \n // Recalculate on fullscreen change with RAF\n this.player.on('enterfullscreen', () => {\n rafWithTimeout(() => this.positionCaptionsOnMobile(), 100);\n });\n \n this.player.on('exitfullscreen', () => {\n rafWithTimeout(() => this.positionCaptionsOnMobile(), 100);\n });\n }\n\n enable(trackIndex = 0) {\n if (this.tracks.length === 0) {\n return;\n }\n\n // Disable current track and clean up alternative listeners\n this._cleanupTrackListeners();\n\n // Enable selected track\n const selectedTrack = this.tracks[trackIndex];\n\n if (selectedTrack && selectedTrack.track) {\n selectedTrack.track.mode = 'hidden';\n this.currentTrack = selectedTrack;\n this.player.state.captionsEnabled = true;\n\n if (selectedTrack.language) {\n this.element.setAttribute('lang', selectedTrack.language);\n }\n\n // Bind handlers to locals so the non-null narrowing survives both\n // closure captures and `this.X = …` reassignments. Without this,\n // `addEventListener(this.cueChangeHandler)` widens to `… | null`\n // and stops type-checking even though we just assigned a function.\n const cueChangeHandler: CueChangeHandler = () => {\n this.updateCaptions();\n };\n this.cueChangeHandler = cueChangeHandler;\n selectedTrack.track.addEventListener('cuechange', cueChangeHandler);\n\n // Native HLS dedup: Safari can expose duplicate TextTrack objects\n // for the same language (SUBTITLES vs CLOSED-CAPTIONS groups) but\n // only one actually delivers cues. Listen on all alternatives and\n // swap to whichever fires first with real cues.\n if (selectedTrack.alternatives && selectedTrack.alternatives.length > 0) {\n const altCueChangeHandler: CueChangeHandler = () => {\n if (this.currentTrack !== selectedTrack) return;\n for (const alt of selectedTrack.alternatives) {\n if (alt.activeCues && alt.activeCues.length > 0) {\n this.player.log(`Switching to alternative caption track for \"${selectedTrack.label}\"`, 'info');\n selectedTrack.track.removeEventListener('cuechange', cueChangeHandler);\n selectedTrack.alternatives.forEach((a) => a.removeEventListener('cuechange', altCueChangeHandler));\n selectedTrack.track = alt;\n selectedTrack.track.addEventListener('cuechange', cueChangeHandler);\n this._altCueChangeHandler = null;\n this.updateCaptions();\n return;\n }\n }\n };\n this._altCueChangeHandler = altCueChangeHandler;\n selectedTrack.alternatives.forEach((alt) => {\n alt.mode = 'hidden';\n alt.addEventListener('cuechange', altCueChangeHandler);\n });\n }\n\n // The \"ready\" sense actually lives on the underlying <track> HTML\n // element — not on the TextTrack itself, which has no `readyState`\n // or `load` event. Look the element up so this is type-safe and\n // also fires the load callback in browsers where TextTrack alone\n // would never have done so.\n const trackElement = this.player.findTrackElement(selectedTrack.track) as HTMLTrackElement | undefined;\n const ensureTrackReady = () => {\n if (trackElement && trackElement.readyState < 2) {\n const onTrackLoad = () => {\n trackElement.removeEventListener('load', onTrackLoad);\n trackElement.removeEventListener('error', onTrackLoad);\n requestAnimationFrame(() => {\n if (this.currentTrack && this.currentTrack.track === selectedTrack.track) {\n this.updateCaptions();\n }\n });\n };\n trackElement.addEventListener('load', onTrackLoad, { once: true });\n trackElement.addEventListener('error', onTrackLoad, { once: true });\n } else {\n requestAnimationFrame(() => {\n if (this.currentTrack && this.currentTrack.track === selectedTrack.track) {\n this.updateCaptions();\n }\n });\n }\n };\n\n requestAnimationFrame(() => {\n if (this.currentTrack && this.currentTrack.track === selectedTrack.track) {\n ensureTrackReady();\n }\n });\n\n // Sync hls.js subtitle rendition to match the selected track language\n this._syncHlsSubtitleTrack(selectedTrack.language, selectedTrack.label);\n\n this.player.emit('captionsenabled', selectedTrack);\n }\n }\n\n _cleanupTrackListeners() {\n if (this.currentTrack && this.currentTrack.track) {\n const cueChangeHandler = this.cueChangeHandler;\n if (cueChangeHandler) {\n this.currentTrack.track.removeEventListener('cuechange', cueChangeHandler);\n }\n const altCueChangeHandler = this._altCueChangeHandler;\n if (altCueChangeHandler && this.currentTrack.alternatives) {\n this.currentTrack.alternatives.forEach((alt) => {\n alt.removeEventListener('cuechange', altCueChangeHandler);\n });\n }\n this.currentTrack.track.mode = 'hidden';\n }\n this._altCueChangeHandler = null;\n }\n\n disable() {\n this._cleanupTrackListeners();\n this.currentTrack = null;\n\n this.element.style.display = 'none';\n this.element.replaceChildren();\n this.element.removeAttribute('lang');\n this.currentCue = null;\n this.player.state.captionsEnabled = false;\n this.player.emit('captionsdisabled');\n }\n\n updateCaptions() {\n if (!this.currentTrack || !this.currentTrack.track) {\n return;\n }\n\n // When the renderer handles its own caption display (e.g. dash.js TTML\n // rendering div), skip VidPly's caption overlay entirely.\n if (this.player.renderer?.handlesOwnCaptions?.()) {\n return;\n }\n\n // Ensure track mode is set to 'hidden' (not 'disabled') to receive cue updates\n // 'hidden' mode loads cues and fires cuechange events, but doesn't show native captions\n if (this.currentTrack.track.mode === 'disabled') {\n this.currentTrack.track.mode = 'hidden';\n }\n\n if (this.currentTrack.track.mode === 'showing') {\n this.currentTrack.track.mode = 'hidden';\n }\n\n if (!this.currentTrack.track.activeCues) {\n if (this.currentTrack.track.cues && this.currentTrack.track.cues.length > 0) {\n if (this.currentCue) {\n this.element.replaceChildren();\n this.element.style.display = 'none';\n this.currentCue = null;\n }\n }\n return;\n }\n\n const activeCues = this.currentTrack.track.activeCues;\n const isAudioPlayer = this.player.element.tagName.toLowerCase() === 'audio';\n\n if (activeCues.length > 0) {\n // VTT/SRT subtitle tracks deliver `VTTCue` instances (which extend\n // TextTrackCue with `.text`). The lib.dom typing widens this to\n // `TextTrackCue`, so cast at the boundary.\n const cue = activeCues[0] as VTTCue;\n\n if (this.currentCue !== cue) {\n this.currentCue = cue;\n\n const rawText = cue.text || '';\n if (!rawText.trim()) {\n return;\n }\n\n const fragment = DOMUtils.renderVTTToDOM(rawText);\n\n if (isAudioPlayer) {\n const existingCues = this.element.querySelectorAll(`.${this.player.options.classPrefix}-caption-cue`);\n existingCues.forEach((el: Element) => el.classList.remove(`${this.player.options.classPrefix}-caption-active`));\n\n const cueId = `cue-${cue.startTime}-${cue.endTime}`;\n let cueElement = this.element.querySelector(`[data-cue-id=\"${cueId}\"]`);\n\n if (!cueElement) {\n cueElement = document.createElement('div');\n cueElement.className = `${this.player.options.classPrefix}-caption-cue`;\n cueElement.setAttribute('data-cue-id', cueId);\n cueElement.replaceChildren(fragment);\n this.element.appendChild(cueElement);\n } else {\n cueElement.replaceChildren(fragment);\n }\n\n cueElement.classList.add(`${this.player.options.classPrefix}-caption-active`);\n\n requestAnimationFrame(() => {\n if (cueElement) {\n cueElement.scrollIntoView(reducedMotionScrollOptions('center'));\n }\n });\n } else {\n this.element.replaceChildren(fragment);\n }\n\n this.element.style.display = 'block';\n this.positionCaptionsOnMobile();\n this.player.emit('captionchange', cue);\n }\n } else if (this.currentCue) {\n if (!isAudioPlayer) {\n this.element.replaceChildren();\n this.element.style.display = 'none';\n }\n this.currentCue = null;\n }\n }\n \n positionCaptionsOnMobile() {\n if (!this.element || this.element.style.display === 'none') {\n return;\n }\n \n const isFullscreen = this.player.state?.fullscreen || false;\n const mobile = isMobile();\n \n if (!mobile && !isFullscreen) {\n // Reset to CSS defaults on desktop\n this.element.style.bottom = '';\n return;\n }\n \n const controls = this.player.controlBar?.element;\n if (!controls) {\n return;\n }\n \n // Use requestAnimationFrame to ensure layout is complete\n requestAnimationFrame(() => {\n if (!this.element || this.element.style.display === 'none') {\n return;\n }\n \n const controlsRect = controls.getBoundingClientRect();\n if (!this.player.videoWrapper) return;\n const wrapperRect = this.player.videoWrapper.getBoundingClientRect();\n const bottomOffset = wrapperRect.bottom - controlsRect.top + 16;\n \n this.element.style.bottom = `${bottomOffset}px`;\n \n if (this.player.options.debug) {\n console.log('[VidPly] Caption position:', {\n mobile,\n isFullscreen,\n controlsHeight: controlsRect.height,\n bottomOffset: `${bottomOffset}px`\n });\n }\n });\n }\n\n // VTT formatting is parsed via DOMUtils.renderVTTToDOM() which returns\n // a DocumentFragment built with createElement / createTextNode. The\n // previous regex-based parseVTTFormatting helper was removed because\n // it produced strings that were assigned to `innerHTML`, which is\n // unsafe for cue text from third-party WebVTT sources.\n\n updateStyles() {\n if (!this.element) return;\n\n const options = this.player.options;\n\n this.element.style.fontSize = options.captionsFontSize;\n this.element.style.fontFamily = options.captionsFontFamily;\n this.element.style.color = options.captionsColor;\n this.element.style.backgroundColor = this.hexToRgba(\n options.captionsBackgroundColor,\n options.captionsOpacity\n );\n }\n\n hexToRgba(hex: string, alpha: number) {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (result && result[1] && result[2] && result[3]) {\n return `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${alpha})`;\n }\n return hex;\n }\n\n setCaptionStyle(property: string, value: string | number) {\n // The persisted preference shape is mixed (string for font/colors,\n // number for opacity). Cast through the player's loose options\n // index so we don't have to split this method into 5 strongly-typed\n // overloads when the underlying string-keyed bag is itself loose.\n switch (property) {\n case 'fontSize':\n this.player.options.captionsFontSize = String(value);\n break;\n case 'fontFamily':\n this.player.options.captionsFontFamily = String(value);\n break;\n case 'color':\n this.player.options.captionsColor = String(value);\n break;\n case 'backgroundColor':\n this.player.options.captionsBackgroundColor = String(value);\n break;\n case 'opacity':\n this.player.options.captionsOpacity = Number(value);\n break;\n }\n\n this.updateStyles();\n this.saveCaptionPreferences();\n this.player.emit('captionschange');\n }\n\n getAvailableTracks() {\n return this.tracks.map((t, index) => ({\n index,\n language: t.language,\n label: t.label || t.language,\n kind: t.kind\n }));\n }\n\n /**\n * Refresh tracks list - useful when HLS adds subtitle tracks dynamically\n */\n refreshTracks() {\n // Store current track info to restore after refresh\n const currentLanguage = this.currentTrack?.language;\n const wasEnabled = this.player.state.captionsEnabled;\n \n // Disable current caption\n if (this.currentTrack) {\n this.disable();\n }\n \n // Clear existing tracks\n this.tracks = [];\n \n // Reload tracks from video element\n this.loadTracks();\n \n this.player.log(`Caption tracks refreshed, found ${this.tracks.length} tracks`, 'info');\n \n // Try to restore previous track if it exists\n if (wasEnabled && currentLanguage && this.tracks.length > 0) {\n const matchingIndex = this.tracks.findIndex((t) => t.language === currentLanguage);\n if (matchingIndex >= 0) {\n this.enable(matchingIndex);\n }\n }\n \n return this.tracks.length;\n }\n\n switchTrack(trackIndex: number) {\n if (trackIndex >= 0 && trackIndex < this.tracks.length) {\n this.disable();\n this.enable(trackIndex);\n }\n }\n\n destroy() {\n this.disable();\n\n if (this.element && this.element.parentNode) {\n this.element.parentNode.removeChild(this.element);\n }\n }\n}\n\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * VidPly v1.2.0 - Universal, Accessible Video Player
2
+ * VidPly v1.2.3 - Universal, Accessible Video Player
3
3
  * (c) 2026 Matthias Peltzer
4
4
  * Released under GPL-2.0-or-later License
5
5
  */
@@ -84,4 +84,4 @@ function loadScriptOnce(url, options = {}) {
84
84
  export {
85
85
  loadScriptOnce
86
86
  };
87
- //# sourceMappingURL=vidply.chunk-DZSDO3B7.js.map
87
+ //# sourceMappingURL=vidply.chunk-JZNZX32Z.js.map
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * VidPly v1.2.0 - Universal, Accessible Video Player
2
+ * VidPly v1.2.3 - Universal, Accessible Video Player
3
3
  * (c) 2026 Matthias Peltzer
4
4
  * Released under GPL-2.0-or-later License
5
5
  */
@@ -27,4 +27,4 @@ export {
27
27
  focusElement,
28
28
  focusFirstElement
29
29
  };
30
- //# sourceMappingURL=vidply.chunk-VMSQAZM7.js.map
30
+ //# sourceMappingURL=vidply.chunk-P25MRJ6O.js.map
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * VidPly v1.2.0 - Universal, Accessible Video Player
2
+ * VidPly v1.2.3 - Universal, Accessible Video Player
3
3
  * (c) 2026 Matthias Peltzer
4
4
  * Released under GPL-2.0-or-later License
5
5
  */
@@ -54,4 +54,4 @@ export {
54
54
  reducedMotionScrollOptions,
55
55
  rafWithTimeout
56
56
  };
57
- //# sourceMappingURL=vidply.chunk-6RQA4ROD.js.map
57
+ //# sourceMappingURL=vidply.chunk-PRFNESYA.js.map
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * VidPly v1.2.0 - Universal, Accessible Video Player
2
+ * VidPly v1.2.3 - Universal, Accessible Video Player
3
3
  * (c) 2026 Matthias Peltzer
4
4
  * Released under GPL-2.0-or-later License
5
5
  */
@@ -44,7 +44,8 @@ var iconPaths = {
44
44
  move: `<path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"/>`,
45
45
  resize: `<path d="M21.71 11.29l-9-9c-.39-.39-1.02-.39-1.41 0l-9 9c-.39.39-.39 1.02 0 1.41l9 9c.39.39 1.02.39 1.41 0l9-9c.39-.38.39-1.01 0-1.41zM14 14.5V12h-4v2.5L7 11l3-3.5V10h4V7.5l3 3.5-3 3.5z"/>`,
46
46
  clock: `<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/><path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"/>`,
47
- download: `<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>`
47
+ download: `<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>`,
48
+ help: `<path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"/>`
48
49
  };
49
50
  var svgWrapper = (paths) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">${paths}</svg>`;
50
51
  var Icons = Object.fromEntries(
@@ -133,4 +134,4 @@ export {
133
134
  createIconElement,
134
135
  createPlayOverlay
135
136
  };
136
- //# sourceMappingURL=vidply.chunk-KI7C3QVL.js.map
137
+ //# sourceMappingURL=vidply.chunk-UI3RYCVF.js.map