@livepeer-frameworks/player-svelte 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/DevModePanel.svelte +650 -0
  2. package/dist/DevModePanel.svelte.d.ts +31 -0
  3. package/dist/DvdLogo.svelte +213 -0
  4. package/dist/DvdLogo.svelte.d.ts +7 -0
  5. package/dist/Icons.svelte +27 -0
  6. package/dist/Icons.svelte.d.ts +25 -0
  7. package/dist/IdleScreen.svelte +752 -0
  8. package/dist/IdleScreen.svelte.d.ts +11 -0
  9. package/dist/LoadingScreen.svelte +689 -0
  10. package/dist/LoadingScreen.svelte.d.ts +7 -0
  11. package/dist/Player.svelte +482 -0
  12. package/dist/Player.svelte.d.ts +26 -0
  13. package/dist/PlayerControls.svelte +739 -0
  14. package/dist/PlayerControls.svelte.d.ts +20 -0
  15. package/dist/SeekBar.svelte +274 -0
  16. package/dist/SeekBar.svelte.d.ts +25 -0
  17. package/dist/SkipIndicator.svelte +95 -0
  18. package/dist/SkipIndicator.svelte.d.ts +14 -0
  19. package/dist/SpeedIndicator.svelte +38 -0
  20. package/dist/SpeedIndicator.svelte.d.ts +8 -0
  21. package/dist/StatsPanel.svelte +155 -0
  22. package/dist/StatsPanel.svelte.d.ts +27 -0
  23. package/dist/StreamStateOverlay.svelte +266 -0
  24. package/dist/StreamStateOverlay.svelte.d.ts +18 -0
  25. package/dist/SubtitleRenderer.svelte +234 -0
  26. package/dist/SubtitleRenderer.svelte.d.ts +41 -0
  27. package/dist/ThumbnailOverlay.svelte +96 -0
  28. package/dist/ThumbnailOverlay.svelte.d.ts +11 -0
  29. package/dist/TitleOverlay.svelte +47 -0
  30. package/dist/TitleOverlay.svelte.d.ts +9 -0
  31. package/dist/assets/logomark.svg +56 -0
  32. package/dist/components/VolumeIcons.svelte +53 -0
  33. package/dist/components/VolumeIcons.svelte.d.ts +10 -0
  34. package/dist/global.d.ts +15 -0
  35. package/dist/icons/FullscreenExitIcon.svelte +33 -0
  36. package/dist/icons/FullscreenExitIcon.svelte.d.ts +8 -0
  37. package/dist/icons/FullscreenIcon.svelte +33 -0
  38. package/dist/icons/FullscreenIcon.svelte.d.ts +8 -0
  39. package/dist/icons/PauseIcon.svelte +28 -0
  40. package/dist/icons/PauseIcon.svelte.d.ts +8 -0
  41. package/dist/icons/PictureInPictureIcon.svelte +28 -0
  42. package/dist/icons/PictureInPictureIcon.svelte.d.ts +8 -0
  43. package/dist/icons/PlayIcon.svelte +27 -0
  44. package/dist/icons/PlayIcon.svelte.d.ts +8 -0
  45. package/dist/icons/SeekToLiveIcon.svelte +30 -0
  46. package/dist/icons/SeekToLiveIcon.svelte.d.ts +8 -0
  47. package/dist/icons/SettingsIcon.svelte +40 -0
  48. package/dist/icons/SettingsIcon.svelte.d.ts +8 -0
  49. package/dist/icons/SkipBackIcon.svelte +32 -0
  50. package/dist/icons/SkipBackIcon.svelte.d.ts +8 -0
  51. package/dist/icons/SkipForwardIcon.svelte +32 -0
  52. package/dist/icons/SkipForwardIcon.svelte.d.ts +8 -0
  53. package/dist/icons/StatsIcon.svelte +29 -0
  54. package/dist/icons/StatsIcon.svelte.d.ts +8 -0
  55. package/dist/icons/VolumeOffIcon.svelte +29 -0
  56. package/dist/icons/VolumeOffIcon.svelte.d.ts +8 -0
  57. package/dist/icons/VolumeUpIcon.svelte +34 -0
  58. package/dist/icons/VolumeUpIcon.svelte.d.ts +8 -0
  59. package/dist/icons/index.d.ts +17 -0
  60. package/dist/icons/index.js +17 -0
  61. package/dist/index.d.ts +50 -0
  62. package/dist/index.js +54 -0
  63. package/dist/player.css +2 -0
  64. package/dist/stores/index.d.ts +15 -0
  65. package/dist/stores/index.js +21 -0
  66. package/dist/stores/playbackQuality.d.ts +43 -0
  67. package/dist/stores/playbackQuality.js +107 -0
  68. package/dist/stores/playerContext.d.ts +73 -0
  69. package/dist/stores/playerContext.js +166 -0
  70. package/dist/stores/playerController.d.ts +178 -0
  71. package/dist/stores/playerController.js +358 -0
  72. package/dist/stores/playerSelection.d.ts +84 -0
  73. package/dist/stores/playerSelection.js +159 -0
  74. package/dist/stores/streamState.d.ts +44 -0
  75. package/dist/stores/streamState.js +314 -0
  76. package/dist/stores/viewerEndpoints.d.ts +48 -0
  77. package/dist/stores/viewerEndpoints.js +178 -0
  78. package/dist/types.d.ts +4 -0
  79. package/dist/types.js +4 -0
  80. package/dist/ui/Badge.svelte +21 -0
  81. package/dist/ui/Badge.svelte.d.ts +32 -0
  82. package/dist/ui/Button.svelte +42 -0
  83. package/dist/ui/Button.svelte.d.ts +35 -0
  84. package/dist/ui/Slider.svelte +100 -0
  85. package/dist/ui/Slider.svelte.d.ts +17 -0
  86. package/dist/ui/badge.d.ts +6 -0
  87. package/dist/ui/badge.js +10 -0
  88. package/dist/ui/button.d.ts +8 -0
  89. package/dist/ui/button.js +21 -0
  90. package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte +34 -0
  91. package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte.d.ts +31 -0
  92. package/dist/ui/context-menu/ContextMenuContent.svelte +17 -0
  93. package/dist/ui/context-menu/ContextMenuContent.svelte.d.ts +7 -0
  94. package/dist/ui/context-menu/ContextMenuItem.svelte +22 -0
  95. package/dist/ui/context-menu/ContextMenuItem.svelte.d.ts +8 -0
  96. package/dist/ui/context-menu/ContextMenuLabel.svelte +22 -0
  97. package/dist/ui/context-menu/ContextMenuLabel.svelte.d.ts +8 -0
  98. package/dist/ui/context-menu/ContextMenuPortal.svelte +11 -0
  99. package/dist/ui/context-menu/ContextMenuPortal.svelte.d.ts +6 -0
  100. package/dist/ui/context-menu/ContextMenuRadioItem.svelte +21 -0
  101. package/dist/ui/context-menu/ContextMenuRadioItem.svelte.d.ts +31 -0
  102. package/dist/ui/context-menu/ContextMenuSeparator.svelte +14 -0
  103. package/dist/ui/context-menu/ContextMenuSeparator.svelte.d.ts +6 -0
  104. package/dist/ui/context-menu/ContextMenuShortcut.svelte +19 -0
  105. package/dist/ui/context-menu/ContextMenuShortcut.svelte.d.ts +7 -0
  106. package/dist/ui/context-menu/ContextMenuSubContent.svelte +20 -0
  107. package/dist/ui/context-menu/ContextMenuSubContent.svelte.d.ts +7 -0
  108. package/dist/ui/context-menu/ContextMenuSubTrigger.svelte +34 -0
  109. package/dist/ui/context-menu/ContextMenuSubTrigger.svelte.d.ts +8 -0
  110. package/dist/ui/context-menu/index.d.ts +17 -0
  111. package/dist/ui/context-menu/index.js +17 -0
  112. package/package.json +51 -0
  113. package/src/DevModePanel.svelte +650 -0
  114. package/src/DvdLogo.svelte +213 -0
  115. package/src/Icons.svelte +27 -0
  116. package/src/IdleScreen.svelte +739 -0
  117. package/src/LoadingScreen.svelte +674 -0
  118. package/src/Player.svelte +483 -0
  119. package/src/PlayerControls.svelte +752 -0
  120. package/src/SeekBar.svelte +274 -0
  121. package/src/SkipIndicator.svelte +95 -0
  122. package/src/SpeedIndicator.svelte +37 -0
  123. package/src/StatsPanel.svelte +155 -0
  124. package/src/StreamStateOverlay.svelte +266 -0
  125. package/src/SubtitleRenderer.svelte +234 -0
  126. package/src/ThumbnailOverlay.svelte +96 -0
  127. package/src/TitleOverlay.svelte +47 -0
  128. package/src/assets/logomark.svg +56 -0
  129. package/src/components/VolumeIcons.svelte +53 -0
  130. package/src/global.d.ts +15 -0
  131. package/src/icons/FullscreenExitIcon.svelte +33 -0
  132. package/src/icons/FullscreenIcon.svelte +33 -0
  133. package/src/icons/PauseIcon.svelte +28 -0
  134. package/src/icons/PictureInPictureIcon.svelte +28 -0
  135. package/src/icons/PlayIcon.svelte +27 -0
  136. package/src/icons/SeekToLiveIcon.svelte +30 -0
  137. package/src/icons/SettingsIcon.svelte +40 -0
  138. package/src/icons/SkipBackIcon.svelte +32 -0
  139. package/src/icons/SkipForwardIcon.svelte +32 -0
  140. package/src/icons/StatsIcon.svelte +29 -0
  141. package/src/icons/VolumeOffIcon.svelte +29 -0
  142. package/src/icons/VolumeUpIcon.svelte +34 -0
  143. package/src/icons/index.ts +18 -0
  144. package/src/index.ts +84 -0
  145. package/src/player.css +2 -0
  146. package/src/stores/index.ts +88 -0
  147. package/src/stores/playbackQuality.ts +137 -0
  148. package/src/stores/playerContext.ts +221 -0
  149. package/src/stores/playerController.ts +568 -0
  150. package/src/stores/playerSelection.ts +216 -0
  151. package/src/stores/streamState.ts +367 -0
  152. package/src/stores/viewerEndpoints.ts +224 -0
  153. package/src/types.ts +6 -0
  154. package/src/ui/Badge.svelte +21 -0
  155. package/src/ui/Button.svelte +42 -0
  156. package/src/ui/Slider.svelte +100 -0
  157. package/src/ui/badge.ts +20 -0
  158. package/src/ui/button.ts +35 -0
  159. package/src/ui/context-menu/ContextMenuCheckboxItem.svelte +34 -0
  160. package/src/ui/context-menu/ContextMenuContent.svelte +17 -0
  161. package/src/ui/context-menu/ContextMenuItem.svelte +22 -0
  162. package/src/ui/context-menu/ContextMenuLabel.svelte +22 -0
  163. package/src/ui/context-menu/ContextMenuPortal.svelte +11 -0
  164. package/src/ui/context-menu/ContextMenuRadioItem.svelte +21 -0
  165. package/src/ui/context-menu/ContextMenuSeparator.svelte +14 -0
  166. package/src/ui/context-menu/ContextMenuShortcut.svelte +19 -0
  167. package/src/ui/context-menu/ContextMenuSubContent.svelte +20 -0
  168. package/src/ui/context-menu/ContextMenuSubTrigger.svelte +34 -0
  169. package/src/ui/context-menu/index.ts +36 -0
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Svelte store for Gateway GraphQL endpoint resolution with retry logic.
3
+ *
4
+ * Port of useViewerEndpoints.ts React hook to Svelte 5 stores.
5
+ */
6
+
7
+ import { writable, derived, type Readable } from 'svelte/store';
8
+ import type { ContentEndpoints, EndpointInfo, ContentMetadata, ContentType } from '@livepeer-frameworks/player-core';
9
+
10
+ export interface ViewerEndpointsOptions {
11
+ gatewayUrl: string;
12
+ contentType: ContentType;
13
+ contentId: string;
14
+ authToken?: string;
15
+ }
16
+
17
+ export type EndpointStatus = 'idle' | 'loading' | 'ready' | 'error';
18
+
19
+ export interface ViewerEndpointsState {
20
+ endpoints: ContentEndpoints | null;
21
+ status: EndpointStatus;
22
+ error: string | null;
23
+ }
24
+
25
+ export interface ViewerEndpointsStore extends Readable<ViewerEndpointsState> {
26
+ refetch: () => void;
27
+ destroy: () => void;
28
+ }
29
+
30
+ const MAX_RETRIES = 3;
31
+ const INITIAL_DELAY_MS = 500;
32
+
33
+ /**
34
+ * Fetch with exponential backoff retry
35
+ */
36
+ async function fetchWithRetry(
37
+ url: string,
38
+ options: RequestInit,
39
+ maxRetries: number = MAX_RETRIES,
40
+ initialDelay: number = INITIAL_DELAY_MS
41
+ ): Promise<Response> {
42
+ let lastError: Error | null = null;
43
+
44
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
45
+ try {
46
+ const response = await fetch(url, options);
47
+ return response;
48
+ } catch (e) {
49
+ lastError = e instanceof Error ? e : new Error('Fetch failed');
50
+
51
+ // Don't retry on abort
52
+ if (options.signal?.aborted) {
53
+ throw lastError;
54
+ }
55
+
56
+ // Wait before retrying (exponential backoff)
57
+ if (attempt < maxRetries - 1) {
58
+ const delay = initialDelay * Math.pow(2, attempt);
59
+ console.warn(`[viewerEndpoints] Retry ${attempt + 1}/${maxRetries - 1} after ${delay}ms`);
60
+ await new Promise(resolve => setTimeout(resolve, delay));
61
+ }
62
+ }
63
+ }
64
+
65
+ throw lastError ?? new Error('Gateway unreachable after retries');
66
+ }
67
+
68
+ const initialState: ViewerEndpointsState = {
69
+ endpoints: null,
70
+ status: 'idle',
71
+ error: null,
72
+ };
73
+
74
+ /**
75
+ * Create a viewer endpoints resolver store for Gateway GraphQL queries.
76
+ *
77
+ * @example
78
+ * ```svelte
79
+ * <script>
80
+ * import { createEndpointResolver } from './stores/viewerEndpoints';
81
+ *
82
+ * const resolver = createEndpointResolver({
83
+ * gatewayUrl: 'https://gateway.example.com/graphql',
84
+ * contentType: 'live',
85
+ * contentId: 'my-stream',
86
+ * });
87
+ *
88
+ * $: endpoints = $resolver.endpoints;
89
+ * $: status = $resolver.status;
90
+ * </script>
91
+ * ```
92
+ */
93
+ export function createEndpointResolver(options: ViewerEndpointsOptions): ViewerEndpointsStore {
94
+ const { gatewayUrl, contentType, contentId, authToken } = options;
95
+
96
+ const store = writable<ViewerEndpointsState>(initialState);
97
+ let abortController: AbortController | null = null;
98
+ let mounted = true;
99
+
100
+ /**
101
+ * Fetch endpoints from Gateway
102
+ */
103
+ async function fetchEndpoints() {
104
+ if (!gatewayUrl || !contentType || !contentId || !mounted) return;
105
+
106
+ // Abort previous request
107
+ abortController?.abort();
108
+ abortController = new AbortController();
109
+
110
+ store.update(s => ({ ...s, status: 'loading', error: null }));
111
+
112
+ try {
113
+ const graphqlEndpoint = gatewayUrl.replace(/\/$/, '');
114
+ const query = `
115
+ query ResolveViewer($contentType: String!, $contentId: String!) {
116
+ resolveViewerEndpoint(contentType: $contentType, contentId: $contentId) {
117
+ primary { nodeId baseUrl protocol url geoDistance loadScore outputs }
118
+ fallbacks { nodeId baseUrl protocol url geoDistance loadScore outputs }
119
+ metadata { contentType contentId title description durationSeconds status isLive viewers recordingSizeBytes clipSource createdAt }
120
+ }
121
+ }
122
+ `;
123
+
124
+ const res = await fetchWithRetry(graphqlEndpoint, {
125
+ method: 'POST',
126
+ headers: {
127
+ 'Content-Type': 'application/json',
128
+ ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
129
+ },
130
+ body: JSON.stringify({ query, variables: { contentType, contentId } }),
131
+ signal: abortController.signal,
132
+ });
133
+
134
+ if (!res.ok) throw new Error(`Gateway GQL error ${res.status}`);
135
+
136
+ const payload = await res.json();
137
+ if (payload.errors?.length) {
138
+ throw new Error(payload.errors[0]?.message || 'GraphQL error');
139
+ }
140
+
141
+ const resp = payload.data?.resolveViewerEndpoint;
142
+ const primary = resp?.primary;
143
+ const fallbacks = Array.isArray(resp?.fallbacks) ? resp.fallbacks : [];
144
+
145
+ if (!primary) throw new Error('No endpoints');
146
+
147
+ // Parse outputs JSON string (GraphQL returns JSON scalar as string)
148
+ if (primary.outputs && typeof primary.outputs === 'string') {
149
+ try { primary.outputs = JSON.parse(primary.outputs); } catch {}
150
+ }
151
+ if (fallbacks) {
152
+ fallbacks.forEach((fb: any) => {
153
+ if (fb.outputs && typeof fb.outputs === 'string') {
154
+ try { fb.outputs = JSON.parse(fb.outputs); } catch {}
155
+ }
156
+ });
157
+ }
158
+
159
+ store.set({
160
+ endpoints: { primary, fallbacks, metadata: resp?.metadata },
161
+ status: 'ready',
162
+ error: null,
163
+ });
164
+ } catch (e) {
165
+ if (abortController?.signal.aborted || !mounted) return;
166
+
167
+ const message = e instanceof Error ? e.message : 'Unknown gateway error';
168
+ console.error('[viewerEndpoints] Gateway resolution failed:', message);
169
+
170
+ store.set({
171
+ endpoints: null,
172
+ status: 'error',
173
+ error: message,
174
+ });
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Manual refetch
180
+ */
181
+ function refetch() {
182
+ fetchEndpoints();
183
+ }
184
+
185
+ /**
186
+ * Cleanup
187
+ */
188
+ function destroy() {
189
+ mounted = false;
190
+ abortController?.abort();
191
+ abortController = null;
192
+ store.set(initialState);
193
+ }
194
+
195
+ // Auto-fetch on creation if params are valid
196
+ if (gatewayUrl && contentType && contentId) {
197
+ fetchEndpoints();
198
+ }
199
+
200
+ return {
201
+ subscribe: store.subscribe,
202
+ refetch,
203
+ destroy,
204
+ };
205
+ }
206
+
207
+ // Convenience derived stores
208
+ export function createDerivedEndpoints(store: ViewerEndpointsStore) {
209
+ return derived(store, $state => $state.endpoints);
210
+ }
211
+
212
+ export function createDerivedPrimaryEndpoint(store: ViewerEndpointsStore) {
213
+ return derived(store, $state => $state.endpoints?.primary ?? null);
214
+ }
215
+
216
+ export function createDerivedMetadata(store: ViewerEndpointsStore) {
217
+ return derived(store, $state => $state.endpoints?.metadata ?? null);
218
+ }
219
+
220
+ export function createDerivedStatus(store: ViewerEndpointsStore) {
221
+ return derived(store, $state => $state.status);
222
+ }
223
+
224
+ export default createEndpointResolver;
package/src/types.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Svelte-specific type exports
3
+ */
4
+
5
+ // Re-export SkipDirection type for external consumers
6
+ export type SkipDirection = 'back' | 'forward' | null;
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ import { cn } from '@livepeer-frameworks/player-core';
3
+ import { badgeVariants, type BadgeVariant } from './badge';
4
+
5
+ type $$Props = {
6
+ variant?: BadgeVariant;
7
+ class?: string;
8
+ } & Record<string, any>;
9
+
10
+ let {
11
+ variant = "default",
12
+ class: className = "",
13
+ ...rest
14
+ }: $$Props = $props();
15
+
16
+ let mergedClasses = $derived(cn(badgeVariants(variant, className)));
17
+ </script>
18
+
19
+ <div class={mergedClasses} {...rest}>
20
+ <slot />
21
+ </div>
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ import { cn } from '@livepeer-frameworks/player-core';
3
+ import { buttonVariants, type ButtonSize, type ButtonVariant } from './button';
4
+ import { getContext, setContext } from 'svelte';
5
+
6
+ type $$Props = {
7
+ variant?: ButtonVariant;
8
+ size?: ButtonSize;
9
+ class?: string;
10
+ asChild?: boolean;
11
+ type?: "button" | "submit" | "reset";
12
+ } & Record<string, any>;
13
+
14
+ let {
15
+ variant = "default",
16
+ size = "default",
17
+ class: className = "",
18
+ asChild = false,
19
+ type = "button",
20
+ ...rest
21
+ }: $$Props = $props();
22
+
23
+ let Comp: string = "button";
24
+ if (asChild) {
25
+ Comp = getContext('__svelte_slot_element') || 'div';
26
+ }
27
+
28
+ // Set context for potential nested slots (though less common for Button)
29
+ setContext('__svelte_slot_element', Comp);
30
+
31
+ let mergedClasses = $derived(cn(buttonVariants(variant, size, className)));
32
+ </script>
33
+
34
+ {#if asChild}
35
+ <svelte:element this={Comp} class={mergedClasses} {...rest}>
36
+ <slot />
37
+ </svelte:element>
38
+ {:else}
39
+ <button class={mergedClasses} type={type} {...rest}>
40
+ <slot />
41
+ </button>
42
+ {/if}
@@ -0,0 +1,100 @@
1
+ <script lang="ts">
2
+ import { Slider as BitsSlider } from "bits-ui";
3
+ import { cn } from "@livepeer-frameworks/player-core";
4
+
5
+ type $$Props = {
6
+ min?: number;
7
+ max?: number;
8
+ step?: number;
9
+ value?: number;
10
+ orientation?: "horizontal" | "vertical";
11
+ className?: string;
12
+ showTrack?: boolean;
13
+ trackClassName?: string;
14
+ thumbClassName?: string;
15
+ hoverThumb?: boolean;
16
+ accentColor?: boolean;
17
+ oninput?: (value: number) => void;
18
+ } & Record<string, any>;
19
+
20
+ let {
21
+ min = 0,
22
+ max = 100,
23
+ step = 1,
24
+ value = 0,
25
+ orientation = "horizontal",
26
+ className = "",
27
+ trackClassName = "",
28
+ thumbClassName = "",
29
+ showTrack = true,
30
+ hoverThumb = false,
31
+ accentColor = false,
32
+ oninput = undefined,
33
+ ...rest
34
+ }: $$Props = $props();
35
+
36
+ // Colors based on accentColor prop
37
+ const rangeColorClass = $derived(accentColor ? "bg-[hsl(var(--tn-cyan,195_100%_50%))]" : "bg-white/90");
38
+ const thumbColorClass = $derived(accentColor ? "bg-[hsl(var(--tn-cyan,195_100%_50%))]" : "bg-white");
39
+
40
+ function handleValueChange(newValue: number) {
41
+ value = newValue;
42
+ if (oninput) {
43
+ // Defensive: ensure we pass a valid finite number (prevents NaN propagation)
44
+ if (typeof newValue === 'number' && Number.isFinite(newValue)) {
45
+ oninput(newValue);
46
+ }
47
+ }
48
+ }
49
+ </script>
50
+
51
+ <div
52
+ class={cn(
53
+ "group relative flex touch-none select-none items-center cursor-pointer",
54
+ orientation === "horizontal" ? "w-full h-5" : "h-full flex-col w-5",
55
+ className
56
+ )}
57
+ >
58
+ <BitsSlider.Root
59
+ type="single"
60
+ {min}
61
+ {max}
62
+ {step}
63
+ {value}
64
+ onValueChange={handleValueChange}
65
+ {orientation}
66
+ class="w-full h-full relative flex items-center"
67
+ {...rest}
68
+ >
69
+ {#if showTrack}
70
+ <div
71
+ class={cn(
72
+ "absolute rounded-full bg-white/30 transition-all duration-150",
73
+ orientation === "horizontal"
74
+ ? "inset-x-0 h-1 group-hover:h-1.5"
75
+ : "inset-y-0 w-1 group-hover:w-1.5",
76
+ trackClassName
77
+ )}
78
+ >
79
+ <BitsSlider.Range
80
+ class={cn(
81
+ "absolute rounded-full transition-all duration-150",
82
+ orientation === "horizontal" ? "h-full" : "w-full bottom-0",
83
+ rangeColorClass
84
+ )}
85
+ />
86
+ </div>
87
+ {/if}
88
+ <BitsSlider.Thumb
89
+ index={0}
90
+ class={cn(
91
+ "block rounded-full border-0 cursor-pointer shadow-md transition-all duration-150",
92
+ "w-2.5 h-2.5 group-hover:w-3.5 group-hover:h-3.5",
93
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/50",
94
+ "disabled:pointer-events-none disabled:opacity-50",
95
+ thumbColorClass,
96
+ thumbClassName
97
+ )}
98
+ />
99
+ </BitsSlider.Root>
100
+ </div>
@@ -0,0 +1,20 @@
1
+ export const badgeVariants = (variant: BadgeVariant = "default", className?: string) => {
2
+ const baseClasses = "inline-flex items-center rounded-full border border-transparent px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ring-offset-background";
3
+
4
+ const variants = {
5
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
6
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
7
+ outline: "border-border text-foreground"
8
+ };
9
+
10
+ const selectedVariant = variants[variant] || variants.default;
11
+
12
+ return `${baseClasses} ${selectedVariant} ${className || ""}`;
13
+ };
14
+
15
+ export type BadgeVariant = "default" | "secondary" | "outline";
16
+
17
+ export type BadgeProps = {
18
+ variant?: BadgeVariant;
19
+ className?: string;
20
+ };
@@ -0,0 +1,35 @@
1
+ export const buttonVariants = (variant: ButtonVariant = "default", size: ButtonSize = "default", className?: string) => {
2
+ const baseClasses = "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 ring-offset-background";
3
+
4
+ const variants = {
5
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
6
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
7
+ ghost: "hover:bg-accent hover:text-accent-foreground",
8
+ outline: "border border-border bg-transparent hover:bg-accent hover:text-accent-foreground",
9
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
10
+ subtle: "bg-muted text-muted-foreground hover:bg-muted/80",
11
+ link: "text-primary underline-offset-4 hover:underline"
12
+ };
13
+
14
+ const sizes = {
15
+ default: "h-10 px-4 py-2",
16
+ sm: "h-9 rounded-md px-3",
17
+ lg: "h-11 rounded-md px-8",
18
+ icon: "h-9 w-9"
19
+ };
20
+
21
+ const selectedVariant = variants[variant] || variants.default;
22
+ const selectedSize = sizes[size] || sizes.default;
23
+
24
+ return `${baseClasses} ${selectedVariant} ${selectedSize} ${className || ""}`;
25
+ };
26
+
27
+ export type ButtonVariant = "default" | "secondary" | "ghost" | "outline" | "destructive" | "subtle" | "link";
28
+ export type ButtonSize = "default" | "sm" | "lg" | "icon";
29
+
30
+ // For type inference with svelte components, similar to VariantProps
31
+ export type ButtonProps = {
32
+ variant?: ButtonVariant;
33
+ size?: ButtonSize;
34
+ className?: string;
35
+ };
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import { ContextMenu } from "bits-ui";
3
+ import { cn } from "@livepeer-frameworks/player-core";
4
+
5
+ let {
6
+ class: className,
7
+ checked = false,
8
+ ...rest
9
+ }: { class?: string; checked?: boolean } & Record<string, any> = $props();
10
+ </script>
11
+
12
+ <ContextMenu.CheckboxItem
13
+ class={cn("fw-context-menu-checkbox", className)}
14
+ {checked}
15
+ {...rest}
16
+ >
17
+ <div class="fw-context-menu-indicator">
18
+ {#if checked}
19
+ <svg
20
+ xmlns="http://www.w3.org/2000/svg"
21
+ viewBox="0 0 24 24"
22
+ fill="none"
23
+ stroke="currentColor"
24
+ stroke-width="2"
25
+ stroke-linecap="round"
26
+ stroke-linejoin="round"
27
+ class="h-4 w-4"
28
+ >
29
+ <polyline points="20 6 9 17 4 12" />
30
+ </svg>
31
+ {/if}
32
+ </div>
33
+ <slot />
34
+ </ContextMenu.CheckboxItem>
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import { ContextMenu } from "bits-ui";
3
+ import { cn } from "@livepeer-frameworks/player-core";
4
+
5
+ let {
6
+ children,
7
+ class: className,
8
+ ...rest
9
+ }: { children?: any; class?: string } = $props();
10
+ </script>
11
+
12
+ <ContextMenu.Content
13
+ class={cn("fw-player-surface fw-context-menu", className)}
14
+ {...rest}
15
+ >
16
+ {@render children?.()}
17
+ </ContextMenu.Content>
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import { ContextMenu } from "bits-ui";
3
+ import { cn } from "@livepeer-frameworks/player-core";
4
+
5
+ let {
6
+ children,
7
+ class: className,
8
+ inset = false,
9
+ ...rest
10
+ }: { children?: any; class?: string; inset?: boolean } & Record<string, any> = $props();
11
+ </script>
12
+
13
+ <ContextMenu.Item
14
+ class={cn(
15
+ "fw-context-menu-item",
16
+ inset && "fw-context-menu-item--inset",
17
+ className
18
+ )}
19
+ {...rest}
20
+ >
21
+ {@render children?.()}
22
+ </ContextMenu.Item>
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import { ContextMenu } from "bits-ui";
3
+ import { cn } from "@livepeer-frameworks/player-core";
4
+
5
+ let {
6
+ children,
7
+ class: className,
8
+ inset = false,
9
+ ...rest
10
+ }: { children?: any; class?: string; inset?: boolean } & Record<string, any> = $props();
11
+ </script>
12
+
13
+ <ContextMenu.GroupHeading
14
+ class={cn(
15
+ "fw-context-menu-label",
16
+ inset && "fw-context-menu-item--inset",
17
+ className
18
+ )}
19
+ {...rest}
20
+ >
21
+ {@render children?.()}
22
+ </ContextMenu.GroupHeading>
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import { ContextMenu } from "bits-ui";
3
+
4
+ let { children, ...rest }: { children?: any } = $props();
5
+ </script>
6
+
7
+ <ContextMenu.Portal {...rest}>
8
+ <div class="fw-player-surface">
9
+ {@render children?.()}
10
+ </div>
11
+ </ContextMenu.Portal>
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ import { ContextMenu } from "bits-ui";
3
+ import { cn } from "@livepeer-frameworks/player-core";
4
+
5
+ let {
6
+ class: className,
7
+ value,
8
+ ...rest
9
+ }: { class?: string; value: string } & Record<string, any> = $props();
10
+ </script>
11
+
12
+ <ContextMenu.RadioItem
13
+ class={cn("fw-context-menu-checkbox", className)}
14
+ {value}
15
+ {...rest}
16
+ >
17
+ <div class="fw-context-menu-indicator">
18
+ <!-- Indicator state is managed by the underlying ContextMenu.RadioGroup. -->
19
+ </div>
20
+ <slot />
21
+ </ContextMenu.RadioItem>
@@ -0,0 +1,14 @@
1
+ <script lang="ts">
2
+ import { ContextMenu } from "bits-ui";
3
+ import { cn } from "@livepeer-frameworks/player-core";
4
+
5
+ let {
6
+ class: className,
7
+ ...rest
8
+ }: { class?: string } = $props();
9
+ </script>
10
+
11
+ <ContextMenu.Separator
12
+ class={cn("fw-context-menu-separator", className)}
13
+ {...rest}
14
+ />
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ import { cn } from "@livepeer-frameworks/player-core";
3
+
4
+ let {
5
+ children,
6
+ class: className,
7
+ ...rest
8
+ }: { children?: any; class?: string } = $props();
9
+ </script>
10
+
11
+ <span
12
+ class={cn(
13
+ "ml-auto text-xs tracking-widest text-muted-foreground",
14
+ className
15
+ )}
16
+ {...rest}
17
+ >
18
+ {@render children?.()}
19
+ </span>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { ContextMenu } from "bits-ui";
3
+ import { cn } from "@livepeer-frameworks/player-core";
4
+
5
+ let {
6
+ children,
7
+ class: className,
8
+ ...rest
9
+ }: { children?: any; class?: string } = $props();
10
+ </script>
11
+
12
+ <ContextMenu.SubContent
13
+ class={cn(
14
+ "z-50 min-w-[8rem] overflow-hidden rounded border border-[#5a607f]/30 bg-[#1a1b26] p-0 text-[#a9b1d6] shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
15
+ className
16
+ )}
17
+ {...rest}
18
+ >
19
+ {@render children?.()}
20
+ </ContextMenu.SubContent>
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import { ContextMenu } from "bits-ui";
3
+ import { cn } from "@livepeer-frameworks/player-core";
4
+
5
+ let {
6
+ children,
7
+ class: className,
8
+ inset = false,
9
+ ...rest
10
+ }: { children?: any; class?: string; inset?: boolean } = $props();
11
+ </script>
12
+
13
+ <ContextMenu.SubTrigger
14
+ class={cn(
15
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-[#292e42] focus:text-[#c0caf5] data-[state=open]:bg-[#292e42] data-[state=open]:text-[#c0caf5] data-[highlighted]:bg-[#292e42] data-[highlighted]:text-[#c0caf5]",
16
+ inset && "pl-8",
17
+ className
18
+ )}
19
+ {...rest}
20
+ >
21
+ {@render children?.()}
22
+ <svg
23
+ xmlns="http://www.w3.org/2000/svg"
24
+ viewBox="0 0 24 24"
25
+ fill="none"
26
+ stroke="currentColor"
27
+ stroke-width="2"
28
+ stroke-linecap="round"
29
+ stroke-linejoin="round"
30
+ class="ml-auto h-4 w-4"
31
+ >
32
+ <polyline points="9 18 15 12 9 6" />
33
+ </svg>
34
+ </ContextMenu.SubTrigger>