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.
- package/README.md +974 -946
- package/dist/dev/{vidply.AudioDescriptionManager-R47Q4YR4.js → vidply.AudioDescriptionManager-JYYLK5KB.js} +275 -19
- package/dist/dev/vidply.AudioDescriptionManager-JYYLK5KB.js.map +7 -0
- package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js → vidply.CaptionStyleMenu-47RCKQF5.js} +4 -4
- package/dist/dev/{vidply.DASHRenderer-PWEMC3BT.js → vidply.DASHRenderer-VEJ4HDHO.js} +3 -3
- package/dist/dev/vidply.DASHRenderer-VEJ4HDHO.js.map +7 -0
- package/dist/dev/{vidply.FloatingPlayerManager-PKNNNAS2.js → vidply.FloatingPlayerManager-C2UVRPZY.js} +5 -5
- package/dist/dev/vidply.FloatingPlayerManager-C2UVRPZY.js.map +7 -0
- package/dist/dev/{vidply.HLSRenderer-E7RQ74HI.js → vidply.HLSRenderer-ZVBK2TNQ.js} +4 -4
- package/dist/dev/vidply.HLSRenderer-ZVBK2TNQ.js.map +7 -0
- package/dist/dev/vidply.HTML5Renderer-HLRQUOCU.js +12 -0
- package/dist/dev/{vidply.SignLanguageManager-FET5FQZ7.js → vidply.SignLanguageManager-FLUCBW5H.js} +8 -8
- package/dist/dev/vidply.SignLanguageManager-FLUCBW5H.js.map +7 -0
- package/dist/dev/{vidply.SoundCloudRenderer-RVSJZODO.js → vidply.SoundCloudRenderer-MGMAJ7NK.js} +3 -3
- package/dist/dev/vidply.SoundCloudRenderer-MGMAJ7NK.js.map +7 -0
- package/dist/dev/{vidply.TranscriptManager-5GXOQCLX.js → vidply.TranscriptManager-Y2RYXHJW.js} +10 -10
- package/dist/dev/vidply.TranscriptManager-Y2RYXHJW.js.map +7 -0
- package/dist/dev/{vidply.VimeoRenderer-ER2LSBBW.js → vidply.VimeoRenderer-RQFQD7OE.js} +3 -3
- package/dist/dev/vidply.VimeoRenderer-RQFQD7OE.js.map +7 -0
- package/dist/dev/{vidply.YouTubeRenderer-NFTOWJAB.js → vidply.YouTubeRenderer-EU2BARZF.js} +3 -3
- package/dist/dev/vidply.YouTubeRenderer-EU2BARZF.js.map +7 -0
- package/dist/dev/{vidply.chunk-BNFIUL2Z.js → vidply.chunk-6LYZYI5G.js} +3 -3
- package/dist/dev/vidply.chunk-6LYZYI5G.js.map +7 -0
- package/dist/dev/{vidply.chunk-NQPURMQY.js → vidply.chunk-A4ESEOQ2.js} +6 -6
- package/dist/dev/vidply.chunk-A4ESEOQ2.js.map +7 -0
- package/dist/dev/{vidply.chunk-W25HUCHS.js → vidply.chunk-CGYIED2I.js} +2 -2
- package/dist/dev/vidply.chunk-CGYIED2I.js.map +7 -0
- package/dist/dev/{vidply.chunk-SK4V2DAU.js → vidply.chunk-HZ7UOKOU.js} +3 -3
- package/dist/dev/{vidply.chunk-RZWZGDV7.js → vidply.chunk-I6EDRYGJ.js} +33 -6
- package/dist/dev/vidply.chunk-I6EDRYGJ.js.map +7 -0
- package/dist/dev/{vidply.chunk-64WXMTGL.js → vidply.chunk-JKMQGLCJ.js} +5 -5
- package/dist/dev/vidply.chunk-JKMQGLCJ.js.map +7 -0
- package/dist/dev/{vidply.chunk-DZSDO3B7.js → vidply.chunk-JZNZX32Z.js} +2 -2
- package/dist/dev/{vidply.chunk-VMSQAZM7.js → vidply.chunk-P25MRJ6O.js} +2 -2
- package/dist/dev/{vidply.chunk-6RQA4ROD.js → vidply.chunk-PRFNESYA.js} +2 -2
- package/dist/dev/{vidply.chunk-KI7C3QVL.js → vidply.chunk-UI3RYCVF.js} +4 -3
- package/dist/dev/vidply.chunk-UI3RYCVF.js.map +7 -0
- package/dist/dev/{vidply.chunk-JJ47NO24.js → vidply.chunk-UNFGMABP.js} +2 -2
- package/dist/dev/vidply.chunk-UNFGMABP.js.map +7 -0
- package/dist/dev/{vidply.de-4YAT7FFM.js → vidply.de-VAUVSQXC.js} +29 -2
- package/dist/dev/vidply.de-VAUVSQXC.js.map +7 -0
- package/dist/dev/{vidply.es-LEWIRUBH.js → vidply.es-G65HFWO6.js} +29 -2
- package/dist/dev/vidply.es-G65HFWO6.js.map +7 -0
- package/dist/dev/vidply.esm.js +648 -35
- package/dist/dev/vidply.esm.js.map +3 -3
- package/dist/dev/{vidply.fr-Q4GV2NOE.js → vidply.fr-MJNOZELM.js} +29 -2
- package/dist/dev/vidply.fr-MJNOZELM.js.map +7 -0
- package/dist/dev/{vidply.ja-S6ITA6WU.js → vidply.ja-INMDHIYJ.js} +29 -2
- package/dist/dev/vidply.ja-INMDHIYJ.js.map +7 -0
- package/dist/legacy/vidply.js +1062 -34
- package/dist/legacy/vidply.js.map +3 -3
- package/dist/legacy/vidply.min.js +2 -2
- package/dist/legacy/vidply.min.meta.json +127 -55
- package/dist/prod/vidply.AudioDescriptionManager-EKWUNOPE.min.js +6 -0
- package/dist/prod/{vidply.CaptionStyleMenu-M3KVQWB5.min.js → vidply.CaptionStyleMenu-JTWSNHRQ.min.js} +2 -2
- package/dist/prod/{vidply.DASHRenderer-DN3S27AV.min.js → vidply.DASHRenderer-CIDAHFWM.min.js} +2 -2
- package/dist/prod/{vidply.FloatingPlayerManager-SKAZ4J43.min.js → vidply.FloatingPlayerManager-VZXHPCPM.min.js} +2 -2
- package/dist/prod/{vidply.HLSRenderer-62HH4YTK.min.js → vidply.HLSRenderer-Q6EFYKID.min.js} +2 -2
- package/dist/prod/vidply.HTML5Renderer-XRDJUTOO.min.js +6 -0
- package/dist/prod/{vidply.SignLanguageManager-V3OBTSSF.min.js → vidply.SignLanguageManager-JUOSA5AM.min.js} +2 -2
- package/dist/prod/{vidply.SoundCloudRenderer-CXRVZIAJ.min.js → vidply.SoundCloudRenderer-7ODD46PO.min.js} +2 -2
- package/dist/prod/{vidply.TranscriptManager-SYM3DFUY.min.js → vidply.TranscriptManager-4B2QKYGQ.min.js} +2 -2
- package/dist/prod/{vidply.VimeoRenderer-72TWRYFQ.min.js → vidply.VimeoRenderer-MR3UYLY7.min.js} +2 -2
- package/dist/prod/{vidply.YouTubeRenderer-UMHEV3KD.min.js → vidply.YouTubeRenderer-ITRTC7NR.min.js} +2 -2
- package/dist/prod/{vidply.chunk-YEENJS3Q.min.js → vidply.chunk-3EEDXU4T.min.js} +2 -2
- package/dist/prod/{vidply.chunk-P4UXOHWA.min.js → vidply.chunk-5HTX5QXW.min.js} +1 -1
- package/dist/prod/{vidply.chunk-ABPXVDLZ.min.js → vidply.chunk-6OGHHIYP.min.js} +2 -2
- package/dist/prod/{vidply.chunk-UK7YGOB5.min.js → vidply.chunk-GUPGMFUZ.min.js} +2 -2
- package/dist/prod/{vidply.chunk-WX3MVEAL.min.js → vidply.chunk-MWEBITCP.min.js} +1 -1
- package/dist/prod/{vidply.chunk-2TB6MUHO.min.js → vidply.chunk-PL3P3JHR.min.js} +1 -1
- package/dist/prod/{vidply.chunk-EKEYJWMG.min.js → vidply.chunk-QQZGV7TD.min.js} +2 -2
- package/dist/prod/{vidply.chunk-Z3YDSA7O.min.js → vidply.chunk-U5IRRYG5.min.js} +1 -1
- package/dist/prod/{vidply.chunk-TM6AXJVD.min.js → vidply.chunk-VKHV4QHA.min.js} +2 -2
- package/dist/prod/{vidply.chunk-P3PQBY7N.min.js → vidply.chunk-XA4M574A.min.js} +1 -1
- package/dist/prod/{vidply.chunk-RI3X7WHK.min.js → vidply.chunk-ZPTV4JEP.min.js} +2 -2
- package/dist/prod/{vidply.de-BCHXRQ53.min.js → vidply.de-UZHFCEZU.min.js} +2 -2
- package/dist/prod/{vidply.es-PICAKMZD.min.js → vidply.es-BOISUV2S.min.js} +2 -2
- package/dist/prod/vidply.esm.min.js +2 -2
- package/dist/prod/{vidply.fr-ZILI7CDR.min.js → vidply.fr-ICSMKPFM.min.js} +2 -2
- package/dist/prod/{vidply.ja-5WQ4OK4J.min.js → vidply.ja-VQ2HKGAY.min.js} +2 -2
- package/dist/vidply.css +5852 -5796
- package/dist/vidply.esm.min.meta.json +204 -149
- package/dist/vidply.min.css +1 -1
- package/package.json +11 -11
- package/src/controls/CaptionManager.ts +598 -598
- package/src/controls/ControlBar.ts +4191 -4159
- package/src/controls/KeyboardHelp.ts +326 -0
- package/src/controls/KeyboardManager.ts +387 -383
- package/src/controls/SettingsDialog.ts +488 -488
- package/src/controls/TranscriptManager.ts +2426 -2426
- package/src/core/AudioDescriptionManager.ts +863 -774
- package/src/core/DescriptionSpeechManager.ts +232 -0
- package/src/core/FloatingPlayerManager.ts +674 -674
- package/src/core/LazyInit.ts +80 -80
- package/src/core/MediaSessionManager.ts +341 -0
- package/src/core/MetadataAlertsManager.ts +514 -514
- package/src/core/Player.ts +2973 -2872
- package/src/core/PosterManager.ts +169 -169
- package/src/core/PseudoFullscreen.ts +179 -179
- package/src/core/ResponsiveManager.ts +231 -231
- package/src/core/ResumeManager.ts +236 -236
- package/src/core/SignLanguageManager.ts +1308 -1308
- package/src/core/ThemeManager.ts +157 -157
- package/src/features/PlaylistManager.ts +1838 -1827
- package/src/i18n/i18n.ts +223 -223
- package/src/i18n/languages/de.ts +244 -217
- package/src/i18n/languages/en.ts +245 -218
- package/src/i18n/languages/es.ts +241 -214
- package/src/i18n/languages/fr.ts +241 -214
- package/src/i18n/languages/ja.ts +241 -214
- package/src/icons/Icons.ts +168 -167
- package/src/index.ts +196 -193
- package/src/renderers/DASHRenderer.ts +1077 -1077
- package/src/renderers/HLSRenderer.ts +944 -944
- package/src/renderers/HTML5Renderer.ts +389 -389
- package/src/renderers/SoundCloudRenderer.ts +437 -437
- package/src/renderers/VimeoRenderer.ts +298 -298
- package/src/renderers/YouTubeRenderer.ts +290 -290
- package/src/styles/vidply.css +5852 -5796
- package/src/types/events.ts +128 -122
- package/src/types/globals.d.ts +313 -313
- package/src/types/options.ts +199 -182
- package/src/types/renderer.ts +58 -58
- package/src/utils/DOMUtils.ts +281 -281
- package/src/utils/DownloadInfo.ts +171 -171
- package/src/utils/DraggablePanel.ts +539 -539
- package/src/utils/DraggablePanelMenu.ts +195 -195
- package/src/utils/DraggableResizable.ts +997 -997
- package/src/utils/EventEmitter.ts +59 -59
- package/src/utils/FormUtils.ts +95 -95
- package/src/utils/MenuFactory.ts +483 -483
- package/src/utils/Sanitize.ts +70 -70
- package/src/utils/SettingsMenuFactory.ts +289 -289
- package/src/utils/StorageManager.ts +287 -287
- package/src/utils/TrackLabelUtils.ts +52 -52
- package/src/utils/UrlSafe.ts +69 -69
- package/src/utils/VideoFrameCapture.ts +114 -114
- package/src/utils/WindowComponents.ts +250 -250
- package/dist/dev/vidply.AudioDescriptionManager-R47Q4YR4.js.map +0 -7
- package/dist/dev/vidply.DASHRenderer-PWEMC3BT.js.map +0 -7
- package/dist/dev/vidply.FloatingPlayerManager-PKNNNAS2.js.map +0 -7
- package/dist/dev/vidply.HLSRenderer-E7RQ74HI.js.map +0 -7
- package/dist/dev/vidply.HTML5Renderer-BU4QJJJY.js +0 -12
- package/dist/dev/vidply.SignLanguageManager-FET5FQZ7.js.map +0 -7
- package/dist/dev/vidply.SoundCloudRenderer-RVSJZODO.js.map +0 -7
- package/dist/dev/vidply.TranscriptManager-5GXOQCLX.js.map +0 -7
- package/dist/dev/vidply.VimeoRenderer-ER2LSBBW.js.map +0 -7
- package/dist/dev/vidply.YouTubeRenderer-NFTOWJAB.js.map +0 -7
- package/dist/dev/vidply.chunk-64WXMTGL.js.map +0 -7
- package/dist/dev/vidply.chunk-BNFIUL2Z.js.map +0 -7
- package/dist/dev/vidply.chunk-JJ47NO24.js.map +0 -7
- package/dist/dev/vidply.chunk-KI7C3QVL.js.map +0 -7
- package/dist/dev/vidply.chunk-NQPURMQY.js.map +0 -7
- package/dist/dev/vidply.chunk-RZWZGDV7.js.map +0 -7
- package/dist/dev/vidply.chunk-W25HUCHS.js.map +0 -7
- package/dist/dev/vidply.de-4YAT7FFM.js.map +0 -7
- package/dist/dev/vidply.es-LEWIRUBH.js.map +0 -7
- package/dist/dev/vidply.fr-Q4GV2NOE.js.map +0 -7
- package/dist/dev/vidply.ja-S6ITA6WU.js.map +0 -7
- package/dist/prod/vidply.AudioDescriptionManager-RHLIJW5C.min.js +0 -6
- package/dist/prod/vidply.HTML5Renderer-IU5T3WEW.min.js +0 -6
- /package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js.map → vidply.CaptionStyleMenu-47RCKQF5.js.map} +0 -0
- /package/dist/dev/{vidply.HTML5Renderer-BU4QJJJY.js.map → vidply.HTML5Renderer-HLRQUOCU.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-SK4V2DAU.js.map → vidply.chunk-HZ7UOKOU.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-DZSDO3B7.js.map → vidply.chunk-JZNZX32Z.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-VMSQAZM7.js.map → vidply.chunk-P25MRJ6O.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-6RQA4ROD.js.map → vidply.chunk-PRFNESYA.js.map} +0 -0
package/src/core/ThemeManager.ts
CHANGED
|
@@ -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
|
+
}
|