@hanifhan1f/vidstack-react 1.12.37 → 1.12.39

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.
@@ -0,0 +1,1680 @@
1
+ "use client"
2
+
3
+ import * as React from 'react';
4
+ import { useSignal, composeRefs, isBoolean, uppercaseFirstChar, isUndefined, isString, signal, camelToKebabCase, onDispose, scoped, keysOf, effect, isArray, isKeyboardClick, listenEvent, toggleClass, useContext } from './vidstack-D_bWd66h.js';
5
+ import { createComputed, createSignal, MediaAnnouncer, Root, Trigger, Content, GoogleCastButton, Captions, useChapterOptions, Root$1 as Root$5, Root$2 as Root$6, Root$3 as Root$7, useScoped, Root$4 as Root$a, Group, useChapterTitle, createEffect, useActiveTextTrack, ChapterTitle as ChapterTitle$1, Title, Root$5 as Root$b, Track as Track$1, TrackFill as TrackFill$1 } from './vidstack-DqUb8JgU.js';
6
+ import { useColorSchemePreference, useActive, useResizeObserver, useLayoutName, useTransitionActive } from './vidstack-D7sOBYbw.js';
7
+ import { useMediaContext, MuteButton, SeekButton, LiveButton, PlayButton, CaptionButton, appendParamsToURL, PIPButton, FullscreenButton, AirPlayButton, Items, Root$3 as Root$1, Item, Root as Root$2, Img, Root$2 as Root$3, Button, Portal, Track, TrackFill, Thumb, Steps, useMediaPlayer, Root$5 as Root$4, useAudioOptions, Root$4 as Root$8, Preview, Value, Root$1 as Root$9, Chapters, Progress, Thumbnail, ChapterTitle, Time, Gesture } from './vidstack-CePUWLrW.js';
8
+ import { useMediaState, isTrackCaptionKind, getDownloadFile, isRemotionSrc, IS_SERVER, useMediaContext as useMediaContext$1, sortVideoQualities, Primitive, mediaContext } from './vidstack-Cb_tNfNU.js';
9
+ import { flushSync } from 'react-dom';
10
+ import { RemotionThumbnail, RemotionSliderThumbnail } from './vidstack-D-hQD1eE.js';
11
+
12
+ const DefaultLayoutContext = React.createContext({});
13
+ DefaultLayoutContext.displayName = "DefaultLayoutContext";
14
+ function useDefaultLayoutContext() {
15
+ return React.useContext(DefaultLayoutContext);
16
+ }
17
+ function useDefaultLayoutWord(word) {
18
+ const { translations } = useDefaultLayoutContext();
19
+ return i18n(translations, word);
20
+ }
21
+ function i18n(translations, word) {
22
+ return translations?.[word] ?? word;
23
+ }
24
+
25
+ function useColorSchemeClass(colorScheme) {
26
+ const systemColorPreference = useColorSchemePreference();
27
+ if (colorScheme === "default") {
28
+ return null;
29
+ } else if (colorScheme === "system") {
30
+ return systemColorPreference;
31
+ } else {
32
+ return colorScheme;
33
+ }
34
+ }
35
+
36
+ function createDefaultMediaLayout({
37
+ type,
38
+ smLayoutWhen,
39
+ renderLayout
40
+ }) {
41
+ const Layout = React.forwardRef(
42
+ ({
43
+ children,
44
+ className,
45
+ disableTimeSlider = false,
46
+ hideQualityBitrate = false,
47
+ icons,
48
+ colorScheme = "system",
49
+ download = null,
50
+ menuContainer = null,
51
+ menuGroup = "bottom",
52
+ noAudioGain = false,
53
+ audioGains = { min: 0, max: 300, step: 25 },
54
+ noGestures = false,
55
+ noKeyboardAnimations = false,
56
+ noModal = false,
57
+ noScrubGesture,
58
+ playbackRates = { min: 0, max: 2, step: 0.25 },
59
+ seekStep = 10,
60
+ episodes = null,
61
+ episodesTitle = "Episodes",
62
+ showMenuDelay,
63
+ showTooltipDelay = 700,
64
+ sliderChaptersMinWidth = 325,
65
+ slots,
66
+ smallLayoutWhen = smLayoutWhen,
67
+ thumbnails = null,
68
+ translations,
69
+ guestMode = false,
70
+ ...props
71
+ }, forwardRef) => {
72
+ const media = useMediaContext(), $load = useSignal(media.$props.load), $canLoad = useMediaState("canLoad"), $viewType = useMediaState("viewType"), $streamType = useMediaState("streamType"), $smallWhen = createComputed(() => {
73
+ return isBoolean(smallLayoutWhen) ? smallLayoutWhen : smallLayoutWhen(media.player.state);
74
+ }, [smallLayoutWhen]), userPrefersAnnouncements = createSignal(true), userPrefersKeyboardAnimations = createSignal(true), isMatch = $viewType === type, isSmallLayout = $smallWhen(), isForcedLayout = isBoolean(smallLayoutWhen), isLoadLayout = $load === "play" && !$canLoad, canRender = $canLoad || isForcedLayout || isLoadLayout, colorSchemeClass = useColorSchemeClass(colorScheme), layoutEl = createSignal(null);
75
+ useSignal($smallWhen);
76
+ return /* @__PURE__ */ React.createElement(
77
+ "div",
78
+ {
79
+ ...props,
80
+ className: `vds-${type}-layout` + (colorSchemeClass ? ` ${colorSchemeClass}` : "") + (className ? ` ${className}` : ""),
81
+ "data-match": isMatch ? "" : null,
82
+ "data-sm": isSmallLayout ? "" : null,
83
+ "data-lg": !isSmallLayout ? "" : null,
84
+ "data-size": isSmallLayout ? "sm" : "lg",
85
+ "data-no-scrub-gesture": noScrubGesture ? "" : null,
86
+ ref: composeRefs(layoutEl.set, forwardRef)
87
+ },
88
+ canRender && isMatch ? /* @__PURE__ */ React.createElement(
89
+ DefaultLayoutContext.Provider,
90
+ {
91
+ value: {
92
+ disableTimeSlider,
93
+ hideQualityBitrate,
94
+ icons,
95
+ colorScheme,
96
+ download,
97
+ isSmallLayout,
98
+ menuContainer,
99
+ menuGroup,
100
+ noAudioGain,
101
+ audioGains,
102
+ layoutEl,
103
+ noGestures,
104
+ noKeyboardAnimations,
105
+ noModal,
106
+ noScrubGesture,
107
+ showMenuDelay,
108
+ showTooltipDelay,
109
+ sliderChaptersMinWidth,
110
+ slots,
111
+ seekStep,
112
+ episodes,
113
+ episodesTitle,
114
+ playbackRates,
115
+ thumbnails,
116
+ translations,
117
+ userPrefersAnnouncements,
118
+ userPrefersKeyboardAnimations,
119
+ guestMode
120
+ }
121
+ },
122
+ renderLayout({ streamType: $streamType, isSmallLayout, isLoadLayout }),
123
+ children
124
+ ) : null
125
+ );
126
+ }
127
+ );
128
+ Layout.displayName = "DefaultMediaLayout";
129
+ return Layout;
130
+ }
131
+
132
+ function useDefaultAudioLayoutSlots() {
133
+ return React.useContext(DefaultLayoutContext).slots;
134
+ }
135
+ function useDefaultVideoLayoutSlots() {
136
+ return React.useContext(DefaultLayoutContext).slots;
137
+ }
138
+ function slot(slots, name, defaultValue) {
139
+ const slot2 = slots?.[name], capitalizedName = uppercaseFirstChar(name);
140
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, slots?.[`before${capitalizedName}`], isUndefined(slot2) ? defaultValue : slot2, slots?.[`after${capitalizedName}`]);
141
+ }
142
+
143
+ function DefaultAnnouncer() {
144
+ const { userPrefersAnnouncements, translations } = useDefaultLayoutContext(), $userPrefersAnnouncements = useSignal(userPrefersAnnouncements);
145
+ if (!$userPrefersAnnouncements) return null;
146
+ return /* @__PURE__ */ React.createElement(MediaAnnouncer, { translations });
147
+ }
148
+ DefaultAnnouncer.displayName = "DefaultAnnouncer";
149
+
150
+ function DefaultTooltip({ content, placement, children }) {
151
+ const { showTooltipDelay } = useDefaultLayoutContext();
152
+ return /* @__PURE__ */ React.createElement(Root, { showDelay: showTooltipDelay }, /* @__PURE__ */ React.createElement(Trigger, { asChild: true }, children), /* @__PURE__ */ React.createElement(Content, { className: "vds-tooltip-content", placement }, content));
153
+ }
154
+ DefaultTooltip.displayName = "DefaultTooltip";
155
+
156
+ function DefaultPlayButton({ tooltip }) {
157
+ const { icons: Icons, guestMode } = useDefaultLayoutContext(), playText = useDefaultLayoutWord("Play"), pauseText = useDefaultLayoutWord("Pause"), $paused = useMediaState("paused"), $ended = useMediaState("ended");
158
+ return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: $paused ? playText : pauseText, placement: tooltip }, /* @__PURE__ */ React.createElement(PlayButton, { className: "vds-play-button vds-button", "aria-label": playText, disabled: guestMode }, $ended ? /* @__PURE__ */ React.createElement(Icons.PlayButton.Replay, { className: "vds-icon" }) : $paused ? /* @__PURE__ */ React.createElement(Icons.PlayButton.Play, { className: "vds-icon" }) : /* @__PURE__ */ React.createElement(Icons.PlayButton.Pause, { className: "vds-icon" })));
159
+ }
160
+ DefaultPlayButton.displayName = "DefaultPlayButton";
161
+ const DefaultMuteButton = React.forwardRef(
162
+ ({ tooltip }, forwardRef) => {
163
+ const { icons: Icons } = useDefaultLayoutContext(), muteText = useDefaultLayoutWord("Mute"), unmuteText = useDefaultLayoutWord("Unmute"), $muted = useMediaState("muted"), $volume = useMediaState("volume");
164
+ return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: $muted ? unmuteText : muteText, placement: tooltip }, /* @__PURE__ */ React.createElement(MuteButton, { className: "vds-mute-button vds-button", "aria-label": muteText, ref: forwardRef }, $muted || $volume == 0 ? /* @__PURE__ */ React.createElement(Icons.MuteButton.Mute, { className: "vds-icon" }) : $volume < 0.5 ? /* @__PURE__ */ React.createElement(Icons.MuteButton.VolumeLow, { className: "vds-icon" }) : /* @__PURE__ */ React.createElement(Icons.MuteButton.VolumeHigh, { className: "vds-icon" })));
165
+ }
166
+ );
167
+ DefaultMuteButton.displayName = "DefaultMuteButton";
168
+ function DefaultCaptionButton({ tooltip }) {
169
+ const { icons: Icons } = useDefaultLayoutContext(), captionsText = useDefaultLayoutWord("Captions"), onText = useDefaultLayoutWord("Closed-Captions On"), offText = useDefaultLayoutWord("Closed-Captions Off"), $track = useMediaState("textTrack"), isOn = $track && isTrackCaptionKind($track);
170
+ return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: isOn ? onText : offText, placement: tooltip }, /* @__PURE__ */ React.createElement(CaptionButton, { className: "vds-caption-button vds-button", "aria-label": captionsText }, isOn ? /* @__PURE__ */ React.createElement(Icons.CaptionButton.On, { className: "vds-icon" }) : /* @__PURE__ */ React.createElement(Icons.CaptionButton.Off, { className: "vds-icon" })));
171
+ }
172
+ DefaultCaptionButton.displayName = "DefaultCaptionButton";
173
+ function DefaultPIPButton({ tooltip }) {
174
+ const { icons: Icons } = useDefaultLayoutContext(), pipText = useDefaultLayoutWord("PiP"), enterText = useDefaultLayoutWord("Enter PiP"), exitText = useDefaultLayoutWord("Exit PiP"), $pip = useMediaState("pictureInPicture");
175
+ return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: $pip ? exitText : enterText, placement: tooltip }, /* @__PURE__ */ React.createElement(PIPButton, { className: "vds-pip-button vds-button", "aria-label": pipText }, $pip ? /* @__PURE__ */ React.createElement(Icons.PIPButton.Exit, { className: "vds-icon" }) : /* @__PURE__ */ React.createElement(Icons.PIPButton.Enter, { className: "vds-icon" })));
176
+ }
177
+ DefaultPIPButton.displayName = "DefaultPIPButton";
178
+ function DefaultFullscreenButton({ tooltip }) {
179
+ const { icons: Icons } = useDefaultLayoutContext(), fullscreenText = useDefaultLayoutWord("Fullscreen"), enterText = useDefaultLayoutWord("Enter Fullscreen"), exitText = useDefaultLayoutWord("Exit Fullscreen"), $fullscreen = useMediaState("fullscreen");
180
+ return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: $fullscreen ? exitText : enterText, placement: tooltip }, /* @__PURE__ */ React.createElement(FullscreenButton, { className: "vds-fullscreen-button vds-button", "aria-label": fullscreenText }, $fullscreen ? /* @__PURE__ */ React.createElement(Icons.FullscreenButton.Exit, { className: "vds-icon" }) : /* @__PURE__ */ React.createElement(Icons.FullscreenButton.Enter, { className: "vds-icon" })));
181
+ }
182
+ DefaultFullscreenButton.displayName = "DefaultFullscreenButton";
183
+ function DefaultSeekButton({
184
+ backward,
185
+ tooltip
186
+ }) {
187
+ const { icons: Icons, seekStep, guestMode } = useDefaultLayoutContext(), seekForwardText = useDefaultLayoutWord("Seek Forward"), seekBackwardText = useDefaultLayoutWord("Seek Backward"), seconds = (backward ? -1 : 1) * seekStep, label = seconds >= 0 ? seekForwardText : seekBackwardText;
188
+ if (guestMode) return null;
189
+ return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: label, placement: tooltip }, /* @__PURE__ */ React.createElement(SeekButton, { className: "vds-seek-button vds-button", seconds, "aria-label": label }, seconds >= 0 ? /* @__PURE__ */ React.createElement(Icons.SeekButton.Forward, { className: "vds-icon" }) : /* @__PURE__ */ React.createElement(Icons.SeekButton.Backward, { className: "vds-icon" })));
190
+ }
191
+ DefaultSeekButton.displayName = "DefaultSeekButton";
192
+ function DefaultEpisodeButton({ tooltip, onPress }) {
193
+ const { icons: Icons } = useDefaultLayoutContext(), episodesText = useDefaultLayoutWord("Episodes");
194
+ return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: episodesText, placement: tooltip }, /* @__PURE__ */ React.createElement(
195
+ "button",
196
+ {
197
+ type: "button",
198
+ className: "vds-episode-button vds-button",
199
+ "aria-label": episodesText,
200
+ onPointerUp: (event) => {
201
+ event.stopPropagation();
202
+ onPress?.(event);
203
+ }
204
+ },
205
+ /* @__PURE__ */ React.createElement(Icons.Menu.Chapters, { className: "vds-icon" })
206
+ ));
207
+ }
208
+ DefaultEpisodeButton.displayName = "DefaultEpisodeButton";
209
+ function DefaultAirPlayButton({ tooltip }) {
210
+ const { icons: Icons } = useDefaultLayoutContext(), airPlayText = useDefaultLayoutWord("AirPlay"), $state = useMediaState("remotePlaybackState"), stateText = useDefaultLayoutWord(uppercaseFirstChar($state)), label = `${airPlayText} ${stateText}`, Icon = ($state === "connecting" ? Icons.AirPlayButton.Connecting : $state === "connected" ? Icons.AirPlayButton.Connected : null) ?? Icons.AirPlayButton.Default;
211
+ return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: airPlayText, placement: tooltip }, /* @__PURE__ */ React.createElement(AirPlayButton, { className: "vds-airplay-button vds-button", "aria-label": label }, /* @__PURE__ */ React.createElement(Icon, { className: "vds-icon" })));
212
+ }
213
+ DefaultAirPlayButton.displayName = "DefaultAirPlayButton";
214
+ function DefaultGoogleCastButton({ tooltip }) {
215
+ const { icons: Icons } = useDefaultLayoutContext(), googleCastText = useDefaultLayoutWord("Google Cast"), $state = useMediaState("remotePlaybackState"), stateText = useDefaultLayoutWord(uppercaseFirstChar($state)), label = `${googleCastText} ${stateText}`, Icon = ($state === "connecting" ? Icons.GoogleCastButton.Connecting : $state === "connected" ? Icons.GoogleCastButton.Connected : null) ?? Icons.GoogleCastButton.Default;
216
+ return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: googleCastText, placement: tooltip }, /* @__PURE__ */ React.createElement(GoogleCastButton, { className: "vds-google-cast-button vds-button", "aria-label": label }, /* @__PURE__ */ React.createElement(Icon, { className: "vds-icon" })));
217
+ }
218
+ DefaultGoogleCastButton.displayName = "DefaultGoogleCastButton";
219
+ function DefaultLiveButton() {
220
+ const $live = useMediaState("live"), label = useDefaultLayoutWord("Skip To Live"), liveText = useDefaultLayoutWord("LIVE");
221
+ return $live ? /* @__PURE__ */ React.createElement(LiveButton, { className: "vds-live-button", "aria-label": label }, /* @__PURE__ */ React.createElement("span", { className: "vds-live-button-text" }, liveText)) : null;
222
+ }
223
+ DefaultLiveButton.displayName = "DefaultLiveButton";
224
+ function DefaultDownloadButton() {
225
+ const { download, icons: Icons } = useDefaultLayoutContext(), $src = useMediaState("source"), $title = useMediaState("title"), file = getDownloadFile({
226
+ title: $title,
227
+ src: $src,
228
+ download
229
+ }), downloadText = useDefaultLayoutWord("Download");
230
+ return isString(file?.url) ? /* @__PURE__ */ React.createElement(DefaultTooltip, { content: downloadText, placement: "top" }, /* @__PURE__ */ React.createElement(
231
+ "a",
232
+ {
233
+ role: "button",
234
+ className: "vds-download-button vds-button",
235
+ "aria-label": downloadText,
236
+ href: appendParamsToURL(file.url, { download: file.name }),
237
+ download: file.name,
238
+ target: "_blank"
239
+ },
240
+ Icons.DownloadButton ? /* @__PURE__ */ React.createElement(Icons.DownloadButton.Default, { className: "vds-icon" }) : null
241
+ )) : null;
242
+ }
243
+ DefaultDownloadButton.displayName = "DefaultDownloadButton";
244
+
245
+ function DefaultCaptions() {
246
+ const exampleText = useDefaultLayoutWord("Captions look like this");
247
+ return /* @__PURE__ */ React.createElement(Captions, { className: "vds-captions", exampleText });
248
+ }
249
+ DefaultCaptions.displayName = "DefaultCaptions";
250
+
251
+ function DefaultControlsSpacer() {
252
+ return /* @__PURE__ */ React.createElement("div", { className: "vds-controls-spacer" });
253
+ }
254
+ DefaultControlsSpacer.displayName = "DefaultControlsSpacer";
255
+
256
+ function useParentDialogEl() {
257
+ const { layoutEl } = useDefaultLayoutContext(), $layoutEl = useSignal(layoutEl);
258
+ return React.useMemo(() => $layoutEl?.closest("dialog"), [$layoutEl]);
259
+ }
260
+
261
+ function DefaultChaptersMenu({ tooltip, placement, portalClass = "" }) {
262
+ const {
263
+ showMenuDelay,
264
+ noModal,
265
+ isSmallLayout,
266
+ icons: Icons,
267
+ menuGroup,
268
+ menuContainer,
269
+ colorScheme
270
+ } = useDefaultLayoutContext(), chaptersText = useDefaultLayoutWord("Chapters"), options = useChapterOptions(), disabled = !options.length, { thumbnails } = useDefaultLayoutContext(), $src = useMediaState("currentSrc"), $viewType = useMediaState("viewType"), $offset = !isSmallLayout && menuGroup === "bottom" && $viewType === "video" ? 26 : 0, $RemotionThumbnail = useSignal(RemotionThumbnail), colorSchemeClass = useColorSchemeClass(colorScheme), [isOpen, setIsOpen] = React.useState(false), dialogEl = useParentDialogEl();
271
+ if (disabled) return null;
272
+ function onOpen() {
273
+ flushSync(() => {
274
+ setIsOpen(true);
275
+ });
276
+ }
277
+ function onClose() {
278
+ setIsOpen(false);
279
+ }
280
+ const Content = /* @__PURE__ */ React.createElement(
281
+ Items,
282
+ {
283
+ className: "vds-chapters-menu-items vds-menu-items",
284
+ placement,
285
+ offset: $offset
286
+ },
287
+ isOpen ? /* @__PURE__ */ React.createElement(
288
+ Root$1,
289
+ {
290
+ className: "vds-chapters-radio-group vds-radio-group",
291
+ value: options.selectedValue,
292
+ "data-thumbnails": thumbnails ? "" : null
293
+ },
294
+ options.map(
295
+ ({ cue, label, value, startTimeText, durationText, select, setProgressVar }) => /* @__PURE__ */ React.createElement(
296
+ Item,
297
+ {
298
+ className: "vds-chapter-radio vds-radio",
299
+ value,
300
+ key: value,
301
+ onSelect: select,
302
+ ref: setProgressVar
303
+ },
304
+ thumbnails ? /* @__PURE__ */ React.createElement(Root$2, { src: thumbnails, className: "vds-thumbnail", time: cue.startTime }, /* @__PURE__ */ React.createElement(Img, null)) : $RemotionThumbnail && isRemotionSrc($src) ? /* @__PURE__ */ React.createElement($RemotionThumbnail, { className: "vds-thumbnail", frame: cue.startTime * $src.fps }) : null,
305
+ /* @__PURE__ */ React.createElement("div", { className: "vds-chapter-radio-content" }, /* @__PURE__ */ React.createElement("span", { className: "vds-chapter-radio-label" }, label), /* @__PURE__ */ React.createElement("span", { className: "vds-chapter-radio-start-time" }, startTimeText), /* @__PURE__ */ React.createElement("span", { className: "vds-chapter-radio-duration" }, durationText))
306
+ )
307
+ )
308
+ ) : null
309
+ );
310
+ return /* @__PURE__ */ React.createElement(
311
+ Root$3,
312
+ {
313
+ className: "vds-chapters-menu vds-menu",
314
+ showDelay: showMenuDelay,
315
+ onOpen,
316
+ onClose
317
+ },
318
+ /* @__PURE__ */ React.createElement(DefaultTooltip, { content: chaptersText, placement: tooltip }, /* @__PURE__ */ React.createElement(
319
+ Button,
320
+ {
321
+ className: "vds-menu-button vds-button",
322
+ disabled,
323
+ "aria-label": chaptersText
324
+ },
325
+ /* @__PURE__ */ React.createElement(Icons.Menu.Chapters, { className: "vds-icon" })
326
+ )),
327
+ noModal || !isSmallLayout ? Content : /* @__PURE__ */ React.createElement(
328
+ Portal,
329
+ {
330
+ container: menuContainer ?? dialogEl,
331
+ className: portalClass + (colorSchemeClass ? ` ${colorSchemeClass}` : ""),
332
+ disabled: "fullscreen",
333
+ "data-sm": isSmallLayout ? "" : null,
334
+ "data-lg": !isSmallLayout ? "" : null,
335
+ "data-size": isSmallLayout ? "sm" : "lg"
336
+ },
337
+ Content
338
+ )
339
+ );
340
+ }
341
+ DefaultChaptersMenu.displayName = "DefaultChaptersMenu";
342
+
343
+ const FONT_COLOR_OPTION = {
344
+ type: "color"
345
+ };
346
+ const FONT_FAMILY_OPTION = {
347
+ type: "radio",
348
+ values: {
349
+ "Monospaced Serif": "mono-serif",
350
+ "Proportional Serif": "pro-serif",
351
+ "Monospaced Sans-Serif": "mono-sans",
352
+ "Proportional Sans-Serif": "pro-sans",
353
+ Casual: "casual",
354
+ Cursive: "cursive",
355
+ "Small Capitals": "capitals"
356
+ }
357
+ };
358
+ const FONT_SIZE_OPTION = {
359
+ type: "slider",
360
+ min: 0,
361
+ max: 400,
362
+ step: 25,
363
+ upIcon: null,
364
+ downIcon: null
365
+ };
366
+ const FONT_OPACITY_OPTION = {
367
+ type: "slider",
368
+ min: 0,
369
+ max: 100,
370
+ step: 5,
371
+ upIcon: null,
372
+ downIcon: null
373
+ };
374
+ const FONT_TEXT_SHADOW_OPTION = {
375
+ type: "radio",
376
+ values: ["None", "Drop Shadow", "Raised", "Depressed", "Outline"]
377
+ };
378
+ const FONT_DEFAULTS = {
379
+ fontFamily: "pro-sans",
380
+ fontSize: "100%",
381
+ textColor: "#ffffff",
382
+ textOpacity: "100%",
383
+ textShadow: "none",
384
+ textBg: "#000000",
385
+ textBgOpacity: "100%",
386
+ displayBg: "#000000",
387
+ displayBgOpacity: "0%"
388
+ };
389
+ const FONT_SIGNALS = Object.keys(FONT_DEFAULTS).reduce(
390
+ (prev, type) => ({
391
+ ...prev,
392
+ [type]: signal(FONT_DEFAULTS[type])
393
+ }),
394
+ {}
395
+ );
396
+ if (!IS_SERVER) {
397
+ for (const type of Object.keys(FONT_SIGNALS)) {
398
+ const value = localStorage.getItem(`vds-player:${camelToKebabCase(type)}`);
399
+ if (isString(value)) FONT_SIGNALS[type].set(value);
400
+ }
401
+ }
402
+ function onFontReset() {
403
+ for (const type of Object.keys(FONT_SIGNALS)) {
404
+ const defaultValue = FONT_DEFAULTS[type];
405
+ FONT_SIGNALS[type].set(defaultValue);
406
+ }
407
+ }
408
+
409
+ function hexToRgb(hex) {
410
+ const { style } = new Option();
411
+ style.color = hex;
412
+ return style.color.match(/\((.*?)\)/)[1].replace(/,/g, " ");
413
+ }
414
+
415
+ let isWatchingVars = false, players = /* @__PURE__ */ new Set();
416
+ function updateFontCssVars() {
417
+ if (IS_SERVER) return;
418
+ const { player } = useMediaContext$1();
419
+ players.add(player);
420
+ onDispose(() => players.delete(player));
421
+ if (!isWatchingVars) {
422
+ scoped(() => {
423
+ for (const type of keysOf(FONT_SIGNALS)) {
424
+ const $value = FONT_SIGNALS[type], defaultValue = FONT_DEFAULTS[type], varName = `--media-user-${camelToKebabCase(type)}`, storageKey = `vds-player:${camelToKebabCase(type)}`;
425
+ effect(() => {
426
+ const value = $value(), isDefaultVarValue = value === defaultValue, varValue = !isDefaultVarValue ? getCssVarValue(player, type, value) : null;
427
+ for (const player2 of players) {
428
+ player2.el?.style.setProperty(varName, varValue);
429
+ }
430
+ if (isDefaultVarValue) {
431
+ localStorage.removeItem(storageKey);
432
+ } else {
433
+ localStorage.setItem(storageKey, value);
434
+ }
435
+ });
436
+ }
437
+ }, null);
438
+ isWatchingVars = true;
439
+ }
440
+ }
441
+ function getCssVarValue(player, type, value) {
442
+ switch (type) {
443
+ case "fontFamily":
444
+ const fontVariant = value === "capitals" ? "small-caps" : "";
445
+ player.el?.style.setProperty("--media-user-font-variant", fontVariant);
446
+ return getFontFamilyCSSVarValue(value);
447
+ case "fontSize":
448
+ case "textOpacity":
449
+ case "textBgOpacity":
450
+ case "displayBgOpacity":
451
+ return percentToRatio(value);
452
+ case "textColor":
453
+ return `rgb(${hexToRgb(value)} / var(--media-user-text-opacity, 1))`;
454
+ case "textShadow":
455
+ return getTextShadowCssVarValue(value);
456
+ case "textBg":
457
+ return `rgb(${hexToRgb(value)} / var(--media-user-text-bg-opacity, 1))`;
458
+ case "displayBg":
459
+ return `rgb(${hexToRgb(value)} / var(--media-user-display-bg-opacity, 1))`;
460
+ }
461
+ }
462
+ function percentToRatio(value) {
463
+ return (parseInt(value) / 100).toString();
464
+ }
465
+ function getFontFamilyCSSVarValue(value) {
466
+ switch (value) {
467
+ case "mono-serif":
468
+ return '"Courier New", Courier, "Nimbus Mono L", "Cutive Mono", monospace';
469
+ case "mono-sans":
470
+ return '"Deja Vu Sans Mono", "Lucida Console", Monaco, Consolas, "PT Mono", monospace';
471
+ case "pro-sans":
472
+ return 'Roboto, "Arial Unicode Ms", Arial, Helvetica, Verdana, "PT Sans Caption", sans-serif';
473
+ case "casual":
474
+ return '"Comic Sans MS", Impact, Handlee, fantasy';
475
+ case "cursive":
476
+ return '"Monotype Corsiva", "URW Chancery L", "Apple Chancery", "Dancing Script", cursive';
477
+ case "capitals":
478
+ return '"Arial Unicode Ms", Arial, Helvetica, Verdana, "Marcellus SC", sans-serif + font-variant=small-caps';
479
+ default:
480
+ return '"Times New Roman", Times, Georgia, Cambria, "PT Serif Caption", serif';
481
+ }
482
+ }
483
+ function getTextShadowCssVarValue(value) {
484
+ switch (value) {
485
+ case "drop shadow":
486
+ return "rgb(34, 34, 34) 1.86389px 1.86389px 2.79583px, rgb(34, 34, 34) 1.86389px 1.86389px 3.72778px, rgb(34, 34, 34) 1.86389px 1.86389px 4.65972px";
487
+ case "raised":
488
+ return "rgb(34, 34, 34) 1px 1px, rgb(34, 34, 34) 2px 2px";
489
+ case "depressed":
490
+ return "rgb(204, 204, 204) 1px 1px, rgb(34, 34, 34) -1px -1px";
491
+ case "outline":
492
+ return "rgb(34, 34, 34) 0px 0px 1.86389px, rgb(34, 34, 34) 0px 0px 1.86389px, rgb(34, 34, 34) 0px 0px 1.86389px, rgb(34, 34, 34) 0px 0px 1.86389px, rgb(34, 34, 34) 0px 0px 1.86389px";
493
+ default:
494
+ return "";
495
+ }
496
+ }
497
+
498
+ function DefaultMenuSection({ label, value, children }) {
499
+ const id = React.useId();
500
+ if (!label) {
501
+ return /* @__PURE__ */ React.createElement("div", { className: "vds-menu-section" }, /* @__PURE__ */ React.createElement("div", { className: "vds-menu-section-body" }, children));
502
+ }
503
+ return /* @__PURE__ */ React.createElement("section", { className: "vds-menu-section", role: "group", "aria-labelledby": id }, /* @__PURE__ */ React.createElement("div", { className: "vds-menu-section-title" }, /* @__PURE__ */ React.createElement("header", { id }, label), value ? /* @__PURE__ */ React.createElement("div", { className: "vds-menu-section-value" }, value) : null), /* @__PURE__ */ React.createElement("div", { className: "vds-menu-section-body" }, children));
504
+ }
505
+ DefaultMenuSection.displayName = "DefaultMenuSection";
506
+ function DefaultMenuButton({ label, hint = "", Icon, disabled = false }) {
507
+ const { icons: Icons } = React.useContext(DefaultLayoutContext);
508
+ return /* @__PURE__ */ React.createElement(Button, { className: "vds-menu-item", disabled }, /* @__PURE__ */ React.createElement(Icons.Menu.ArrowLeft, { className: "vds-menu-close-icon vds-icon" }), Icon ? /* @__PURE__ */ React.createElement(Icon, { className: "vds-menu-item-icon vds-icon" }) : null, /* @__PURE__ */ React.createElement("span", { className: "vds-menu-item-label" }, label), /* @__PURE__ */ React.createElement("span", { className: "vds-menu-item-hint" }, hint), /* @__PURE__ */ React.createElement(Icons.Menu.ArrowRight, { className: "vds-menu-open-icon vds-icon" }));
509
+ }
510
+ DefaultMenuButton.displayName = "DefaultMenuButton";
511
+ function DefaultMenuItem({ label, children }) {
512
+ return /* @__PURE__ */ React.createElement("div", { className: "vds-menu-item" }, /* @__PURE__ */ React.createElement("div", { className: "vds-menu-item-label" }, label), children);
513
+ }
514
+ DefaultMenuItem.displayName = "DefaultMenuItem";
515
+ function DefaultMenuRadioGroup({ value, options, onChange }) {
516
+ const { icons: Icons } = useDefaultLayoutContext();
517
+ return /* @__PURE__ */ React.createElement(Root$1, { className: "vds-radio-group", value, onChange }, options.map((option) => /* @__PURE__ */ React.createElement(Item, { className: "vds-radio", value: option.value, key: option.value }, /* @__PURE__ */ React.createElement(Icons.Menu.RadioCheck, { className: "vds-icon" }), /* @__PURE__ */ React.createElement("span", { className: "vds-radio-label", "data-part": "label" }, option.label))));
518
+ }
519
+ DefaultMenuRadioGroup.displayName = "DefaultMenuRadioGroup";
520
+ function createRadioOptions(entries) {
521
+ return React.useMemo(
522
+ () => isArray(entries) ? entries.map((entry) => ({ label: entry, value: entry.toLowerCase() })) : Object.keys(entries).map((label) => ({ label, value: entries[label] })),
523
+ [entries]
524
+ );
525
+ }
526
+
527
+ function DefaultMenuSliderItem({
528
+ label,
529
+ value,
530
+ UpIcon,
531
+ DownIcon,
532
+ children,
533
+ isMin,
534
+ isMax
535
+ }) {
536
+ const hasTitle = label || value, Content = /* @__PURE__ */ React.createElement(React.Fragment, null, DownIcon ? /* @__PURE__ */ React.createElement(DownIcon, { className: "vds-icon down" }) : null, children, UpIcon ? /* @__PURE__ */ React.createElement(UpIcon, { className: "vds-icon up" }) : null);
537
+ return /* @__PURE__ */ React.createElement(
538
+ "div",
539
+ {
540
+ className: `vds-menu-item vds-menu-slider-item${hasTitle ? " group" : ""}`,
541
+ "data-min": isMin ? "" : null,
542
+ "data-max": isMax ? "" : null
543
+ },
544
+ hasTitle ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "vds-menu-slider-title" }, label ? /* @__PURE__ */ React.createElement("div", null, label) : null, value ? /* @__PURE__ */ React.createElement("div", null, value) : null), /* @__PURE__ */ React.createElement("div", { className: "vds-menu-slider-body" }, Content)) : Content
545
+ );
546
+ }
547
+ DefaultMenuSliderItem.displayName = "DefaultMenuSliderItem";
548
+ function DefaultSliderParts() {
549
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Track, { className: "vds-slider-track" }), /* @__PURE__ */ React.createElement(TrackFill, { className: "vds-slider-track-fill vds-slider-track" }), /* @__PURE__ */ React.createElement(Thumb, { className: "vds-slider-thumb" }));
550
+ }
551
+ DefaultSliderParts.displayName = "DefaultSliderParts";
552
+ function DefaultSliderSteps() {
553
+ return /* @__PURE__ */ React.createElement(Steps, { className: "vds-slider-steps" }, (step) => /* @__PURE__ */ React.createElement("div", { className: "vds-slider-step", key: String(step) }));
554
+ }
555
+ DefaultSliderSteps.displayName = "DefaultSliderSteps";
556
+
557
+ function DefaultFontMenu() {
558
+ const label = useDefaultLayoutWord("Caption Styles"), $hasCaptions = useMediaState("hasCaptions"), fontSectionLabel = useDefaultLayoutWord("Font"), textSectionLabel = useDefaultLayoutWord("Text"), textBgSectionLabel = useDefaultLayoutWord("Text Background"), displayBgSectionLabel = useDefaultLayoutWord("Display Background");
559
+ if (!$hasCaptions) return null;
560
+ return /* @__PURE__ */ React.createElement(Root$3, { className: "vds-font-menu vds-menu" }, /* @__PURE__ */ React.createElement(DefaultMenuButton, { label }), /* @__PURE__ */ React.createElement(Items, { className: "vds-font-style-items vds-menu-items" }, /* @__PURE__ */ React.createElement(DefaultMenuSection, { label: fontSectionLabel }, /* @__PURE__ */ React.createElement(DefaultFontFamilyMenu, null), /* @__PURE__ */ React.createElement(DefaultFontSizeSlider, null)), /* @__PURE__ */ React.createElement(DefaultMenuSection, { label: textSectionLabel }, /* @__PURE__ */ React.createElement(DefaultTextColorInput, null), /* @__PURE__ */ React.createElement(DefaultTextShadowMenu, null), /* @__PURE__ */ React.createElement(DefaultTextOpacitySlider, null)), /* @__PURE__ */ React.createElement(DefaultMenuSection, { label: textBgSectionLabel }, /* @__PURE__ */ React.createElement(DefaultTextBgInput, null), /* @__PURE__ */ React.createElement(DefaultTextBgOpacitySlider, null)), /* @__PURE__ */ React.createElement(DefaultMenuSection, { label: displayBgSectionLabel }, /* @__PURE__ */ React.createElement(DefaultDisplayBgInput, null), /* @__PURE__ */ React.createElement(DefaultDisplayBgOpacitySlider, null)), /* @__PURE__ */ React.createElement(DefaultMenuSection, null, /* @__PURE__ */ React.createElement(DefaultResetMenuItem, null))));
561
+ }
562
+ DefaultFontMenu.displayName = "DefaultFontMenu";
563
+ function DefaultFontFamilyMenu() {
564
+ return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Family", type: "fontFamily", option: FONT_FAMILY_OPTION });
565
+ }
566
+ DefaultFontFamilyMenu.displayName = "DefaultFontFamilyMenu";
567
+ function DefaultFontSizeSlider() {
568
+ const { icons: Icons } = useDefaultLayoutContext(), option = {
569
+ ...FONT_SIZE_OPTION,
570
+ upIcon: Icons.Menu.FontSizeUp,
571
+ downIcon: Icons.Menu.FontSizeDown
572
+ };
573
+ return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Size", type: "fontSize", option });
574
+ }
575
+ DefaultFontSizeSlider.displayName = "DefaultFontSizeSlider";
576
+ function DefaultTextColorInput() {
577
+ return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Color", type: "textColor", option: FONT_COLOR_OPTION });
578
+ }
579
+ DefaultTextColorInput.displayName = "DefaultTextColorInput";
580
+ function DefaultTextOpacitySlider() {
581
+ const { icons: Icons } = useDefaultLayoutContext(), option = {
582
+ ...FONT_OPACITY_OPTION,
583
+ upIcon: Icons.Menu.OpacityUp,
584
+ downIcon: Icons.Menu.OpacityDown
585
+ };
586
+ return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Opacity", type: "textOpacity", option });
587
+ }
588
+ DefaultTextOpacitySlider.displayName = "DefaultTextOpacitySlider";
589
+ function DefaultTextShadowMenu() {
590
+ return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Shadow", type: "textShadow", option: FONT_TEXT_SHADOW_OPTION });
591
+ }
592
+ DefaultTextShadowMenu.displayName = "DefaultTextShadowMenu";
593
+ function DefaultTextBgInput() {
594
+ return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Color", type: "textBg", option: FONT_COLOR_OPTION });
595
+ }
596
+ DefaultTextBgInput.displayName = "DefaultTextBgInput";
597
+ function DefaultTextBgOpacitySlider() {
598
+ const { icons: Icons } = useDefaultLayoutContext(), option = {
599
+ ...FONT_OPACITY_OPTION,
600
+ upIcon: Icons.Menu.OpacityUp,
601
+ downIcon: Icons.Menu.OpacityDown
602
+ };
603
+ return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Opacity", type: "textBgOpacity", option });
604
+ }
605
+ DefaultTextBgOpacitySlider.displayName = "DefaultTextBgOpacitySlider";
606
+ function DefaultDisplayBgInput() {
607
+ return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Color", type: "displayBg", option: FONT_COLOR_OPTION });
608
+ }
609
+ DefaultDisplayBgInput.displayName = "DefaultDisplayBgInput";
610
+ function DefaultDisplayBgOpacitySlider() {
611
+ const { icons: Icons } = useDefaultLayoutContext(), option = {
612
+ ...FONT_OPACITY_OPTION,
613
+ upIcon: Icons.Menu.OpacityUp,
614
+ downIcon: Icons.Menu.OpacityDown
615
+ };
616
+ return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Opacity", type: "displayBgOpacity", option });
617
+ }
618
+ DefaultDisplayBgOpacitySlider.displayName = "DefaultDisplayBgOpacitySlider";
619
+ function DefaultFontSetting({ label, option, type }) {
620
+ const player = useMediaPlayer(), $currentValue = FONT_SIGNALS[type], $value = useSignal($currentValue), translatedLabel = useDefaultLayoutWord(label);
621
+ const notify = React.useCallback(() => {
622
+ player?.dispatchEvent(new Event("vds-font-change"));
623
+ }, [player]);
624
+ const onChange = React.useCallback(
625
+ (newValue) => {
626
+ $currentValue.set(newValue);
627
+ notify();
628
+ },
629
+ [$currentValue, notify]
630
+ );
631
+ if (option.type === "color") {
632
+ let onColorChange2 = function(event) {
633
+ onChange(event.target.value);
634
+ };
635
+ return /* @__PURE__ */ React.createElement(DefaultMenuItem, { label: translatedLabel }, /* @__PURE__ */ React.createElement("input", { className: "vds-color-picker", type: "color", value: $value, onChange: onColorChange2 }));
636
+ }
637
+ if (option.type === "slider") {
638
+ let onSliderValueChange2 = function(value) {
639
+ onChange(value + "%");
640
+ };
641
+ const { min, max, step, upIcon, downIcon } = option;
642
+ return /* @__PURE__ */ React.createElement(
643
+ DefaultMenuSliderItem,
644
+ {
645
+ label: translatedLabel,
646
+ value: $value,
647
+ UpIcon: upIcon,
648
+ DownIcon: downIcon,
649
+ isMin: $value === min + "%",
650
+ isMax: $value === max + "%"
651
+ },
652
+ /* @__PURE__ */ React.createElement(
653
+ Root$4,
654
+ {
655
+ className: "vds-slider",
656
+ min,
657
+ max,
658
+ step,
659
+ keyStep: step,
660
+ value: parseInt($value),
661
+ "aria-label": translatedLabel,
662
+ onValueChange: onSliderValueChange2,
663
+ onDragValueChange: onSliderValueChange2
664
+ },
665
+ /* @__PURE__ */ React.createElement(DefaultSliderParts, null),
666
+ /* @__PURE__ */ React.createElement(DefaultSliderSteps, null)
667
+ )
668
+ );
669
+ }
670
+ if (option.type === "radio") {
671
+ return /* @__PURE__ */ React.createElement(
672
+ DefaultFontRadioGroup,
673
+ {
674
+ id: camelToKebabCase(type),
675
+ label: translatedLabel,
676
+ value: $value,
677
+ values: option.values,
678
+ onChange
679
+ }
680
+ );
681
+ }
682
+ return null;
683
+ }
684
+ DefaultFontSetting.displayName = "DefaultFontSetting";
685
+ function DefaultFontRadioGroup({ id, label, value, values, onChange }) {
686
+ const radioOptions = createRadioOptions(values), { translations } = useDefaultLayoutContext(), hint = React.useMemo(() => {
687
+ const label2 = radioOptions.find((radio) => radio.value === value)?.label || "";
688
+ return i18n(translations, label2);
689
+ }, [value, radioOptions]);
690
+ return /* @__PURE__ */ React.createElement(Root$3, { className: `vds-${id}-menu vds-menu` }, /* @__PURE__ */ React.createElement(DefaultMenuButton, { label, hint }), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, /* @__PURE__ */ React.createElement(DefaultMenuRadioGroup, { value, options: radioOptions, onChange })));
691
+ }
692
+ DefaultFontRadioGroup.displayName = "DefaultFontRadioGroup";
693
+ function DefaultResetMenuItem() {
694
+ const resetText = useDefaultLayoutWord("Reset");
695
+ return /* @__PURE__ */ React.createElement("button", { className: "vds-menu-item", role: "menuitem", onClick: onFontReset }, /* @__PURE__ */ React.createElement("span", { className: "vds-menu-item-label" }, resetText));
696
+ }
697
+ DefaultResetMenuItem.displayName = "DefaultResetMenuItem";
698
+
699
+ function DefaultMenuCheckbox({
700
+ label,
701
+ checked,
702
+ storageKey,
703
+ defaultChecked = false,
704
+ onChange
705
+ }) {
706
+ const [isChecked, setIsChecked] = React.useState(defaultChecked), [isActive, setIsActive] = React.useState(false);
707
+ React.useEffect(() => {
708
+ const savedValue = storageKey ? localStorage.getItem(storageKey) : null, checked2 = !!(savedValue ?? defaultChecked);
709
+ setIsChecked(checked2);
710
+ onChange?.(checked2);
711
+ }, []);
712
+ React.useEffect(() => {
713
+ if (isBoolean(checked)) setIsChecked(checked);
714
+ }, [checked]);
715
+ function onPress(event) {
716
+ if (event && "button" in event && event?.button === 1) return;
717
+ const toggledCheck = !isChecked;
718
+ setIsChecked(toggledCheck);
719
+ if (storageKey) localStorage.setItem(storageKey, toggledCheck ? "1" : "");
720
+ onChange?.(toggledCheck, event?.nativeEvent);
721
+ setIsActive(false);
722
+ }
723
+ function onActive(event) {
724
+ if (event.button !== 0) return;
725
+ setIsActive(true);
726
+ }
727
+ function onKeyDown(event) {
728
+ if (isKeyboardClick(event.nativeEvent)) onPress();
729
+ }
730
+ return /* @__PURE__ */ React.createElement(
731
+ "div",
732
+ {
733
+ className: "vds-menu-checkbox",
734
+ role: "menuitemcheckbox",
735
+ tabIndex: 0,
736
+ "aria-label": label,
737
+ "aria-checked": isChecked ? "true" : "false",
738
+ "data-active": isActive ? "" : null,
739
+ onPointerUp: onPress,
740
+ onPointerDown: onActive,
741
+ onKeyDown
742
+ }
743
+ );
744
+ }
745
+ DefaultMenuCheckbox.displayName = "DefaultMenuCheckbox";
746
+
747
+ function DefaultAccessibilityMenu({ slots }) {
748
+ const label = useDefaultLayoutWord("Accessibility"), { icons: Icons } = useDefaultLayoutContext();
749
+ return /* @__PURE__ */ React.createElement(Root$3, { className: "vds-accessibility-menu vds-menu" }, /* @__PURE__ */ React.createElement(DefaultMenuButton, { label, Icon: Icons.Menu.Accessibility }), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, slot(slots, "accessibilityMenuItemsStart", null), /* @__PURE__ */ React.createElement(DefaultMenuSection, null, /* @__PURE__ */ React.createElement(DefaultAnnouncementsMenuCheckbox, null), /* @__PURE__ */ React.createElement(DefaultKeyboardAnimationsMenuCheckbox, null)), /* @__PURE__ */ React.createElement(DefaultMenuSection, null, /* @__PURE__ */ React.createElement(DefaultFontMenu, null)), slot(slots, "accessibilityMenuItemsEnd", null)));
750
+ }
751
+ DefaultAccessibilityMenu.displayName = "DefaultAccessibilityMenu";
752
+ function DefaultAnnouncementsMenuCheckbox() {
753
+ const { userPrefersAnnouncements } = useDefaultLayoutContext(), label = useDefaultLayoutWord("Announcements");
754
+ function onChange(checked) {
755
+ userPrefersAnnouncements.set(checked);
756
+ }
757
+ return /* @__PURE__ */ React.createElement(DefaultMenuItem, { label }, /* @__PURE__ */ React.createElement(
758
+ DefaultMenuCheckbox,
759
+ {
760
+ label,
761
+ defaultChecked: true,
762
+ storageKey: "vds-player::announcements",
763
+ onChange
764
+ }
765
+ ));
766
+ }
767
+ DefaultAnnouncementsMenuCheckbox.displayName = "DefaultAnnouncementsMenuCheckbox";
768
+ function DefaultKeyboardAnimationsMenuCheckbox() {
769
+ const $viewType = useMediaState("viewType"), { userPrefersKeyboardAnimations, noKeyboardAnimations } = useDefaultLayoutContext(), label = useDefaultLayoutWord("Keyboard Animations");
770
+ if ($viewType !== "video" || noKeyboardAnimations) return null;
771
+ function onChange(checked) {
772
+ userPrefersKeyboardAnimations.set(checked);
773
+ }
774
+ return /* @__PURE__ */ React.createElement(DefaultMenuItem, { label }, /* @__PURE__ */ React.createElement(
775
+ DefaultMenuCheckbox,
776
+ {
777
+ label,
778
+ defaultChecked: true,
779
+ storageKey: "vds-player::keyboard-animations",
780
+ onChange
781
+ }
782
+ ));
783
+ }
784
+ DefaultKeyboardAnimationsMenuCheckbox.displayName = "DefaultKeyboardAnimationsMenuCheckbox";
785
+
786
+ function DefaultAudioMenu({ slots }) {
787
+ const label = useDefaultLayoutWord("Audio"), $canSetAudioGain = useMediaState("canSetAudioGain"), $audioTracks = useMediaState("audioTracks"), { noAudioGain, icons: Icons } = useDefaultLayoutContext(), hasGainSlider = $canSetAudioGain && !noAudioGain, $disabled = !hasGainSlider && $audioTracks.length <= 1;
788
+ if ($disabled) return null;
789
+ return /* @__PURE__ */ React.createElement(Root$3, { className: "vds-audio-menu vds-menu" }, /* @__PURE__ */ React.createElement(DefaultMenuButton, { label, Icon: Icons.Menu.Audio }), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, slot(slots, "audioMenuItemsStart", null), /* @__PURE__ */ React.createElement(DefaultAudioTracksMenu, null), hasGainSlider ? /* @__PURE__ */ React.createElement(DefaultAudioBoostMenuSection, null) : null, slot(slots, "audioMenuItemsEnd", null)));
790
+ }
791
+ DefaultAudioMenu.displayName = "DefaultAudioMenu";
792
+ function DefaultAudioBoostMenuSection() {
793
+ const $audioGain = useMediaState("audioGain"), label = useDefaultLayoutWord("Boost"), value = Math.round((($audioGain ?? 1) - 1) * 100) + "%", $canSetAudioGain = useMediaState("canSetAudioGain"), { noAudioGain, icons: Icons } = useDefaultLayoutContext(), $disabled = !$canSetAudioGain || noAudioGain, min = useGainMin(), max = useGainMax();
794
+ if ($disabled) return null;
795
+ return /* @__PURE__ */ React.createElement(DefaultMenuSection, { label, value }, /* @__PURE__ */ React.createElement(
796
+ DefaultMenuSliderItem,
797
+ {
798
+ UpIcon: Icons.Menu.AudioBoostUp,
799
+ DownIcon: Icons.Menu.AudioBoostDown,
800
+ isMin: (($audioGain ?? 1) - 1) * 100 <= min,
801
+ isMax: (($audioGain ?? 1) - 1) * 100 === max
802
+ },
803
+ /* @__PURE__ */ React.createElement(DefaultAudioGainSlider, null)
804
+ ));
805
+ }
806
+ DefaultAudioBoostMenuSection.displayName = "DefaultAudioBoostMenuSection";
807
+ function useGainMin() {
808
+ const { audioGains } = useDefaultLayoutContext(), min = isArray(audioGains) ? audioGains[0] : audioGains?.min;
809
+ return min ?? 0;
810
+ }
811
+ function useGainMax() {
812
+ const { audioGains } = useDefaultLayoutContext(), max = isArray(audioGains) ? audioGains[audioGains.length - 1] : audioGains?.max;
813
+ return max ?? 300;
814
+ }
815
+ function useGainStep() {
816
+ const { audioGains } = useDefaultLayoutContext(), step = isArray(audioGains) ? audioGains[1] - audioGains[0] : audioGains?.step;
817
+ return step || 25;
818
+ }
819
+ function DefaultAudioGainSlider() {
820
+ const label = useDefaultLayoutWord("Audio Boost"), min = useGainMin(), max = useGainMax(), step = useGainStep();
821
+ return /* @__PURE__ */ React.createElement(
822
+ Root$5,
823
+ {
824
+ className: "vds-audio-gain-slider vds-slider",
825
+ "aria-label": label,
826
+ min,
827
+ max,
828
+ step,
829
+ keyStep: step
830
+ },
831
+ /* @__PURE__ */ React.createElement(DefaultSliderParts, null),
832
+ /* @__PURE__ */ React.createElement(DefaultSliderSteps, null)
833
+ );
834
+ }
835
+ DefaultAudioGainSlider.displayName = "DefaultAudioGainSlider";
836
+ function DefaultAudioTracksMenu() {
837
+ const { icons: Icons } = useDefaultLayoutContext(), label = useDefaultLayoutWord("Track"), defaultText = useDefaultLayoutWord("Default"), $track = useMediaState("audioTrack"), options = useAudioOptions();
838
+ if (options.disabled) return null;
839
+ return /* @__PURE__ */ React.createElement(Root$3, { className: "vds-audio-track-menu vds-menu" }, /* @__PURE__ */ React.createElement(
840
+ DefaultMenuButton,
841
+ {
842
+ label,
843
+ hint: $track?.label ?? defaultText,
844
+ disabled: options.disabled,
845
+ Icon: Icons.Menu.Audio
846
+ }
847
+ ), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, /* @__PURE__ */ React.createElement(
848
+ Root$1,
849
+ {
850
+ className: "vds-audio-radio-group vds-radio-group",
851
+ value: options.selectedValue
852
+ },
853
+ options.map(({ label: label2, value, select }) => /* @__PURE__ */ React.createElement(
854
+ Item,
855
+ {
856
+ className: "vds-audio-radio vds-radio",
857
+ value,
858
+ onSelect: select,
859
+ key: value
860
+ },
861
+ /* @__PURE__ */ React.createElement(Icons.Menu.RadioCheck, { className: "vds-icon" }),
862
+ /* @__PURE__ */ React.createElement("span", { className: "vds-radio-label" }, label2)
863
+ ))
864
+ )));
865
+ }
866
+ DefaultAudioTracksMenu.displayName = "DefaultAudioTracksMenu";
867
+
868
+ const LANG_FLAGS = {
869
+ en: "gb",
870
+ eng: "gb",
871
+ english: "gb",
872
+ id: "id",
873
+ ind: "id",
874
+ indonesian: "id",
875
+ ja: "jp",
876
+ jpn: "jp",
877
+ japanese: "jp",
878
+ ko: "kr",
879
+ kor: "kr",
880
+ korean: "kr",
881
+ zh: "cn",
882
+ zho: "cn",
883
+ chi: "cn",
884
+ chinese: "cn",
885
+ es: "es",
886
+ spa: "es",
887
+ spanish: "es",
888
+ fr: "fr",
889
+ fra: "fr",
890
+ french: "fr",
891
+ de: "de",
892
+ deu: "de",
893
+ ger: "de",
894
+ german: "de",
895
+ pt: "br",
896
+ por: "br",
897
+ portuguese: "br",
898
+ ru: "ru",
899
+ rus: "ru",
900
+ russian: "ru",
901
+ ar: "sa",
902
+ ara: "sa",
903
+ arabic: "sa",
904
+ hi: "in",
905
+ hin: "in",
906
+ hindi: "in",
907
+ th: "th",
908
+ tha: "th",
909
+ thai: "th",
910
+ vi: "vn",
911
+ vie: "vn",
912
+ vietnamese: "vn",
913
+ it: "it",
914
+ ita: "it",
915
+ italian: "it",
916
+ tr: "tr",
917
+ tur: "tr",
918
+ turkish: "tr",
919
+ pl: "pl",
920
+ pol: "pl",
921
+ polish: "pl",
922
+ nl: "nl",
923
+ nld: "nl",
924
+ dutch: "nl",
925
+ ms: "my",
926
+ msa: "my",
927
+ malay: "my"
928
+ };
929
+ function getFlagCode(label) {
930
+ const lower = label.toLowerCase().replace(/\s\d+$/, "").trim();
931
+ if (LANG_FLAGS[lower]) return LANG_FLAGS[lower];
932
+ for (const [key, code] of Object.entries(LANG_FLAGS)) {
933
+ if (lower.startsWith(key) || lower.includes(key)) return code;
934
+ }
935
+ return null;
936
+ }
937
+ function DefaultCaptionMenu({ slots }) {
938
+ const { icons: Icons } = useDefaultLayoutContext(), label = useDefaultLayoutWord("Captions"), offText = useDefaultLayoutWord("Off"), media = useMediaContext(), hasCaptions = useMediaState("hasCaptions"), textTracks = useMediaState("textTracks"), activeTrack = useMediaState("textTrack");
939
+ if (!hasCaptions) return null;
940
+ const tracks = textTracks.filter(isTrackCaptionKind);
941
+ const grouped = /* @__PURE__ */ new Map();
942
+ for (const track of tracks) {
943
+ const normalizedLabel = track.label.replace(/\s\d+$/, "").trim();
944
+ if (!grouped.has(normalizedLabel)) {
945
+ grouped.set(normalizedLabel, []);
946
+ }
947
+ grouped.get(normalizedLabel).push(track);
948
+ }
949
+ const priorities = ["indonesian", "english", "japanese", "korean", "chinese"];
950
+ const getPriority = (lbl) => {
951
+ const lower = lbl.toLowerCase();
952
+ const pIndex = priorities.findIndex((p) => lower.includes(p));
953
+ return pIndex !== -1 ? pIndex : 999;
954
+ };
955
+ const sortedGroups = Array.from(grouped.entries()).sort((a, b) => {
956
+ const pa = getPriority(a[0]);
957
+ const pb = getPriority(b[0]);
958
+ if (pa !== pb) return pa - pb;
959
+ return a[0].localeCompare(b[0]);
960
+ });
961
+ const isOff = tracks.every((t) => t.mode !== "showing");
962
+ const onOffSelect = (e) => {
963
+ const selected = media.textTracks.selected;
964
+ if (selected) {
965
+ const idx = media.textTracks.indexOf(selected);
966
+ media.remote.changeTextTrackMode(idx, "disabled", e);
967
+ }
968
+ };
969
+ return /* @__PURE__ */ React.createElement(Root$3, { className: "vds-captions-menu vds-menu" }, /* @__PURE__ */ React.createElement(
970
+ DefaultMenuButton,
971
+ {
972
+ label,
973
+ hint: activeTrack?.label ?? offText,
974
+ disabled: false,
975
+ Icon: Icons.Menu.Captions
976
+ }
977
+ ), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, slot(slots, "captionsMenuItemsStart", null), /* @__PURE__ */ React.createElement(
978
+ Root$1,
979
+ {
980
+ className: "vds-captions-radio-group vds-radio-group",
981
+ value: isOff ? "off" : "none"
982
+ },
983
+ /* @__PURE__ */ React.createElement(
984
+ Item,
985
+ {
986
+ className: "vds-caption-radio vds-radio",
987
+ value: "off",
988
+ onSelect: onOffSelect
989
+ },
990
+ /* @__PURE__ */ React.createElement(Icons.Menu.RadioCheck, { className: "vds-icon" }),
991
+ /* @__PURE__ */ React.createElement("span", { className: "vds-radio-label" }, offText)
992
+ )
993
+ ), sortedGroups.map(([groupLabel, groupTracks]) => {
994
+ const flagCode = getFlagCode(groupLabel);
995
+ const activeIndex = groupTracks.findIndex((t) => t === activeTrack);
996
+ return /* @__PURE__ */ React.createElement(Root$3, { key: groupLabel, className: "vds-captions-group-menu vds-menu" }, /* @__PURE__ */ React.createElement(Button, { className: "vds-menu-item" }, /* @__PURE__ */ React.createElement(Icons.Menu.ArrowLeft, { className: "vds-menu-close-icon vds-icon" }), /* @__PURE__ */ React.createElement("span", { className: "vds-menu-item-label", style: { display: "flex", alignItems: "center", gap: "8px" } }, flagCode ? /* @__PURE__ */ React.createElement(
997
+ "span",
998
+ {
999
+ className: `fi fi-${flagCode}`,
1000
+ style: {
1001
+ fontSize: "1.25em",
1002
+ borderRadius: "2px",
1003
+ width: "1.33em",
1004
+ height: "1em",
1005
+ display: "inline-block",
1006
+ backgroundImage: `url('https://cdnjs.cloudflare.com/ajax/libs/flag-icons/7.1.0/flags/4x3/${flagCode}.svg')`,
1007
+ backgroundSize: "cover",
1008
+ backgroundPosition: "center"
1009
+ }
1010
+ }
1011
+ ) : null, /* @__PURE__ */ React.createElement("span", null, groupLabel)), /* @__PURE__ */ React.createElement("span", { className: "vds-menu-item-hint" }, activeIndex !== -1 ? `${activeIndex + 1}/${groupTracks.length}` : ""), /* @__PURE__ */ React.createElement(Icons.Menu.ArrowRight, { className: "vds-menu-open-icon vds-icon" })), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, /* @__PURE__ */ React.createElement(
1012
+ Root$1,
1013
+ {
1014
+ className: "vds-captions-radio-group vds-radio-group",
1015
+ value: String(activeIndex)
1016
+ },
1017
+ groupTracks.map((track, idx) => {
1018
+ const displayLabel = groupTracks.length > 1 ? `${groupLabel} ${idx + 1}` : groupLabel;
1019
+ const onSelect = (e) => {
1020
+ const globalIdx = media.textTracks.indexOf(track);
1021
+ media.remote.changeTextTrackMode(globalIdx, "showing", e);
1022
+ };
1023
+ return /* @__PURE__ */ React.createElement(
1024
+ Item,
1025
+ {
1026
+ key: idx,
1027
+ className: "vds-caption-radio vds-radio",
1028
+ value: String(idx),
1029
+ onSelect
1030
+ },
1031
+ /* @__PURE__ */ React.createElement(Icons.Menu.RadioCheck, { className: "vds-icon" }),
1032
+ /* @__PURE__ */ React.createElement("span", { className: "vds-radio-label" }, displayLabel)
1033
+ );
1034
+ })
1035
+ )));
1036
+ }), slot(slots, "captionsMenuItemsEnd", null)));
1037
+ }
1038
+ DefaultCaptionMenu.displayName = "DefaultCaptionMenu";
1039
+
1040
+ function DefaultPlaybackMenu({ slots }) {
1041
+ const label = useDefaultLayoutWord("Playback"), { icons: Icons } = useDefaultLayoutContext();
1042
+ return /* @__PURE__ */ React.createElement(Root$3, { className: "vds-playback-menu vds-menu" }, /* @__PURE__ */ React.createElement(DefaultMenuButton, { label, Icon: Icons.Menu.Playback }), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, slot(slots, "playbackMenuItemsStart", null), /* @__PURE__ */ React.createElement(DefaultMenuSection, null, slot(slots, "playbackMenuLoop", /* @__PURE__ */ React.createElement(DefaultLoopMenuCheckbox, null))), /* @__PURE__ */ React.createElement(DefaultSpeedMenuSection, null), /* @__PURE__ */ React.createElement(DefaultQualityMenuSection, null), slot(slots, "playbackMenuItemsEnd", null)));
1043
+ }
1044
+ DefaultPlaybackMenu.displayName = "DefaultPlaybackMenu";
1045
+ function DefaultLoopMenuCheckbox() {
1046
+ const { remote } = useMediaContext(), label = useDefaultLayoutWord("Loop");
1047
+ function onChange(checked, trigger) {
1048
+ remote.userPrefersLoopChange(checked, trigger);
1049
+ }
1050
+ return /* @__PURE__ */ React.createElement(DefaultMenuItem, { label }, /* @__PURE__ */ React.createElement(DefaultMenuCheckbox, { label, storageKey: "vds-player::user-loop", onChange }));
1051
+ }
1052
+ DefaultLoopMenuCheckbox.displayName = "DefaultLoopMenuCheckbox";
1053
+ function DefaultAutoQualityMenuCheckbox() {
1054
+ const { remote, qualities } = useMediaContext(), $autoQuality = useMediaState("autoQuality"), label = useDefaultLayoutWord("Auto");
1055
+ function onChange(checked, trigger) {
1056
+ if (checked) {
1057
+ remote.requestAutoQuality(trigger);
1058
+ } else {
1059
+ remote.changeQuality(qualities.selectedIndex, trigger);
1060
+ }
1061
+ }
1062
+ return /* @__PURE__ */ React.createElement(DefaultMenuItem, { label }, /* @__PURE__ */ React.createElement(
1063
+ DefaultMenuCheckbox,
1064
+ {
1065
+ label,
1066
+ checked: $autoQuality,
1067
+ onChange,
1068
+ defaultChecked: $autoQuality
1069
+ }
1070
+ ));
1071
+ }
1072
+ DefaultAutoQualityMenuCheckbox.displayName = "DefaultAutoQualityMenuCheckbox";
1073
+ function DefaultQualityMenuSection() {
1074
+ const { hideQualityBitrate, icons: Icons } = useDefaultLayoutContext(), $canSetQuality = useMediaState("canSetQuality"), $qualities = useMediaState("qualities"), $quality = useMediaState("quality"), label = useDefaultLayoutWord("Quality"), autoText = useDefaultLayoutWord("Auto"), sortedQualities = React.useMemo(() => sortVideoQualities($qualities), [$qualities]);
1075
+ if (!$canSetQuality || $qualities.length <= 1) return null;
1076
+ const height = $quality?.height, bitrate = !hideQualityBitrate ? $quality?.bitrate : null, bitrateText = bitrate && bitrate > 0 ? `${(bitrate / 1e6).toFixed(2)} Mbps` : null, value = height ? `${height}p${bitrateText ? ` (${bitrateText})` : ""}` : autoText, isMin = sortedQualities[0] === $quality, isMax = sortedQualities.at(-1) === $quality;
1077
+ return /* @__PURE__ */ React.createElement(DefaultMenuSection, { label, value }, /* @__PURE__ */ React.createElement(
1078
+ DefaultMenuSliderItem,
1079
+ {
1080
+ UpIcon: Icons.Menu.QualityUp,
1081
+ DownIcon: Icons.Menu.QualityDown,
1082
+ isMin,
1083
+ isMax
1084
+ },
1085
+ /* @__PURE__ */ React.createElement(DefaultQualitySlider, null)
1086
+ ), /* @__PURE__ */ React.createElement(DefaultAutoQualityMenuCheckbox, null));
1087
+ }
1088
+ DefaultQualityMenuSection.displayName = "DefaultQualityMenuSection";
1089
+ function DefaultQualitySlider() {
1090
+ const label = useDefaultLayoutWord("Quality");
1091
+ return /* @__PURE__ */ React.createElement(Root$7, { className: "vds-quality-slider vds-slider", "aria-label": label }, /* @__PURE__ */ React.createElement(DefaultSliderParts, null), /* @__PURE__ */ React.createElement(DefaultSliderSteps, null));
1092
+ }
1093
+ DefaultQualitySlider.displayName = "DefaultQualitySlider";
1094
+ function DefaultSpeedMenuSection() {
1095
+ const { icons: Icons } = useDefaultLayoutContext(), $playbackRate = useMediaState("playbackRate"), $canSetPlaybackRate = useMediaState("canSetPlaybackRate"), label = useDefaultLayoutWord("Speed"), normalText = useDefaultLayoutWord("Normal"), min = useSpeedMin(), max = useSpeedMax(), value = $playbackRate === 1 ? normalText : $playbackRate + "x";
1096
+ if (!$canSetPlaybackRate) return null;
1097
+ return /* @__PURE__ */ React.createElement(DefaultMenuSection, { label, value }, /* @__PURE__ */ React.createElement(
1098
+ DefaultMenuSliderItem,
1099
+ {
1100
+ UpIcon: Icons.Menu.SpeedUp,
1101
+ DownIcon: Icons.Menu.SpeedDown,
1102
+ isMin: $playbackRate === min,
1103
+ isMax: $playbackRate === max
1104
+ },
1105
+ /* @__PURE__ */ React.createElement(DefaultSpeedSlider, null)
1106
+ ));
1107
+ }
1108
+ function useSpeedMin() {
1109
+ const { playbackRates } = useDefaultLayoutContext(), rates = playbackRates;
1110
+ return (isArray(rates) ? rates[0] : rates?.min) ?? 0;
1111
+ }
1112
+ function useSpeedMax() {
1113
+ const { playbackRates } = useDefaultLayoutContext(), rates = playbackRates;
1114
+ return (isArray(rates) ? rates[rates.length - 1] : rates?.max) ?? 2;
1115
+ }
1116
+ function useSpeedStep() {
1117
+ const { playbackRates } = useDefaultLayoutContext(), rates = playbackRates;
1118
+ return (isArray(rates) ? rates[1] - rates[0] : rates?.step) || 0.25;
1119
+ }
1120
+ function DefaultSpeedSlider() {
1121
+ const label = useDefaultLayoutWord("Speed"), min = useSpeedMin(), max = useSpeedMax(), step = useSpeedStep();
1122
+ return /* @__PURE__ */ React.createElement(
1123
+ Root$6,
1124
+ {
1125
+ className: "vds-speed-slider vds-slider",
1126
+ "aria-label": label,
1127
+ min,
1128
+ max,
1129
+ step,
1130
+ keyStep: step
1131
+ },
1132
+ /* @__PURE__ */ React.createElement(DefaultSliderParts, null),
1133
+ /* @__PURE__ */ React.createElement(DefaultSliderSteps, null)
1134
+ );
1135
+ }
1136
+ DefaultSpeedSlider.displayName = "DefaultSpeedSlider";
1137
+
1138
+ function DefaultSettingsMenu({
1139
+ tooltip,
1140
+ placement,
1141
+ portalClass = "",
1142
+ slots
1143
+ }) {
1144
+ const {
1145
+ showMenuDelay,
1146
+ icons: Icons,
1147
+ isSmallLayout,
1148
+ menuContainer,
1149
+ menuGroup,
1150
+ noModal,
1151
+ colorScheme
1152
+ } = useDefaultLayoutContext(), settingsText = useDefaultLayoutWord("Settings"), $viewType = useMediaState("viewType"), $offset = !isSmallLayout && menuGroup === "bottom" && $viewType === "video" ? 26 : 0, colorSchemeClass = useColorSchemeClass(colorScheme), [isOpen, setIsOpen] = React.useState(false), dialogEl = useParentDialogEl();
1153
+ useScoped(updateFontCssVars);
1154
+ function onOpen() {
1155
+ flushSync(() => {
1156
+ setIsOpen(true);
1157
+ });
1158
+ }
1159
+ function onClose() {
1160
+ setIsOpen(false);
1161
+ }
1162
+ const Content = /* @__PURE__ */ React.createElement(
1163
+ Items,
1164
+ {
1165
+ className: "vds-settings-menu-items vds-menu-items",
1166
+ placement,
1167
+ offset: $offset
1168
+ },
1169
+ isOpen ? /* @__PURE__ */ React.createElement(React.Fragment, null, slot(slots, "settingsMenuItemsStart", null), slot(slots, "settingsMenuStartItems", null), /* @__PURE__ */ React.createElement(DefaultPlaybackMenu, { slots }), /* @__PURE__ */ React.createElement(DefaultAccessibilityMenu, { slots }), /* @__PURE__ */ React.createElement(DefaultAudioMenu, { slots }), /* @__PURE__ */ React.createElement(DefaultCaptionMenu, { slots }), slot(slots, "settingsMenuEndItems", null), slot(slots, "settingsMenuItemsEnd", null)) : null
1170
+ );
1171
+ return /* @__PURE__ */ React.createElement(
1172
+ Root$3,
1173
+ {
1174
+ className: "vds-settings-menu vds-menu",
1175
+ showDelay: showMenuDelay,
1176
+ onOpen,
1177
+ onClose
1178
+ },
1179
+ /* @__PURE__ */ React.createElement(DefaultTooltip, { content: settingsText, placement: tooltip }, /* @__PURE__ */ React.createElement(Button, { className: "vds-menu-button vds-button", "aria-label": settingsText }, /* @__PURE__ */ React.createElement(Icons.Menu.Settings, { className: "vds-icon vds-rotate-icon" }))),
1180
+ noModal || !isSmallLayout ? Content : /* @__PURE__ */ React.createElement(
1181
+ Portal,
1182
+ {
1183
+ className: portalClass + (colorSchemeClass ? ` ${colorSchemeClass}` : ""),
1184
+ container: menuContainer ?? dialogEl,
1185
+ disabled: "fullscreen",
1186
+ "data-sm": isSmallLayout ? "" : null,
1187
+ "data-lg": !isSmallLayout ? "" : null,
1188
+ "data-size": isSmallLayout ? "sm" : "lg",
1189
+ "data-view-type": $viewType
1190
+ },
1191
+ Content
1192
+ )
1193
+ );
1194
+ }
1195
+ DefaultSettingsMenu.displayName = "DefaultSettingsMenu";
1196
+
1197
+ function DefaultVolumePopup({ tooltip, orientation, slots }) {
1198
+ const $pointer = useMediaState("pointer"), $muted = useMediaState("muted"), $canSetVolume = useMediaState("canSetVolume"), [rootEl, setRootEl] = React.useState(null), isRootActive = useActive(rootEl), muteButton = slot(slots, "muteButton", /* @__PURE__ */ React.createElement(DefaultMuteButton, { tooltip }));
1199
+ if (!$canSetVolume) {
1200
+ return muteButton;
1201
+ }
1202
+ return $pointer === "coarse" && !$muted ? null : /* @__PURE__ */ React.createElement("div", { className: "vds-volume", "data-active": isRootActive ? "" : null, ref: setRootEl }, muteButton, /* @__PURE__ */ React.createElement("div", { className: "vds-volume-popup" }, slot(slots, "volumeSlider", /* @__PURE__ */ React.createElement(DefaultVolumeSlider, { orientation }))));
1203
+ }
1204
+ DefaultVolumePopup.displayName = "DefaultVolumePopup";
1205
+ function DefaultVolumeSlider(props) {
1206
+ const label = useDefaultLayoutWord("Volume");
1207
+ return /* @__PURE__ */ React.createElement(Root$8, { className: "vds-volume-slider vds-slider", "aria-label": label, ...props }, /* @__PURE__ */ React.createElement(Track, { className: "vds-slider-track" }), /* @__PURE__ */ React.createElement(TrackFill, { className: "vds-slider-track-fill vds-slider-track" }), /* @__PURE__ */ React.createElement(Thumb, { className: "vds-slider-thumb" }), /* @__PURE__ */ React.createElement(Preview, { className: "vds-slider-preview", noClamp: true }, /* @__PURE__ */ React.createElement(Value, { className: "vds-slider-value" })));
1208
+ }
1209
+ DefaultVolumeSlider.displayName = "DefaultVolumeSlider";
1210
+ function DefaultTimeSlider() {
1211
+ const [instance, setInstance] = React.useState(null), [width, setWidth] = React.useState(0), $src = useMediaState("currentSrc"), { thumbnails, sliderChaptersMinWidth, disableTimeSlider, seekStep, noScrubGesture, guestMode } = useDefaultLayoutContext(), label = useDefaultLayoutWord("Seek"), $RemotionSliderThumbnail = useSignal(RemotionSliderThumbnail);
1212
+ const onResize = React.useCallback(() => {
1213
+ const el = instance?.el;
1214
+ el && setWidth(el.clientWidth);
1215
+ }, [instance]);
1216
+ useResizeObserver(instance?.el, onResize);
1217
+ return /* @__PURE__ */ React.createElement(
1218
+ Root$9,
1219
+ {
1220
+ className: "vds-time-slider vds-slider",
1221
+ "aria-label": label,
1222
+ disabled: disableTimeSlider || guestMode,
1223
+ noSwipeGesture: noScrubGesture || guestMode,
1224
+ keyStep: seekStep,
1225
+ ref: setInstance
1226
+ },
1227
+ /* @__PURE__ */ React.createElement(
1228
+ Chapters,
1229
+ {
1230
+ className: "vds-slider-chapters",
1231
+ disabled: width < sliderChaptersMinWidth
1232
+ },
1233
+ (cues, forwardRef) => cues.map((cue) => /* @__PURE__ */ React.createElement("div", { className: "vds-slider-chapter", key: cue.startTime, ref: forwardRef }, /* @__PURE__ */ React.createElement(Track, { className: "vds-slider-track" }), /* @__PURE__ */ React.createElement(TrackFill, { className: "vds-slider-track-fill vds-slider-track" }), /* @__PURE__ */ React.createElement(Progress, { className: "vds-slider-progress vds-slider-track" })))
1234
+ ),
1235
+ /* @__PURE__ */ React.createElement(Thumb, { className: "vds-slider-thumb" }),
1236
+ /* @__PURE__ */ React.createElement(Preview, { className: "vds-slider-preview" }, thumbnails ? /* @__PURE__ */ React.createElement(
1237
+ Thumbnail.Root,
1238
+ {
1239
+ src: thumbnails,
1240
+ className: "vds-slider-thumbnail vds-thumbnail"
1241
+ },
1242
+ /* @__PURE__ */ React.createElement(Thumbnail.Img, null)
1243
+ ) : $RemotionSliderThumbnail && isRemotionSrc($src) ? /* @__PURE__ */ React.createElement($RemotionSliderThumbnail, { className: "vds-slider-thumbnail vds-thumbnail" }) : null, /* @__PURE__ */ React.createElement(ChapterTitle, { className: "vds-slider-chapter-title" }), /* @__PURE__ */ React.createElement(Value, { className: "vds-slider-value" }))
1244
+ );
1245
+ }
1246
+ DefaultTimeSlider.displayName = "DefaultTimeSlider";
1247
+
1248
+ function DefaultTimeGroup({ slots }) {
1249
+ const $duration = useMediaState("duration");
1250
+ if (!$duration) return null;
1251
+ return /* @__PURE__ */ React.createElement("div", { className: "vds-time-group" }, slot(slots, "currentTime", /* @__PURE__ */ React.createElement(Time, { className: "vds-time", type: "current" })), slot(slots, "timeDivider", /* @__PURE__ */ React.createElement("div", { className: "vds-time-divider" }, "/")), slot(slots, "endTime", /* @__PURE__ */ React.createElement(Time, { className: "vds-time", type: "duration" })));
1252
+ }
1253
+ DefaultTimeGroup.displayName = "DefaultTimeGroup";
1254
+ function DefaultTimeInfo({ slots }) {
1255
+ const $live = useMediaState("live");
1256
+ return $live ? slot(slots, "liveButton", /* @__PURE__ */ React.createElement(DefaultLiveButton, null)) : /* @__PURE__ */ React.createElement(DefaultTimeGroup, { slots });
1257
+ }
1258
+ DefaultTimeInfo.displayName = "DefaultTimeInfo";
1259
+ function DefaultTimeInvert({ slots }) {
1260
+ const $live = useMediaState("live"), $duration = useMediaState("duration");
1261
+ return $live ? slot(slots, "liveButton", /* @__PURE__ */ React.createElement(DefaultLiveButton, null)) : slot(
1262
+ slots,
1263
+ "endTime",
1264
+ $duration ? /* @__PURE__ */ React.createElement(Time, { className: "vds-time", type: "current", toggle: true, remainder: true }) : null
1265
+ );
1266
+ }
1267
+ DefaultTimeInvert.displayName = "DefaultTimeInvert";
1268
+
1269
+ const MediaLayout$1 = createDefaultMediaLayout({
1270
+ type: "audio",
1271
+ smLayoutWhen({ width }) {
1272
+ return width < 576;
1273
+ },
1274
+ renderLayout: () => /* @__PURE__ */ React.createElement(AudioLayout, null)
1275
+ });
1276
+ function DefaultAudioLayout(props) {
1277
+ const [scrubbing, setScrubbing] = React.useState(false), $pointer = useMediaState("pointer");
1278
+ const onStartScrubbing = React.useCallback((event) => {
1279
+ const { target } = event, hasTimeSlider = !!(target instanceof HTMLElement && target.closest(".vds-time-slider"));
1280
+ if (!hasTimeSlider) return;
1281
+ event.nativeEvent.stopImmediatePropagation();
1282
+ setScrubbing(true);
1283
+ }, []);
1284
+ const onStopScrubbing = React.useCallback(() => {
1285
+ setScrubbing(false);
1286
+ }, []);
1287
+ React.useEffect(() => {
1288
+ if (scrubbing) return listenEvent(window, "pointerdown", onStopScrubbing);
1289
+ }, [scrubbing, onStopScrubbing]);
1290
+ return /* @__PURE__ */ React.createElement(
1291
+ MediaLayout$1,
1292
+ {
1293
+ ...props,
1294
+ "data-scrubbing": scrubbing ? "" : null,
1295
+ onPointerDown: scrubbing ? (e) => e.stopPropagation() : void 0,
1296
+ onPointerDownCapture: $pointer === "coarse" && !scrubbing ? onStartScrubbing : void 0
1297
+ }
1298
+ );
1299
+ }
1300
+ DefaultAudioLayout.displayName = "DefaultAudioLayout";
1301
+ function AudioLayout() {
1302
+ const slots = useDefaultAudioLayoutSlots();
1303
+ useLayoutName("audio");
1304
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DefaultAnnouncer, null), /* @__PURE__ */ React.createElement(DefaultCaptions, null), /* @__PURE__ */ React.createElement(Root$a, { className: "vds-controls" }, /* @__PURE__ */ React.createElement(Group, { className: "vds-controls-group" }, slot(slots, "seekBackwardButton", /* @__PURE__ */ React.createElement(DefaultSeekButton, { backward: true, tooltip: "top start" })), slot(slots, "playButton", /* @__PURE__ */ React.createElement(DefaultPlayButton, { tooltip: "top center" })), slot(slots, "seekForwardButton", /* @__PURE__ */ React.createElement(DefaultSeekButton, { tooltip: "top center" })), /* @__PURE__ */ React.createElement(DefaultAudioTitle, null), slot(slots, "timeSlider", /* @__PURE__ */ React.createElement(DefaultTimeSlider, null)), /* @__PURE__ */ React.createElement(DefaultTimeInvert, { slots }), /* @__PURE__ */ React.createElement(DefaultVolumePopup, { orientation: "vertical", tooltip: "top", slots }), slot(slots, "captionButton", /* @__PURE__ */ React.createElement(DefaultCaptionButton, { tooltip: "top center" })), slot(slots, "downloadButton", /* @__PURE__ */ React.createElement(DefaultDownloadButton, null)), /* @__PURE__ */ React.createElement(DefaultAudioMenus, { slots }))));
1305
+ }
1306
+ AudioLayout.displayName = "AudioLayout";
1307
+ function DefaultAudioMenus({ slots }) {
1308
+ const { isSmallLayout, noModal } = useDefaultLayoutContext(), placement = noModal ? "top end" : !isSmallLayout ? "top end" : null;
1309
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, slot(
1310
+ slots,
1311
+ "chaptersMenu",
1312
+ /* @__PURE__ */ React.createElement(DefaultChaptersMenu, { tooltip: "top", placement, portalClass: "vds-audio-layout" })
1313
+ ), slot(
1314
+ slots,
1315
+ "settingsMenu",
1316
+ /* @__PURE__ */ React.createElement(
1317
+ DefaultSettingsMenu,
1318
+ {
1319
+ tooltip: "top end",
1320
+ placement,
1321
+ portalClass: "vds-audio-layout",
1322
+ slots
1323
+ }
1324
+ )
1325
+ ));
1326
+ }
1327
+ DefaultAudioMenus.displayName = "DefaultAudioMenus";
1328
+ function DefaultAudioTitle() {
1329
+ const [rootEl, setRootEl] = React.useState(null), media = useMediaContext(), { translations } = useDefaultLayoutContext(), [isTextOverflowing, setIsTextOverflowing] = React.useState(false);
1330
+ const isContinued = createComputed(() => {
1331
+ const { started, currentTime } = media.$state;
1332
+ return started() || currentTime() > 0;
1333
+ });
1334
+ const $title = useSignal(
1335
+ createComputed(() => {
1336
+ const { title, ended } = media.$state;
1337
+ if (!title()) return "";
1338
+ const word = ended() ? "Replay" : isContinued() ? "Continue" : "Play";
1339
+ return `${i18n(translations, word)}: ${title()}`;
1340
+ })
1341
+ );
1342
+ const chapterTitle = useChapterTitle(), $isContinued = useSignal(isContinued), $chapterTitle = $isContinued ? chapterTitle : "", isTransitionActive = useTransitionActive(rootEl);
1343
+ React.useEffect(() => {
1344
+ if (isTransitionActive && document.activeElement === document.body) {
1345
+ media.player.el?.focus({ preventScroll: true });
1346
+ }
1347
+ }, []);
1348
+ const onResize = React.useCallback(() => {
1349
+ const el = rootEl, isOverflowing = !!el && !isTransitionActive && el.clientWidth < el.children[0].clientWidth;
1350
+ if (el) toggleClass(el, "vds-marquee", isOverflowing);
1351
+ setIsTextOverflowing(isOverflowing);
1352
+ }, [rootEl, isTransitionActive]);
1353
+ useResizeObserver(rootEl, onResize);
1354
+ return $title ? /* @__PURE__ */ React.createElement("span", { className: "vds-title", title: $title, ref: setRootEl }, /* @__PURE__ */ React.createElement(AudioTitle, { title: $title, chapterTitle: $chapterTitle }), isTextOverflowing && !isTransitionActive ? /* @__PURE__ */ React.createElement(AudioTitle, { title: $title, chapterTitle: $chapterTitle }) : null) : /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null);
1355
+ }
1356
+ DefaultAudioTitle.displayName = "DefaultAudioTitle";
1357
+ function AudioTitle({ title, chapterTitle }) {
1358
+ const slots = useDefaultAudioLayoutSlots();
1359
+ return /* @__PURE__ */ React.createElement("span", { className: "vds-title-text" }, slot(slots, "title", title), slot(slots, "chapterTitle", /* @__PURE__ */ React.createElement("span", { className: "vds-chapter-title" }, chapterTitle)));
1360
+ }
1361
+ AudioTitle.displayName = "AudioTitle";
1362
+
1363
+ const DefaultKeyboardDisplay = React.forwardRef(
1364
+ ({ icons: Icons, ...props }, forwardRef) => {
1365
+ const [visible, setVisible] = React.useState(false), [Icon, setIcon] = React.useState(null), [count, setCount] = React.useState(0), $lastKeyboardAction = useMediaState("lastKeyboardAction");
1366
+ React.useEffect(() => {
1367
+ setCount((n) => n + 1);
1368
+ }, [$lastKeyboardAction]);
1369
+ const actionDataAttr = React.useMemo(() => {
1370
+ const action = $lastKeyboardAction?.action;
1371
+ return action && visible ? camelToKebabCase(action) : null;
1372
+ }, [visible, $lastKeyboardAction]);
1373
+ const className = React.useMemo(
1374
+ () => `vds-kb-action${!visible ? " hidden" : ""}${props.className ? ` ${props.className}` : ""}`,
1375
+ [visible]
1376
+ );
1377
+ const $$text = createComputed(getText), $text = useSignal($$text);
1378
+ createEffect(() => {
1379
+ const Icon2 = getIcon(Icons);
1380
+ setIcon(() => Icon2);
1381
+ }, [Icons]);
1382
+ React.useEffect(() => {
1383
+ setVisible(!!$lastKeyboardAction);
1384
+ const id = setTimeout(() => setVisible(false), 500);
1385
+ return () => {
1386
+ setVisible(false);
1387
+ window.clearTimeout(id);
1388
+ };
1389
+ }, [$lastKeyboardAction]);
1390
+ return Icon ? /* @__PURE__ */ React.createElement(
1391
+ Primitive.div,
1392
+ {
1393
+ ...props,
1394
+ className,
1395
+ "data-action": actionDataAttr,
1396
+ ref: forwardRef
1397
+ },
1398
+ /* @__PURE__ */ React.createElement("div", { className: "vds-kb-text-wrapper" }, /* @__PURE__ */ React.createElement("div", { className: "vds-kb-text" }, $text)),
1399
+ /* @__PURE__ */ React.createElement("div", { className: "vds-kb-bezel", key: count }, /* @__PURE__ */ React.createElement("div", { className: "vds-kb-icon" }, /* @__PURE__ */ React.createElement(Icon, null)))
1400
+ ) : null;
1401
+ }
1402
+ );
1403
+ DefaultKeyboardDisplay.displayName = "DefaultKeyboardDisplay";
1404
+ function getText() {
1405
+ const { $state } = useContext(mediaContext), action = $state.lastKeyboardAction()?.action, audioGain = $state.audioGain() ?? 1;
1406
+ switch (action) {
1407
+ case "toggleMuted":
1408
+ return $state.muted() ? "0%" : getVolumeText($state.volume(), audioGain);
1409
+ case "volumeUp":
1410
+ case "volumeDown":
1411
+ return getVolumeText($state.volume(), audioGain);
1412
+ default:
1413
+ return "";
1414
+ }
1415
+ }
1416
+ function getVolumeText(volume, gain) {
1417
+ return `${Math.round(volume * gain * 100)}%`;
1418
+ }
1419
+ function getIcon(Icons) {
1420
+ const { $state } = useContext(mediaContext), action = $state.lastKeyboardAction()?.action;
1421
+ switch (action) {
1422
+ case "togglePaused":
1423
+ return !$state.paused() ? Icons.Play : Icons.Pause;
1424
+ case "toggleMuted":
1425
+ return $state.muted() || $state.volume() === 0 ? Icons.Mute : $state.volume() >= 0.5 ? Icons.VolumeUp : Icons.VolumeDown;
1426
+ case "toggleFullscreen":
1427
+ return $state.fullscreen() ? Icons.EnterFullscreen : Icons.ExitFullscreen;
1428
+ case "togglePictureInPicture":
1429
+ return $state.pictureInPicture() ? Icons.EnterPiP : Icons.ExitPiP;
1430
+ case "toggleCaptions":
1431
+ return $state.hasCaptions() ? $state.textTrack() ? Icons.CaptionsOn : Icons.CaptionsOff : null;
1432
+ case "volumeUp":
1433
+ return Icons.VolumeUp;
1434
+ case "volumeDown":
1435
+ return Icons.VolumeDown;
1436
+ case "seekForward":
1437
+ return Icons.SeekForward;
1438
+ case "seekBackward":
1439
+ return Icons.SeekBackward;
1440
+ default:
1441
+ return null;
1442
+ }
1443
+ }
1444
+
1445
+ function DefaultTitle() {
1446
+ const $started = useMediaState("started"), $title = useMediaState("title"), $hasChapters = useActiveTextTrack("chapters");
1447
+ return $hasChapters && ($started || !$title) ? /* @__PURE__ */ React.createElement(ChapterTitle$1, { className: "vds-chapter-title" }) : /* @__PURE__ */ React.createElement(Title, { className: "vds-chapter-title" });
1448
+ }
1449
+ DefaultTitle.displayName = "DefaultTitle";
1450
+
1451
+ const MediaLayout = createDefaultMediaLayout({
1452
+ type: "video",
1453
+ smLayoutWhen({ width, height }) {
1454
+ return width < 576 || height < 380;
1455
+ },
1456
+ renderLayout(props) {
1457
+ return /* @__PURE__ */ React.createElement(VideoLayout, { ...props });
1458
+ }
1459
+ });
1460
+ function DefaultVideoLayout(props) {
1461
+ return /* @__PURE__ */ React.createElement(MediaLayout, { ...props });
1462
+ }
1463
+ DefaultVideoLayout.displayName = "DefaultVideoLayout";
1464
+ function VideoLayout({ streamType, isLoadLayout, isSmallLayout }) {
1465
+ useLayoutName("video");
1466
+ return isLoadLayout ? /* @__PURE__ */ React.createElement(DefaultVideoLoadLayout, null) : streamType === "unknown" ? /* @__PURE__ */ React.createElement(DefaultBufferingIndicator, null) : isSmallLayout ? /* @__PURE__ */ React.createElement(DefaultVideoSmallLayout, null) : /* @__PURE__ */ React.createElement(DefaultVideoLargeLayout, null);
1467
+ }
1468
+ VideoLayout.displayName = "VideoLayout";
1469
+ function DefaultVideoLargeLayout() {
1470
+ const { menuGroup, episodes, episodesTitle, isSmallLayout } = useDefaultLayoutContext(), baseSlots = useDefaultVideoLayoutSlots(), slots = { ...baseSlots, ...baseSlots?.largeLayout }, $fullscreen = useMediaState("fullscreen"), [episodesOpen, setEpisodesOpen] = React.useState(false), list = episodes ?? [];
1471
+ React.useEffect(() => {
1472
+ if (!$fullscreen) setEpisodesOpen(false);
1473
+ }, [$fullscreen]);
1474
+ const canOpenEpisodes = $fullscreen && list.length > 0;
1475
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DefaultAnnouncer, null), /* @__PURE__ */ React.createElement(DefaultVideoGestures, null), /* @__PURE__ */ React.createElement(DefaultVideoKeyboardDisplay, null), slot(slots, "bufferingIndicator", /* @__PURE__ */ React.createElement(DefaultBufferingIndicator, null)), slot(slots, "captions", /* @__PURE__ */ React.createElement(DefaultCaptions, null)), /* @__PURE__ */ React.createElement(Root$a, { className: "vds-controls" }, /* @__PURE__ */ React.createElement(Group, { className: "vds-controls-group" }, slot(slots, "topControlsGroupStart", null), /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null), slot(slots, "topControlsGroupCenter", null), /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null), slot(slots, "topControlsGroupEnd", null), menuGroup === "top" && /* @__PURE__ */ React.createElement(DefaultVideoMenus, { slots })), /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null), /* @__PURE__ */ React.createElement(Group, { className: "vds-controls-group" }, slot(slots, "centerControlsGroupStart", null), /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null), slot(slots, "centerControlsGroupCenter", null), /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null), slot(slots, "centerControlsGroupEnd", null)), /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null), /* @__PURE__ */ React.createElement(Group, { className: "vds-controls-group" }, slot(slots, "timeSlider", /* @__PURE__ */ React.createElement(DefaultTimeSlider, null))), /* @__PURE__ */ React.createElement(Group, { className: "vds-controls-group" }, slot(slots, "playButton", /* @__PURE__ */ React.createElement(DefaultPlayButton, { tooltip: "top start" })), /* @__PURE__ */ React.createElement(DefaultSeekButton, { backward: true, tooltip: "top" }), /* @__PURE__ */ React.createElement(DefaultSeekButton, { tooltip: "top" }), /* @__PURE__ */ React.createElement(DefaultVolumePopup, { orientation: "horizontal", tooltip: "top", slots }), /* @__PURE__ */ React.createElement(DefaultTimeInfo, { slots }), slot(slots, "chapterTitle", /* @__PURE__ */ React.createElement(DefaultTitle, null)), canOpenEpisodes ? /* @__PURE__ */ React.createElement(DefaultEpisodeButton, { tooltip: "top", onPress: () => setEpisodesOpen((open) => !open) }) : null, slot(slots, "captionButton", /* @__PURE__ */ React.createElement(DefaultCaptionButton, { tooltip: "top" })), menuGroup === "bottom" && /* @__PURE__ */ React.createElement(DefaultVideoMenus, { slots }), slot(slots, "airPlayButton", /* @__PURE__ */ React.createElement(DefaultAirPlayButton, { tooltip: "top" })), slot(slots, "googleCastButton", /* @__PURE__ */ React.createElement(DefaultGoogleCastButton, { tooltip: "top" })), slot(slots, "downloadButton", /* @__PURE__ */ React.createElement(DefaultDownloadButton, null)), slot(slots, "pipButton", /* @__PURE__ */ React.createElement(DefaultPIPButton, { tooltip: "top" })), slot(slots, "fullscreenButton", /* @__PURE__ */ React.createElement(DefaultFullscreenButton, { tooltip: "top end" })))), /* @__PURE__ */ React.createElement(
1476
+ DefaultEpisodesSidebar,
1477
+ {
1478
+ open: episodesOpen,
1479
+ onClose: () => setEpisodesOpen(false),
1480
+ episodes: list,
1481
+ episodesTitle: episodesTitle ?? "Episodes"
1482
+ }
1483
+ ));
1484
+ }
1485
+ DefaultVideoLargeLayout.displayName = "DefaultVideoLargeLayout";
1486
+ function DefaultVideoSmallLayout() {
1487
+ const { episodes, episodesTitle, isSmallLayout } = useDefaultLayoutContext(), baseSlots = useDefaultVideoLayoutSlots(), slots = { ...baseSlots, ...baseSlots?.smallLayout }, $fullscreen = useMediaState("fullscreen"), [episodesOpen, setEpisodesOpen] = React.useState(false), list = episodes ?? [];
1488
+ React.useEffect(() => {
1489
+ if (!$fullscreen) setEpisodesOpen(false);
1490
+ }, [$fullscreen]);
1491
+ const canOpenEpisodes = $fullscreen && list.length > 0;
1492
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DefaultAnnouncer, null), /* @__PURE__ */ React.createElement(DefaultVideoGestures, null), /* @__PURE__ */ React.createElement(DefaultVideoKeyboardDisplay, null), slot(slots, "bufferingIndicator", /* @__PURE__ */ React.createElement(DefaultBufferingIndicator, null)), slot(slots, "captions", /* @__PURE__ */ React.createElement(DefaultCaptions, null)), /* @__PURE__ */ React.createElement(Root$a, { className: "vds-controls" }, /* @__PURE__ */ React.createElement(Group, { className: "vds-controls-group" }, slot(slots, "topControlsGroupStart", null), slot(slots, "airPlayButton", /* @__PURE__ */ React.createElement(DefaultAirPlayButton, { tooltip: "top start" })), slot(slots, "googleCastButton", /* @__PURE__ */ React.createElement(DefaultGoogleCastButton, { tooltip: "top start" })), /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null), slot(slots, "topControlsGroupCenter", null), /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null), slot(slots, "captionButton", /* @__PURE__ */ React.createElement(DefaultCaptionButton, { tooltip: "bottom" })), slot(slots, "downloadButton", /* @__PURE__ */ React.createElement(DefaultDownloadButton, null)), /* @__PURE__ */ React.createElement(DefaultVideoMenus, { slots }), /* @__PURE__ */ React.createElement(DefaultVolumePopup, { orientation: "vertical", tooltip: "bottom end", slots }), slot(slots, "topControlsGroupEnd", null)), /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null), /* @__PURE__ */ React.createElement(Group, { className: "vds-controls-group" }, slot(slots, "centerControlsGroupStart", null), /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null), /* @__PURE__ */ React.createElement(DefaultSeekButton, { backward: true, tooltip: "top" }), slot(slots, "centerControlsGroupCenter", null), slot(slots, "playButton", /* @__PURE__ */ React.createElement(DefaultPlayButton, { tooltip: "top" })), /* @__PURE__ */ React.createElement(DefaultSeekButton, { tooltip: "top" }), canOpenEpisodes ? /* @__PURE__ */ React.createElement(DefaultEpisodeButton, { tooltip: "top", onPress: () => setEpisodesOpen((open) => !open) }) : null, /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null), slot(slots, "centerControlsGroupEnd", null)), /* @__PURE__ */ React.createElement(DefaultControlsSpacer, null), /* @__PURE__ */ React.createElement(Group, { className: "vds-controls-group" }, /* @__PURE__ */ React.createElement(DefaultTimeInfo, { slots }), slot(slots, "chapterTitle", /* @__PURE__ */ React.createElement(DefaultTitle, null)), slot(slots, "fullscreenButton", /* @__PURE__ */ React.createElement(DefaultFullscreenButton, { tooltip: "top end" }))), /* @__PURE__ */ React.createElement(Group, { className: "vds-controls-group" }, slot(slots, "timeSlider", /* @__PURE__ */ React.createElement(DefaultTimeSlider, null)))), slot(slots, "startDuration", /* @__PURE__ */ React.createElement(DefaultVideoStartDuration, null)), /* @__PURE__ */ React.createElement(
1493
+ DefaultEpisodesSidebar,
1494
+ {
1495
+ open: episodesOpen,
1496
+ onClose: () => setEpisodesOpen(false),
1497
+ episodes: list,
1498
+ episodesTitle: episodesTitle ?? "Episodes"
1499
+ }
1500
+ ));
1501
+ }
1502
+ DefaultVideoSmallLayout.displayName = "DefaultVideoSmallLayout";
1503
+ function DefaultVideoStartDuration() {
1504
+ const $duration = useMediaState("duration");
1505
+ if ($duration === 0) return null;
1506
+ return /* @__PURE__ */ React.createElement("div", { className: "vds-start-duration" }, /* @__PURE__ */ React.createElement(Time, { className: "vds-time", type: "duration" }));
1507
+ }
1508
+ DefaultVideoStartDuration.displayName = "DefaultVideoStartDuration";
1509
+ function DefaultVideoGestures() {
1510
+ const { noGestures, guestMode } = useDefaultLayoutContext();
1511
+ if (noGestures || guestMode) return null;
1512
+ return /* @__PURE__ */ React.createElement("div", { className: "vds-gestures" }, /* @__PURE__ */ React.createElement(Gesture, { className: "vds-gesture", event: "pointerup", action: "toggle:paused" }), /* @__PURE__ */ React.createElement(Gesture, { className: "vds-gesture", event: "pointerup", action: "toggle:controls" }), /* @__PURE__ */ React.createElement(Gesture, { className: "vds-gesture", event: "dblpointerup", action: "toggle:fullscreen" }), /* @__PURE__ */ React.createElement(Gesture, { className: "vds-gesture", event: "dblpointerup", action: "seek:-10" }), /* @__PURE__ */ React.createElement(Gesture, { className: "vds-gesture", event: "dblpointerup", action: "seek:10" }));
1513
+ }
1514
+ DefaultVideoGestures.displayName = "DefaultVideoGestures";
1515
+ function DefaultBufferingIndicator() {
1516
+ return /* @__PURE__ */ React.createElement("div", { className: "vds-buffering-indicator" }, /* @__PURE__ */ React.createElement(Root$b, { className: "vds-buffering-spinner" }, /* @__PURE__ */ React.createElement(Track$1, { className: "vds-buffering-track" }), /* @__PURE__ */ React.createElement(TrackFill$1, { className: "vds-buffering-track-fill" })));
1517
+ }
1518
+ DefaultBufferingIndicator.displayName = "DefaultBufferingIndicator";
1519
+ function DefaultVideoMenus({ slots }) {
1520
+ const { isSmallLayout, noModal, menuGroup } = useDefaultLayoutContext(), side = menuGroup === "top" || isSmallLayout ? "bottom" : "top", tooltip = `${side} end`, placement = noModal ? `${side} end` : !isSmallLayout ? `${side} end` : null;
1521
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, slot(
1522
+ slots,
1523
+ "chaptersMenu",
1524
+ /* @__PURE__ */ React.createElement(
1525
+ DefaultChaptersMenu,
1526
+ {
1527
+ tooltip,
1528
+ placement,
1529
+ portalClass: "vds-video-layout"
1530
+ }
1531
+ )
1532
+ ), slot(
1533
+ slots,
1534
+ "settingsMenu",
1535
+ /* @__PURE__ */ React.createElement(
1536
+ DefaultSettingsMenu,
1537
+ {
1538
+ tooltip,
1539
+ placement,
1540
+ portalClass: "vds-video-layout",
1541
+ slots
1542
+ }
1543
+ )
1544
+ ));
1545
+ }
1546
+ DefaultVideoMenus.displayName = "DefaultVideoMenus";
1547
+ function formatMinutesLeftLabel(secondsLeft) {
1548
+ if (secondsLeft <= 0) return null;
1549
+ if (secondsLeft < 60) return "<1m left";
1550
+ const m = Math.floor(secondsLeft / 60);
1551
+ return `${m}m left`;
1552
+ }
1553
+ function DefaultEpisodesSidebar({
1554
+ open,
1555
+ onClose,
1556
+ episodes,
1557
+ episodesTitle
1558
+ }) {
1559
+ const $fullscreen = useMediaState("fullscreen"), currentTime = useMediaState("currentTime"), duration = useMediaState("duration"), listRef = React.useRef(null), wasEpisodesOpenRef = React.useRef(false);
1560
+ React.useLayoutEffect(() => {
1561
+ const justOpened = open && !wasEpisodesOpenRef.current;
1562
+ wasEpisodesOpenRef.current = open;
1563
+ if (!justOpened || !$fullscreen) return;
1564
+ const root = listRef.current;
1565
+ if (!root) return;
1566
+ requestAnimationFrame(() => {
1567
+ root.querySelector('[data-active="true"]')?.scrollIntoView({
1568
+ block: "nearest",
1569
+ behavior: "smooth"
1570
+ });
1571
+ });
1572
+ }, [open, $fullscreen]);
1573
+ if (!$fullscreen || episodes.length === 0) return null;
1574
+ return /* @__PURE__ */ React.createElement(
1575
+ "div",
1576
+ {
1577
+ className: "vds-episodes-backdrop",
1578
+ "data-open": open ? "true" : "false",
1579
+ onPointerUp: (event) => {
1580
+ if (event.target === event.currentTarget) onClose();
1581
+ }
1582
+ },
1583
+ /* @__PURE__ */ React.createElement(
1584
+ "aside",
1585
+ {
1586
+ className: "vds-episodes-panel",
1587
+ "data-open": open ? "true" : "false",
1588
+ onKeyDown: (event) => {
1589
+ if (event.key === "Escape") onClose();
1590
+ }
1591
+ },
1592
+ /* @__PURE__ */ React.createElement("header", { className: "vds-episodes-panel-header" }, /* @__PURE__ */ React.createElement("h3", { className: "vds-episodes-panel-title" }, episodesTitle), /* @__PURE__ */ React.createElement(
1593
+ "button",
1594
+ {
1595
+ type: "button",
1596
+ className: "vds-episodes-close-btn",
1597
+ "aria-label": "Close episodes",
1598
+ onPointerUp: (event) => {
1599
+ event.stopPropagation();
1600
+ onClose();
1601
+ }
1602
+ },
1603
+ /* @__PURE__ */ React.createElement("span", { "aria-hidden": "true" }, "\u2715")
1604
+ )),
1605
+ /* @__PURE__ */ React.createElement("div", { ref: listRef, className: "vds-episodes-list", role: "list" }, episodes.map((episode, index) => {
1606
+ const episodeName = episode.episodeTitle || `Episode ${episode.episodeNumber ?? index + 1}`;
1607
+ const seasonEpLabel = episode.seasonNumber != null && episode.episodeNumber != null ? `S${String(episode.seasonNumber).padStart(2, "0")} \xB7 E${String(episode.episodeNumber).padStart(2, "0")}` : episodeName;
1608
+ const isActive = !!episode.isActive;
1609
+ const onEpisodeSelect = (event) => {
1610
+ event.stopPropagation();
1611
+ if (isActive) return;
1612
+ event.currentTarget.dispatchEvent(
1613
+ new CustomEvent("vds-episode-select", {
1614
+ bubbles: true,
1615
+ composed: true,
1616
+ detail: { episode, index }
1617
+ })
1618
+ );
1619
+ onClose();
1620
+ };
1621
+ let runtimeText = null;
1622
+ if (isActive && duration > 0) {
1623
+ const secsLeft = Math.max(0, duration - currentTime);
1624
+ runtimeText = formatMinutesLeftLabel(secsLeft);
1625
+ } else if (episode.timeLeft != null && episode.timeLeft > 0) {
1626
+ runtimeText = `${episode.timeLeft}m left`;
1627
+ } else if (Number.isFinite(episode.runtime) && (episode.runtime ?? 0) > 0) {
1628
+ runtimeText = `${episode.runtime}m`;
1629
+ }
1630
+ let progressPct = 0;
1631
+ if (isActive && duration > 0) {
1632
+ progressPct = Math.min(100, currentTime / duration * 100);
1633
+ } else if (episode.progressPercent != null && episode.progressPercent > 0) {
1634
+ progressPct = Math.min(100, episode.progressPercent);
1635
+ }
1636
+ const showSubtitle = !!episode.episodeTitle && episode.episodeTitle.trim() !== "" && episode.episodeTitle !== episode.title;
1637
+ return /* @__PURE__ */ React.createElement(
1638
+ "article",
1639
+ {
1640
+ key: `${episode.episodeNumber ?? index}-${episode.title ?? ""}`,
1641
+ className: "vds-episode-item",
1642
+ "data-active": isActive ? "true" : "false",
1643
+ "aria-current": isActive ? "true" : void 0,
1644
+ role: "button",
1645
+ tabIndex: 0,
1646
+ "aria-label": episode.title || episodeName,
1647
+ onPointerUp: onEpisodeSelect,
1648
+ onKeyDown: (event) => {
1649
+ if (event.key === "Enter" || event.key === " ") onEpisodeSelect(event);
1650
+ }
1651
+ },
1652
+ /* @__PURE__ */ React.createElement("div", { className: "vds-episode-thumb-wrap" }, episode.thumbnail ? /* @__PURE__ */ React.createElement(
1653
+ "img",
1654
+ {
1655
+ className: "vds-episode-thumb",
1656
+ src: episode.thumbnail,
1657
+ alt: episode.title || episodeName,
1658
+ loading: "lazy",
1659
+ decoding: "async"
1660
+ }
1661
+ ) : /* @__PURE__ */ React.createElement("div", { className: "vds-episode-thumb vds-episode-thumb-placeholder" }), progressPct > 0 ? /* @__PURE__ */ React.createElement("div", { className: "vds-episode-progress-track", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("div", { className: "vds-episode-progress-fill", style: { width: `${progressPct}%` } })) : null),
1662
+ /* @__PURE__ */ React.createElement("div", { className: "vds-episode-body" }, /* @__PURE__ */ React.createElement("div", { className: "vds-episode-meta-row" }, /* @__PURE__ */ React.createElement("div", { className: "vds-episode-meta-primary" }, isActive ? /* @__PURE__ */ React.createElement("span", { className: "vds-episode-now-chip" }, "Playing") : null, /* @__PURE__ */ React.createElement("span", { className: "vds-episode-label" }, seasonEpLabel)), runtimeText ? /* @__PURE__ */ React.createElement("span", { className: "vds-episode-runtime" }, runtimeText) : null), /* @__PURE__ */ React.createElement("h4", { className: "vds-episode-title", title: episode.title || "" }, episode.title || "-"), showSubtitle ? /* @__PURE__ */ React.createElement("p", { className: "vds-episode-subtitle", title: episode.episodeTitle }, episode.episodeTitle) : null, episode.overview ? /* @__PURE__ */ React.createElement("p", { className: "vds-episode-desc", title: episode.overview }, episode.overview) : /* @__PURE__ */ React.createElement("p", { className: "vds-episode-desc", style: { fontStyle: "italic", opacity: 0.7 } }, "Deskripsi tidak tersedia."))
1663
+ );
1664
+ }))
1665
+ )
1666
+ );
1667
+ }
1668
+ function DefaultVideoLoadLayout() {
1669
+ const { isSmallLayout } = useDefaultLayoutContext(), baseSlots = useDefaultVideoLayoutSlots(), slots = { ...baseSlots, ...baseSlots?.[isSmallLayout ? "smallLayout" : "largeLayout"] };
1670
+ return /* @__PURE__ */ React.createElement("div", { className: "vds-load-container" }, slot(slots, "bufferingIndicator", /* @__PURE__ */ React.createElement(DefaultBufferingIndicator, null)), slot(slots, "loadButton", /* @__PURE__ */ React.createElement(DefaultPlayButton, { tooltip: "top" })));
1671
+ }
1672
+ DefaultVideoLoadLayout.displayName = "DefaultVideoLoadLayout";
1673
+ function DefaultVideoKeyboardDisplay() {
1674
+ const { noKeyboardAnimations, icons, userPrefersKeyboardAnimations } = useDefaultLayoutContext(), $userPrefersKeyboardAnimations = useSignal(userPrefersKeyboardAnimations), disabled = noKeyboardAnimations || !$userPrefersKeyboardAnimations;
1675
+ if (disabled || !icons.KeyboardDisplay) return null;
1676
+ return /* @__PURE__ */ React.createElement(DefaultKeyboardDisplay, { icons: icons.KeyboardDisplay });
1677
+ }
1678
+ DefaultVideoKeyboardDisplay.displayName = "DefaultVideoKeyboardDisplay";
1679
+
1680
+ export { DefaultAudioLayout, DefaultBufferingIndicator, DefaultKeyboardDisplay, DefaultLayoutContext, DefaultMenuButton, DefaultMenuCheckbox, DefaultMenuItem, DefaultMenuRadioGroup, DefaultMenuSection, DefaultMenuSliderItem, DefaultSliderParts, DefaultSliderSteps, DefaultTooltip, DefaultVideoGestures, DefaultVideoLargeLayout, DefaultVideoLayout, DefaultVideoSmallLayout, createRadioOptions, i18n, useDefaultLayoutContext, useDefaultLayoutWord };