vidply 1.2.0 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +974 -946
  2. package/dist/dev/{vidply.AudioDescriptionManager-R47Q4YR4.js → vidply.AudioDescriptionManager-JYYLK5KB.js} +275 -19
  3. package/dist/dev/vidply.AudioDescriptionManager-JYYLK5KB.js.map +7 -0
  4. package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js → vidply.CaptionStyleMenu-47RCKQF5.js} +4 -4
  5. package/dist/dev/{vidply.DASHRenderer-PWEMC3BT.js → vidply.DASHRenderer-VEJ4HDHO.js} +3 -3
  6. package/dist/dev/vidply.DASHRenderer-VEJ4HDHO.js.map +7 -0
  7. package/dist/dev/{vidply.FloatingPlayerManager-PKNNNAS2.js → vidply.FloatingPlayerManager-C2UVRPZY.js} +5 -5
  8. package/dist/dev/vidply.FloatingPlayerManager-C2UVRPZY.js.map +7 -0
  9. package/dist/dev/{vidply.HLSRenderer-E7RQ74HI.js → vidply.HLSRenderer-ZVBK2TNQ.js} +4 -4
  10. package/dist/dev/vidply.HLSRenderer-ZVBK2TNQ.js.map +7 -0
  11. package/dist/dev/vidply.HTML5Renderer-HLRQUOCU.js +12 -0
  12. package/dist/dev/{vidply.SignLanguageManager-FET5FQZ7.js → vidply.SignLanguageManager-FLUCBW5H.js} +8 -8
  13. package/dist/dev/vidply.SignLanguageManager-FLUCBW5H.js.map +7 -0
  14. package/dist/dev/{vidply.SoundCloudRenderer-RVSJZODO.js → vidply.SoundCloudRenderer-MGMAJ7NK.js} +3 -3
  15. package/dist/dev/vidply.SoundCloudRenderer-MGMAJ7NK.js.map +7 -0
  16. package/dist/dev/{vidply.TranscriptManager-5GXOQCLX.js → vidply.TranscriptManager-Y2RYXHJW.js} +10 -10
  17. package/dist/dev/vidply.TranscriptManager-Y2RYXHJW.js.map +7 -0
  18. package/dist/dev/{vidply.VimeoRenderer-ER2LSBBW.js → vidply.VimeoRenderer-RQFQD7OE.js} +3 -3
  19. package/dist/dev/vidply.VimeoRenderer-RQFQD7OE.js.map +7 -0
  20. package/dist/dev/{vidply.YouTubeRenderer-NFTOWJAB.js → vidply.YouTubeRenderer-EU2BARZF.js} +3 -3
  21. package/dist/dev/vidply.YouTubeRenderer-EU2BARZF.js.map +7 -0
  22. package/dist/dev/{vidply.chunk-BNFIUL2Z.js → vidply.chunk-6LYZYI5G.js} +3 -3
  23. package/dist/dev/vidply.chunk-6LYZYI5G.js.map +7 -0
  24. package/dist/dev/{vidply.chunk-NQPURMQY.js → vidply.chunk-A4ESEOQ2.js} +6 -6
  25. package/dist/dev/vidply.chunk-A4ESEOQ2.js.map +7 -0
  26. package/dist/dev/{vidply.chunk-W25HUCHS.js → vidply.chunk-CGYIED2I.js} +2 -2
  27. package/dist/dev/vidply.chunk-CGYIED2I.js.map +7 -0
  28. package/dist/dev/{vidply.chunk-SK4V2DAU.js → vidply.chunk-HZ7UOKOU.js} +3 -3
  29. package/dist/dev/{vidply.chunk-RZWZGDV7.js → vidply.chunk-I6EDRYGJ.js} +33 -6
  30. package/dist/dev/vidply.chunk-I6EDRYGJ.js.map +7 -0
  31. package/dist/dev/{vidply.chunk-64WXMTGL.js → vidply.chunk-JKMQGLCJ.js} +5 -5
  32. package/dist/dev/vidply.chunk-JKMQGLCJ.js.map +7 -0
  33. package/dist/dev/{vidply.chunk-DZSDO3B7.js → vidply.chunk-JZNZX32Z.js} +2 -2
  34. package/dist/dev/{vidply.chunk-VMSQAZM7.js → vidply.chunk-P25MRJ6O.js} +2 -2
  35. package/dist/dev/{vidply.chunk-6RQA4ROD.js → vidply.chunk-PRFNESYA.js} +2 -2
  36. package/dist/dev/{vidply.chunk-KI7C3QVL.js → vidply.chunk-UI3RYCVF.js} +4 -3
  37. package/dist/dev/vidply.chunk-UI3RYCVF.js.map +7 -0
  38. package/dist/dev/{vidply.chunk-JJ47NO24.js → vidply.chunk-UNFGMABP.js} +2 -2
  39. package/dist/dev/vidply.chunk-UNFGMABP.js.map +7 -0
  40. package/dist/dev/{vidply.de-4YAT7FFM.js → vidply.de-VAUVSQXC.js} +29 -2
  41. package/dist/dev/vidply.de-VAUVSQXC.js.map +7 -0
  42. package/dist/dev/{vidply.es-LEWIRUBH.js → vidply.es-G65HFWO6.js} +29 -2
  43. package/dist/dev/vidply.es-G65HFWO6.js.map +7 -0
  44. package/dist/dev/vidply.esm.js +648 -35
  45. package/dist/dev/vidply.esm.js.map +3 -3
  46. package/dist/dev/{vidply.fr-Q4GV2NOE.js → vidply.fr-MJNOZELM.js} +29 -2
  47. package/dist/dev/vidply.fr-MJNOZELM.js.map +7 -0
  48. package/dist/dev/{vidply.ja-S6ITA6WU.js → vidply.ja-INMDHIYJ.js} +29 -2
  49. package/dist/dev/vidply.ja-INMDHIYJ.js.map +7 -0
  50. package/dist/legacy/vidply.js +1062 -34
  51. package/dist/legacy/vidply.js.map +3 -3
  52. package/dist/legacy/vidply.min.js +2 -2
  53. package/dist/legacy/vidply.min.meta.json +127 -55
  54. package/dist/prod/vidply.AudioDescriptionManager-EKWUNOPE.min.js +6 -0
  55. package/dist/prod/{vidply.CaptionStyleMenu-M3KVQWB5.min.js → vidply.CaptionStyleMenu-JTWSNHRQ.min.js} +2 -2
  56. package/dist/prod/{vidply.DASHRenderer-DN3S27AV.min.js → vidply.DASHRenderer-CIDAHFWM.min.js} +2 -2
  57. package/dist/prod/{vidply.FloatingPlayerManager-SKAZ4J43.min.js → vidply.FloatingPlayerManager-VZXHPCPM.min.js} +2 -2
  58. package/dist/prod/{vidply.HLSRenderer-62HH4YTK.min.js → vidply.HLSRenderer-Q6EFYKID.min.js} +2 -2
  59. package/dist/prod/vidply.HTML5Renderer-XRDJUTOO.min.js +6 -0
  60. package/dist/prod/{vidply.SignLanguageManager-V3OBTSSF.min.js → vidply.SignLanguageManager-JUOSA5AM.min.js} +2 -2
  61. package/dist/prod/{vidply.SoundCloudRenderer-CXRVZIAJ.min.js → vidply.SoundCloudRenderer-7ODD46PO.min.js} +2 -2
  62. package/dist/prod/{vidply.TranscriptManager-SYM3DFUY.min.js → vidply.TranscriptManager-4B2QKYGQ.min.js} +2 -2
  63. package/dist/prod/{vidply.VimeoRenderer-72TWRYFQ.min.js → vidply.VimeoRenderer-MR3UYLY7.min.js} +2 -2
  64. package/dist/prod/{vidply.YouTubeRenderer-UMHEV3KD.min.js → vidply.YouTubeRenderer-ITRTC7NR.min.js} +2 -2
  65. package/dist/prod/{vidply.chunk-YEENJS3Q.min.js → vidply.chunk-3EEDXU4T.min.js} +2 -2
  66. package/dist/prod/{vidply.chunk-P4UXOHWA.min.js → vidply.chunk-5HTX5QXW.min.js} +1 -1
  67. package/dist/prod/{vidply.chunk-ABPXVDLZ.min.js → vidply.chunk-6OGHHIYP.min.js} +2 -2
  68. package/dist/prod/{vidply.chunk-UK7YGOB5.min.js → vidply.chunk-GUPGMFUZ.min.js} +2 -2
  69. package/dist/prod/{vidply.chunk-WX3MVEAL.min.js → vidply.chunk-MWEBITCP.min.js} +1 -1
  70. package/dist/prod/{vidply.chunk-2TB6MUHO.min.js → vidply.chunk-PL3P3JHR.min.js} +1 -1
  71. package/dist/prod/{vidply.chunk-EKEYJWMG.min.js → vidply.chunk-QQZGV7TD.min.js} +2 -2
  72. package/dist/prod/{vidply.chunk-Z3YDSA7O.min.js → vidply.chunk-U5IRRYG5.min.js} +1 -1
  73. package/dist/prod/{vidply.chunk-TM6AXJVD.min.js → vidply.chunk-VKHV4QHA.min.js} +2 -2
  74. package/dist/prod/{vidply.chunk-P3PQBY7N.min.js → vidply.chunk-XA4M574A.min.js} +1 -1
  75. package/dist/prod/{vidply.chunk-RI3X7WHK.min.js → vidply.chunk-ZPTV4JEP.min.js} +2 -2
  76. package/dist/prod/{vidply.de-BCHXRQ53.min.js → vidply.de-UZHFCEZU.min.js} +2 -2
  77. package/dist/prod/{vidply.es-PICAKMZD.min.js → vidply.es-BOISUV2S.min.js} +2 -2
  78. package/dist/prod/vidply.esm.min.js +2 -2
  79. package/dist/prod/{vidply.fr-ZILI7CDR.min.js → vidply.fr-ICSMKPFM.min.js} +2 -2
  80. package/dist/prod/{vidply.ja-5WQ4OK4J.min.js → vidply.ja-VQ2HKGAY.min.js} +2 -2
  81. package/dist/vidply.css +5852 -5796
  82. package/dist/vidply.esm.min.meta.json +204 -149
  83. package/dist/vidply.min.css +1 -1
  84. package/package.json +11 -11
  85. package/src/controls/CaptionManager.ts +598 -598
  86. package/src/controls/ControlBar.ts +4191 -4159
  87. package/src/controls/KeyboardHelp.ts +326 -0
  88. package/src/controls/KeyboardManager.ts +387 -383
  89. package/src/controls/SettingsDialog.ts +488 -488
  90. package/src/controls/TranscriptManager.ts +2426 -2426
  91. package/src/core/AudioDescriptionManager.ts +863 -774
  92. package/src/core/DescriptionSpeechManager.ts +232 -0
  93. package/src/core/FloatingPlayerManager.ts +674 -674
  94. package/src/core/LazyInit.ts +80 -80
  95. package/src/core/MediaSessionManager.ts +341 -0
  96. package/src/core/MetadataAlertsManager.ts +514 -514
  97. package/src/core/Player.ts +2973 -2872
  98. package/src/core/PosterManager.ts +169 -169
  99. package/src/core/PseudoFullscreen.ts +179 -179
  100. package/src/core/ResponsiveManager.ts +231 -231
  101. package/src/core/ResumeManager.ts +236 -236
  102. package/src/core/SignLanguageManager.ts +1308 -1308
  103. package/src/core/ThemeManager.ts +157 -157
  104. package/src/features/PlaylistManager.ts +1838 -1827
  105. package/src/i18n/i18n.ts +223 -223
  106. package/src/i18n/languages/de.ts +244 -217
  107. package/src/i18n/languages/en.ts +245 -218
  108. package/src/i18n/languages/es.ts +241 -214
  109. package/src/i18n/languages/fr.ts +241 -214
  110. package/src/i18n/languages/ja.ts +241 -214
  111. package/src/icons/Icons.ts +168 -167
  112. package/src/index.ts +196 -193
  113. package/src/renderers/DASHRenderer.ts +1077 -1077
  114. package/src/renderers/HLSRenderer.ts +944 -944
  115. package/src/renderers/HTML5Renderer.ts +389 -389
  116. package/src/renderers/SoundCloudRenderer.ts +437 -437
  117. package/src/renderers/VimeoRenderer.ts +298 -298
  118. package/src/renderers/YouTubeRenderer.ts +290 -290
  119. package/src/styles/vidply.css +5852 -5796
  120. package/src/types/events.ts +128 -122
  121. package/src/types/globals.d.ts +313 -313
  122. package/src/types/options.ts +199 -182
  123. package/src/types/renderer.ts +58 -58
  124. package/src/utils/DOMUtils.ts +281 -281
  125. package/src/utils/DownloadInfo.ts +171 -171
  126. package/src/utils/DraggablePanel.ts +539 -539
  127. package/src/utils/DraggablePanelMenu.ts +195 -195
  128. package/src/utils/DraggableResizable.ts +997 -997
  129. package/src/utils/EventEmitter.ts +59 -59
  130. package/src/utils/FormUtils.ts +95 -95
  131. package/src/utils/MenuFactory.ts +483 -483
  132. package/src/utils/Sanitize.ts +70 -70
  133. package/src/utils/SettingsMenuFactory.ts +289 -289
  134. package/src/utils/StorageManager.ts +287 -287
  135. package/src/utils/TrackLabelUtils.ts +52 -52
  136. package/src/utils/UrlSafe.ts +69 -69
  137. package/src/utils/VideoFrameCapture.ts +114 -114
  138. package/src/utils/WindowComponents.ts +250 -250
  139. package/dist/dev/vidply.AudioDescriptionManager-R47Q4YR4.js.map +0 -7
  140. package/dist/dev/vidply.DASHRenderer-PWEMC3BT.js.map +0 -7
  141. package/dist/dev/vidply.FloatingPlayerManager-PKNNNAS2.js.map +0 -7
  142. package/dist/dev/vidply.HLSRenderer-E7RQ74HI.js.map +0 -7
  143. package/dist/dev/vidply.HTML5Renderer-BU4QJJJY.js +0 -12
  144. package/dist/dev/vidply.SignLanguageManager-FET5FQZ7.js.map +0 -7
  145. package/dist/dev/vidply.SoundCloudRenderer-RVSJZODO.js.map +0 -7
  146. package/dist/dev/vidply.TranscriptManager-5GXOQCLX.js.map +0 -7
  147. package/dist/dev/vidply.VimeoRenderer-ER2LSBBW.js.map +0 -7
  148. package/dist/dev/vidply.YouTubeRenderer-NFTOWJAB.js.map +0 -7
  149. package/dist/dev/vidply.chunk-64WXMTGL.js.map +0 -7
  150. package/dist/dev/vidply.chunk-BNFIUL2Z.js.map +0 -7
  151. package/dist/dev/vidply.chunk-JJ47NO24.js.map +0 -7
  152. package/dist/dev/vidply.chunk-KI7C3QVL.js.map +0 -7
  153. package/dist/dev/vidply.chunk-NQPURMQY.js.map +0 -7
  154. package/dist/dev/vidply.chunk-RZWZGDV7.js.map +0 -7
  155. package/dist/dev/vidply.chunk-W25HUCHS.js.map +0 -7
  156. package/dist/dev/vidply.de-4YAT7FFM.js.map +0 -7
  157. package/dist/dev/vidply.es-LEWIRUBH.js.map +0 -7
  158. package/dist/dev/vidply.fr-Q4GV2NOE.js.map +0 -7
  159. package/dist/dev/vidply.ja-S6ITA6WU.js.map +0 -7
  160. package/dist/prod/vidply.AudioDescriptionManager-RHLIJW5C.min.js +0 -6
  161. package/dist/prod/vidply.HTML5Renderer-IU5T3WEW.min.js +0 -6
  162. /package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js.map → vidply.CaptionStyleMenu-47RCKQF5.js.map} +0 -0
  163. /package/dist/dev/{vidply.HTML5Renderer-BU4QJJJY.js.map → vidply.HTML5Renderer-HLRQUOCU.js.map} +0 -0
  164. /package/dist/dev/{vidply.chunk-SK4V2DAU.js.map → vidply.chunk-HZ7UOKOU.js.map} +0 -0
  165. /package/dist/dev/{vidply.chunk-DZSDO3B7.js.map → vidply.chunk-JZNZX32Z.js.map} +0 -0
  166. /package/dist/dev/{vidply.chunk-VMSQAZM7.js.map → vidply.chunk-P25MRJ6O.js.map} +0 -0
  167. /package/dist/dev/{vidply.chunk-6RQA4ROD.js.map → vidply.chunk-PRFNESYA.js.map} +0 -0
@@ -1,157 +1,157 @@
1
- /**
2
- * Theme + CSS custom-property support extracted from Player.
3
- *
4
- * Every path that used to live on `Player.applyTheme` /
5
- * `Player.setTheme` / `Player.setThemeVariable` / `Player.resetTheme`
6
- * now routes through this module. Player keeps thin public delegates
7
- * so existing consumers (`player.setTheme('light')`, etc.) keep
8
- * working unchanged.
9
- *
10
- * The validation rules are deliberately strict: names must start with
11
- * `--vidply-` followed by `[A-Za-z0-9_-]+`, values must be short and
12
- * must not contain characters that would let a malicious override
13
- * escape the declaration or rule. That way a consumer that passes
14
- * user-supplied text through `setThemeVariable` cannot accidentally
15
- * turn the player into a CSS-injection sink.
16
- */
17
-
18
- import { isForbiddenKey } from '../utils/Sanitize.js';
19
- import type { Player } from './Player.js';
20
-
21
- export type ThemeName = 'dark' | 'light' | 'minimal' | 'high-contrast';
22
-
23
- export const PLAYER_THEMES: readonly ThemeName[] = ['dark', 'light', 'minimal', 'high-contrast'];
24
-
25
- /**
26
- * Validate a CSS variable name before it reaches `setProperty`. The
27
- * browser would silently drop unparseable names; the bigger risk is an
28
- * attacker smuggling `;` / `:` to escape into another declaration, so
29
- * the allow-list is intentionally narrow.
30
- */
31
- export function isValidThemeVariableName(name: string): boolean {
32
- return /^--vidply-[A-Za-z0-9_-]{1,64}$/.test(name);
33
- }
34
-
35
- export function isValidThemeVariableValue(value: unknown): value is string {
36
- if (typeof value !== 'string') return false;
37
- if (value.length > 200) return false;
38
- return !/[<>{};@\\]/.test(value);
39
- }
40
-
41
- export class ThemeManager {
42
- private readonly player: Player;
43
-
44
- constructor(player: Player) {
45
- this.player = player;
46
- }
47
-
48
- /**
49
- * Apply `options.theme` and validate-and-apply every entry in
50
- * `options.themeVariables` to the container. Bad entries are logged
51
- * and skipped so a single malformed override cannot poison siblings.
52
- */
53
- apply(): void {
54
- const player = this.player;
55
- if (!player.container) return;
56
-
57
- // Clear any previous theme class so switching themes at runtime
58
- // does not leave two active theme classes on the element.
59
- const themeClasses = PLAYER_THEMES.map(t => `${player.options.classPrefix}-theme-${t}`);
60
- player.container.classList.remove(...themeClasses);
61
-
62
- const theme = player.options.theme as ThemeName | undefined;
63
- if (theme && (PLAYER_THEMES as readonly string[]).includes(theme)) {
64
- player.container.classList.add(`${player.options.classPrefix}-theme-${theme}`);
65
- }
66
-
67
- if (player.options.themeVariables && typeof player.options.themeVariables === 'object') {
68
- for (const [rawKey, rawValue] of Object.entries(player.options.themeVariables)) {
69
- if (isForbiddenKey(rawKey)) continue;
70
- const cssVar = rawKey.startsWith('--vidply-') ? rawKey : `--vidply-${rawKey}`;
71
- if (!isValidThemeVariableName(cssVar)) {
72
- player.log(`[VidPly] Ignoring invalid theme variable name: ${rawKey}`, 'warn');
73
- continue;
74
- }
75
- if (!isValidThemeVariableValue(rawValue)) {
76
- player.log(`[VidPly] Ignoring invalid theme variable value for ${cssVar}`, 'warn');
77
- continue;
78
- }
79
- player.container.style.setProperty(cssVar, rawValue);
80
- }
81
- }
82
- }
83
-
84
- /**
85
- * Swap the active theme at runtime. Emits `themechange` with the old
86
- * and new names so external consumers (e.g. telemetry) can react.
87
- */
88
- set(themeName: ThemeName, customVariables: Record<string, string> = {}): void {
89
- const player = this.player;
90
- const previousTheme = player.options.theme;
91
-
92
- player.options.theme = themeName;
93
-
94
- if (customVariables && Object.keys(customVariables).length > 0) {
95
- player.options.themeVariables = {
96
- ...player.options.themeVariables,
97
- ...customVariables
98
- };
99
- }
100
-
101
- this.apply();
102
-
103
- player.emit('themechange', {
104
- theme: themeName,
105
- previousTheme,
106
- customVariables: player.options.themeVariables
107
- });
108
- }
109
-
110
- get(): ThemeName | undefined {
111
- return this.player.options.theme as ThemeName | undefined;
112
- }
113
-
114
- /** Set a single CSS variable override, validating the (name, value)
115
- * pair before it reaches the DOM. Callers must pass a string value. */
116
- setVariable(variableName: string, value: string): void {
117
- const player = this.player;
118
- if (!player.container) return;
119
-
120
- const cssVar = variableName.startsWith('--vidply-')
121
- ? variableName
122
- : `--vidply-${variableName}`;
123
-
124
- if (!isValidThemeVariableName(cssVar) || !isValidThemeVariableValue(value)) {
125
- player.log(`[VidPly] Ignoring unsafe setThemeVariable(${variableName})`, 'warn');
126
- return;
127
- }
128
-
129
- player.container.style.setProperty(cssVar, value);
130
-
131
- if (!player.options.themeVariables) {
132
- player.options.themeVariables = {};
133
- }
134
- player.options.themeVariables[variableName] = value;
135
- }
136
-
137
- /**
138
- * Reset to the default theme (dark) and clear every override that was
139
- * applied through `options.themeVariables`.
140
- */
141
- reset(): void {
142
- const player = this.player;
143
- if (player.container && player.options.themeVariables) {
144
- Object.keys(player.options.themeVariables).forEach(key => {
145
- const cssVar = key.startsWith('--vidply-') ? key : `--vidply-${key}`;
146
- player.container.style.removeProperty(cssVar);
147
- });
148
- }
149
-
150
- const previousTheme = player.options.theme;
151
- player.options.theme = 'dark';
152
- player.options.themeVariables = {};
153
-
154
- this.apply();
155
- player.emit('themechange', { theme: 'dark', previousTheme });
156
- }
157
- }
1
+ /**
2
+ * Theme + CSS custom-property support extracted from Player.
3
+ *
4
+ * Every path that used to live on `Player.applyTheme` /
5
+ * `Player.setTheme` / `Player.setThemeVariable` / `Player.resetTheme`
6
+ * now routes through this module. Player keeps thin public delegates
7
+ * so existing consumers (`player.setTheme('light')`, etc.) keep
8
+ * working unchanged.
9
+ *
10
+ * The validation rules are deliberately strict: names must start with
11
+ * `--vidply-` followed by `[A-Za-z0-9_-]+`, values must be short and
12
+ * must not contain characters that would let a malicious override
13
+ * escape the declaration or rule. That way a consumer that passes
14
+ * user-supplied text through `setThemeVariable` cannot accidentally
15
+ * turn the player into a CSS-injection sink.
16
+ */
17
+
18
+ import { isForbiddenKey } from '../utils/Sanitize.js';
19
+ import type { Player } from './Player.js';
20
+
21
+ export type ThemeName = 'dark' | 'light' | 'minimal' | 'high-contrast';
22
+
23
+ export const PLAYER_THEMES: readonly ThemeName[] = ['dark', 'light', 'minimal', 'high-contrast'];
24
+
25
+ /**
26
+ * Validate a CSS variable name before it reaches `setProperty`. The
27
+ * browser would silently drop unparseable names; the bigger risk is an
28
+ * attacker smuggling `;` / `:` to escape into another declaration, so
29
+ * the allow-list is intentionally narrow.
30
+ */
31
+ export function isValidThemeVariableName(name: string): boolean {
32
+ return /^--vidply-[A-Za-z0-9_-]{1,64}$/.test(name);
33
+ }
34
+
35
+ export function isValidThemeVariableValue(value: unknown): value is string {
36
+ if (typeof value !== 'string') return false;
37
+ if (value.length > 200) return false;
38
+ return !/[<>{};@\\]/.test(value);
39
+ }
40
+
41
+ export class ThemeManager {
42
+ private readonly player: Player;
43
+
44
+ constructor(player: Player) {
45
+ this.player = player;
46
+ }
47
+
48
+ /**
49
+ * Apply `options.theme` and validate-and-apply every entry in
50
+ * `options.themeVariables` to the container. Bad entries are logged
51
+ * and skipped so a single malformed override cannot poison siblings.
52
+ */
53
+ apply(): void {
54
+ const player = this.player;
55
+ if (!player.container) return;
56
+
57
+ // Clear any previous theme class so switching themes at runtime
58
+ // does not leave two active theme classes on the element.
59
+ const themeClasses = PLAYER_THEMES.map(t => `${player.options.classPrefix}-theme-${t}`);
60
+ player.container.classList.remove(...themeClasses);
61
+
62
+ const theme = player.options.theme as ThemeName | undefined;
63
+ if (theme && (PLAYER_THEMES as readonly string[]).includes(theme)) {
64
+ player.container.classList.add(`${player.options.classPrefix}-theme-${theme}`);
65
+ }
66
+
67
+ if (player.options.themeVariables && typeof player.options.themeVariables === 'object') {
68
+ for (const [rawKey, rawValue] of Object.entries(player.options.themeVariables)) {
69
+ if (isForbiddenKey(rawKey)) continue;
70
+ const cssVar = rawKey.startsWith('--vidply-') ? rawKey : `--vidply-${rawKey}`;
71
+ if (!isValidThemeVariableName(cssVar)) {
72
+ player.log(`[VidPly] Ignoring invalid theme variable name: ${rawKey}`, 'warn');
73
+ continue;
74
+ }
75
+ if (!isValidThemeVariableValue(rawValue)) {
76
+ player.log(`[VidPly] Ignoring invalid theme variable value for ${cssVar}`, 'warn');
77
+ continue;
78
+ }
79
+ player.container.style.setProperty(cssVar, rawValue);
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Swap the active theme at runtime. Emits `themechange` with the old
86
+ * and new names so external consumers (e.g. telemetry) can react.
87
+ */
88
+ set(themeName: ThemeName, customVariables: Record<string, string> = {}): void {
89
+ const player = this.player;
90
+ const previousTheme = player.options.theme;
91
+
92
+ player.options.theme = themeName;
93
+
94
+ if (customVariables && Object.keys(customVariables).length > 0) {
95
+ player.options.themeVariables = {
96
+ ...player.options.themeVariables,
97
+ ...customVariables
98
+ };
99
+ }
100
+
101
+ this.apply();
102
+
103
+ player.emit('themechange', {
104
+ theme: themeName,
105
+ previousTheme,
106
+ customVariables: player.options.themeVariables
107
+ });
108
+ }
109
+
110
+ get(): ThemeName | undefined {
111
+ return this.player.options.theme as ThemeName | undefined;
112
+ }
113
+
114
+ /** Set a single CSS variable override, validating the (name, value)
115
+ * pair before it reaches the DOM. Callers must pass a string value. */
116
+ setVariable(variableName: string, value: string): void {
117
+ const player = this.player;
118
+ if (!player.container) return;
119
+
120
+ const cssVar = variableName.startsWith('--vidply-')
121
+ ? variableName
122
+ : `--vidply-${variableName}`;
123
+
124
+ if (!isValidThemeVariableName(cssVar) || !isValidThemeVariableValue(value)) {
125
+ player.log(`[VidPly] Ignoring unsafe setThemeVariable(${variableName})`, 'warn');
126
+ return;
127
+ }
128
+
129
+ player.container.style.setProperty(cssVar, value);
130
+
131
+ if (!player.options.themeVariables) {
132
+ player.options.themeVariables = {};
133
+ }
134
+ player.options.themeVariables[variableName] = value;
135
+ }
136
+
137
+ /**
138
+ * Reset to the default theme (dark) and clear every override that was
139
+ * applied through `options.themeVariables`.
140
+ */
141
+ reset(): void {
142
+ const player = this.player;
143
+ if (player.container && player.options.themeVariables) {
144
+ Object.keys(player.options.themeVariables).forEach(key => {
145
+ const cssVar = key.startsWith('--vidply-') ? key : `--vidply-${key}`;
146
+ player.container.style.removeProperty(cssVar);
147
+ });
148
+ }
149
+
150
+ const previousTheme = player.options.theme;
151
+ player.options.theme = 'dark';
152
+ player.options.themeVariables = {};
153
+
154
+ this.apply();
155
+ player.emit('themechange', { theme: 'dark', previousTheme });
156
+ }
157
+ }