@videojs/html 10.2.0 → 10.4.0

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 (374) hide show
  1. package/dist/default/define/audio/compat-skin.css +161 -41
  2. package/dist/default/define/audio/compat-skin2.js +1 -1
  3. package/dist/default/define/audio/neutral-skin.css +73 -34
  4. package/dist/default/define/audio/neutral-skin2.js +1 -1
  5. package/dist/default/define/audio/scaffold-skin.css +166 -42
  6. package/dist/default/define/audio/scaffold-skin2.js +1 -1
  7. package/dist/default/define/audio/skin.css +72 -34
  8. package/dist/default/define/audio/skin2.js +1 -1
  9. package/dist/default/define/extensions/google-analytics.js +6 -0
  10. package/dist/default/define/live-audio/compat-skin.css +143 -41
  11. package/dist/default/define/live-audio/compat-skin2.js +1 -1
  12. package/dist/default/define/live-audio/neutral-skin.css +56 -31
  13. package/dist/default/define/live-audio/neutral-skin2.js +1 -1
  14. package/dist/default/define/live-audio/scaffold-skin.css +148 -42
  15. package/dist/default/define/live-audio/scaffold-skin2.js +1 -1
  16. package/dist/default/define/live-audio/skin.css +55 -31
  17. package/dist/default/define/live-audio/skin2.js +1 -1
  18. package/dist/default/define/live-video/compat-skin.css +234 -51
  19. package/dist/default/define/live-video/compat-skin2.js +1 -1
  20. package/dist/default/define/live-video/neutral-skin.css +300 -46
  21. package/dist/default/define/live-video/neutral-skin2.js +1 -1
  22. package/dist/default/define/live-video/scaffold-skin.css +231 -53
  23. package/dist/default/define/live-video/scaffold-skin2.js +1 -1
  24. package/dist/default/define/live-video/skin.css +314 -49
  25. package/dist/default/define/live-video/skin2.js +1 -1
  26. package/dist/default/define/media/dailymotion-video.js +11 -0
  27. package/dist/default/define/media/mixcloud-audio.js +11 -0
  28. package/dist/default/define/media/soundcloud-audio.js +11 -0
  29. package/dist/default/define/ui/time-slider-markers.js +5 -0
  30. package/dist/default/define/video/compat-skin.css +242 -51
  31. package/dist/default/define/video/compat-skin2.js +1 -1
  32. package/dist/default/define/video/neutral-skin.css +382 -57
  33. package/dist/default/define/video/neutral-skin2.js +1 -1
  34. package/dist/default/define/video/scaffold-skin.css +240 -53
  35. package/dist/default/define/video/scaffold-skin2.js +1 -1
  36. package/dist/default/define/video/skin.css +415 -55
  37. package/dist/default/define/video/skin2.js +1 -1
  38. package/dist/default/extensions/google-analytics/extension.js +75 -0
  39. package/dist/default/extensions/mux-data/extension.js +7 -2
  40. package/dist/default/index.js +3 -1
  41. package/dist/default/internal/skins/compat-audio/skin.css +161 -41
  42. package/dist/default/internal/skins/compat-audio/template.js +1 -1
  43. package/dist/default/internal/skins/compat-live-audio/skin.css +143 -41
  44. package/dist/default/internal/skins/compat-live-video/skin.css +234 -51
  45. package/dist/default/internal/skins/compat-live-video/template.js +2 -0
  46. package/dist/default/internal/skins/compat-video/skin.css +242 -51
  47. package/dist/default/internal/skins/compat-video/template.js +2 -0
  48. package/dist/default/internal/skins/default-audio/skin.css +72 -34
  49. package/dist/default/internal/skins/default-live-audio/skin.css +55 -31
  50. package/dist/default/internal/skins/default-live-video/skin.css +314 -49
  51. package/dist/default/internal/skins/default-live-video/template.js +14 -0
  52. package/dist/default/internal/skins/default-video/register.js +3 -0
  53. package/dist/default/internal/skins/default-video/skin.css +415 -55
  54. package/dist/default/internal/skins/default-video/template.js +33 -5
  55. package/dist/default/internal/skins/neutral-audio/skin.css +73 -34
  56. package/dist/default/internal/skins/neutral-live-audio/skin.css +56 -31
  57. package/dist/default/internal/skins/neutral-live-video/skin.css +300 -46
  58. package/dist/default/internal/skins/neutral-live-video/template.js +14 -0
  59. package/dist/default/internal/skins/neutral-video/skin.css +382 -57
  60. package/dist/default/internal/skins/neutral-video/template.js +25 -11
  61. package/dist/default/internal/skins/scaffold-audio/skin.css +166 -42
  62. package/dist/default/internal/skins/scaffold-audio/template.js +1 -1
  63. package/dist/default/internal/skins/scaffold-live-audio/skin.css +148 -42
  64. package/dist/default/internal/skins/scaffold-live-video/skin.css +231 -53
  65. package/dist/default/internal/skins/scaffold-live-video/template.js +2 -0
  66. package/dist/default/internal/skins/scaffold-video/skin.css +240 -53
  67. package/dist/default/internal/skins/scaffold-video/template.js +3 -1
  68. package/dist/default/media/background-video/adapter.js +13 -35
  69. package/dist/default/media/background-video/base.js +95 -0
  70. package/dist/default/media/background-video/template.js +13 -20
  71. package/dist/default/media/dailymotion-video/adapter.js +31 -0
  72. package/dist/default/media/hls-background-video/adapter.js +23 -35
  73. package/dist/default/media/mixcloud-audio/adapter.js +16 -0
  74. package/dist/default/media/soundcloud-audio/adapter.js +16 -0
  75. package/dist/default/player/element.js +1 -5
  76. package/dist/default/presets/background/skin.js +2 -2
  77. package/dist/default/presets/skin.js +4 -3
  78. package/dist/default/ui/captions-radio-group/element.js +14 -1
  79. package/dist/default/ui/controls/element.js +6 -3
  80. package/dist/default/ui/dialog/element.js +13 -1
  81. package/dist/default/ui/gesture/element.js +3 -0
  82. package/dist/default/ui/media-button-element.js +10 -1
  83. package/dist/default/ui/playback-rate-button/element.js +7 -2
  84. package/dist/default/ui/playback-rate-radio-group/element.js +10 -3
  85. package/dist/default/ui/radio-options/controller.js +18 -5
  86. package/dist/default/ui/time-slider/markers.js +160 -0
  87. package/dist/default/version.js +1 -1
  88. package/dist/dev/define/audio/compat-skin.css +161 -41
  89. package/dist/dev/define/audio/compat-skin2.js +1 -1
  90. package/dist/dev/define/audio/compat-skin2.js.map +1 -1
  91. package/dist/dev/define/audio/neutral-skin.css +73 -34
  92. package/dist/dev/define/audio/neutral-skin2.js +1 -1
  93. package/dist/dev/define/audio/neutral-skin2.js.map +1 -1
  94. package/dist/dev/define/audio/scaffold-skin.css +166 -42
  95. package/dist/dev/define/audio/scaffold-skin2.js +1 -1
  96. package/dist/dev/define/audio/scaffold-skin2.js.map +1 -1
  97. package/dist/dev/define/audio/skin.css +72 -34
  98. package/dist/dev/define/audio/skin2.js +1 -1
  99. package/dist/dev/define/audio/skin2.js.map +1 -1
  100. package/dist/dev/define/extensions/google-analytics.d.ts +11 -0
  101. package/dist/dev/define/extensions/google-analytics.js +8 -0
  102. package/dist/dev/define/extensions/google-analytics.js.map +1 -0
  103. package/dist/dev/define/live-audio/compat-skin.css +143 -41
  104. package/dist/dev/define/live-audio/compat-skin2.js +1 -1
  105. package/dist/dev/define/live-audio/compat-skin2.js.map +1 -1
  106. package/dist/dev/define/live-audio/neutral-skin.css +56 -31
  107. package/dist/dev/define/live-audio/neutral-skin2.js +1 -1
  108. package/dist/dev/define/live-audio/neutral-skin2.js.map +1 -1
  109. package/dist/dev/define/live-audio/scaffold-skin.css +148 -42
  110. package/dist/dev/define/live-audio/scaffold-skin2.js +1 -1
  111. package/dist/dev/define/live-audio/scaffold-skin2.js.map +1 -1
  112. package/dist/dev/define/live-audio/skin.css +55 -31
  113. package/dist/dev/define/live-audio/skin2.js +1 -1
  114. package/dist/dev/define/live-audio/skin2.js.map +1 -1
  115. package/dist/dev/define/live-video/compat-skin.css +234 -51
  116. package/dist/dev/define/live-video/compat-skin2.js +1 -1
  117. package/dist/dev/define/live-video/compat-skin2.js.map +1 -1
  118. package/dist/dev/define/live-video/neutral-skin.css +300 -46
  119. package/dist/dev/define/live-video/neutral-skin2.js +1 -1
  120. package/dist/dev/define/live-video/neutral-skin2.js.map +1 -1
  121. package/dist/dev/define/live-video/scaffold-skin.css +231 -53
  122. package/dist/dev/define/live-video/scaffold-skin2.js +1 -1
  123. package/dist/dev/define/live-video/scaffold-skin2.js.map +1 -1
  124. package/dist/dev/define/live-video/skin.css +314 -49
  125. package/dist/dev/define/live-video/skin2.js +1 -1
  126. package/dist/dev/define/live-video/skin2.js.map +1 -1
  127. package/dist/dev/define/media/dailymotion-video.d.ts +15 -0
  128. package/dist/dev/define/media/dailymotion-video.js +13 -0
  129. package/dist/dev/define/media/dailymotion-video.js.map +1 -0
  130. package/dist/dev/define/media/mixcloud-audio.d.ts +15 -0
  131. package/dist/dev/define/media/mixcloud-audio.js +13 -0
  132. package/dist/dev/define/media/mixcloud-audio.js.map +1 -0
  133. package/dist/dev/define/media/soundcloud-audio.d.ts +15 -0
  134. package/dist/dev/define/media/soundcloud-audio.js +13 -0
  135. package/dist/dev/define/media/soundcloud-audio.js.map +1 -0
  136. package/dist/dev/define/ui/time-slider-markers.d.ts +8 -0
  137. package/dist/dev/define/ui/time-slider-markers.js +7 -0
  138. package/dist/dev/define/ui/time-slider-markers.js.map +1 -0
  139. package/dist/dev/define/video/compat-skin.css +242 -51
  140. package/dist/dev/define/video/compat-skin2.js +1 -1
  141. package/dist/dev/define/video/compat-skin2.js.map +1 -1
  142. package/dist/dev/define/video/neutral-skin.css +382 -57
  143. package/dist/dev/define/video/neutral-skin2.js +1 -1
  144. package/dist/dev/define/video/neutral-skin2.js.map +1 -1
  145. package/dist/dev/define/video/scaffold-skin.css +240 -53
  146. package/dist/dev/define/video/scaffold-skin2.js +1 -1
  147. package/dist/dev/define/video/scaffold-skin2.js.map +1 -1
  148. package/dist/dev/define/video/skin.css +415 -55
  149. package/dist/dev/define/video/skin2.js +1 -1
  150. package/dist/dev/define/video/skin2.js.map +1 -1
  151. package/dist/dev/extensions/google-analytics/extension.d.ts +52 -0
  152. package/dist/dev/extensions/google-analytics/extension.js +77 -0
  153. package/dist/dev/extensions/google-analytics/extension.js.map +1 -0
  154. package/dist/dev/extensions/google-analytics/index.d.ts +2 -0
  155. package/dist/dev/extensions/mux-data/extension.d.ts +4 -1
  156. package/dist/dev/extensions/mux-data/extension.js +7 -2
  157. package/dist/dev/extensions/mux-data/extension.js.map +1 -1
  158. package/dist/dev/index.d.ts +5 -4
  159. package/dist/dev/index.js +3 -1
  160. package/dist/dev/internal/skins/compat-audio/skin.css +161 -41
  161. package/dist/dev/internal/skins/compat-audio/template.js +1 -1
  162. package/dist/dev/internal/skins/compat-audio/template.js.map +1 -1
  163. package/dist/dev/internal/skins/compat-live-audio/skin.css +143 -41
  164. package/dist/dev/internal/skins/compat-live-video/skin.css +234 -51
  165. package/dist/dev/internal/skins/compat-live-video/template.js +2 -0
  166. package/dist/dev/internal/skins/compat-live-video/template.js.map +1 -1
  167. package/dist/dev/internal/skins/compat-video/skin.css +242 -51
  168. package/dist/dev/internal/skins/compat-video/template.js +2 -0
  169. package/dist/dev/internal/skins/compat-video/template.js.map +1 -1
  170. package/dist/dev/internal/skins/default-audio/skin.css +72 -34
  171. package/dist/dev/internal/skins/default-live-audio/skin.css +55 -31
  172. package/dist/dev/internal/skins/default-live-video/register.js.map +1 -1
  173. package/dist/dev/internal/skins/default-live-video/skin.css +314 -49
  174. package/dist/dev/internal/skins/default-live-video/template.js +14 -0
  175. package/dist/dev/internal/skins/default-live-video/template.js.map +1 -1
  176. package/dist/dev/internal/skins/default-video/register.d.ts +1 -0
  177. package/dist/dev/internal/skins/default-video/register.js +3 -0
  178. package/dist/dev/internal/skins/default-video/register.js.map +1 -1
  179. package/dist/dev/internal/skins/default-video/skin.css +415 -55
  180. package/dist/dev/internal/skins/default-video/template.js +33 -5
  181. package/dist/dev/internal/skins/default-video/template.js.map +1 -1
  182. package/dist/dev/internal/skins/neutral-audio/skin.css +73 -34
  183. package/dist/dev/internal/skins/neutral-live-audio/skin.css +56 -31
  184. package/dist/dev/internal/skins/neutral-live-video/register.js.map +1 -1
  185. package/dist/dev/internal/skins/neutral-live-video/skin.css +300 -46
  186. package/dist/dev/internal/skins/neutral-live-video/template.js +14 -0
  187. package/dist/dev/internal/skins/neutral-live-video/template.js.map +1 -1
  188. package/dist/dev/internal/skins/neutral-video/register.js.map +1 -1
  189. package/dist/dev/internal/skins/neutral-video/skin.css +382 -57
  190. package/dist/dev/internal/skins/neutral-video/template.js +25 -11
  191. package/dist/dev/internal/skins/neutral-video/template.js.map +1 -1
  192. package/dist/dev/internal/skins/scaffold-audio/skin.css +166 -42
  193. package/dist/dev/internal/skins/scaffold-audio/template.js +1 -1
  194. package/dist/dev/internal/skins/scaffold-audio/template.js.map +1 -1
  195. package/dist/dev/internal/skins/scaffold-live-audio/skin.css +148 -42
  196. package/dist/dev/internal/skins/scaffold-live-video/skin.css +231 -53
  197. package/dist/dev/internal/skins/scaffold-live-video/template.js +2 -0
  198. package/dist/dev/internal/skins/scaffold-live-video/template.js.map +1 -1
  199. package/dist/dev/internal/skins/scaffold-video/skin.css +240 -53
  200. package/dist/dev/internal/skins/scaffold-video/template.js +3 -1
  201. package/dist/dev/internal/skins/scaffold-video/template.js.map +1 -1
  202. package/dist/dev/media/background-video/adapter.d.ts +7 -13
  203. package/dist/dev/media/background-video/adapter.js +13 -35
  204. package/dist/dev/media/background-video/adapter.js.map +1 -1
  205. package/dist/dev/media/background-video/base.d.ts +44 -0
  206. package/dist/dev/media/background-video/base.js +97 -0
  207. package/dist/dev/media/background-video/base.js.map +1 -0
  208. package/dist/dev/media/background-video/template.d.ts +3 -0
  209. package/dist/dev/media/background-video/template.js +13 -20
  210. package/dist/dev/media/background-video/template.js.map +1 -1
  211. package/dist/dev/media/dailymotion-video/adapter.d.ts +17 -0
  212. package/dist/dev/media/dailymotion-video/adapter.js +47 -0
  213. package/dist/dev/media/dailymotion-video/adapter.js.map +1 -0
  214. package/dist/dev/media/dailymotion-video/index.d.ts +2 -0
  215. package/dist/dev/media/hls-background-video/adapter.d.ts +14 -19
  216. package/dist/dev/media/hls-background-video/adapter.js +23 -35
  217. package/dist/dev/media/hls-background-video/adapter.js.map +1 -1
  218. package/dist/dev/media/mixcloud-audio/adapter.d.ts +11 -0
  219. package/dist/dev/media/mixcloud-audio/adapter.js +52 -0
  220. package/dist/dev/media/mixcloud-audio/adapter.js.map +1 -0
  221. package/dist/dev/media/mixcloud-audio/index.d.ts +2 -0
  222. package/dist/dev/media/soundcloud-audio/adapter.d.ts +11 -0
  223. package/dist/dev/media/soundcloud-audio/adapter.js +53 -0
  224. package/dist/dev/media/soundcloud-audio/adapter.js.map +1 -0
  225. package/dist/dev/media/soundcloud-audio/index.d.ts +2 -0
  226. package/dist/dev/player/element.js +1 -5
  227. package/dist/dev/player/element.js.map +1 -1
  228. package/dist/dev/presets/background/skin.d.ts +2 -1
  229. package/dist/dev/presets/background/skin.js +2 -2
  230. package/dist/dev/presets/background/skin.js.map +1 -1
  231. package/dist/dev/presets/skin.d.ts +4 -3
  232. package/dist/dev/presets/skin.js +4 -3
  233. package/dist/dev/presets/skin.js.map +1 -1
  234. package/dist/dev/ui/captions-button/element.d.ts +2 -0
  235. package/dist/dev/ui/captions-radio-group/element.d.ts +2 -1
  236. package/dist/dev/ui/captions-radio-group/element.js +14 -1
  237. package/dist/dev/ui/captions-radio-group/element.js.map +1 -1
  238. package/dist/dev/ui/controls/element.d.ts +2 -2
  239. package/dist/dev/ui/controls/element.js +6 -3
  240. package/dist/dev/ui/controls/element.js.map +1 -1
  241. package/dist/dev/ui/dialog/element.js +13 -1
  242. package/dist/dev/ui/dialog/element.js.map +1 -1
  243. package/dist/dev/ui/gesture/element.js +3 -0
  244. package/dist/dev/ui/gesture/element.js.map +1 -1
  245. package/dist/dev/ui/media-button-element.d.ts +9 -0
  246. package/dist/dev/ui/media-button-element.js +10 -1
  247. package/dist/dev/ui/media-button-element.js.map +1 -1
  248. package/dist/dev/ui/play-button/element.d.ts +2 -0
  249. package/dist/dev/ui/playback-rate-button/element.d.ts +9 -1
  250. package/dist/dev/ui/playback-rate-button/element.js +7 -2
  251. package/dist/dev/ui/playback-rate-button/element.js.map +1 -1
  252. package/dist/dev/ui/playback-rate-radio-group/element.d.ts +4 -0
  253. package/dist/dev/ui/playback-rate-radio-group/element.js +10 -3
  254. package/dist/dev/ui/playback-rate-radio-group/element.js.map +1 -1
  255. package/dist/dev/ui/radio-options/controller.js +18 -5
  256. package/dist/dev/ui/radio-options/controller.js.map +1 -1
  257. package/dist/dev/ui/time-slider/markers.d.ts +48 -0
  258. package/dist/dev/ui/time-slider/markers.js +165 -0
  259. package/dist/dev/ui/time-slider/markers.js.map +1 -0
  260. package/dist/dev/version.js +1 -1
  261. package/docs/README.md +1 -1
  262. package/docs/guides/autoplay.md +2 -2
  263. package/docs/guides/background-video.md +2 -4
  264. package/docs/guides/browser-support.md +23 -15
  265. package/docs/guides/build-your-own-skin.md +3 -1
  266. package/docs/guides/cdn.md +10 -9
  267. package/docs/guides/content-security-policy.md +3 -0
  268. package/docs/guides/customize-skins.md +7 -2
  269. package/docs/guides/features.md +1 -1
  270. package/docs/guides/google-analytics.md +199 -0
  271. package/docs/guides/installation-cdn.md +4 -4
  272. package/docs/guides/installation-shadcn.md +3 -3
  273. package/docs/guides/installation-svelte.md +4 -4
  274. package/docs/guides/installation-vue.md +4 -4
  275. package/docs/guides/installation.md +4 -4
  276. package/docs/guides/internationalization.md +5 -5
  277. package/docs/guides/keyboard-shortcuts.md +11 -1
  278. package/docs/guides/llms.txt +3 -1
  279. package/docs/guides/media-sources.md +3 -0
  280. package/docs/guides/migrate-from-media-chrome.md +4 -4
  281. package/docs/guides/migrate-from-mux-player.md +9 -9
  282. package/docs/guides/migrate-from-plyr.md +8 -8
  283. package/docs/guides/migrate-from-video-js-8.md +8 -9
  284. package/docs/guides/migrate-from-vidstack.md +10 -10
  285. package/docs/guides/mux-data.md +22 -0
  286. package/docs/guides/presets.md +5 -5
  287. package/docs/guides/self-hosting.md +5 -5
  288. package/docs/guides/skins.md +21 -5
  289. package/docs/guides/timed-metadata.md +318 -0
  290. package/docs/guides/ui-components.md +1 -1
  291. package/docs/llms.txt +25 -6
  292. package/docs/reference/api/chapters-track.md +71 -0
  293. package/docs/reference/api/define-text-track.md +113 -0
  294. package/docs/reference/api/feature-playback-rate.md +54 -3
  295. package/docs/reference/api/feature-playback.md +5 -1
  296. package/docs/reference/api/feature-text-cues.md +133 -0
  297. package/docs/reference/api/feature-text-tracks.md +11 -5
  298. package/docs/reference/api/get-active-text-cues.md +69 -0
  299. package/docs/reference/api/get-text-cues.md +69 -0
  300. package/docs/reference/api/get-text-tracks.md +41 -0
  301. package/docs/reference/api/hls-metadata-track.md +48 -0
  302. package/docs/reference/api/html-create-player.md +2 -2
  303. package/docs/reference/api/llms.txt +13 -1
  304. package/docs/reference/api/media-capabilities.md +4 -4
  305. package/docs/reference/api/player-store.md +15 -7
  306. package/docs/reference/api/preset-audio.md +15 -5
  307. package/docs/reference/api/preset-background.md +2 -2
  308. package/docs/reference/api/preset-live-audio.md +15 -5
  309. package/docs/reference/api/preset-live-video.md +14 -5
  310. package/docs/reference/api/preset-video.md +14 -5
  311. package/docs/reference/api/resolve-adapter-type.md +4 -1
  312. package/docs/reference/api/ui-element.md +2 -2
  313. package/docs/reference/components/alert-dialog.md +21 -16
  314. package/docs/reference/components/audio-compat-skin.md +8 -2
  315. package/docs/reference/components/audio-neutral-skin.md +2 -2
  316. package/docs/reference/components/audio-scaffold-skin.md +2 -2
  317. package/docs/reference/components/audio-skin.md +2 -2
  318. package/docs/reference/components/audio-track-radio-group.md +2 -2
  319. package/docs/reference/components/audio.md +19 -1
  320. package/docs/reference/components/background-video-skin.md +1 -1
  321. package/docs/reference/components/background-video.md +26 -11
  322. package/docs/reference/components/buffering-indicator.md +2 -2
  323. package/docs/reference/components/captions-radio-group.md +3 -3
  324. package/docs/reference/components/cloudflare-video.md +1 -1
  325. package/docs/reference/components/controls.md +16 -3
  326. package/docs/reference/components/dailymotion-video.md +192 -0
  327. package/docs/reference/components/dash-video.md +20 -2
  328. package/docs/reference/components/dialog.md +49 -22
  329. package/docs/reference/components/error-dialog.md +13 -10
  330. package/docs/reference/components/gesture.md +2 -2
  331. package/docs/reference/components/google-analytics.md +51 -0
  332. package/docs/reference/components/google-cast.md +1 -1
  333. package/docs/reference/components/hls-audio.md +56 -2
  334. package/docs/reference/components/hls-background-video.md +25 -5
  335. package/docs/reference/components/hls-video.md +99 -7
  336. package/docs/reference/components/hlsjs-video.md +53 -12
  337. package/docs/reference/components/hotkey.md +2 -2
  338. package/docs/reference/components/live-audio-compat-skin.md +8 -2
  339. package/docs/reference/components/live-audio-neutral-skin.md +2 -2
  340. package/docs/reference/components/live-audio-scaffold-skin.md +2 -2
  341. package/docs/reference/components/live-audio-skin.md +2 -2
  342. package/docs/reference/components/live-video-compat-skin.md +12 -2
  343. package/docs/reference/components/live-video-neutral-skin.md +6 -2
  344. package/docs/reference/components/live-video-scaffold-skin.md +6 -2
  345. package/docs/reference/components/live-video-skin.md +6 -2
  346. package/docs/reference/components/llms.txt +10 -5
  347. package/docs/reference/components/mixcloud-audio.md +203 -0
  348. package/docs/reference/components/mux-audio.md +35 -2
  349. package/docs/reference/components/mux-background-video.md +1 -1
  350. package/docs/reference/components/mux-data.md +20 -2
  351. package/docs/reference/components/mux-video.md +18 -2
  352. package/docs/reference/components/native-hls-video.md +32 -2
  353. package/docs/reference/components/play-button.md +7 -5
  354. package/docs/reference/components/playback-rate-button.md +26 -7
  355. package/docs/reference/components/playback-rate-radio-group.md +7 -4
  356. package/docs/reference/components/player.md +3 -3
  357. package/docs/reference/components/playerjs-video.md +2 -2
  358. package/docs/reference/components/quality-radio-group.md +2 -2
  359. package/docs/reference/components/shaka-video.md +2 -2
  360. package/docs/reference/components/soundcloud-audio.md +227 -0
  361. package/docs/reference/components/spotify-audio.md +3 -3
  362. package/docs/reference/components/tiktok-video.md +3 -3
  363. package/docs/reference/components/time-slider-markers.md +194 -0
  364. package/docs/reference/components/time-slider.md +14 -0
  365. package/docs/reference/components/twitch-video.md +2 -3
  366. package/docs/reference/components/video-compat-skin.md +12 -2
  367. package/docs/reference/components/video-neutral-skin.md +6 -2
  368. package/docs/reference/components/video-scaffold-skin.md +6 -2
  369. package/docs/reference/components/video-skin.md +6 -2
  370. package/docs/reference/components/video.md +19 -1
  371. package/docs/reference/components/vimeo-video.md +1 -1
  372. package/docs/reference/components/wistia-video.md +1 -1
  373. package/docs/reference/components/youtube-video.md +1 -1
  374. package/package.json +60 -40
@@ -3,13 +3,13 @@ import { PlayerController } from "../../player/controller.js";
3
3
  import { UIElement } from "../ui-element.js";
4
4
  import { controlsContext } from "./context.js";
5
5
  import { ControlsCore, ControlsDataAttrs, POPUP_HOST_SELECTOR } from "@videojs/core";
6
- import { applyStateDataAttrs, logMissingFeature, selectControls } from "@videojs/core/dom";
6
+ import { applyStateDataAttrs, logMissingFeature, selectControls, selectPlayback } from "@videojs/core/dom";
7
7
  import { ContextProvider } from "@videojs/element/context";
8
8
  import { isFunction } from "@videojs/utils/predicate";
9
9
  //#region src/ui/controls/element.ts
10
10
  /**
11
- * Tracks controls visibility, reflects it as data attributes, and shares it with its descendant controls parts. Hiding
12
- * the controls closes any popup open inside it.
11
+ * Tracks controls visibility and whether playback has started, reflects them as data attributes, and shares them with
12
+ * its descendant controls parts. Hiding the controls closes any popup open inside it.
13
13
  */
14
14
  var ControlsElement = class extends UIElement {
15
15
  constructor(..._args) {
@@ -17,6 +17,7 @@ var ControlsElement = class extends UIElement {
17
17
  this.visibility = ControlsCore.defaultProps.visibility;
18
18
  this.#core = new ControlsCore();
19
19
  this.#mediaState = new PlayerController(this, playerContext, selectControls);
20
+ this.#playback = new PlayerController(this, playerContext, selectPlayback);
20
21
  this.#provider = new ContextProvider(this, { context: controlsContext });
21
22
  this.#visible = true;
22
23
  }
@@ -28,6 +29,7 @@ var ControlsElement = class extends UIElement {
28
29
  }
29
30
  #core;
30
31
  #mediaState;
32
+ #playback;
31
33
  #provider;
32
34
  #visible;
33
35
  connectedCallback() {
@@ -38,6 +40,7 @@ var ControlsElement = class extends UIElement {
38
40
  super.update(_changed);
39
41
  this.#core.setProps({ visibility: this.visibility });
40
42
  this.#core.setMedia(this.#mediaState.value ?? null);
43
+ this.#core.setPlayback(this.#playback.value ?? null);
41
44
  const state = this.#core.getState();
42
45
  if (!state) return;
43
46
  applyStateDataAttrs(this, state, ControlsDataAttrs);
@@ -1 +1 @@
1
- {"version":3,"file":"element.js","names":[],"sources":["../../../../src/ui/controls/element.ts"],"sourcesContent":["import { ControlsCore, ControlsDataAttrs, type ControlsVisibility, POPUP_HOST_SELECTOR } from '@videojs/core';\nimport { applyStateDataAttrs, logMissingFeature, selectControls } from '@videojs/core/dom';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport { ContextProvider } from '@videojs/element/context';\nimport { isFunction } from '@videojs/utils/predicate';\n\nimport { playerContext } from '../../player/context';\nimport { PlayerController } from '../../player/controller';\nimport { UIElement } from '../ui-element';\nimport { controlsContext } from './context';\n\n/**\n * Tracks controls visibility, reflects it as data attributes, and shares it with its descendant controls parts. Hiding\n * the controls closes any popup open inside it.\n */\nexport class ControlsElement extends UIElement {\n static readonly tagName = 'media-controls';\n\n static override properties = {\n visibility: { type: String },\n } satisfies PropertyDeclarationMap<'visibility'>;\n\n visibility: ControlsVisibility = ControlsCore.defaultProps.visibility;\n\n readonly #core = new ControlsCore();\n readonly #mediaState = new PlayerController(this, playerContext, selectControls);\n readonly #provider = new ContextProvider(this, { context: controlsContext });\n #visible = true;\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n if (__DEV__ && this.visibility === 'auto' && !this.#mediaState.value && this.#mediaState.displayName) {\n logMissingFeature(this.localName, this.#mediaState.displayName);\n }\n }\n\n protected override update(_changed: PropertyValues): void {\n super.update(_changed);\n\n this.#core.setProps({ visibility: this.visibility });\n this.#core.setMedia(this.#mediaState.value ?? null);\n\n const state = this.#core.getState();\n if (!state) return;\n\n applyStateDataAttrs(this, state, ControlsDataAttrs);\n\n this.#provider.setValue({\n state,\n stateAttrMap: ControlsDataAttrs,\n });\n\n const wasVisible = this.#visible;\n\n this.#visible = state.visible;\n\n if (wasVisible && !state.visible) {\n this.#closeOwnedOverlays();\n }\n }\n\n #closeOwnedOverlays(): void {\n for (const element of this.querySelectorAll(POPUP_HOST_SELECTOR)) {\n const host = element as Element & { close?: unknown };\n if (!isFunction(host.close)) continue;\n\n host.close('imperative-action');\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,IAAa,kBAAb,cAAqC,UAAU;;;EAOZ,KAAA,aAAA,aAAa,aAAa;EAE1C,KAAA,QAAA,IAAI,aAAa;EACX,KAAA,cAAA,IAAI,iBAAiB,MAAM,eAAe,cAAc;EAC1D,KAAA,YAAA,IAAI,gBAAgB,MAAM,EAAE,SAAS,gBAAgB,CAAC;EAChE,KAAA,WAAA;;;EAXe,KAAA,UAAA;;;EAEG,KAAA,aAAA,EAC3B,YAAY,EAAE,MAAM,OAAO,EAC7B;;CAIA;CACA;CACA;CACA;CAEA,oBAAmC;EACjC,MAAM,kBAAkB;EAExB,IAAe,KAAK,eAAe,UAAU,CAAC,KAAK,YAAY,SAAS,KAAK,YAAY,aACvF,kBAAkB,KAAK,WAAW,KAAK,YAAY,WAAW;CAElE;CAEA,OAA0B,UAAgC;EACxD,MAAM,OAAO,QAAQ;EAErB,KAAK,MAAM,SAAS,EAAE,YAAY,KAAK,WAAW,CAAC;EACnD,KAAK,MAAM,SAAS,KAAK,YAAY,SAAS,IAAI;EAElD,MAAM,QAAQ,KAAK,MAAM,SAAS;EAClC,IAAI,CAAC,OAAO;EAEZ,oBAAoB,MAAM,OAAO,iBAAiB;EAElD,KAAK,UAAU,SAAS;GACtB;GACA,cAAc;EAChB,CAAC;EAED,MAAM,aAAa,KAAK;EAExB,KAAK,WAAW,MAAM;EAEtB,IAAI,cAAc,CAAC,MAAM,SACvB,KAAK,oBAAoB;CAE7B;CAEA,sBAA4B;EAC1B,KAAK,MAAM,WAAW,KAAK,iBAAiB,mBAAmB,GAAG;GAChE,MAAM,OAAO;GACb,IAAI,CAAC,WAAW,KAAK,KAAK,GAAG;GAE7B,KAAK,MAAM,mBAAmB;EAChC;CACF;AACF"}
1
+ {"version":3,"file":"element.js","names":[],"sources":["../../../../src/ui/controls/element.ts"],"sourcesContent":["import { ControlsCore, ControlsDataAttrs, type ControlsVisibility, POPUP_HOST_SELECTOR } from '@videojs/core';\nimport { applyStateDataAttrs, logMissingFeature, selectControls, selectPlayback } from '@videojs/core/dom';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport { ContextProvider } from '@videojs/element/context';\nimport { isFunction } from '@videojs/utils/predicate';\n\nimport { playerContext } from '../../player/context';\nimport { PlayerController } from '../../player/controller';\nimport { UIElement } from '../ui-element';\nimport { controlsContext } from './context';\n\n/**\n * Tracks controls visibility and whether playback has started, reflects them as data attributes, and shares them with\n * its descendant controls parts. Hiding the controls closes any popup open inside it.\n */\nexport class ControlsElement extends UIElement {\n static readonly tagName = 'media-controls';\n\n static override properties = {\n visibility: { type: String },\n } satisfies PropertyDeclarationMap<'visibility'>;\n\n visibility: ControlsVisibility = ControlsCore.defaultProps.visibility;\n\n readonly #core = new ControlsCore();\n readonly #mediaState = new PlayerController(this, playerContext, selectControls);\n readonly #playback = new PlayerController(this, playerContext, selectPlayback);\n readonly #provider = new ContextProvider(this, { context: controlsContext });\n #visible = true;\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n if (__DEV__ && this.visibility === 'auto' && !this.#mediaState.value && this.#mediaState.displayName) {\n logMissingFeature(this.localName, this.#mediaState.displayName);\n }\n }\n\n protected override update(_changed: PropertyValues): void {\n super.update(_changed);\n\n this.#core.setProps({ visibility: this.visibility });\n this.#core.setMedia(this.#mediaState.value ?? null);\n this.#core.setPlayback(this.#playback.value ?? null);\n\n const state = this.#core.getState();\n if (!state) return;\n\n applyStateDataAttrs(this, state, ControlsDataAttrs);\n\n this.#provider.setValue({\n state,\n stateAttrMap: ControlsDataAttrs,\n });\n\n const wasVisible = this.#visible;\n\n this.#visible = state.visible;\n\n if (wasVisible && !state.visible) {\n this.#closeOwnedOverlays();\n }\n }\n\n #closeOwnedOverlays(): void {\n for (const element of this.querySelectorAll(POPUP_HOST_SELECTOR)) {\n const host = element as Element & { close?: unknown };\n if (!isFunction(host.close)) continue;\n\n host.close('imperative-action');\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,IAAa,kBAAb,cAAqC,UAAU;;;EAOZ,KAAA,aAAA,aAAa,aAAa;EAE1C,KAAA,QAAA,IAAI,aAAa;EACX,KAAA,cAAA,IAAI,iBAAiB,MAAM,eAAe,cAAc;EAC1D,KAAA,YAAA,IAAI,iBAAiB,MAAM,eAAe,cAAc;EACxD,KAAA,YAAA,IAAI,gBAAgB,MAAM,EAAE,SAAS,gBAAgB,CAAC;EAChE,KAAA,WAAA;;;EAZe,KAAA,UAAA;;;EAEG,KAAA,aAAA,EAC3B,YAAY,EAAE,MAAM,OAAO,EAC7B;;CAIA;CACA;CACA;CACA;CACA;CAEA,oBAAmC;EACjC,MAAM,kBAAkB;EAExB,IAAe,KAAK,eAAe,UAAU,CAAC,KAAK,YAAY,SAAS,KAAK,YAAY,aACvF,kBAAkB,KAAK,WAAW,KAAK,YAAY,WAAW;CAElE;CAEA,OAA0B,UAAgC;EACxD,MAAM,OAAO,QAAQ;EAErB,KAAK,MAAM,SAAS,EAAE,YAAY,KAAK,WAAW,CAAC;EACnD,KAAK,MAAM,SAAS,KAAK,YAAY,SAAS,IAAI;EAClD,KAAK,MAAM,YAAY,KAAK,UAAU,SAAS,IAAI;EAEnD,MAAM,QAAQ,KAAK,MAAM,SAAS;EAClC,IAAI,CAAC,OAAO;EAEZ,oBAAoB,MAAM,OAAO,iBAAiB;EAElD,KAAK,UAAU,SAAS;GACtB;GACA,cAAc;EAChB,CAAC;EAED,MAAM,aAAa,KAAK;EAExB,KAAK,WAAW,MAAM;EAEtB,IAAI,cAAc,CAAC,MAAM,SACvB,KAAK,oBAAoB;CAE7B;CAEA,sBAA4B;EAC1B,KAAK,MAAM,WAAW,KAAK,iBAAiB,mBAAmB,GAAG;GAChE,MAAM,OAAO;GACb,IAAI,CAAC,WAAW,KAAK,KAAK,GAAG;GAE7B,KAAK,MAAM,mBAAmB;EAChC;CACF;AACF"}
@@ -1,9 +1,10 @@
1
+ import { containerContext } from "../../player/context.js";
1
2
  import { UIElement } from "../ui-element.js";
2
3
  import { PositionController } from "../position-controller.js";
3
4
  import { dialogContext } from "./context.js";
4
5
  import { DialogCore, DialogDataAttrs } from "@videojs/core";
5
6
  import { applyElementProps, applyStateDataAttrs, createDialog, createTransition } from "@videojs/core/dom";
6
- import { ContextProvider } from "@videojs/element/context";
7
+ import { ContextConsumer, ContextProvider } from "@videojs/element/context";
7
8
  import { SnapshotController } from "@videojs/store/html";
8
9
  //#region src/ui/dialog/element.ts
9
10
  let idCounter = 0;
@@ -25,12 +26,14 @@ var DialogElementBase = class extends UIElement {
25
26
  #stateAttrMap;
26
27
  #provider;
27
28
  #position;
29
+ #container;
28
30
  #popupId;
29
31
  #titleId;
30
32
  #descriptionId;
31
33
  #bindTrigger;
32
34
  #dialog;
33
35
  #snapshot;
36
+ #modalitySnapshot;
34
37
  #triggerAbort;
35
38
  #triggerElement;
36
39
  constructor({ core = new DialogCore(), stateAttrMap = DialogDataAttrs, idPrefix = "dialog", bindTrigger = true } = {}) {
@@ -40,8 +43,13 @@ var DialogElementBase = class extends UIElement {
40
43
  this.closeOnEscape = DialogCore.defaultProps.closeOnEscape;
41
44
  this.#provider = new ContextProvider(this, { context: dialogContext });
42
45
  this.#position = new PositionController(this);
46
+ this.#container = new ContextConsumer(this, {
47
+ context: containerContext,
48
+ subscribe: true
49
+ });
43
50
  this.#dialog = null;
44
51
  this.#snapshot = null;
52
+ this.#modalitySnapshot = null;
45
53
  this.#triggerAbort = null;
46
54
  this.#triggerElement = null;
47
55
  this.#core = core;
@@ -69,6 +77,8 @@ var DialogElementBase = class extends UIElement {
69
77
  });
70
78
  if (this.#snapshot) this.#snapshot.track(this.#dialog.input);
71
79
  else this.#snapshot = new SnapshotController(this, this.#dialog.input);
80
+ if (this.#modalitySnapshot) this.#modalitySnapshot.track(this.#dialog.modality);
81
+ else this.#modalitySnapshot = new SnapshotController(this, this.#dialog.modality);
72
82
  }
73
83
  firstUpdated(changed) {
74
84
  super.firstUpdated(changed);
@@ -86,6 +96,7 @@ var DialogElementBase = class extends UIElement {
86
96
  willUpdate(changed) {
87
97
  super.willUpdate(changed);
88
98
  this.#core.setProps(this);
99
+ this.#dialog?.setInteractionRoot(this.#container.value?.container ?? null);
89
100
  if (this.#dialog && changed.has("open")) {
90
101
  const { active: inputOpen } = this.#dialog.input.current;
91
102
  if (this.open !== inputOpen) {
@@ -101,6 +112,7 @@ var DialogElementBase = class extends UIElement {
101
112
  this.#syncTrigger(triggerElement);
102
113
  const input = this.#dialog.input.current;
103
114
  this.#core.setInput(input);
115
+ this.#core.setDocumentModal(this.#dialog.modality.current.documentModal);
104
116
  const state = this.#core.getState();
105
117
  applyStateDataAttrs(this, state, this.#stateAttrMap);
106
118
  if (this.#triggerElement) applyElementProps(this.#triggerElement, this.#core.getTriggerAttrs(state, this.#popupId));
@@ -1 +1 @@
1
- {"version":3,"file":"element.js","names":[],"sources":["../../../../src/ui/dialog/element.ts"],"sourcesContent":["import {\n DialogCore,\n DialogDataAttrs,\n type DialogInput,\n type DialogState,\n type StateAttrMap,\n type DialogProps,\n} from '@videojs/core';\nimport {\n applyElementProps,\n applyStateDataAttrs,\n createDialog,\n createTransition,\n type DialogApi,\n} from '@videojs/core/dom';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport { ContextProvider } from '@videojs/element/context';\nimport { SnapshotController } from '@videojs/store/html';\n\nimport { PositionController } from '../position-controller';\nimport { UIElement } from '../ui-element';\nimport { dialogContext } from './context';\n\nlet idCounter = 0;\n\nexport interface DialogElementBaseOptions {\n /** @internal */\n core?: DialogCore;\n /** @internal */\n stateAttrMap?: StateAttrMap<DialogState>;\n idPrefix?: string;\n bindTrigger?: boolean;\n}\n\nexport class DialogElementBase extends UIElement {\n static override properties = {\n open: { type: Boolean },\n defaultOpen: { type: Boolean, attribute: 'default-open' },\n closeOnEscape: { type: Boolean, attribute: 'close-on-escape' },\n } satisfies PropertyDeclarationMap<keyof DialogProps>;\n\n open = DialogCore.defaultProps.open;\n defaultOpen = DialogCore.defaultProps.defaultOpen;\n closeOnEscape = DialogCore.defaultProps.closeOnEscape;\n\n readonly #core: DialogCore;\n readonly #stateAttrMap: StateAttrMap<DialogState>;\n readonly #provider = new ContextProvider(this, { context: dialogContext });\n readonly #position = new PositionController(this);\n readonly #popupId: string;\n readonly #titleId: string;\n readonly #descriptionId: string;\n readonly #bindTrigger: boolean;\n\n #dialog: DialogApi | null = null;\n #snapshot: SnapshotController<DialogInput> | null = null;\n #triggerAbort: AbortController | null = null;\n #triggerElement: HTMLElement | null = null;\n\n constructor({\n core = new DialogCore(),\n stateAttrMap = DialogDataAttrs,\n idPrefix = 'dialog',\n bindTrigger = true,\n }: DialogElementBaseOptions = {}) {\n super();\n this.#core = core;\n this.#stateAttrMap = stateAttrMap;\n this.#bindTrigger = bindTrigger;\n this.#popupId = `vjs-${idPrefix}-popup-${idCounter++}`;\n this.#titleId = `vjs-${idPrefix}-title-${idCounter++}`;\n this.#descriptionId = `vjs-${idPrefix}-desc-${idCounter++}`;\n this.#core.setTitleId(this.#titleId);\n this.#core.setDescriptionId(this.#descriptionId);\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n if (this.destroyed) return;\n\n this.#dialog = createDialog({\n transition: createTransition(),\n closeOnEscape: () => this.closeOnEscape,\n onOpenChange: (nextOpen: boolean) => {\n this.open = nextOpen;\n this.dispatchEvent(new CustomEvent('open-change', { detail: { open: nextOpen } }));\n },\n onOpenChangeComplete: (nextOpen: boolean) => {\n this.dispatchEvent(new CustomEvent('open-change-complete', { detail: { open: nextOpen } }));\n },\n });\n\n if (this.#snapshot) {\n this.#snapshot.track(this.#dialog.input);\n } else {\n this.#snapshot = new SnapshotController(this, this.#dialog.input);\n }\n }\n\n protected override firstUpdated(changed: PropertyValues): void {\n super.firstUpdated(changed);\n\n if (this.defaultOpen && !this.open) this.#dialog?.open();\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.#cleanupTrigger();\n this.#dialog?.destroy();\n this.#dialog = null;\n }\n\n close(): void {\n this.#dialog?.close();\n }\n\n protected override willUpdate(changed: PropertyValues): void {\n super.willUpdate(changed);\n this.#core.setProps(this);\n\n if (this.#dialog && changed.has('open')) {\n const { active: inputOpen } = this.#dialog.input.current;\n\n if (this.open !== inputOpen) {\n if (this.open) this.#dialog.open();\n else this.#dialog.close();\n }\n }\n }\n\n protected override update(_changed: PropertyValues): void {\n super.update(_changed);\n\n if (!this.#dialog) return;\n\n const triggerElement = this.#bindTrigger ? this.#position.findTrigger() : null;\n\n this.#syncTrigger(triggerElement);\n\n const input = this.#dialog.input.current;\n\n this.#core.setInput(input);\n const state = this.#core.getState();\n\n applyStateDataAttrs(this, state, this.#stateAttrMap);\n\n if (this.#triggerElement) {\n applyElementProps(this.#triggerElement, this.#core.getTriggerAttrs(state, this.#popupId));\n }\n\n this.#provider.setValue({\n state,\n stateAttrMap: this.#stateAttrMap,\n dialog: this.#dialog,\n popupId: this.#popupId,\n popupAttrs: this.#core.getPopupAttrs(state),\n close: () => this.#dialog?.close(),\n });\n }\n\n #syncTrigger(triggerElement: HTMLElement | null): void {\n if (triggerElement === this.#triggerElement) return;\n\n this.#cleanupTrigger();\n this.#triggerElement = triggerElement;\n this.#dialog?.setTriggerElement(triggerElement);\n\n if (triggerElement && this.#dialog) {\n this.#triggerAbort = new AbortController();\n applyElementProps(triggerElement, this.#dialog.triggerProps, { signal: this.#triggerAbort.signal });\n }\n }\n\n #cleanupTrigger(): void {\n if (this.#triggerElement) {\n applyElementProps(this.#triggerElement, {\n 'aria-expanded': undefined,\n 'aria-haspopup': undefined,\n 'aria-controls': undefined,\n });\n }\n\n this.#triggerAbort?.abort();\n this.#triggerAbort = null;\n this.#triggerElement = null;\n this.#dialog?.setTriggerElement(null);\n }\n}\n\nexport class DialogElement extends DialogElementBase {\n static readonly tagName = 'media-dialog';\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"element.js","names":[],"sources":["../../../../src/ui/dialog/element.ts"],"sourcesContent":["import {\n DialogCore,\n DialogDataAttrs,\n type DialogInput,\n type DialogState,\n type StateAttrMap,\n type DialogProps,\n} from '@videojs/core';\nimport {\n applyElementProps,\n applyStateDataAttrs,\n createDialog,\n createTransition,\n type DialogApi,\n type DialogModality,\n} from '@videojs/core/dom';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport { ContextConsumer, ContextProvider } from '@videojs/element/context';\nimport { SnapshotController } from '@videojs/store/html';\n\nimport { containerContext } from '../../player/context';\nimport { PositionController } from '../position-controller';\nimport { UIElement } from '../ui-element';\nimport { dialogContext } from './context';\n\nlet idCounter = 0;\n\nexport interface DialogElementBaseOptions {\n /** @internal */\n core?: DialogCore;\n /** @internal */\n stateAttrMap?: StateAttrMap<DialogState>;\n idPrefix?: string;\n bindTrigger?: boolean;\n}\n\nexport class DialogElementBase extends UIElement {\n static override properties = {\n open: { type: Boolean },\n defaultOpen: { type: Boolean, attribute: 'default-open' },\n closeOnEscape: { type: Boolean, attribute: 'close-on-escape' },\n } satisfies PropertyDeclarationMap<keyof DialogProps>;\n\n open = DialogCore.defaultProps.open;\n defaultOpen = DialogCore.defaultProps.defaultOpen;\n closeOnEscape = DialogCore.defaultProps.closeOnEscape;\n\n readonly #core: DialogCore;\n readonly #stateAttrMap: StateAttrMap<DialogState>;\n readonly #provider = new ContextProvider(this, { context: dialogContext });\n readonly #position = new PositionController(this);\n readonly #container = new ContextConsumer(this, { context: containerContext, subscribe: true });\n readonly #popupId: string;\n readonly #titleId: string;\n readonly #descriptionId: string;\n readonly #bindTrigger: boolean;\n\n #dialog: DialogApi | null = null;\n #snapshot: SnapshotController<DialogInput> | null = null;\n #modalitySnapshot: SnapshotController<DialogModality> | null = null;\n #triggerAbort: AbortController | null = null;\n #triggerElement: HTMLElement | null = null;\n\n constructor({\n core = new DialogCore(),\n stateAttrMap = DialogDataAttrs,\n idPrefix = 'dialog',\n bindTrigger = true,\n }: DialogElementBaseOptions = {}) {\n super();\n this.#core = core;\n this.#stateAttrMap = stateAttrMap;\n this.#bindTrigger = bindTrigger;\n this.#popupId = `vjs-${idPrefix}-popup-${idCounter++}`;\n this.#titleId = `vjs-${idPrefix}-title-${idCounter++}`;\n this.#descriptionId = `vjs-${idPrefix}-desc-${idCounter++}`;\n this.#core.setTitleId(this.#titleId);\n this.#core.setDescriptionId(this.#descriptionId);\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n if (this.destroyed) return;\n\n this.#dialog = createDialog({\n transition: createTransition(),\n closeOnEscape: () => this.closeOnEscape,\n onOpenChange: (nextOpen: boolean) => {\n this.open = nextOpen;\n this.dispatchEvent(new CustomEvent('open-change', { detail: { open: nextOpen } }));\n },\n onOpenChangeComplete: (nextOpen: boolean) => {\n this.dispatchEvent(new CustomEvent('open-change-complete', { detail: { open: nextOpen } }));\n },\n });\n\n if (this.#snapshot) {\n this.#snapshot.track(this.#dialog.input);\n } else {\n this.#snapshot = new SnapshotController(this, this.#dialog.input);\n }\n\n if (this.#modalitySnapshot) {\n this.#modalitySnapshot.track(this.#dialog.modality);\n } else {\n this.#modalitySnapshot = new SnapshotController(this, this.#dialog.modality);\n }\n }\n\n protected override firstUpdated(changed: PropertyValues): void {\n super.firstUpdated(changed);\n\n if (this.defaultOpen && !this.open) this.#dialog?.open();\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.#cleanupTrigger();\n this.#dialog?.destroy();\n this.#dialog = null;\n }\n\n close(): void {\n this.#dialog?.close();\n }\n\n protected override willUpdate(changed: PropertyValues): void {\n super.willUpdate(changed);\n this.#core.setProps(this);\n this.#dialog?.setInteractionRoot(this.#container.value?.container ?? null);\n\n if (this.#dialog && changed.has('open')) {\n const { active: inputOpen } = this.#dialog.input.current;\n\n if (this.open !== inputOpen) {\n if (this.open) this.#dialog.open();\n else this.#dialog.close();\n }\n }\n }\n\n protected override update(_changed: PropertyValues): void {\n super.update(_changed);\n\n if (!this.#dialog) return;\n\n const triggerElement = this.#bindTrigger ? this.#position.findTrigger() : null;\n\n this.#syncTrigger(triggerElement);\n\n const input = this.#dialog.input.current;\n\n this.#core.setInput(input);\n this.#core.setDocumentModal(this.#dialog.modality.current.documentModal);\n const state = this.#core.getState();\n\n applyStateDataAttrs(this, state, this.#stateAttrMap);\n\n if (this.#triggerElement) {\n applyElementProps(this.#triggerElement, this.#core.getTriggerAttrs(state, this.#popupId));\n }\n\n this.#provider.setValue({\n state,\n stateAttrMap: this.#stateAttrMap,\n dialog: this.#dialog,\n popupId: this.#popupId,\n popupAttrs: this.#core.getPopupAttrs(state),\n close: () => this.#dialog?.close(),\n });\n }\n\n #syncTrigger(triggerElement: HTMLElement | null): void {\n if (triggerElement === this.#triggerElement) return;\n\n this.#cleanupTrigger();\n this.#triggerElement = triggerElement;\n this.#dialog?.setTriggerElement(triggerElement);\n\n if (triggerElement && this.#dialog) {\n this.#triggerAbort = new AbortController();\n applyElementProps(triggerElement, this.#dialog.triggerProps, { signal: this.#triggerAbort.signal });\n }\n }\n\n #cleanupTrigger(): void {\n if (this.#triggerElement) {\n applyElementProps(this.#triggerElement, {\n 'aria-expanded': undefined,\n 'aria-haspopup': undefined,\n 'aria-controls': undefined,\n });\n }\n\n this.#triggerAbort?.abort();\n this.#triggerAbort = null;\n this.#triggerElement = null;\n this.#dialog?.setTriggerElement(null);\n }\n}\n\nexport class DialogElement extends DialogElementBase {\n static readonly tagName = 'media-dialog';\n}\n"],"mappings":"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"}
@@ -61,14 +61,17 @@ var GestureElement = class extends UIElement {
61
61
  if (!resolver) return;
62
62
  const { value, region } = this;
63
63
  const actionValue = getGestureActionValue(this.action, region, value);
64
+ let state;
64
65
  const onActivate = (event) => {
65
66
  resolver({
66
67
  store,
68
+ state,
67
69
  value: actionValue,
68
70
  event
69
71
  });
70
72
  };
71
73
  const options = {
74
+ onPointerDown: () => state = store.state,
72
75
  pointer: this.pointer,
73
76
  region,
74
77
  disabled: this.disabled,
@@ -1 +1 @@
1
- {"version":3,"file":"element.js","names":[],"sources":["../../../../src/ui/gesture/element.ts"],"sourcesContent":["import type { GestureProps } from '@videojs/core';\nimport {\n createDoubleTapGesture,\n createTapGesture,\n getGestureActionValue,\n resolveGestureAction,\n} from '@videojs/core/dom';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport { ContextConsumer } from '@videojs/element/context';\n\nimport { containerContext, playerContext } from '../../player/context';\nimport { PlayerController } from '../../player/controller';\nimport { UIElement } from '../ui-element';\n\nexport class GestureElement extends UIElement {\n static readonly tagName = 'media-gesture';\n\n static override properties: PropertyDeclarationMap = {\n type: { type: String },\n action: { type: String },\n value: { type: Number },\n pointer: { type: String },\n region: { type: String },\n disabled: { type: Boolean },\n };\n\n type: GestureProps['type'] | '' = '';\n action: GestureProps['action'] = '';\n value: GestureProps['value'] = undefined;\n pointer: GestureProps['pointer'] = undefined;\n region: GestureProps['region'] = undefined;\n disabled: NonNullable<GestureProps['disabled']> = false;\n\n readonly #player = new PlayerController(this, playerContext);\n readonly #container = new ContextConsumer(this, {\n context: containerContext,\n callback: () => this.requestUpdate(),\n subscribe: true,\n });\n #cleanup: (() => void) | null = null;\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.style.display = 'none';\n this.#register();\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.#unregister();\n }\n\n protected override update(changed: PropertyValues): void {\n super.update(changed);\n\n // Re-register when attributes change.\n if (this.isConnected) {\n this.#unregister();\n this.#register();\n }\n }\n\n #register(): void {\n const store = this.#player.value;\n const container = this.#container.value?.container;\n if (!this.type || !this.action || !store || !container) return;\n\n const resolver = resolveGestureAction(this.action);\n if (!resolver) return;\n\n const { value, region } = this;\n const actionValue = getGestureActionValue(this.action, region, value);\n\n const onActivate = (event: PointerEvent) => {\n resolver({ store, value: actionValue, event });\n };\n\n const options = {\n pointer: this.pointer,\n region,\n disabled: this.disabled,\n action: this.action,\n value: actionValue,\n };\n\n if (this.type === 'doubletap') {\n this.#cleanup = createDoubleTapGesture(container, onActivate, options);\n } else if (this.type === 'tap') {\n this.#cleanup = createTapGesture(container, onActivate, options);\n }\n }\n\n #unregister(): void {\n this.#cleanup?.();\n this.#cleanup = null;\n }\n}\n"],"mappings":";;;;;;AAcA,IAAa,iBAAb,cAAoC,UAAU;;;EAYV,KAAA,OAAA;EACD,KAAA,SAAA;EACF,KAAA,QAAA,KAAA;EACI,KAAA,UAAA,KAAA;EACF,KAAA,SAAA,KAAA;EACiB,KAAA,WAAA;EAE/B,KAAA,UAAA,IAAI,iBAAiB,MAAM,aAAa;EACrC,KAAA,aAAA,IAAI,gBAAgB,MAAM;GAC9C,SAAS;GACT,gBAAgB,KAAK,cAAc;GACnC,WAAW;EACb,CAAC;EAC+B,KAAA,WAAA;;;EAxBN,KAAA,UAAA;;;EAE2B,KAAA,aAAA;GACnD,MAAM,EAAE,MAAM,OAAO;GACrB,QAAQ,EAAE,MAAM,OAAO;GACvB,OAAO,EAAE,MAAM,OAAO;GACtB,SAAS,EAAE,MAAM,OAAO;GACxB,QAAQ,EAAE,MAAM,OAAO;GACvB,UAAU,EAAE,MAAM,QAAQ;EAC5B;;CASA;CACA;CAKA;CAEA,oBAAmC;EACjC,MAAM,kBAAkB;EACxB,KAAK,MAAM,UAAU;EACrB,KAAK,UAAU;CACjB;CAEA,uBAAsC;EACpC,MAAM,qBAAqB;EAC3B,KAAK,YAAY;CACnB;CAEA,OAA0B,SAA+B;EACvD,MAAM,OAAO,OAAO;EAGpB,IAAI,KAAK,aAAa;GACpB,KAAK,YAAY;GACjB,KAAK,UAAU;EACjB;CACF;CAEA,YAAkB;EAChB,MAAM,QAAQ,KAAK,QAAQ;EAC3B,MAAM,YAAY,KAAK,WAAW,OAAO;EACzC,IAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,UAAU,CAAC,SAAS,CAAC,WAAW;EAExD,MAAM,WAAW,qBAAqB,KAAK,MAAM;EACjD,IAAI,CAAC,UAAU;EAEf,MAAM,EAAE,OAAO,WAAW;EAC1B,MAAM,cAAc,sBAAsB,KAAK,QAAQ,QAAQ,KAAK;EAEpE,MAAM,cAAc,UAAwB;GAC1C,SAAS;IAAE;IAAO,OAAO;IAAa;GAAM,CAAC;EAC/C;EAEA,MAAM,UAAU;GACd,SAAS,KAAK;GACd;GACA,UAAU,KAAK;GACf,QAAQ,KAAK;GACb,OAAO;EACT;EAEA,IAAI,KAAK,SAAS,aAChB,KAAK,WAAW,uBAAuB,WAAW,YAAY,OAAO;OAChE,IAAI,KAAK,SAAS,OACvB,KAAK,WAAW,iBAAiB,WAAW,YAAY,OAAO;CAEnE;CAEA,cAAoB;EAClB,KAAK,WAAW;EAChB,KAAK,WAAW;CAClB;AACF"}
1
+ {"version":3,"file":"element.js","names":[],"sources":["../../../../src/ui/gesture/element.ts"],"sourcesContent":["import type { GestureProps } from '@videojs/core';\nimport {\n createDoubleTapGesture,\n createTapGesture,\n getGestureActionValue,\n resolveGestureAction,\n} from '@videojs/core/dom';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport { ContextConsumer } from '@videojs/element/context';\n\nimport { containerContext, playerContext } from '../../player/context';\nimport { PlayerController } from '../../player/controller';\nimport { UIElement } from '../ui-element';\n\nexport class GestureElement extends UIElement {\n static readonly tagName = 'media-gesture';\n\n static override properties: PropertyDeclarationMap = {\n type: { type: String },\n action: { type: String },\n value: { type: Number },\n pointer: { type: String },\n region: { type: String },\n disabled: { type: Boolean },\n };\n\n type: GestureProps['type'] | '' = '';\n action: GestureProps['action'] = '';\n value: GestureProps['value'] = undefined;\n pointer: GestureProps['pointer'] = undefined;\n region: GestureProps['region'] = undefined;\n disabled: NonNullable<GestureProps['disabled']> = false;\n\n readonly #player = new PlayerController(this, playerContext);\n readonly #container = new ContextConsumer(this, {\n context: containerContext,\n callback: () => this.requestUpdate(),\n subscribe: true,\n });\n #cleanup: (() => void) | null = null;\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.style.display = 'none';\n this.#register();\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.#unregister();\n }\n\n protected override update(changed: PropertyValues): void {\n super.update(changed);\n\n // Re-register when attributes change.\n if (this.isConnected) {\n this.#unregister();\n this.#register();\n }\n }\n\n #register(): void {\n const store = this.#player.value;\n const container = this.#container.value?.container;\n if (!this.type || !this.action || !store || !container) return;\n\n const resolver = resolveGestureAction(this.action);\n if (!resolver) return;\n\n const { value, region } = this;\n const actionValue = getGestureActionValue(this.action, region, value);\n\n let state: object | undefined;\n\n const onActivate = (event: PointerEvent) => {\n resolver({ store, state, value: actionValue, event });\n };\n\n const options = {\n onPointerDown: () => (state = store.state),\n pointer: this.pointer,\n region,\n disabled: this.disabled,\n action: this.action,\n value: actionValue,\n };\n\n if (this.type === 'doubletap') {\n this.#cleanup = createDoubleTapGesture(container, onActivate, options);\n } else if (this.type === 'tap') {\n this.#cleanup = createTapGesture(container, onActivate, options);\n }\n }\n\n #unregister(): void {\n this.#cleanup?.();\n this.#cleanup = null;\n }\n}\n"],"mappings":";;;;;;AAcA,IAAa,iBAAb,cAAoC,UAAU;;;EAYV,KAAA,OAAA;EACD,KAAA,SAAA;EACF,KAAA,QAAA,KAAA;EACI,KAAA,UAAA,KAAA;EACF,KAAA,SAAA,KAAA;EACiB,KAAA,WAAA;EAE/B,KAAA,UAAA,IAAI,iBAAiB,MAAM,aAAa;EACrC,KAAA,aAAA,IAAI,gBAAgB,MAAM;GAC9C,SAAS;GACT,gBAAgB,KAAK,cAAc;GACnC,WAAW;EACb,CAAC;EAC+B,KAAA,WAAA;;;EAxBN,KAAA,UAAA;;;EAE2B,KAAA,aAAA;GACnD,MAAM,EAAE,MAAM,OAAO;GACrB,QAAQ,EAAE,MAAM,OAAO;GACvB,OAAO,EAAE,MAAM,OAAO;GACtB,SAAS,EAAE,MAAM,OAAO;GACxB,QAAQ,EAAE,MAAM,OAAO;GACvB,UAAU,EAAE,MAAM,QAAQ;EAC5B;;CASA;CACA;CAKA;CAEA,oBAAmC;EACjC,MAAM,kBAAkB;EACxB,KAAK,MAAM,UAAU;EACrB,KAAK,UAAU;CACjB;CAEA,uBAAsC;EACpC,MAAM,qBAAqB;EAC3B,KAAK,YAAY;CACnB;CAEA,OAA0B,SAA+B;EACvD,MAAM,OAAO,OAAO;EAGpB,IAAI,KAAK,aAAa;GACpB,KAAK,YAAY;GACjB,KAAK,UAAU;EACjB;CACF;CAEA,YAAkB;EAChB,MAAM,QAAQ,KAAK,QAAQ;EAC3B,MAAM,YAAY,KAAK,WAAW,OAAO;EACzC,IAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,UAAU,CAAC,SAAS,CAAC,WAAW;EAExD,MAAM,WAAW,qBAAqB,KAAK,MAAM;EACjD,IAAI,CAAC,UAAU;EAEf,MAAM,EAAE,OAAO,WAAW;EAC1B,MAAM,cAAc,sBAAsB,KAAK,QAAQ,QAAQ,KAAK;EAEpE,IAAI;EAEJ,MAAM,cAAc,UAAwB;GAC1C,SAAS;IAAE;IAAO;IAAO,OAAO;IAAa;GAAM,CAAC;EACtD;EAEA,MAAM,UAAU;GACd,qBAAsB,QAAQ,MAAM;GACpC,SAAS,KAAK;GACd;GACA,UAAU,KAAK;GACf,QAAQ,KAAK;GACb,OAAO;EACT;EAEA,IAAI,KAAK,SAAS,aAChB,KAAK,WAAW,uBAAuB,WAAW,YAAY,OAAO;OAChE,IAAI,KAAK,SAAS,OACvB,KAAK,WAAW,iBAAiB,WAAW,YAAY,OAAO;CAEnE;CAEA,cAAoB;EAClB,KAAK,WAAW;EAChB,KAAK,WAAW;CAClB;AACF"}
@@ -11,6 +11,10 @@ type LabelParams = Record<string, string | number>;
11
11
  type MediaButtonCore<ComponentState extends ButtonState, MediaState> = MediaButtonComponent<object, ComponentState> & {
12
12
  setMedia(media: MediaState): void;
13
13
  getLabelParams?: (state: ComponentState) => LabelParams | undefined;
14
+ /** Shorter text for the tooltip than the accessible name, when the two differ. */
15
+ getTooltipLabel?: (state: ComponentState) => Text | string;
16
+ /** CSS custom properties the button publishes for skins. */
17
+ getCSSVars?: (state: ComponentState) => Record<string, string>;
14
18
  };
15
19
  /**
16
20
  * Abstract base for HTML custom elements that render a media-control button. `ComponentState` is the state the button
@@ -42,6 +46,11 @@ export declare abstract class MediaButtonElement<ComponentState extends ButtonSt
42
46
  getResolvedLabel(): string | undefined;
43
47
  protected willUpdate(changed: PropertyValues): void;
44
48
  protected update(changed: PropertyValues): void;
49
+ /**
50
+ * Whether this button writes its core's `hidden` state to the `hidden` attribute. A subclass returns `false` while
51
+ * another owner, such as a linked menu, manages its visibility.
52
+ */
53
+ protected reflectsHidden(): boolean;
45
54
  }
46
55
  //#endregion
47
56
  //# sourceMappingURL=media-button-element.d.ts.map
@@ -74,6 +74,7 @@ var MediaButtonElement = class extends UIElement {
74
74
  if (!media) return void 0;
75
75
  this.core.setMedia(media);
76
76
  const state = this.core.getState();
77
+ if (this.core.getTooltipLabel) return translateText(this.core.getTooltipLabel(state), this.#i18n.value);
77
78
  return translateText(this.core.getLabel(state), this.#i18n.value, this.core.getLabelParams?.(state));
78
79
  }
79
80
  willUpdate(changed) {
@@ -92,9 +93,17 @@ var MediaButtonElement = class extends UIElement {
92
93
  applyElementProps(this, {
93
94
  ...attrs,
94
95
  "aria-keyshortcuts": this.#hotkeyRegistry?.aria,
95
- ...isHideable(state) && { hidden: state.hidden ? "" : void 0 }
96
+ ...isHideable(state) && this.reflectsHidden() && { hidden: state.hidden ? "" : void 0 }
96
97
  });
97
98
  applyStateDataAttrs(this, state, this.stateAttrMap);
99
+ for (const [name, value] of Object.entries(this.core.getCSSVars?.(state) ?? {})) this.style.setProperty(name, value);
100
+ }
101
+ /**
102
+ * Whether this button writes its core's `hidden` state to the `hidden` attribute. A subclass returns `false` while
103
+ * another owner, such as a linked menu, manages its visibility.
104
+ */
105
+ reflectsHidden() {
106
+ return true;
98
107
  }
99
108
  #syncHotkeyShortcut() {
100
109
  const shortcut = this.getShortcut();
@@ -1 +1 @@
1
- {"version":3,"file":"media-button-element.js","names":[],"sources":["../../../src/ui/media-button-element.ts"],"sourcesContent":["import type { ButtonState, MediaButtonComponent, StateAttrMap } from '@videojs/core';\nimport {\n applyElementProps,\n applyStateDataAttrs,\n type ButtonActivationSource,\n createButton,\n HOTKEY_SHORTCUT_CHANGE_EVENT,\n logMissingFeature,\n type UIEvent,\n} from '@videojs/core/dom';\nimport { isText, resolveText, type Text, translateText } from '@videojs/core/i18n';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport type { State } from '@videojs/store';\nimport { isBoolean, isObject } from '@videojs/utils/predicate';\n\nimport { i18nContext } from '../i18n/context';\nimport { I18nController } from '../i18n/controller';\nimport type { PlayerController } from '../player/controller';\nimport { AriaKeyShortcutsController } from './hotkey/aria-key-shortcuts-controller';\nimport { UIElement } from './ui-element';\n\ntype LabelParams = Record<string, string | number>;\n\n/** The core a media button element drives: it reads `MediaState` and computes `ComponentState`. */\ntype MediaButtonCore<ComponentState extends ButtonState, MediaState> = MediaButtonComponent<object, ComponentState> & {\n setMedia(media: MediaState): void;\n getLabelParams?: (state: ComponentState) => LabelParams | undefined;\n};\n\n/**\n * Abstract base for HTML custom elements that render a media-control button. `ComponentState` is the state the button\n * reflects to data attributes, and `MediaState` is the player state it reads and acts on. Pass both: a subclass that\n * omits them still compiles, but types `activate(state)` and `mediaState` as the `ButtonState` and `object` defaults.\n */\nexport abstract class MediaButtonElement<\n ComponentState extends ButtonState = ButtonState,\n MediaState extends object = object,\n> extends UIElement {\n static override properties: PropertyDeclarationMap = {\n label: { type: String },\n disabled: { type: Boolean },\n };\n\n disabled = false;\n label: Text | string = '';\n\n protected abstract readonly core: MediaButtonCore<ComponentState, MediaState>;\n protected abstract readonly stateAttrMap: StateAttrMap<ComponentState>;\n protected abstract readonly mediaState: PlayerController<any, MediaState | undefined>;\n\n protected abstract activate(state: MediaState, event: UIEvent, source: ButtonActivationSource): void | Promise<void>;\n\n protected getIsButtonDisabled(): boolean {\n return this.disabled || !this.mediaState.value;\n }\n\n protected handleActivate(event: UIEvent, source: ButtonActivationSource): void {\n // `createButton` invokes `onActivate` synchronously from click/keyup\n // handlers, so any rejection here would be unhandled. Log in dev for\n // visibility but absorb the failure at this UI boundary.\n Promise.resolve(this.activate(this.mediaState.value!, event, source)).catch((error) => {\n if (__DEV__) console.error(`[${this.localName}]`, error);\n });\n }\n\n /** Override to set the hotkey action name for `aria-keyshortcuts`. */\n protected readonly hotkeyAction: string | undefined = undefined;\n\n /** Override to match hotkeys that use action values, such as seek steps. */\n protected get hotkeyValue(): number | undefined {\n return undefined;\n }\n\n get $state(): State<ButtonState> {\n return this.core.state;\n }\n\n #disconnect: AbortController | null = null;\n #hotkeyRegistry: AriaKeyShortcutsController | null = null;\n #lastHotkeyShortcut: string | undefined;\n readonly #i18n = new I18nController(this, i18nContext);\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n if (this.destroyed) return;\n\n if (this.hotkeyAction && !this.#hotkeyRegistry) {\n this.#hotkeyRegistry = new AriaKeyShortcutsController(this, this.hotkeyAction, {\n value: () => this.hotkeyValue,\n });\n }\n\n this.#disconnect = new AbortController();\n\n const buttonProps = createButton({\n onActivate: (event, source) => this.handleActivate(event, source),\n isDisabled: () => this.getIsButtonDisabled(),\n });\n\n applyElementProps(this, buttonProps, { signal: this.#disconnect.signal });\n\n if (__DEV__ && !this.mediaState.value && this.mediaState.displayName) {\n logMissingFeature(this.localName, this.mediaState.displayName);\n }\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.#disconnect?.abort();\n this.#disconnect = null;\n }\n\n /** Returns the button's current label derived from media state. */\n getLabel(): string | undefined {\n return this.core.state.current.label ? resolveText(this.core.state.current.label) : undefined;\n }\n\n getShortcut(): string | undefined {\n return this.#hotkeyRegistry?.shortcut;\n }\n\n /** Resolved label for tooltips and other display surfaces. */\n getResolvedLabel(): string | undefined {\n const media = this.mediaState.value;\n if (!media) return undefined;\n\n this.core.setMedia(media);\n const state = this.core.getState();\n\n return translateText(this.core.getLabel(state), this.#i18n.value, this.core.getLabelParams?.(state));\n }\n\n protected override willUpdate(changed: PropertyValues): void {\n super.willUpdate(changed);\n this.core.setProps?.(this);\n }\n\n protected override update(changed: PropertyValues): void {\n super.update(changed);\n\n const media = this.mediaState.value;\n\n this.#syncHotkeyShortcut();\n\n if (!media) return;\n\n this.core.setMedia(media);\n const state = this.core.getState();\n const attrs = (this.core.getAttrs?.(state) ?? {}) as Record<string, unknown>;\n\n if (isText(attrs['aria-label'])) {\n attrs['aria-label'] = translateText(attrs['aria-label'], this.#i18n.value, this.core.getLabelParams?.(state));\n }\n\n applyElementProps(this, {\n ...attrs,\n 'aria-keyshortcuts': this.#hotkeyRegistry?.aria,\n // A button whose core reports itself hidden takes the real attribute, not\n // just the data one: `data-hidden` is a styling hook a skin may or may not\n // act on, where `hidden` removes the control the way the React components\n // do by rendering nothing.\n ...(isHideable(state) && { hidden: state.hidden ? '' : undefined }),\n });\n applyStateDataAttrs(this, state, this.stateAttrMap);\n }\n\n #syncHotkeyShortcut(): void {\n const shortcut = this.getShortcut();\n if (shortcut === this.#lastHotkeyShortcut) return;\n\n this.#lastHotkeyShortcut = shortcut;\n this.dispatchEvent(new CustomEvent(HOTKEY_SHORTCUT_CHANGE_EVENT));\n }\n}\n\n/** Whether a button's core reports whether it should be shown at all. */\nfunction isHideable(state: unknown): state is { hidden: boolean } {\n return isObject(state) && isBoolean((state as { hidden?: unknown }).hidden);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"media-button-element.js","names":[],"sources":["../../../src/ui/media-button-element.ts"],"sourcesContent":["import type { ButtonState, MediaButtonComponent, StateAttrMap } from '@videojs/core';\nimport {\n applyElementProps,\n applyStateDataAttrs,\n type ButtonActivationSource,\n createButton,\n HOTKEY_SHORTCUT_CHANGE_EVENT,\n logMissingFeature,\n type UIEvent,\n} from '@videojs/core/dom';\nimport { isText, resolveText, type Text, translateText } from '@videojs/core/i18n';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport type { State } from '@videojs/store';\nimport { isBoolean, isObject } from '@videojs/utils/predicate';\n\nimport { i18nContext } from '../i18n/context';\nimport { I18nController } from '../i18n/controller';\nimport type { PlayerController } from '../player/controller';\nimport { AriaKeyShortcutsController } from './hotkey/aria-key-shortcuts-controller';\nimport { UIElement } from './ui-element';\n\ntype LabelParams = Record<string, string | number>;\n\n/** The core a media button element drives: it reads `MediaState` and computes `ComponentState`. */\ntype MediaButtonCore<ComponentState extends ButtonState, MediaState> = MediaButtonComponent<object, ComponentState> & {\n setMedia(media: MediaState): void;\n getLabelParams?: (state: ComponentState) => LabelParams | undefined;\n /** Shorter text for the tooltip than the accessible name, when the two differ. */\n getTooltipLabel?: (state: ComponentState) => Text | string;\n /** CSS custom properties the button publishes for skins. */\n getCSSVars?: (state: ComponentState) => Record<string, string>;\n};\n\n/**\n * Abstract base for HTML custom elements that render a media-control button. `ComponentState` is the state the button\n * reflects to data attributes, and `MediaState` is the player state it reads and acts on. Pass both: a subclass that\n * omits them still compiles, but types `activate(state)` and `mediaState` as the `ButtonState` and `object` defaults.\n */\nexport abstract class MediaButtonElement<\n ComponentState extends ButtonState = ButtonState,\n MediaState extends object = object,\n> extends UIElement {\n static override properties: PropertyDeclarationMap = {\n label: { type: String },\n disabled: { type: Boolean },\n };\n\n disabled = false;\n label: Text | string = '';\n\n protected abstract readonly core: MediaButtonCore<ComponentState, MediaState>;\n protected abstract readonly stateAttrMap: StateAttrMap<ComponentState>;\n protected abstract readonly mediaState: PlayerController<any, MediaState | undefined>;\n\n protected abstract activate(state: MediaState, event: UIEvent, source: ButtonActivationSource): void | Promise<void>;\n\n protected getIsButtonDisabled(): boolean {\n return this.disabled || !this.mediaState.value;\n }\n\n protected handleActivate(event: UIEvent, source: ButtonActivationSource): void {\n // `createButton` invokes `onActivate` synchronously from click/keyup\n // handlers, so any rejection here would be unhandled. Log in dev for\n // visibility but absorb the failure at this UI boundary.\n Promise.resolve(this.activate(this.mediaState.value!, event, source)).catch((error) => {\n if (__DEV__) console.error(`[${this.localName}]`, error);\n });\n }\n\n /** Override to set the hotkey action name for `aria-keyshortcuts`. */\n protected readonly hotkeyAction: string | undefined = undefined;\n\n /** Override to match hotkeys that use action values, such as seek steps. */\n protected get hotkeyValue(): number | undefined {\n return undefined;\n }\n\n get $state(): State<ButtonState> {\n return this.core.state;\n }\n\n #disconnect: AbortController | null = null;\n #hotkeyRegistry: AriaKeyShortcutsController | null = null;\n #lastHotkeyShortcut: string | undefined;\n readonly #i18n = new I18nController(this, i18nContext);\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n if (this.destroyed) return;\n\n if (this.hotkeyAction && !this.#hotkeyRegistry) {\n this.#hotkeyRegistry = new AriaKeyShortcutsController(this, this.hotkeyAction, {\n value: () => this.hotkeyValue,\n });\n }\n\n this.#disconnect = new AbortController();\n\n const buttonProps = createButton({\n onActivate: (event, source) => this.handleActivate(event, source),\n isDisabled: () => this.getIsButtonDisabled(),\n });\n\n applyElementProps(this, buttonProps, { signal: this.#disconnect.signal });\n\n if (__DEV__ && !this.mediaState.value && this.mediaState.displayName) {\n logMissingFeature(this.localName, this.mediaState.displayName);\n }\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.#disconnect?.abort();\n this.#disconnect = null;\n }\n\n /** Returns the button's current label derived from media state. */\n getLabel(): string | undefined {\n return this.core.state.current.label ? resolveText(this.core.state.current.label) : undefined;\n }\n\n getShortcut(): string | undefined {\n return this.#hotkeyRegistry?.shortcut;\n }\n\n /** Resolved label for tooltips and other display surfaces. */\n getResolvedLabel(): string | undefined {\n const media = this.mediaState.value;\n if (!media) return undefined;\n\n this.core.setMedia(media);\n const state = this.core.getState();\n\n if (this.core.getTooltipLabel) return translateText(this.core.getTooltipLabel(state), this.#i18n.value);\n\n return translateText(this.core.getLabel(state), this.#i18n.value, this.core.getLabelParams?.(state));\n }\n\n protected override willUpdate(changed: PropertyValues): void {\n super.willUpdate(changed);\n this.core.setProps?.(this);\n }\n\n protected override update(changed: PropertyValues): void {\n super.update(changed);\n\n const media = this.mediaState.value;\n\n this.#syncHotkeyShortcut();\n\n if (!media) return;\n\n this.core.setMedia(media);\n const state = this.core.getState();\n const attrs = (this.core.getAttrs?.(state) ?? {}) as Record<string, unknown>;\n\n if (isText(attrs['aria-label'])) {\n attrs['aria-label'] = translateText(attrs['aria-label'], this.#i18n.value, this.core.getLabelParams?.(state));\n }\n\n applyElementProps(this, {\n ...attrs,\n 'aria-keyshortcuts': this.#hotkeyRegistry?.aria,\n // A button whose core reports itself hidden takes the real attribute, not\n // just the data one: `data-hidden` is a styling hook a skin may or may not\n // act on, where `hidden` removes the control the way the React components\n // do by rendering nothing.\n ...(isHideable(state) && this.reflectsHidden() && { hidden: state.hidden ? '' : undefined }),\n });\n applyStateDataAttrs(this, state, this.stateAttrMap);\n\n for (const [name, value] of Object.entries(this.core.getCSSVars?.(state) ?? {})) {\n this.style.setProperty(name, value);\n }\n }\n\n /**\n * Whether this button writes its core's `hidden` state to the `hidden` attribute. A subclass returns `false` while\n * another owner, such as a linked menu, manages its visibility.\n */\n protected reflectsHidden(): boolean {\n return true;\n }\n\n #syncHotkeyShortcut(): void {\n const shortcut = this.getShortcut();\n if (shortcut === this.#lastHotkeyShortcut) return;\n\n this.#lastHotkeyShortcut = shortcut;\n this.dispatchEvent(new CustomEvent(HOTKEY_SHORTCUT_CHANGE_EVENT));\n }\n}\n\n/** Whether a button's core reports whether it should be shown at all. */\nfunction isHideable(state: unknown): state is { hidden: boolean } {\n return isObject(state) && isBoolean((state as { hidden?: unknown }).hidden);\n}\n"],"mappings":"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"}
@@ -17,6 +17,8 @@ export declare class PlayButtonElement extends MediaButtonElement<PlayButtonStat
17
17
  ended: boolean;
18
18
  started: boolean;
19
19
  waiting: boolean;
20
+ autoplay: boolean;
21
+ autoplaying: boolean;
20
22
  play: () => Promise<void>;
21
23
  pause: () => void;
22
24
  } | undefined>;
@@ -18,20 +18,28 @@ export declare class PlaybackRateButtonElement extends MediaButtonElement<Playba
18
18
  commandfor: {
19
19
  type: StringConstructor;
20
20
  };
21
+ rates: {
22
+ type: StringConstructor;
23
+ };
21
24
  };
22
25
  commandfor: string | undefined;
26
+ rates: readonly number[] | string | undefined;
23
27
  protected readonly core: PlaybackRateButtonCore;
24
28
  protected readonly stateAttrMap: {
25
29
  readonly rate: 'data-rate';
30
+ readonly availability: 'data-availability';
31
+ readonly hidden: 'data-hidden';
26
32
  };
27
33
  protected readonly mediaState: PlayerController<index_d_exports.AnyPlayerStore, {
28
- readonly playbackRates: readonly number[];
29
34
  playbackRate: number;
35
+ playbackRateAvailability: import("@videojs/media").MediaFeatureAvailability;
30
36
  setPlaybackRate: (rate: number) => void;
37
+ readonly playbackRates: readonly number[];
31
38
  } | undefined>;
32
39
  protected readonly hotkeyAction = "speedUp";
33
40
  protected activate(state: MediaPlaybackRateState, event?: UIEvent): void;
34
41
  protected getIsButtonDisabled(): boolean;
42
+ protected reflectsHidden(): boolean;
35
43
  protected willUpdate(changed: PropertyValues): void;
36
44
  protected update(changed: PropertyValues): void;
37
45
  }
@@ -8,6 +8,7 @@ var PlaybackRateButtonElement = class extends MediaButtonElement {
8
8
  constructor(..._args) {
9
9
  super(..._args);
10
10
  this.commandfor = void 0;
11
+ this.rates = void 0;
11
12
  this.core = new PlaybackRateButtonCore();
12
13
  this.stateAttrMap = PlaybackRateButtonDataAttrs;
13
14
  this.mediaState = new PlayerController(this, playerContext, selectPlaybackRate);
@@ -20,7 +21,8 @@ var PlaybackRateButtonElement = class extends MediaButtonElement {
20
21
  this.properties = {
21
22
  label: { type: String },
22
23
  disabled: { type: Boolean },
23
- commandfor: { type: String }
24
+ commandfor: { type: String },
25
+ rates: { type: String }
24
26
  };
25
27
  }
26
28
  activate(state, event) {
@@ -33,9 +35,12 @@ var PlaybackRateButtonElement = class extends MediaButtonElement {
33
35
  getIsButtonDisabled() {
34
36
  const media = this.mediaState.value;
35
37
  if (super.getIsButtonDisabled()) return true;
36
- if (this.commandfor && media && media.playbackRates.length === 0) return true;
38
+ if (this.commandfor && media && this.core.getRates(media).length === 0) return true;
37
39
  return false;
38
40
  }
41
+ reflectsHidden() {
42
+ return !this.commandfor;
43
+ }
39
44
  willUpdate(changed) {
40
45
  super.willUpdate(changed);
41
46
  if (changed.has("commandfor")) {
@@ -1 +1 @@
1
- {"version":3,"file":"element.js","names":[],"sources":["../../../../src/ui/playback-rate-button/element.ts"],"sourcesContent":["import { PlaybackRateButtonCore, PlaybackRateButtonDataAttrs, type PlaybackRateButtonState } from '@videojs/core';\nimport { applyElementProps, selectPlaybackRate, type UIEvent } from '@videojs/core/dom';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport type { MediaPlaybackRateState } from '@videojs/media';\n\nimport { playerContext } from '../../player/context';\nimport { PlayerController } from '../../player/controller';\nimport { MediaButtonElement } from '../media-button-element';\n\nexport class PlaybackRateButtonElement extends MediaButtonElement<PlaybackRateButtonState, MediaPlaybackRateState> {\n static readonly tagName = 'media-playback-rate-button';\n\n static override properties = {\n label: { type: String },\n disabled: { type: Boolean },\n commandfor: { type: String },\n } satisfies PropertyDeclarationMap<'label' | 'disabled' | 'commandfor'>;\n\n commandfor: string | undefined = undefined;\n\n protected readonly core = new PlaybackRateButtonCore();\n protected readonly stateAttrMap = PlaybackRateButtonDataAttrs;\n protected readonly mediaState = new PlayerController(this, playerContext, selectPlaybackRate);\n protected override readonly hotkeyAction = 'speedUp';\n\n protected activate(state: MediaPlaybackRateState, event?: UIEvent): void {\n if (this.commandfor) {\n if (event instanceof KeyboardEvent) {\n // Custom elements do not synthesize a click from keyboard activation.\n // Dispatch one so the linked menu follows its normal open lifecycle.\n this.click();\n }\n\n return;\n }\n\n this.core.cycle(state);\n }\n\n protected override getIsButtonDisabled(): boolean {\n const media = this.mediaState.value;\n\n if (super.getIsButtonDisabled()) return true;\n\n if (this.commandfor && media && media.playbackRates.length === 0) return true;\n\n return false;\n }\n\n protected override willUpdate(changed: PropertyValues): void {\n super.willUpdate(changed);\n\n if (changed.has('commandfor')) {\n if (this.commandfor) {\n this.setAttribute('commandfor', this.commandfor);\n } else {\n this.removeAttribute('commandfor');\n }\n }\n }\n\n protected override update(changed: PropertyValues): void {\n super.update(changed);\n\n const media = this.mediaState.value;\n if (!media || !this.commandfor) return;\n\n applyElementProps(this, {\n 'aria-disabled': this.getIsButtonDisabled() ? 'true' : undefined,\n });\n }\n}\n\nexport namespace PlaybackRateButtonElement {\n export type State = PlaybackRateButtonState;\n}\n"],"mappings":";;;;;;AASA,IAAa,4BAAb,cAA+C,mBAAoE;;;EAShF,KAAA,aAAA,KAAA;EAEP,KAAA,OAAA,IAAI,uBAAuB;EACnB,KAAA,eAAA;EACF,KAAA,aAAA,IAAI,iBAAiB,MAAM,eAAe,kBAAkB;EACjD,KAAA,eAAA;;;EAbjB,KAAA,UAAA;;;EAEG,KAAA,aAAA;GAC3B,OAAO,EAAE,MAAM,OAAO;GACtB,UAAU,EAAE,MAAM,QAAQ;GAC1B,YAAY,EAAE,MAAM,OAAO;EAC7B;;CASA,SAAmB,OAA+B,OAAuB;EACvE,IAAI,KAAK,YAAY;GACnB,IAAI,iBAAiB,eAGnB,KAAK,MAAM;GAGb;EACF;EAEA,KAAK,KAAK,MAAM,KAAK;CACvB;CAEA,sBAAkD;EAChD,MAAM,QAAQ,KAAK,WAAW;EAE9B,IAAI,MAAM,oBAAoB,GAAG,OAAO;EAExC,IAAI,KAAK,cAAc,SAAS,MAAM,cAAc,WAAW,GAAG,OAAO;EAEzE,OAAO;CACT;CAEA,WAA8B,SAA+B;EAC3D,MAAM,WAAW,OAAO;EAExB,IAAI,QAAQ,IAAI,YAAY,GAAG;GAC7B,IAAI,KAAK,YACP,KAAK,aAAa,cAAc,KAAK,UAAU;QAE/C,KAAK,gBAAgB,YAAY;EAErC;CACF;CAEA,OAA0B,SAA+B;EACvD,MAAM,OAAO,OAAO;EAGpB,IAAI,CADU,KAAK,WAAW,SAChB,CAAC,KAAK,YAAY;EAEhC,kBAAkB,MAAM,EACtB,iBAAiB,KAAK,oBAAoB,IAAI,SAAS,KAAA,EACzD,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"element.js","names":[],"sources":["../../../../src/ui/playback-rate-button/element.ts"],"sourcesContent":["import { PlaybackRateButtonCore, PlaybackRateButtonDataAttrs, type PlaybackRateButtonState } from '@videojs/core';\nimport { applyElementProps, selectPlaybackRate, type UIEvent } from '@videojs/core/dom';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport type { MediaPlaybackRateState } from '@videojs/media';\n\nimport { playerContext } from '../../player/context';\nimport { PlayerController } from '../../player/controller';\nimport { MediaButtonElement } from '../media-button-element';\n\nexport class PlaybackRateButtonElement extends MediaButtonElement<PlaybackRateButtonState, MediaPlaybackRateState> {\n static readonly tagName = 'media-playback-rate-button';\n\n static override properties = {\n label: { type: String },\n disabled: { type: Boolean },\n commandfor: { type: String },\n rates: { type: String },\n } satisfies PropertyDeclarationMap<'label' | 'disabled' | 'commandfor' | 'rates'>;\n\n commandfor: string | undefined = undefined;\n rates: readonly number[] | string | undefined = undefined;\n\n protected readonly core = new PlaybackRateButtonCore();\n protected readonly stateAttrMap = PlaybackRateButtonDataAttrs;\n protected readonly mediaState = new PlayerController(this, playerContext, selectPlaybackRate);\n protected override readonly hotkeyAction = 'speedUp';\n\n protected activate(state: MediaPlaybackRateState, event?: UIEvent): void {\n if (this.commandfor) {\n if (event instanceof KeyboardEvent) {\n // Custom elements do not synthesize a click from keyboard activation.\n // Dispatch one so the linked menu follows its normal open lifecycle.\n this.click();\n }\n\n return;\n }\n\n this.core.cycle(state);\n }\n\n protected override getIsButtonDisabled(): boolean {\n const media = this.mediaState.value;\n\n if (super.getIsButtonDisabled()) return true;\n\n if (this.commandfor && media && this.core.getRates(media).length === 0) return true;\n\n return false;\n }\n\n // A linked menu hides its trigger from the rate options' availability and\n // treats a `hidden` trigger as author-hidden, so the button must not write\n // its own pre-attach `hidden` state there.\n protected override reflectsHidden(): boolean {\n return !this.commandfor;\n }\n\n protected override willUpdate(changed: PropertyValues): void {\n super.willUpdate(changed);\n\n if (changed.has('commandfor')) {\n if (this.commandfor) {\n this.setAttribute('commandfor', this.commandfor);\n } else {\n this.removeAttribute('commandfor');\n }\n }\n }\n\n protected override update(changed: PropertyValues): void {\n super.update(changed);\n\n const media = this.mediaState.value;\n if (!media || !this.commandfor) return;\n\n applyElementProps(this, {\n 'aria-disabled': this.getIsButtonDisabled() ? 'true' : undefined,\n });\n }\n}\n\nexport namespace PlaybackRateButtonElement {\n export type State = PlaybackRateButtonState;\n}\n"],"mappings":";;;;;;AASA,IAAa,4BAAb,cAA+C,mBAAoE;;;EAUhF,KAAA,aAAA,KAAA;EACe,KAAA,QAAA,KAAA;EAEtB,KAAA,OAAA,IAAI,uBAAuB;EACnB,KAAA,eAAA;EACF,KAAA,aAAA,IAAI,iBAAiB,MAAM,eAAe,kBAAkB;EACjD,KAAA,eAAA;;;EAfjB,KAAA,UAAA;;;EAEG,KAAA,aAAA;GAC3B,OAAO,EAAE,MAAM,OAAO;GACtB,UAAU,EAAE,MAAM,QAAQ;GAC1B,YAAY,EAAE,MAAM,OAAO;GAC3B,OAAO,EAAE,MAAM,OAAO;EACxB;;CAUA,SAAmB,OAA+B,OAAuB;EACvE,IAAI,KAAK,YAAY;GACnB,IAAI,iBAAiB,eAGnB,KAAK,MAAM;GAGb;EACF;EAEA,KAAK,KAAK,MAAM,KAAK;CACvB;CAEA,sBAAkD;EAChD,MAAM,QAAQ,KAAK,WAAW;EAE9B,IAAI,MAAM,oBAAoB,GAAG,OAAO;EAExC,IAAI,KAAK,cAAc,SAAS,KAAK,KAAK,SAAS,KAAK,CAAC,CAAC,WAAW,GAAG,OAAO;EAE/E,OAAO;CACT;CAKA,iBAA6C;EAC3C,OAAO,CAAC,KAAK;CACf;CAEA,WAA8B,SAA+B;EAC3D,MAAM,WAAW,OAAO;EAExB,IAAI,QAAQ,IAAI,YAAY,GAAG;GAC7B,IAAI,KAAK,YACP,KAAK,aAAa,cAAc,KAAK,UAAU;QAE/C,KAAK,gBAAgB,YAAY;EAErC;CACF;CAEA,OAA0B,SAA+B;EACvD,MAAM,OAAO,OAAO;EAGpB,IAAI,CADU,KAAK,WAAW,SAChB,CAAC,KAAK,YAAY;EAEhC,kBAAkB,MAAM,EACtB,iBAAiB,KAAK,oBAAoB,IAAI,SAAS,KAAA,EACzD,CAAC;CACH;AACF"}
@@ -17,8 +17,12 @@ export declare class PlaybackRateRadioGroupElement extends MenuRadioGroupElement
17
17
  disabled: {
18
18
  type: BooleanConstructor;
19
19
  };
20
+ rates: {
21
+ type: StringConstructor;
22
+ };
20
23
  };
21
24
  disabled: boolean;
25
+ rates: readonly number[] | string | undefined;
22
26
  formatRate: (rate: number) => string;
23
27
  connectedCallback(): void;
24
28
  protected update(changed: PropertyValues): void;
@@ -17,12 +17,17 @@ var PlaybackRateRadioGroupElement = class extends MenuRadioGroupElement {
17
17
  constructor(..._args) {
18
18
  super(..._args);
19
19
  this.disabled = false;
20
+ this.rates = void 0;
20
21
  this.formatRate = PlaybackRateRadioGroupCore.defaultProps.formatRate;
21
22
  this.#core = new PlaybackRateRadioGroupCore();
22
23
  this.#i18n = new I18nController(this, i18nContext);
23
24
  this.#mediaState = new PlayerController(this, playerContext, selectPlaybackRate);
24
25
  this.#options = new RadioOptionsController(this, {
25
- setItemAttributes: (item, option) => item.setAttribute("data-rate", option.value),
26
+ setItemAttributes: (item, option) => {
27
+ item.setAttribute("data-rate", option.value);
28
+ item.toggleAttribute("data-custom", option.custom);
29
+ },
30
+ getOptionCacheKey: (option) => option.custom ? "custom" : "",
26
31
  onValueChange: (value) => {
27
32
  const media = this.#mediaState.value;
28
33
  if (media) this.#core.selectValue(media, value);
@@ -35,7 +40,8 @@ var PlaybackRateRadioGroupElement = class extends MenuRadioGroupElement {
35
40
  static {
36
41
  this.properties = {
37
42
  ...MenuRadioGroupElement.properties,
38
- disabled: { type: Boolean }
43
+ disabled: { type: Boolean },
44
+ rates: { type: String }
39
45
  };
40
46
  }
41
47
  #core;
@@ -53,7 +59,8 @@ var PlaybackRateRadioGroupElement = class extends MenuRadioGroupElement {
53
59
  if (media) {
54
60
  this.#core.setProps({
55
61
  formatRate: this.formatRate,
56
- disabled: this.disabled
62
+ disabled: this.disabled,
63
+ rates: this.rates
57
64
  });
58
65
  this.#core.setMedia(media);
59
66
  state = this.#core.getState();
@@ -1 +1 @@
1
- {"version":3,"file":"element.js","names":[],"sources":["../../../../src/ui/playback-rate-radio-group/element.ts"],"sourcesContent":["import {\n PlaybackRateRadioGroupCore,\n PlaybackRateRadioGroupDataAttrs,\n type PlaybackRateRadioGroupOption,\n type PlaybackRateRadioGroupState,\n} from '@videojs/core';\nimport { applyStateDataAttrs, logMissingFeature, selectPlaybackRate } from '@videojs/core/dom';\nimport { translateText } from '@videojs/core/i18n';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\n\nimport { i18nContext } from '../../i18n/context';\nimport { I18nController } from '../../i18n/controller';\nimport { playerContext } from '../../player/context';\nimport { PlayerController } from '../../player/controller';\nimport { MenuRadioGroupElement } from '../menu/radio-group';\nimport { RadioOptionsController } from '../radio-options/controller';\n\n/**\n * Menu radio group that generates a `<media-menu-radio-item>` per available playback rate and shares the selected label\n * and availability with an enclosing menu. An optional `<template>` holding one `<media-menu-radio-item>` customizes\n * each generated item.\n */\nexport class PlaybackRateRadioGroupElement extends MenuRadioGroupElement {\n static override readonly tagName = 'media-playback-rate-radio-group';\n\n static override properties = {\n ...MenuRadioGroupElement.properties,\n disabled: { type: Boolean },\n } satisfies PropertyDeclarationMap<'value' | 'disabled'>;\n\n disabled = false;\n formatRate = PlaybackRateRadioGroupCore.defaultProps.formatRate;\n\n readonly #core = new PlaybackRateRadioGroupCore();\n readonly #i18n = new I18nController(this, i18nContext);\n readonly #mediaState = new PlayerController(this, playerContext, selectPlaybackRate);\n readonly #options = new RadioOptionsController<PlaybackRateRadioGroupOption>(this, {\n setItemAttributes: (item, option) => item.setAttribute('data-rate', option.value),\n onValueChange: (value) => {\n const media = this.#mediaState.value;\n\n if (media) this.#core.selectValue(media, value);\n },\n });\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n if (this.destroyed) return;\n\n if (__DEV__ && !this.#mediaState.value && this.#mediaState.displayName) {\n logMissingFeature(this.localName, this.#mediaState.displayName);\n }\n }\n\n protected override update(changed: PropertyValues): void {\n const media = this.#mediaState.value;\n let state: PlaybackRateRadioGroupState | null = null;\n\n if (media) {\n this.#core.setProps({ formatRate: this.formatRate, disabled: this.disabled });\n this.#core.setMedia(media);\n state = this.#core.getState();\n\n this.applyDefaultAriaLabel(\n translateText(this.#core.getLabel(state), this.#i18n.value, this.#core.getLabelParams(state))\n );\n this.#options.sync(state, this.#i18n.value, this.#i18n.locale);\n this.publishMenuOptionState(state.disabled, state.hidden, state.availability);\n } else {\n this.publishMenuOptionState(true, true, 'unsupported');\n }\n\n super.update(changed);\n\n if (state) applyStateDataAttrs(this, state, PlaybackRateRadioGroupDataAttrs);\n }\n}\n\nexport namespace PlaybackRateRadioGroupElement {\n export type State = PlaybackRateRadioGroupState;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsBA,IAAa,gCAAb,cAAmD,sBAAsB;;;EAQ5D,KAAA,WAAA;EACE,KAAA,aAAA,2BAA2B,aAAa;EAEpC,KAAA,QAAA,IAAI,2BAA2B;EAC/B,KAAA,QAAA,IAAI,eAAe,MAAM,WAAW;EAC9B,KAAA,cAAA,IAAI,iBAAiB,MAAM,eAAe,kBAAkB;EAC/D,KAAA,WAAA,IAAI,uBAAqD,MAAM;GACjF,oBAAoB,MAAM,WAAW,KAAK,aAAa,aAAa,OAAO,KAAK;GAChF,gBAAgB,UAAU;IACxB,MAAM,QAAQ,KAAK,YAAY;IAE/B,IAAI,OAAO,KAAK,MAAM,YAAY,OAAO,KAAK;GAChD;EACF,CAAC;;;EApBkC,KAAA,UAAA;;;EAEN,KAAA,aAAA;GAC3B,GAAG,sBAAsB;GACzB,UAAU,EAAE,MAAM,QAAQ;EAC5B;;CAKA;CACA;CACA;CACA;CASA,oBAAmC;EACjC,MAAM,kBAAkB;EAExB,IAAI,KAAK,WAAW;EAEpB,IAAe,CAAC,KAAK,YAAY,SAAS,KAAK,YAAY,aACzD,kBAAkB,KAAK,WAAW,KAAK,YAAY,WAAW;CAElE;CAEA,OAA0B,SAA+B;EACvD,MAAM,QAAQ,KAAK,YAAY;EAC/B,IAAI,QAA4C;EAEhD,IAAI,OAAO;GACT,KAAK,MAAM,SAAS;IAAE,YAAY,KAAK;IAAY,UAAU,KAAK;GAAS,CAAC;GAC5E,KAAK,MAAM,SAAS,KAAK;GACzB,QAAQ,KAAK,MAAM,SAAS;GAE5B,KAAK,sBACH,cAAc,KAAK,MAAM,SAAS,KAAK,GAAG,KAAK,MAAM,OAAO,KAAK,MAAM,eAAe,KAAK,CAAC,CAC9F;GACA,KAAK,SAAS,KAAK,OAAO,KAAK,MAAM,OAAO,KAAK,MAAM,MAAM;GAC7D,KAAK,uBAAuB,MAAM,UAAU,MAAM,QAAQ,MAAM,YAAY;EAC9E,OACE,KAAK,uBAAuB,MAAM,MAAM,aAAa;EAGvD,MAAM,OAAO,OAAO;EAEpB,IAAI,OAAO,oBAAoB,MAAM,OAAO,+BAA+B;CAC7E;AACF"}
1
+ {"version":3,"file":"element.js","names":[],"sources":["../../../../src/ui/playback-rate-radio-group/element.ts"],"sourcesContent":["import {\n PlaybackRateRadioGroupCore,\n PlaybackRateRadioGroupDataAttrs,\n type PlaybackRateRadioGroupOption,\n type PlaybackRateRadioGroupState,\n} from '@videojs/core';\nimport { applyStateDataAttrs, logMissingFeature, selectPlaybackRate } from '@videojs/core/dom';\nimport { translateText } from '@videojs/core/i18n';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\n\nimport { i18nContext } from '../../i18n/context';\nimport { I18nController } from '../../i18n/controller';\nimport { playerContext } from '../../player/context';\nimport { PlayerController } from '../../player/controller';\nimport { MenuRadioGroupElement } from '../menu/radio-group';\nimport { RadioOptionsController } from '../radio-options/controller';\n\n/**\n * Menu radio group that generates a `<media-menu-radio-item>` per available playback rate and shares the selected label\n * and availability with an enclosing menu. An optional `<template>` holding one `<media-menu-radio-item>` customizes\n * each generated item.\n */\nexport class PlaybackRateRadioGroupElement extends MenuRadioGroupElement {\n static override readonly tagName = 'media-playback-rate-radio-group';\n\n static override properties = {\n ...MenuRadioGroupElement.properties,\n disabled: { type: Boolean },\n rates: { type: String },\n } satisfies PropertyDeclarationMap<'value' | 'disabled' | 'rates'>;\n\n disabled = false;\n rates: readonly number[] | string | undefined = undefined;\n formatRate = PlaybackRateRadioGroupCore.defaultProps.formatRate;\n\n readonly #core = new PlaybackRateRadioGroupCore();\n readonly #i18n = new I18nController(this, i18nContext);\n readonly #mediaState = new PlayerController(this, playerContext, selectPlaybackRate);\n readonly #options = new RadioOptionsController<PlaybackRateRadioGroupOption>(this, {\n setItemAttributes: (item, option) => {\n item.setAttribute('data-rate', option.value);\n item.toggleAttribute('data-custom', option.custom);\n },\n getOptionCacheKey: (option) => (option.custom ? 'custom' : ''),\n onValueChange: (value) => {\n const media = this.#mediaState.value;\n\n if (media) this.#core.selectValue(media, value);\n },\n });\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n if (this.destroyed) return;\n\n if (__DEV__ && !this.#mediaState.value && this.#mediaState.displayName) {\n logMissingFeature(this.localName, this.#mediaState.displayName);\n }\n }\n\n protected override update(changed: PropertyValues): void {\n const media = this.#mediaState.value;\n let state: PlaybackRateRadioGroupState | null = null;\n\n if (media) {\n this.#core.setProps({ formatRate: this.formatRate, disabled: this.disabled, rates: this.rates });\n this.#core.setMedia(media);\n state = this.#core.getState();\n\n this.applyDefaultAriaLabel(\n translateText(this.#core.getLabel(state), this.#i18n.value, this.#core.getLabelParams(state))\n );\n this.#options.sync(state, this.#i18n.value, this.#i18n.locale);\n this.publishMenuOptionState(state.disabled, state.hidden, state.availability);\n } else {\n this.publishMenuOptionState(true, true, 'unsupported');\n }\n\n super.update(changed);\n\n if (state) applyStateDataAttrs(this, state, PlaybackRateRadioGroupDataAttrs);\n }\n}\n\nexport namespace PlaybackRateRadioGroupElement {\n export type State = PlaybackRateRadioGroupState;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsBA,IAAa,gCAAb,cAAmD,sBAAsB;;;EAS5D,KAAA,WAAA;EACqC,KAAA,QAAA,KAAA;EACnC,KAAA,aAAA,2BAA2B,aAAa;EAEpC,KAAA,QAAA,IAAI,2BAA2B;EAC/B,KAAA,QAAA,IAAI,eAAe,MAAM,WAAW;EAC9B,KAAA,cAAA,IAAI,iBAAiB,MAAM,eAAe,kBAAkB;EAC/D,KAAA,WAAA,IAAI,uBAAqD,MAAM;GACjF,oBAAoB,MAAM,WAAW;IACnC,KAAK,aAAa,aAAa,OAAO,KAAK;IAC3C,KAAK,gBAAgB,eAAe,OAAO,MAAM;GACnD;GACA,oBAAoB,WAAY,OAAO,SAAS,WAAW;GAC3D,gBAAgB,UAAU;IACxB,MAAM,QAAQ,KAAK,YAAY;IAE/B,IAAI,OAAO,KAAK,MAAM,YAAY,OAAO,KAAK;GAChD;EACF,CAAC;;;EA1BkC,KAAA,UAAA;;;EAEN,KAAA,aAAA;GAC3B,GAAG,sBAAsB;GACzB,UAAU,EAAE,MAAM,QAAQ;GAC1B,OAAO,EAAE,MAAM,OAAO;EACxB;;CAMA;CACA;CACA;CACA;CAaA,oBAAmC;EACjC,MAAM,kBAAkB;EAExB,IAAI,KAAK,WAAW;EAEpB,IAAe,CAAC,KAAK,YAAY,SAAS,KAAK,YAAY,aACzD,kBAAkB,KAAK,WAAW,KAAK,YAAY,WAAW;CAElE;CAEA,OAA0B,SAA+B;EACvD,MAAM,QAAQ,KAAK,YAAY;EAC/B,IAAI,QAA4C;EAEhD,IAAI,OAAO;GACT,KAAK,MAAM,SAAS;IAAE,YAAY,KAAK;IAAY,UAAU,KAAK;IAAU,OAAO,KAAK;GAAM,CAAC;GAC/F,KAAK,MAAM,SAAS,KAAK;GACzB,QAAQ,KAAK,MAAM,SAAS;GAE5B,KAAK,sBACH,cAAc,KAAK,MAAM,SAAS,KAAK,GAAG,KAAK,MAAM,OAAO,KAAK,MAAM,eAAe,KAAK,CAAC,CAC9F;GACA,KAAK,SAAS,KAAK,OAAO,KAAK,MAAM,OAAO,KAAK,MAAM,MAAM;GAC7D,KAAK,uBAAuB,MAAM,UAAU,MAAM,QAAQ,MAAM,YAAY;EAC9E,OACE,KAAK,uBAAuB,MAAM,MAAM,aAAa;EAGvD,MAAM,OAAO,OAAO;EAEpB,IAAI,OAAO,oBAAoB,MAAM,OAAO,+BAA+B;CAC7E;AACF"}
@@ -4,6 +4,7 @@ import { MenuItemIndicatorElement } from "../menu/item-indicator.js";
4
4
  import { applyElementProps } from "@videojs/core/dom";
5
5
  import { translateText } from "@videojs/core/i18n";
6
6
  import { cloneTemplateRoot, getTemplateElement, getTemplateRoot } from "@videojs/utils/dom";
7
+ import { isUndefined } from "@videojs/utils/predicate";
7
8
  //#region src/ui/radio-options/controller.ts
8
9
  /** Renders normalized options into menu radio items and manages their interaction lifecycle. */
9
10
  var RadioOptionsController = class {
@@ -37,14 +38,12 @@ var RadioOptionsController = class {
37
38
  const template = getTemplateElement(this.#host);
38
39
  const templateRoot = template ? getTemplateRoot(template) : null;
39
40
  const itemRoot = templateRoot?.localName === MenuRadioItemElement.tagName ? templateRoot : null;
40
- const contentKey = `${state.options.map((option) => `${option.value}:${cacheKey(option.label, option.labelParams)}:${this.#config.getOptionCacheKey?.(option) ?? ""}`).join("|")}::${locale}::${template?.innerHTML ?? ""}`;
41
+ const contentKey = `${state.options.map((option) => `${option.value}:${cacheKey(option.label, option.labelParams)}:${cacheKey(option.ariaLabel ?? "", option.ariaLabelParams)}:${this.#config.getOptionCacheKey?.(option) ?? ""}`).join("|")}::${locale}::${template?.innerHTML ?? ""}`;
41
42
  if (contentKey !== this.#contentKey || translator !== this.#translator) {
42
43
  this.#contentKey = contentKey;
43
44
  this.#translator = translator;
44
- for (const child of [...this.#host.children]) {
45
- if (child === template) continue;
46
- child.remove();
47
- }
45
+ const staleChildren = [...this.#host.children].filter((child) => child !== template);
46
+ const focusedValue = this.#getFocusedItem()?.value;
48
47
  const items = state.options.map((option) => {
49
48
  const item = itemRoot ? cloneTemplateRoot(itemRoot, this.#host.ownerDocument) : this.#host.ownerDocument.createElement(MenuRadioItemElement.tagName);
50
49
  item.value = option.value;
@@ -52,9 +51,19 @@ var RadioOptionsController = class {
52
51
  const label = translateText(option.label, translator, option.labelParams);
53
52
  if (this.#config.renderItem) this.#config.renderItem(item, label, option);
54
53
  else this.#setItemLabel(item, label);
54
+ if (!isUndefined(option.ariaLabel)) item.setAttribute("aria-label", translateText(option.ariaLabel, translator, option.ariaLabelParams));
55
55
  return item;
56
56
  });
57
57
  this.#host.append(...items);
58
+ const removeStale = () => {
59
+ for (const child of staleChildren) child.remove();
60
+ };
61
+ const replacement = isUndefined(focusedValue) ? void 0 : items.find((item) => item.value === focusedValue) ?? items.find((item) => item.value === state.value);
62
+ if (replacement) replacement.updateComplete.then(() => {
63
+ replacement.focus();
64
+ removeStale();
65
+ });
66
+ else removeStale();
58
67
  }
59
68
  const optionsByValue = new Map(state.options.map((option) => [option.value, option]));
60
69
  for (const item of this.#host.querySelectorAll(MenuRadioItemElement.tagName)) {
@@ -69,6 +78,10 @@ var RadioOptionsController = class {
69
78
  const { value } = event.detail;
70
79
  this.#config.onValueChange(value);
71
80
  };
81
+ #getFocusedItem() {
82
+ const active = this.#host.getRootNode().activeElement;
83
+ return active instanceof MenuRadioItemElement && active.parentElement === this.#host ? active : null;
84
+ }
72
85
  #setItemLabel(item, label) {
73
86
  const labelPart = item.querySelector("[data-part~=\"label\"]");
74
87
  if (labelPart) labelPart.textContent = label;
@@ -1 +1 @@
1
- {"version":3,"file":"controller.js","names":[],"sources":["../../../../src/ui/radio-options/controller.ts"],"sourcesContent":["import type { RadioOption, RadioOptionsState } from '@videojs/core';\nimport { applyElementProps } from '@videojs/core/dom';\nimport { type Translator, translateText } from '@videojs/core/i18n';\nimport type { ReactiveController, ReactiveControllerHost } from '@videojs/element';\nimport { cloneTemplateRoot, getTemplateElement, getTemplateRoot } from '@videojs/utils/dom';\n\nimport { cacheKey } from '../../i18n/cache-key';\nimport { MenuItemIndicatorElement } from '../menu/item-indicator';\nimport { MenuRadioItemElement } from '../menu/radio-item';\n\nexport type RadioOptionsControllerHost = ReactiveControllerHost &\n HTMLElement & {\n value: string;\n };\n\nexport interface RadioOptionsControllerConfig<Option extends RadioOption> {\n renderItem?: ((item: MenuRadioItemElement, label: string, option: Option) => void) | undefined;\n setItemAttributes?: ((item: MenuRadioItemElement, option: Option) => void) | undefined;\n getOptionCacheKey?: ((option: Option) => string) | undefined;\n onValueChange: (value: string) => void;\n}\n\n/** Renders normalized options into menu radio items and manages their interaction lifecycle. */\nexport class RadioOptionsController<Option extends RadioOption> implements ReactiveController {\n readonly #host: RadioOptionsControllerHost;\n readonly #config: RadioOptionsControllerConfig<Option>;\n\n #contentKey = '';\n #translator: Translator | null = null;\n #disconnect: AbortController | null = null;\n\n constructor(host: RadioOptionsControllerHost, config: RadioOptionsControllerConfig<Option>) {\n this.#host = host;\n this.#config = config;\n host.addController(this);\n }\n\n hostConnected(): void {\n this.#disconnect = new AbortController();\n this.#host.addEventListener('value-change', this.#handleValueChange, { signal: this.#disconnect.signal });\n }\n\n hostDisconnected(): void {\n this.#disconnect?.abort();\n this.#disconnect = null;\n }\n\n hostDestroyed(): void {\n this.hostDisconnected();\n }\n\n sync(state: RadioOptionsState<Option>, translator: Translator, locale: string): void {\n this.#host.value = state.value;\n applyElementProps(this.#host, {\n 'aria-disabled': state.disabled ? 'true' : undefined,\n hidden: state.hidden ? '' : undefined,\n });\n\n const template = getTemplateElement(this.#host);\n const templateRoot = template ? getTemplateRoot(template) : null;\n const itemRoot = templateRoot?.localName === MenuRadioItemElement.tagName ? templateRoot : null;\n\n const contentKey = `${state.options\n .map(\n (option) =>\n `${option.value}:${cacheKey(option.label, option.labelParams)}:${this.#config.getOptionCacheKey?.(option) ?? ''}`\n )\n .join('|')}::${locale}::${template?.innerHTML ?? ''}`;\n\n if (contentKey !== this.#contentKey || translator !== this.#translator) {\n this.#contentKey = contentKey;\n this.#translator = translator;\n\n for (const child of [...this.#host.children]) {\n if (child === template) continue;\n\n child.remove();\n }\n\n const items = state.options.map((option) => {\n const item = itemRoot\n ? (cloneTemplateRoot(itemRoot, this.#host.ownerDocument) as MenuRadioItemElement)\n : (this.#host.ownerDocument.createElement(MenuRadioItemElement.tagName) as MenuRadioItemElement);\n\n item.value = option.value;\n this.#config.setItemAttributes?.(item, option);\n const label = translateText(option.label, translator, option.labelParams);\n\n if (this.#config.renderItem) this.#config.renderItem(item, label, option);\n else this.#setItemLabel(item, label);\n\n return item;\n });\n\n this.#host.append(...items);\n }\n\n const optionsByValue = new Map(state.options.map((option) => [option.value, option]));\n\n for (const item of this.#host.querySelectorAll<MenuRadioItemElement>(MenuRadioItemElement.tagName)) {\n const checked = item.value === state.value;\n const option = optionsByValue.get(item.value);\n\n item.disabled = state.disabled || option?.disabled === true;\n\n for (const indicator of item.querySelectorAll<MenuItemIndicatorElement>(MenuItemIndicatorElement.tagName)) {\n indicator.checked = checked;\n }\n }\n }\n\n #handleValueChange = (event: Event): void => {\n if (event.target !== this.#host) return;\n\n const { value } = (event as CustomEvent<{ value: string }>).detail;\n\n this.#config.onValueChange(value);\n };\n\n #setItemLabel(item: MenuRadioItemElement, label: string): void {\n const labelPart = item.querySelector<HTMLElement>('[data-part~=\"label\"]');\n\n if (labelPart) labelPart.textContent = label;\n else item.textContent = label;\n }\n}\n"],"mappings":";;;;;;;;AAuBA,IAAa,yBAAb,MAA8F;CAC5F;CACA;CAEA,cAAc;CACd,cAAiC;CACjC,cAAsC;CAEtC,YAAY,MAAkC,QAA8C;EAC1F,KAAK,QAAQ;EACb,KAAK,UAAU;EACf,KAAK,cAAc,IAAI;CACzB;CAEA,gBAAsB;EACpB,KAAK,cAAc,IAAI,gBAAgB;EACvC,KAAK,MAAM,iBAAiB,gBAAgB,KAAK,oBAAoB,EAAE,QAAQ,KAAK,YAAY,OAAO,CAAC;CAC1G;CAEA,mBAAyB;EACvB,KAAK,aAAa,MAAM;EACxB,KAAK,cAAc;CACrB;CAEA,gBAAsB;EACpB,KAAK,iBAAiB;CACxB;CAEA,KAAK,OAAkC,YAAwB,QAAsB;EACnF,KAAK,MAAM,QAAQ,MAAM;EACzB,kBAAkB,KAAK,OAAO;GAC5B,iBAAiB,MAAM,WAAW,SAAS,KAAA;GAC3C,QAAQ,MAAM,SAAS,KAAK,KAAA;EAC9B,CAAC;EAED,MAAM,WAAW,mBAAmB,KAAK,KAAK;EAC9C,MAAM,eAAe,WAAW,gBAAgB,QAAQ,IAAI;EAC5D,MAAM,WAAW,cAAc,cAAc,qBAAqB,UAAU,eAAe;EAE3F,MAAM,aAAa,GAAG,MAAM,QACzB,KACE,WACC,GAAG,OAAO,MAAM,GAAG,SAAS,OAAO,OAAO,OAAO,WAAW,EAAE,GAAG,KAAK,QAAQ,oBAAoB,MAAM,KAAK,IACjH,CAAC,CACA,KAAK,GAAG,EAAE,IAAI,OAAO,IAAI,UAAU,aAAa;EAEnD,IAAI,eAAe,KAAK,eAAe,eAAe,KAAK,aAAa;GACtE,KAAK,cAAc;GACnB,KAAK,cAAc;GAEnB,KAAK,MAAM,SAAS,CAAC,GAAG,KAAK,MAAM,QAAQ,GAAG;IAC5C,IAAI,UAAU,UAAU;IAExB,MAAM,OAAO;GACf;GAEA,MAAM,QAAQ,MAAM,QAAQ,KAAK,WAAW;IAC1C,MAAM,OAAO,WACR,kBAAkB,UAAU,KAAK,MAAM,aAAa,IACpD,KAAK,MAAM,cAAc,cAAc,qBAAqB,OAAO;IAExE,KAAK,QAAQ,OAAO;IACpB,KAAK,QAAQ,oBAAoB,MAAM,MAAM;IAC7C,MAAM,QAAQ,cAAc,OAAO,OAAO,YAAY,OAAO,WAAW;IAExE,IAAI,KAAK,QAAQ,YAAY,KAAK,QAAQ,WAAW,MAAM,OAAO,MAAM;SACnE,KAAK,cAAc,MAAM,KAAK;IAEnC,OAAO;GACT,CAAC;GAED,KAAK,MAAM,OAAO,GAAG,KAAK;EAC5B;EAEA,MAAM,iBAAiB,IAAI,IAAI,MAAM,QAAQ,KAAK,WAAW,CAAC,OAAO,OAAO,MAAM,CAAC,CAAC;EAEpF,KAAK,MAAM,QAAQ,KAAK,MAAM,iBAAuC,qBAAqB,OAAO,GAAG;GAClG,MAAM,UAAU,KAAK,UAAU,MAAM;GACrC,MAAM,SAAS,eAAe,IAAI,KAAK,KAAK;GAE5C,KAAK,WAAW,MAAM,YAAY,QAAQ,aAAa;GAEvD,KAAK,MAAM,aAAa,KAAK,iBAA2C,yBAAyB,OAAO,GACtG,UAAU,UAAU;EAExB;CACF;CAEA,sBAAsB,UAAuB;EAC3C,IAAI,MAAM,WAAW,KAAK,OAAO;EAEjC,MAAM,EAAE,UAAW,MAAyC;EAE5D,KAAK,QAAQ,cAAc,KAAK;CAClC;CAEA,cAAc,MAA4B,OAAqB;EAC7D,MAAM,YAAY,KAAK,cAA2B,wBAAsB;EAExE,IAAI,WAAW,UAAU,cAAc;OAClC,KAAK,cAAc;CAC1B;AACF"}
1
+ {"version":3,"file":"controller.js","names":[],"sources":["../../../../src/ui/radio-options/controller.ts"],"sourcesContent":["import type { RadioOption, RadioOptionsState } from '@videojs/core';\nimport { applyElementProps } from '@videojs/core/dom';\nimport { type Translator, translateText } from '@videojs/core/i18n';\nimport type { ReactiveController, ReactiveControllerHost } from '@videojs/element';\nimport { cloneTemplateRoot, getTemplateElement, getTemplateRoot } from '@videojs/utils/dom';\nimport { isUndefined } from '@videojs/utils/predicate';\n\nimport { cacheKey } from '../../i18n/cache-key';\nimport { MenuItemIndicatorElement } from '../menu/item-indicator';\nimport { MenuRadioItemElement } from '../menu/radio-item';\n\nexport type RadioOptionsControllerHost = ReactiveControllerHost &\n HTMLElement & {\n value: string;\n };\n\nexport interface RadioOptionsControllerConfig<Option extends RadioOption> {\n renderItem?: ((item: MenuRadioItemElement, label: string, option: Option) => void) | undefined;\n setItemAttributes?: ((item: MenuRadioItemElement, option: Option) => void) | undefined;\n getOptionCacheKey?: ((option: Option) => string) | undefined;\n onValueChange: (value: string) => void;\n}\n\n/** Renders normalized options into menu radio items and manages their interaction lifecycle. */\nexport class RadioOptionsController<Option extends RadioOption> implements ReactiveController {\n readonly #host: RadioOptionsControllerHost;\n readonly #config: RadioOptionsControllerConfig<Option>;\n\n #contentKey = '';\n #translator: Translator | null = null;\n #disconnect: AbortController | null = null;\n\n constructor(host: RadioOptionsControllerHost, config: RadioOptionsControllerConfig<Option>) {\n this.#host = host;\n this.#config = config;\n host.addController(this);\n }\n\n hostConnected(): void {\n this.#disconnect = new AbortController();\n this.#host.addEventListener('value-change', this.#handleValueChange, { signal: this.#disconnect.signal });\n }\n\n hostDisconnected(): void {\n this.#disconnect?.abort();\n this.#disconnect = null;\n }\n\n hostDestroyed(): void {\n this.hostDisconnected();\n }\n\n sync(state: RadioOptionsState<Option>, translator: Translator, locale: string): void {\n this.#host.value = state.value;\n applyElementProps(this.#host, {\n 'aria-disabled': state.disabled ? 'true' : undefined,\n hidden: state.hidden ? '' : undefined,\n });\n\n const template = getTemplateElement(this.#host);\n const templateRoot = template ? getTemplateRoot(template) : null;\n const itemRoot = templateRoot?.localName === MenuRadioItemElement.tagName ? templateRoot : null;\n\n const contentKey = `${state.options\n .map(\n (option) =>\n `${option.value}:${cacheKey(option.label, option.labelParams)}:${cacheKey(option.ariaLabel ?? '', option.ariaLabelParams)}:${this.#config.getOptionCacheKey?.(option) ?? ''}`\n )\n .join('|')}::${locale}::${template?.innerHTML ?? ''}`;\n\n if (contentKey !== this.#contentKey || translator !== this.#translator) {\n this.#contentKey = contentKey;\n this.#translator = translator;\n\n const staleChildren = [...this.#host.children].filter((child) => child !== template);\n const focusedValue = this.#getFocusedItem()?.value;\n\n const items = state.options.map((option) => {\n const item = itemRoot\n ? (cloneTemplateRoot(itemRoot, this.#host.ownerDocument) as MenuRadioItemElement)\n : (this.#host.ownerDocument.createElement(MenuRadioItemElement.tagName) as MenuRadioItemElement);\n\n item.value = option.value;\n this.#config.setItemAttributes?.(item, option);\n const label = translateText(option.label, translator, option.labelParams);\n\n if (this.#config.renderItem) this.#config.renderItem(item, label, option);\n else this.#setItemLabel(item, label);\n\n if (!isUndefined(option.ariaLabel)) {\n item.setAttribute('aria-label', translateText(option.ariaLabel, translator, option.ariaLabelParams));\n }\n\n return item;\n });\n\n this.#host.append(...items);\n\n const removeStale = () => {\n for (const child of staleChildren) child.remove();\n };\n\n // Removing the focused item fires a `focusout` with no destination, which closes an open menu. Its replacement\n // registers with the menu, and becomes focusable, on its first update, so the old items stay until focus can move.\n const replacement = isUndefined(focusedValue)\n ? undefined\n : (items.find((item) => item.value === focusedValue) ?? items.find((item) => item.value === state.value));\n\n if (replacement) {\n void replacement.updateComplete.then(() => {\n replacement.focus();\n removeStale();\n });\n } else {\n removeStale();\n }\n }\n\n const optionsByValue = new Map(state.options.map((option) => [option.value, option]));\n\n for (const item of this.#host.querySelectorAll<MenuRadioItemElement>(MenuRadioItemElement.tagName)) {\n const checked = item.value === state.value;\n const option = optionsByValue.get(item.value);\n\n item.disabled = state.disabled || option?.disabled === true;\n\n for (const indicator of item.querySelectorAll<MenuItemIndicatorElement>(MenuItemIndicatorElement.tagName)) {\n indicator.checked = checked;\n }\n }\n }\n\n #handleValueChange = (event: Event): void => {\n if (event.target !== this.#host) return;\n\n const { value } = (event as CustomEvent<{ value: string }>).detail;\n\n this.#config.onValueChange(value);\n };\n\n #getFocusedItem(): MenuRadioItemElement | null {\n const active = (this.#host.getRootNode() as Document | ShadowRoot).activeElement;\n\n return active instanceof MenuRadioItemElement && active.parentElement === this.#host ? active : null;\n }\n\n #setItemLabel(item: MenuRadioItemElement, label: string): void {\n const labelPart = item.querySelector<HTMLElement>('[data-part~=\"label\"]');\n\n if (labelPart) labelPart.textContent = label;\n else item.textContent = label;\n }\n}\n"],"mappings":";;;;;;;;;AAwBA,IAAa,yBAAb,MAA8F;CAC5F;CACA;CAEA,cAAc;CACd,cAAiC;CACjC,cAAsC;CAEtC,YAAY,MAAkC,QAA8C;EAC1F,KAAK,QAAQ;EACb,KAAK,UAAU;EACf,KAAK,cAAc,IAAI;CACzB;CAEA,gBAAsB;EACpB,KAAK,cAAc,IAAI,gBAAgB;EACvC,KAAK,MAAM,iBAAiB,gBAAgB,KAAK,oBAAoB,EAAE,QAAQ,KAAK,YAAY,OAAO,CAAC;CAC1G;CAEA,mBAAyB;EACvB,KAAK,aAAa,MAAM;EACxB,KAAK,cAAc;CACrB;CAEA,gBAAsB;EACpB,KAAK,iBAAiB;CACxB;CAEA,KAAK,OAAkC,YAAwB,QAAsB;EACnF,KAAK,MAAM,QAAQ,MAAM;EACzB,kBAAkB,KAAK,OAAO;GAC5B,iBAAiB,MAAM,WAAW,SAAS,KAAA;GAC3C,QAAQ,MAAM,SAAS,KAAK,KAAA;EAC9B,CAAC;EAED,MAAM,WAAW,mBAAmB,KAAK,KAAK;EAC9C,MAAM,eAAe,WAAW,gBAAgB,QAAQ,IAAI;EAC5D,MAAM,WAAW,cAAc,cAAc,qBAAqB,UAAU,eAAe;EAE3F,MAAM,aAAa,GAAG,MAAM,QACzB,KACE,WACC,GAAG,OAAO,MAAM,GAAG,SAAS,OAAO,OAAO,OAAO,WAAW,EAAE,GAAG,SAAS,OAAO,aAAa,IAAI,OAAO,eAAe,EAAE,GAAG,KAAK,QAAQ,oBAAoB,MAAM,KAAK,IAC7K,CAAC,CACA,KAAK,GAAG,EAAE,IAAI,OAAO,IAAI,UAAU,aAAa;EAEnD,IAAI,eAAe,KAAK,eAAe,eAAe,KAAK,aAAa;GACtE,KAAK,cAAc;GACnB,KAAK,cAAc;GAEnB,MAAM,gBAAgB,CAAC,GAAG,KAAK,MAAM,QAAQ,CAAC,CAAC,QAAQ,UAAU,UAAU,QAAQ;GACnF,MAAM,eAAe,KAAK,gBAAgB,CAAC,EAAE;GAE7C,MAAM,QAAQ,MAAM,QAAQ,KAAK,WAAW;IAC1C,MAAM,OAAO,WACR,kBAAkB,UAAU,KAAK,MAAM,aAAa,IACpD,KAAK,MAAM,cAAc,cAAc,qBAAqB,OAAO;IAExE,KAAK,QAAQ,OAAO;IACpB,KAAK,QAAQ,oBAAoB,MAAM,MAAM;IAC7C,MAAM,QAAQ,cAAc,OAAO,OAAO,YAAY,OAAO,WAAW;IAExE,IAAI,KAAK,QAAQ,YAAY,KAAK,QAAQ,WAAW,MAAM,OAAO,MAAM;SACnE,KAAK,cAAc,MAAM,KAAK;IAEnC,IAAI,CAAC,YAAY,OAAO,SAAS,GAC/B,KAAK,aAAa,cAAc,cAAc,OAAO,WAAW,YAAY,OAAO,eAAe,CAAC;IAGrG,OAAO;GACT,CAAC;GAED,KAAK,MAAM,OAAO,GAAG,KAAK;GAE1B,MAAM,oBAAoB;IACxB,KAAK,MAAM,SAAS,eAAe,MAAM,OAAO;GAClD;GAIA,MAAM,cAAc,YAAY,YAAY,IACxC,KAAA,IACC,MAAM,MAAM,SAAS,KAAK,UAAU,YAAY,KAAK,MAAM,MAAM,SAAS,KAAK,UAAU,MAAM,KAAK;GAEzG,IAAI,aACF,YAAiB,eAAe,WAAW;IACzC,YAAY,MAAM;IAClB,YAAY;GACd,CAAC;QAED,YAAY;EAEhB;EAEA,MAAM,iBAAiB,IAAI,IAAI,MAAM,QAAQ,KAAK,WAAW,CAAC,OAAO,OAAO,MAAM,CAAC,CAAC;EAEpF,KAAK,MAAM,QAAQ,KAAK,MAAM,iBAAuC,qBAAqB,OAAO,GAAG;GAClG,MAAM,UAAU,KAAK,UAAU,MAAM;GACrC,MAAM,SAAS,eAAe,IAAI,KAAK,KAAK;GAE5C,KAAK,WAAW,MAAM,YAAY,QAAQ,aAAa;GAEvD,KAAK,MAAM,aAAa,KAAK,iBAA2C,yBAAyB,OAAO,GACtG,UAAU,UAAU;EAExB;CACF;CAEA,sBAAsB,UAAuB;EAC3C,IAAI,MAAM,WAAW,KAAK,OAAO;EAEjC,MAAM,EAAE,UAAW,MAAyC;EAE5D,KAAK,QAAQ,cAAc,KAAK;CAClC;CAEA,kBAA+C;EAC7C,MAAM,SAAU,KAAK,MAAM,YAAY,CAAC,CAA2B;EAEnE,OAAO,kBAAkB,wBAAwB,OAAO,kBAAkB,KAAK,QAAQ,SAAS;CAClG;CAEA,cAAc,MAA4B,OAAqB;EAC7D,MAAM,YAAY,KAAK,cAA2B,wBAAsB;EAExE,IAAI,WAAW,UAAU,cAAc;OAClC,KAAK,cAAc;CAC1B;AACF"}