@giddaa-housing/ui 0.1.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/README.md +18 -6
  2. package/css/base.css +37 -0
  3. package/css/components.css +135 -0
  4. package/css/generated/shared.css +1 -0
  5. package/css/giddaa.css +402 -0
  6. package/css/preset.css +77 -0
  7. package/css/styles.css +2 -0
  8. package/css/theme.css +255 -0
  9. package/dist/accordion.d.ts +9 -10
  10. package/dist/accordion.js +41 -117
  11. package/dist/activity-timeline.d.ts +29 -0
  12. package/dist/activity-timeline.js +97 -0
  13. package/dist/alert.d.ts +8 -9
  14. package/dist/alert.js +53 -141
  15. package/dist/auth-layout.d.ts +23 -0
  16. package/dist/auth-layout.js +153 -0
  17. package/dist/avatar.d.ts +6 -6
  18. package/dist/avatar.js +40 -142
  19. package/dist/badge.d.ts +12 -13
  20. package/dist/badge.js +66 -114
  21. package/dist/bar-chart.d.ts +136 -114
  22. package/dist/bar-chart.js +244 -297
  23. package/dist/breadcrumb.d.ts +5 -5
  24. package/dist/breadcrumb.js +56 -148
  25. package/dist/bullet-chart.d.ts +33 -33
  26. package/dist/bullet-chart.js +123 -183
  27. package/dist/button-BZIeyUw6.d.ts +17 -0
  28. package/dist/button-group.d.ts +13 -16
  29. package/dist/button-group.js +38 -163
  30. package/dist/button.d.ts +2 -19
  31. package/dist/button.js +90 -142
  32. package/dist/calendar.d.ts +6 -6
  33. package/dist/calendar.js +134 -288
  34. package/dist/card.d.ts +14 -16
  35. package/dist/card.js +58 -174
  36. package/dist/carousel.d.ts +34 -35
  37. package/dist/carousel.js +319 -378
  38. package/dist/chart.d.ts +58 -59
  39. package/dist/chart.js +350 -466
  40. package/dist/checkbox.d.ts +10 -12
  41. package/dist/checkbox.js +84 -84
  42. package/dist/cn-BI_4DMBf.js +23 -0
  43. package/dist/collapsible.d.ts +7 -8
  44. package/dist/collapsible.js +23 -77
  45. package/dist/combobox.d.ts +10 -10
  46. package/dist/combobox.js +220 -696
  47. package/dist/cta.d.ts +64 -0
  48. package/dist/cta.js +192 -0
  49. package/dist/currency-selector.d.ts +41 -0
  50. package/dist/currency-selector.js +114 -0
  51. package/dist/data-table.d.ts +164 -0
  52. package/dist/data-table.js +673 -0
  53. package/dist/date-picker.d.ts +27 -20
  54. package/dist/date-picker.js +122 -520
  55. package/dist/details-card.d.ts +19 -0
  56. package/dist/details-card.js +118 -0
  57. package/dist/dialog.d.ts +13 -14
  58. package/dist/dialog.js +117 -320
  59. package/dist/dropdown-menu.d.ts +14 -14
  60. package/dist/dropdown-menu.js +170 -365
  61. package/dist/dropzone.d.ts +55 -55
  62. package/dist/dropzone.js +328 -467
  63. package/dist/editorial-card.d.ts +27 -0
  64. package/dist/editorial-card.js +144 -0
  65. package/dist/empty-state.d.ts +8 -9
  66. package/dist/empty-state.js +48 -135
  67. package/dist/field.d.ts +23 -25
  68. package/dist/field.js +111 -327
  69. package/dist/filter.d.ts +118 -0
  70. package/dist/filter.js +775 -0
  71. package/dist/footer.d.ts +40 -0
  72. package/dist/footer.js +227 -0
  73. package/dist/funnel-chart.d.ts +38 -41
  74. package/dist/funnel-chart.js +168 -563
  75. package/dist/gallery-indicator.d.ts +17 -0
  76. package/dist/gallery-indicator.js +75 -0
  77. package/dist/heatmap-chart.d.ts +95 -0
  78. package/dist/heatmap-chart.js +197 -0
  79. package/dist/infotip.d.ts +10 -0
  80. package/dist/infotip.js +40 -0
  81. package/dist/input-CfoEJ8A6.d.ts +12 -0
  82. package/dist/input-group.d.ts +16 -19
  83. package/dist/input-group.js +92 -372
  84. package/dist/input-size-context-C-SFx9sx.d.ts +10 -0
  85. package/dist/input-size-context.d.ts +2 -11
  86. package/dist/input-size-context.js +14 -20
  87. package/dist/input.d.ts +2 -13
  88. package/dist/input.js +23 -79
  89. package/dist/item.d.ts +7 -8
  90. package/dist/item.js +22 -89
  91. package/dist/kpi-card.d.ts +18 -20
  92. package/dist/kpi-card.js +84 -232
  93. package/dist/label.d.ts +6 -6
  94. package/dist/label.js +39 -66
  95. package/dist/line-chart.d.ts +88 -89
  96. package/dist/line-chart.js +143 -262
  97. package/dist/logo.d.ts +4 -4
  98. package/dist/logo.js +87 -141
  99. package/dist/map-indicator.d.ts +13 -0
  100. package/dist/map-indicator.js +83 -0
  101. package/dist/match.d.ts +15 -0
  102. package/dist/match.js +68 -0
  103. package/dist/media-gallery-hero.d.ts +58 -0
  104. package/dist/media-gallery-hero.js +201 -0
  105. package/dist/media-player.d.ts +60 -0
  106. package/dist/media-player.js +919 -0
  107. package/dist/multi-step-form.d.ts +56 -0
  108. package/dist/multi-step-form.js +342 -0
  109. package/dist/number-input.d.ts +34 -34
  110. package/dist/number-input.js +62 -413
  111. package/dist/page-header.d.ts +10 -11
  112. package/dist/page-header.js +34 -107
  113. package/dist/pagination.d.ts +17 -20
  114. package/dist/pagination.js +132 -341
  115. package/dist/password-input.d.ts +5 -12
  116. package/dist/password-input.js +32 -354
  117. package/dist/phone-input.d.ts +19 -19
  118. package/dist/phone-input.js +232 -561
  119. package/dist/photo-gallery.d.ts +20 -20
  120. package/dist/photo-gallery.js +314 -746
  121. package/dist/picker-lzgmsNfG.js +32 -0
  122. package/dist/pie-chart.d.ts +60 -60
  123. package/dist/pie-chart.js +141 -184
  124. package/dist/popover.d.ts +9 -9
  125. package/dist/popover.js +60 -156
  126. package/dist/price-tag.d.ts +34 -0
  127. package/dist/price-tag.js +118 -0
  128. package/dist/progress.d.ts +18 -16
  129. package/dist/progress.js +156 -300
  130. package/dist/property-listing-card.d.ts +29 -0
  131. package/dist/property-listing-card.js +117 -0
  132. package/dist/radar-chart.d.ts +108 -0
  133. package/dist/radar-chart.js +172 -0
  134. package/dist/radio-group.d.ts +11 -13
  135. package/dist/radio-group.js +47 -96
  136. package/dist/review-block.d.ts +18 -0
  137. package/dist/review-block.js +154 -0
  138. package/dist/rich-text-editor.d.ts +30 -14
  139. package/dist/rich-text-editor.js +594 -979
  140. package/dist/scatter-chart.d.ts +140 -0
  141. package/dist/scatter-chart.js +248 -0
  142. package/dist/search-input.d.ts +8 -15
  143. package/dist/search-input.js +58 -408
  144. package/dist/select.d.ts +8 -8
  145. package/dist/select.js +131 -291
  146. package/dist/separator.d.ts +8 -9
  147. package/dist/separator.js +31 -70
  148. package/dist/sheet.d.ts +11 -11
  149. package/dist/sheet.js +84 -262
  150. package/dist/sidebar.d.ts +32 -37
  151. package/dist/sidebar.js +380 -1055
  152. package/dist/size-context-D4mYvcg8.d.ts +10 -0
  153. package/dist/size-context.d.ts +2 -11
  154. package/dist/size-context.js +14 -18
  155. package/dist/skeleton.d.ts +4 -5
  156. package/dist/skeleton.js +9 -45
  157. package/dist/slider.d.ts +27 -0
  158. package/dist/slider.js +81 -0
  159. package/dist/sparkline.d.ts +43 -46
  160. package/dist/sparkline.js +223 -561
  161. package/dist/stepper.d.ts +23 -24
  162. package/dist/stepper.js +149 -276
  163. package/dist/styles.css +9995 -2
  164. package/dist/switch.d.ts +10 -12
  165. package/dist/switch.js +49 -106
  166. package/dist/table-card.d.ts +35 -0
  167. package/dist/table-card.js +125 -0
  168. package/dist/table.d.ts +18 -14
  169. package/dist/table.js +114 -182
  170. package/dist/tabs.d.ts +23 -15
  171. package/dist/tabs.js +62 -149
  172. package/dist/tag.d.ts +22 -23
  173. package/dist/tag.js +104 -156
  174. package/dist/testimonial-block.d.ts +33 -0
  175. package/dist/testimonial-block.js +208 -0
  176. package/dist/textarea-IS_D8pXf.d.ts +11 -0
  177. package/dist/textarea.d.ts +2 -12
  178. package/dist/textarea.js +20 -74
  179. package/dist/time-picker.d.ts +11 -11
  180. package/dist/time-picker.js +247 -464
  181. package/dist/toast.d.ts +16 -18
  182. package/dist/toast.js +154 -310
  183. package/dist/tooltip.d.ts +9 -10
  184. package/dist/tooltip.js +33 -91
  185. package/dist/tree-map.d.ts +54 -57
  186. package/dist/tree-map.js +231 -646
  187. package/dist/use-uncontrolled-CLfM1XbB.js +21 -0
  188. package/dist/waterfall-chart.d.ts +126 -0
  189. package/dist/waterfall-chart.js +480 -0
  190. package/package.json +123 -34
  191. package/dist/accordion.js.map +0 -1
  192. package/dist/alert.js.map +0 -1
  193. package/dist/avatar.js.map +0 -1
  194. package/dist/badge.js.map +0 -1
  195. package/dist/bar-chart.js.map +0 -1
  196. package/dist/base.css +0 -32
  197. package/dist/breadcrumb.js.map +0 -1
  198. package/dist/bullet-chart.js.map +0 -1
  199. package/dist/button-group.js.map +0 -1
  200. package/dist/button.js.map +0 -1
  201. package/dist/calendar.js.map +0 -1
  202. package/dist/card.js.map +0 -1
  203. package/dist/carousel.js.map +0 -1
  204. package/dist/chart.js.map +0 -1
  205. package/dist/checkbox.js.map +0 -1
  206. package/dist/collapsible.js.map +0 -1
  207. package/dist/combobox.js.map +0 -1
  208. package/dist/date-picker.js.map +0 -1
  209. package/dist/dialog.js.map +0 -1
  210. package/dist/dropdown-menu.js.map +0 -1
  211. package/dist/dropzone.js.map +0 -1
  212. package/dist/empty-state.js.map +0 -1
  213. package/dist/field.js.map +0 -1
  214. package/dist/files/red-hat-display-latin-300-italic.woff +0 -0
  215. package/dist/files/red-hat-display-latin-300-italic.woff2 +0 -0
  216. package/dist/files/red-hat-display-latin-300-normal.woff +0 -0
  217. package/dist/files/red-hat-display-latin-300-normal.woff2 +0 -0
  218. package/dist/files/red-hat-display-latin-400-italic.woff +0 -0
  219. package/dist/files/red-hat-display-latin-400-italic.woff2 +0 -0
  220. package/dist/files/red-hat-display-latin-400-normal.woff +0 -0
  221. package/dist/files/red-hat-display-latin-400-normal.woff2 +0 -0
  222. package/dist/files/red-hat-display-latin-500-italic.woff +0 -0
  223. package/dist/files/red-hat-display-latin-500-italic.woff2 +0 -0
  224. package/dist/files/red-hat-display-latin-500-normal.woff +0 -0
  225. package/dist/files/red-hat-display-latin-500-normal.woff2 +0 -0
  226. package/dist/files/red-hat-display-latin-600-italic.woff +0 -0
  227. package/dist/files/red-hat-display-latin-600-italic.woff2 +0 -0
  228. package/dist/files/red-hat-display-latin-600-normal.woff +0 -0
  229. package/dist/files/red-hat-display-latin-600-normal.woff2 +0 -0
  230. package/dist/files/red-hat-display-latin-700-italic.woff +0 -0
  231. package/dist/files/red-hat-display-latin-700-italic.woff2 +0 -0
  232. package/dist/files/red-hat-display-latin-700-normal.woff +0 -0
  233. package/dist/files/red-hat-display-latin-700-normal.woff2 +0 -0
  234. package/dist/files/red-hat-display-latin-800-italic.woff +0 -0
  235. package/dist/files/red-hat-display-latin-800-italic.woff2 +0 -0
  236. package/dist/files/red-hat-display-latin-800-normal.woff +0 -0
  237. package/dist/files/red-hat-display-latin-800-normal.woff2 +0 -0
  238. package/dist/files/red-hat-display-latin-900-italic.woff +0 -0
  239. package/dist/files/red-hat-display-latin-900-italic.woff2 +0 -0
  240. package/dist/files/red-hat-display-latin-900-normal.woff +0 -0
  241. package/dist/files/red-hat-display-latin-900-normal.woff2 +0 -0
  242. package/dist/files/red-hat-display-latin-ext-300-italic.woff +0 -0
  243. package/dist/files/red-hat-display-latin-ext-300-italic.woff2 +0 -0
  244. package/dist/files/red-hat-display-latin-ext-300-normal.woff +0 -0
  245. package/dist/files/red-hat-display-latin-ext-300-normal.woff2 +0 -0
  246. package/dist/files/red-hat-display-latin-ext-400-italic.woff +0 -0
  247. package/dist/files/red-hat-display-latin-ext-400-italic.woff2 +0 -0
  248. package/dist/files/red-hat-display-latin-ext-400-normal.woff +0 -0
  249. package/dist/files/red-hat-display-latin-ext-400-normal.woff2 +0 -0
  250. package/dist/files/red-hat-display-latin-ext-500-italic.woff +0 -0
  251. package/dist/files/red-hat-display-latin-ext-500-italic.woff2 +0 -0
  252. package/dist/files/red-hat-display-latin-ext-500-normal.woff +0 -0
  253. package/dist/files/red-hat-display-latin-ext-500-normal.woff2 +0 -0
  254. package/dist/files/red-hat-display-latin-ext-600-italic.woff +0 -0
  255. package/dist/files/red-hat-display-latin-ext-600-italic.woff2 +0 -0
  256. package/dist/files/red-hat-display-latin-ext-600-normal.woff +0 -0
  257. package/dist/files/red-hat-display-latin-ext-600-normal.woff2 +0 -0
  258. package/dist/files/red-hat-display-latin-ext-700-italic.woff +0 -0
  259. package/dist/files/red-hat-display-latin-ext-700-italic.woff2 +0 -0
  260. package/dist/files/red-hat-display-latin-ext-700-normal.woff +0 -0
  261. package/dist/files/red-hat-display-latin-ext-700-normal.woff2 +0 -0
  262. package/dist/files/red-hat-display-latin-ext-800-italic.woff +0 -0
  263. package/dist/files/red-hat-display-latin-ext-800-italic.woff2 +0 -0
  264. package/dist/files/red-hat-display-latin-ext-800-normal.woff +0 -0
  265. package/dist/files/red-hat-display-latin-ext-800-normal.woff2 +0 -0
  266. package/dist/files/red-hat-display-latin-ext-900-italic.woff +0 -0
  267. package/dist/files/red-hat-display-latin-ext-900-italic.woff2 +0 -0
  268. package/dist/files/red-hat-display-latin-ext-900-normal.woff +0 -0
  269. package/dist/files/red-hat-display-latin-ext-900-normal.woff2 +0 -0
  270. package/dist/funnel-chart.js.map +0 -1
  271. package/dist/index.d.ts +0 -87
  272. package/dist/index.js +0 -10448
  273. package/dist/index.js.map +0 -1
  274. package/dist/input-group.js.map +0 -1
  275. package/dist/input-size-context.js.map +0 -1
  276. package/dist/input.js.map +0 -1
  277. package/dist/item.js.map +0 -1
  278. package/dist/kpi-card.js.map +0 -1
  279. package/dist/label.js.map +0 -1
  280. package/dist/line-chart.js.map +0 -1
  281. package/dist/logo.js.map +0 -1
  282. package/dist/number-input.js.map +0 -1
  283. package/dist/page-header.js.map +0 -1
  284. package/dist/pagination.js.map +0 -1
  285. package/dist/password-input.js.map +0 -1
  286. package/dist/phone-input.js.map +0 -1
  287. package/dist/photo-gallery.js.map +0 -1
  288. package/dist/pie-chart.js.map +0 -1
  289. package/dist/popover.js.map +0 -1
  290. package/dist/progress.js.map +0 -1
  291. package/dist/radio-group.js.map +0 -1
  292. package/dist/red-hat-display.css +0 -2
  293. package/dist/rich-text-editor.js.map +0 -1
  294. package/dist/search-input.js.map +0 -1
  295. package/dist/select.js.map +0 -1
  296. package/dist/separator.js.map +0 -1
  297. package/dist/sheet.js.map +0 -1
  298. package/dist/sidebar.js.map +0 -1
  299. package/dist/size-context.js.map +0 -1
  300. package/dist/skeleton.js.map +0 -1
  301. package/dist/sparkline.js.map +0 -1
  302. package/dist/stepper.js.map +0 -1
  303. package/dist/switch.js.map +0 -1
  304. package/dist/tab-list.d.ts +0 -38
  305. package/dist/tab-list.js +0 -394
  306. package/dist/tab-list.js.map +0 -1
  307. package/dist/table.js.map +0 -1
  308. package/dist/tabs.js.map +0 -1
  309. package/dist/tag.js.map +0 -1
  310. package/dist/textarea.js.map +0 -1
  311. package/dist/theme/giddaa.css +0 -2
  312. package/dist/time-picker.js.map +0 -1
  313. package/dist/toast.js.map +0 -1
  314. package/dist/tooltip.js.map +0 -1
  315. package/dist/tree-map.js.map +0 -1
@@ -0,0 +1,919 @@
1
+ "use client";
2
+ import { t as cn } from "./cn-BI_4DMBf.js";
3
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "./dropdown-menu.js";
4
+ import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.js";
5
+ import { Captions, CaptionsOff, Maximize, Minimize, Pause, PictureInPicture2, Play, RefreshCcw, Repeat2, RotateCcw, RotateCw, Settings, TriangleAlert, Volume1, Volume2, VolumeX } from "lucide-react";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import * as React from "react";
8
+ import { Slider } from "@base-ui/react/slider";
9
+ import { MediaActionTypes, MediaProvider, useMediaDispatch, useMediaFullscreenRef, useMediaRef, useMediaSelector, useMediaStore } from "media-chrome/react/media-store";
10
+ import * as ReactDOM from "react-dom";
11
+ //#region src/media-player.tsx
12
+ const DEFAULT_PLAYBACK_RATES = [
13
+ .5,
14
+ .75,
15
+ 1,
16
+ 1.25,
17
+ 1.5,
18
+ 1.75,
19
+ 2
20
+ ];
21
+ const CONTROLS_HIDE_DELAY = 2500;
22
+ const SEEK_PREVIEW_WIDTH = 160;
23
+ const MediaPlayerContext = React.createContext({
24
+ onMenuOpenChange: () => {},
25
+ portalContainer: null,
26
+ rootNode: null
27
+ });
28
+ function setRef(ref, value) {
29
+ if (typeof ref === "function") {
30
+ ref(value);
31
+ return;
32
+ }
33
+ if (ref) ref.current = value;
34
+ }
35
+ function formatTime(value) {
36
+ if (!Number.isFinite(value)) return "0:00";
37
+ const safeValue = Math.max(0, Math.floor(value ?? 0));
38
+ const hours = Math.floor(safeValue / 3600);
39
+ const minutes = Math.floor(safeValue % 3600 / 60);
40
+ const seconds = safeValue % 60;
41
+ if (hours > 0) return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
42
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
43
+ }
44
+ function formatRate(value) {
45
+ if (!value || value === 1) return "1x";
46
+ return `${Number.parseFloat(value.toFixed(2))}x`;
47
+ }
48
+ function subtitleTrackId(track) {
49
+ return `${track.kind}:${track.language}:${track.label}`;
50
+ }
51
+ function subtitleTrackLabel(track) {
52
+ return track.label || track.language || "Captions";
53
+ }
54
+ function hasAudioChild(children) {
55
+ return React.Children.toArray(children).some((child) => {
56
+ if (!React.isValidElement(child)) return false;
57
+ if (child.type === MediaPlayerAudio) return true;
58
+ const props = child.props;
59
+ return hasAudioChild(props.children);
60
+ });
61
+ }
62
+ function getSeekBounds(seekable, duration) {
63
+ const min = seekable?.[0] ?? 0;
64
+ const fallbackMax = Number.isFinite(duration) ? duration ?? 0 : 0;
65
+ const max = seekable?.[1] ?? fallbackMax;
66
+ return {
67
+ min,
68
+ max: Number.isFinite(max) ? Math.max(min, max) : min
69
+ };
70
+ }
71
+ function clamp(value, min, max) {
72
+ return Math.min(Math.max(value, min), max);
73
+ }
74
+ function getSeekValueFromPointer(event, min, max) {
75
+ const rect = event.currentTarget.getBoundingClientRect();
76
+ const percent = clamp((event.clientX - rect.left) / rect.width, 0, 1);
77
+ return {
78
+ percent: percent * 100,
79
+ value: min + percent * (max - min)
80
+ };
81
+ }
82
+ function MediaPlayer({ mediaStore, ...props }) {
83
+ const audio = props.audio ?? (hasAudioChild(props.children) ? true : void 0);
84
+ return /* @__PURE__ */ jsx(MediaProvider, {
85
+ mediaStore,
86
+ children: /* @__PURE__ */ jsx(MediaPlayerRoot, {
87
+ ...props,
88
+ audio
89
+ })
90
+ });
91
+ }
92
+ function MediaPlayerRoot({ audio, children, className, onBlurCapture, onFocusCapture, onKeyDown, onPointerDown, onPointerLeave, onPointerMove, ref, ...props }) {
93
+ const dispatch = useMediaDispatch();
94
+ const store = useMediaStore();
95
+ const fullscreenRef = useMediaFullscreenRef();
96
+ const paused = useMediaSelector((state) => state.mediaPaused !== false);
97
+ const muted = useMediaSelector((state) => state.mediaMuted === true);
98
+ const isFullscreen = useMediaSelector((state) => state.mediaIsFullscreen === true);
99
+ const [rootNode, setRootNode] = React.useState(null);
100
+ const [controlsVisible, setControlsVisible] = React.useState(true);
101
+ const [menuOpenCount, setMenuOpenCount] = React.useState(0);
102
+ const [hoveringControls, setHoveringControls] = React.useState(false);
103
+ const [focusPinned, setFocusPinned] = React.useState(false);
104
+ const hideTimerRef = React.useRef(void 0);
105
+ const lastActivityRef = React.useRef(0);
106
+ const alwaysVisible = Boolean(audio) || paused || menuOpenCount > 0 || hoveringControls || focusPinned;
107
+ const alwaysVisibleRef = React.useRef(alwaysVisible);
108
+ alwaysVisibleRef.current = alwaysVisible;
109
+ const scheduleHide = React.useCallback(function schedule(delay = CONTROLS_HIDE_DELAY) {
110
+ window.clearTimeout(hideTimerRef.current);
111
+ hideTimerRef.current = window.setTimeout(() => {
112
+ if (alwaysVisibleRef.current) return;
113
+ const idleFor = Date.now() - lastActivityRef.current;
114
+ if (idleFor < CONTROLS_HIDE_DELAY) {
115
+ schedule(CONTROLS_HIDE_DELAY - idleFor);
116
+ return;
117
+ }
118
+ setControlsVisible(false);
119
+ }, delay);
120
+ }, []);
121
+ const wake = React.useCallback(() => {
122
+ lastActivityRef.current = Date.now();
123
+ setControlsVisible(true);
124
+ scheduleHide();
125
+ }, [scheduleHide]);
126
+ React.useEffect(() => {
127
+ if (alwaysVisible) {
128
+ window.clearTimeout(hideTimerRef.current);
129
+ setControlsVisible(true);
130
+ return;
131
+ }
132
+ scheduleHide();
133
+ }, [alwaysVisible, scheduleHide]);
134
+ React.useEffect(() => {
135
+ return () => window.clearTimeout(hideTimerRef.current);
136
+ }, []);
137
+ React.useEffect(() => {
138
+ if (!rootNode) return;
139
+ const loadChapters = () => {
140
+ const media = rootNode.querySelector("video, audio");
141
+ for (const track of media?.textTracks ?? []) if (track.kind === "chapters" && track.mode === "disabled") track.mode = "hidden";
142
+ };
143
+ loadChapters();
144
+ const textTracks = rootNode.querySelector("video, audio")?.textTracks;
145
+ textTracks?.addEventListener("addtrack", loadChapters);
146
+ return () => textTracks?.removeEventListener("addtrack", loadChapters);
147
+ }, [rootNode]);
148
+ const onMenuOpenChange = React.useCallback((open) => {
149
+ setMenuOpenCount((count) => open ? count + 1 : Math.max(0, count - 1));
150
+ }, []);
151
+ const contextValue = React.useMemo(() => ({
152
+ onMenuOpenChange,
153
+ portalContainer: isFullscreen ? rootNode : null,
154
+ rootNode
155
+ }), [
156
+ isFullscreen,
157
+ onMenuOpenChange,
158
+ rootNode
159
+ ]);
160
+ const fullscreenRefRef = React.useRef(fullscreenRef);
161
+ fullscreenRefRef.current = fullscreenRef;
162
+ const rootRef = React.useCallback((node) => {
163
+ fullscreenRefRef.current(node);
164
+ setRootNode(node);
165
+ setRef(ref, node);
166
+ }, [ref]);
167
+ return /* @__PURE__ */ jsx(MediaPlayerContext.Provider, {
168
+ value: contextValue,
169
+ children: /* @__PURE__ */ jsx("div", {
170
+ ref: rootRef,
171
+ "aria-label": audio ? "Audio player" : "Video player",
172
+ "data-audio": audio ? "" : void 0,
173
+ "data-controls": controlsVisible ? "visible" : "hidden",
174
+ "data-fullscreen": isFullscreen ? "" : void 0,
175
+ "data-slot": "media-player",
176
+ role: "region",
177
+ tabIndex: 0,
178
+ className: cn("group/media-player relative isolate block w-full overflow-hidden rounded-2xl bg-fg-primary text-fg-on-brand shadow-2 outline-none", "data-[controls=hidden]:cursor-none", "data-[audio]:bg-[image:var(--gradient-accent-forest-to-brass)]", "data-[fullscreen]:rounded-none data-[fullscreen]:shadow-none", className),
179
+ ...props,
180
+ onBlurCapture: (event) => {
181
+ onBlurCapture?.(event);
182
+ if (!event.currentTarget.contains(event.relatedTarget)) setFocusPinned(false);
183
+ },
184
+ onFocusCapture: (event) => {
185
+ onFocusCapture?.(event);
186
+ const keyboardFocus = event.target.matches?.(":focus-visible") ?? false;
187
+ setFocusPinned(keyboardFocus);
188
+ if (keyboardFocus) setControlsVisible(true);
189
+ },
190
+ onKeyDown: (event) => {
191
+ onKeyDown?.(event);
192
+ if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.altKey) return;
193
+ const target = event.target;
194
+ if (target.closest("input, textarea, select, [contenteditable], [role='slider'], [role='menu'], [role='menuitem'], [role='menuitemradio']")) return;
195
+ const state = store.getState();
196
+ const currentTime = state.mediaCurrentTime ?? 0;
197
+ const { min, max } = getSeekBounds(state.mediaSeekable, state.mediaDuration);
198
+ switch (event.key) {
199
+ case " ":
200
+ if (target.closest("button")) return;
201
+ event.preventDefault();
202
+ dispatch({ type: paused ? MediaActionTypes.MEDIA_PLAY_REQUEST : MediaActionTypes.MEDIA_PAUSE_REQUEST });
203
+ break;
204
+ case "k":
205
+ case "K":
206
+ dispatch({ type: paused ? MediaActionTypes.MEDIA_PLAY_REQUEST : MediaActionTypes.MEDIA_PAUSE_REQUEST });
207
+ break;
208
+ case "m":
209
+ case "M":
210
+ dispatch({ type: muted ? MediaActionTypes.MEDIA_UNMUTE_REQUEST : MediaActionTypes.MEDIA_MUTE_REQUEST });
211
+ break;
212
+ case "f":
213
+ case "F":
214
+ if (audio) return;
215
+ dispatch({ type: isFullscreen ? MediaActionTypes.MEDIA_EXIT_FULLSCREEN_REQUEST : MediaActionTypes.MEDIA_ENTER_FULLSCREEN_REQUEST });
216
+ break;
217
+ case "c":
218
+ case "C":
219
+ dispatch({ type: MediaActionTypes.MEDIA_TOGGLE_SUBTITLES_REQUEST });
220
+ break;
221
+ case "ArrowLeft":
222
+ event.preventDefault();
223
+ dispatch({
224
+ type: MediaActionTypes.MEDIA_SEEK_REQUEST,
225
+ detail: clamp(currentTime - 5, min, max)
226
+ });
227
+ break;
228
+ case "ArrowRight":
229
+ event.preventDefault();
230
+ dispatch({
231
+ type: MediaActionTypes.MEDIA_SEEK_REQUEST,
232
+ detail: clamp(currentTime + 5, min, max)
233
+ });
234
+ break;
235
+ default: return;
236
+ }
237
+ wake();
238
+ },
239
+ onPointerDown: (event) => {
240
+ onPointerDown?.(event);
241
+ wake();
242
+ },
243
+ onPointerLeave: (event) => {
244
+ onPointerLeave?.(event);
245
+ setHoveringControls(false);
246
+ if (!alwaysVisibleRef.current) {
247
+ window.clearTimeout(hideTimerRef.current);
248
+ setControlsVisible(false);
249
+ }
250
+ },
251
+ onPointerMove: (event) => {
252
+ onPointerMove?.(event);
253
+ wake();
254
+ const rect = event.currentTarget.querySelector("[data-slot='media-player-controls']")?.getBoundingClientRect();
255
+ setHoveringControls(!!rect && event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom);
256
+ },
257
+ children
258
+ })
259
+ });
260
+ }
261
+ function MediaPlayerVideo({ className, ref, ...props }) {
262
+ const mediaRef = useMediaRef();
263
+ const mediaRefRef = React.useRef(mediaRef);
264
+ mediaRefRef.current = mediaRef;
265
+ return /* @__PURE__ */ jsx("video", {
266
+ ref: React.useCallback((node) => {
267
+ mediaRefRef.current(node);
268
+ setRef(ref, node);
269
+ }, [ref]),
270
+ "data-slot": "media-player-video",
271
+ className: cn("block aspect-video size-full object-cover", "group-data-[fullscreen]/media-player:aspect-auto group-data-[fullscreen]/media-player:object-contain", className),
272
+ playsInline: true,
273
+ ...props
274
+ });
275
+ }
276
+ function MediaPlayerAudio({ className, ref, ...props }) {
277
+ const mediaRef = useMediaRef();
278
+ const mediaRefRef = React.useRef(mediaRef);
279
+ mediaRefRef.current = mediaRef;
280
+ return /* @__PURE__ */ jsx("audio", {
281
+ ref: React.useCallback((node) => {
282
+ mediaRefRef.current(node);
283
+ setRef(ref, node);
284
+ }, [ref]),
285
+ "data-slot": "media-player-audio",
286
+ className: cn("sr-only", className),
287
+ ...props
288
+ });
289
+ }
290
+ function MediaPlayerControls({ className, ...props }) {
291
+ return /* @__PURE__ */ jsx("div", {
292
+ "data-slot": "media-player-controls",
293
+ className: cn("absolute inset-x-0 bottom-0 z-10 flex w-full items-center gap-2 p-3 text-fg-on-brand md:p-4", "transition-opacity duration-300 ease-out group-data-[controls=hidden]/media-player:pointer-events-none group-data-[controls=hidden]/media-player:opacity-0", className),
294
+ ...props
295
+ });
296
+ }
297
+ function MediaPlayerControlsOverlay({ className, ...props }) {
298
+ return /* @__PURE__ */ jsx("div", {
299
+ "aria-hidden": "true",
300
+ "data-slot": "media-player-controls-overlay",
301
+ className: cn("pointer-events-none absolute inset-x-0 bottom-0 -z-10 h-36 bg-[image:var(--gradient-overlay-photo-bottom)]", className),
302
+ ...props
303
+ });
304
+ }
305
+ function MediaControlButton({ className, shortcut, tooltip, ...props }) {
306
+ const { portalContainer } = React.useContext(MediaPlayerContext);
307
+ const button = /* @__PURE__ */ jsx("button", {
308
+ type: "button",
309
+ className: cn("inline-flex size-9 shrink-0 items-center justify-center rounded-full bg-surface-glass-overlay-button text-fg-on-brand shadow-1 backdrop-blur-sm transition-[background-color,color,box-shadow,transform] outline-none select-none hover:bg-surface-brand hover:text-fg-on-brand focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-focus)] active:scale-95 aria-pressed:bg-surface-brand disabled:pointer-events-none disabled:opacity-45 [&_svg]:size-4.5", className),
310
+ ...props
311
+ });
312
+ if (!tooltip) return button;
313
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
314
+ delay: 300,
315
+ render: button
316
+ }), /* @__PURE__ */ jsxs(TooltipContent, {
317
+ className: "border-line-subtle px-2.5 py-1.5 text-gdt-xs font-bold",
318
+ container: portalContainer ?? void 0,
319
+ side: "top",
320
+ sideOffset: 8,
321
+ children: [tooltip, shortcut ? /* @__PURE__ */ jsx("kbd", {
322
+ "data-slot": "kbd",
323
+ className: "rounded-sm border border-line bg-surface px-1.5 py-0.5 font-sans text-gdt-xs font-bold text-fg-primary",
324
+ children: shortcut
325
+ }) : null]
326
+ })] });
327
+ }
328
+ function MediaPlayerPlay({ className, onClick, ...props }) {
329
+ const dispatch = useMediaDispatch();
330
+ const paused = useMediaSelector((state) => state.mediaPaused !== false);
331
+ return /* @__PURE__ */ jsx(MediaControlButton, {
332
+ "aria-label": paused ? "Play media" : "Pause media",
333
+ "aria-pressed": !paused,
334
+ "data-slot": "media-player-play",
335
+ className,
336
+ shortcut: "Space",
337
+ tooltip: paused ? "Play" : "Pause",
338
+ onClick: (event) => {
339
+ onClick?.(event);
340
+ if (event.defaultPrevented) return;
341
+ dispatch({ type: paused ? MediaActionTypes.MEDIA_PLAY_REQUEST : MediaActionTypes.MEDIA_PAUSE_REQUEST });
342
+ },
343
+ ...props,
344
+ children: paused ? /* @__PURE__ */ jsx(Play, { fill: "currentColor" }) : /* @__PURE__ */ jsx(Pause, { fill: "currentColor" })
345
+ });
346
+ }
347
+ function MediaPlayerSeekBackward({ className, onClick, seekOffset = 10, ...props }) {
348
+ const dispatch = useMediaDispatch();
349
+ const currentTime = useMediaSelector((state) => state.mediaCurrentTime ?? 0);
350
+ return /* @__PURE__ */ jsx(MediaControlButton, {
351
+ "aria-label": `Seek back ${seekOffset} seconds`,
352
+ "data-slot": "media-player-seek-backward",
353
+ className,
354
+ tooltip: `Back ${seekOffset}s`,
355
+ onClick: (event) => {
356
+ onClick?.(event);
357
+ if (event.defaultPrevented) return;
358
+ dispatch({
359
+ type: MediaActionTypes.MEDIA_SEEK_REQUEST,
360
+ detail: Math.max(0, currentTime - seekOffset)
361
+ });
362
+ },
363
+ ...props,
364
+ children: /* @__PURE__ */ jsx(RotateCcw, {})
365
+ });
366
+ }
367
+ function MediaPlayerSeekForward({ className, onClick, seekOffset = 10, ...props }) {
368
+ const dispatch = useMediaDispatch();
369
+ const currentTime = useMediaSelector((state) => state.mediaCurrentTime ?? 0);
370
+ const duration = useMediaSelector((state) => state.mediaDuration);
371
+ const { max } = getSeekBounds(useMediaSelector((state) => state.mediaSeekable), duration);
372
+ return /* @__PURE__ */ jsx(MediaControlButton, {
373
+ "aria-label": `Seek forward ${seekOffset} seconds`,
374
+ "data-slot": "media-player-seek-forward",
375
+ className,
376
+ tooltip: `Forward ${seekOffset}s`,
377
+ onClick: (event) => {
378
+ onClick?.(event);
379
+ if (event.defaultPrevented) return;
380
+ dispatch({
381
+ type: MediaActionTypes.MEDIA_SEEK_REQUEST,
382
+ detail: max > 0 ? Math.min(max, currentTime + seekOffset) : currentTime + seekOffset
383
+ });
384
+ },
385
+ ...props,
386
+ children: /* @__PURE__ */ jsx(RotateCw, {})
387
+ });
388
+ }
389
+ function MediaPlayerVolume({ className, disabled, expandable = false, onClick, rangeClassName, ...props }) {
390
+ const dispatch = useMediaDispatch();
391
+ const muted = useMediaSelector((state) => state.mediaMuted === true);
392
+ const volume = useMediaSelector((state) => state.mediaVolume ?? 1);
393
+ const unavailable = useMediaSelector((state) => !!state.mediaVolumeUnavailable);
394
+ const resolvedDisabled = disabled || unavailable;
395
+ const Icon = muted || volume === 0 ? VolumeX : volume < .5 ? Volume1 : Volume2;
396
+ return /* @__PURE__ */ jsxs("div", {
397
+ "data-slot": "media-player-volume",
398
+ className: cn("group/volume flex items-center gap-2 rounded-full", expandable && "w-9 overflow-visible transition-[width] hover:w-32 focus-within:w-32"),
399
+ children: [/* @__PURE__ */ jsx(MediaControlButton, {
400
+ "aria-label": muted ? "Unmute media" : "Mute media",
401
+ "aria-pressed": muted,
402
+ className,
403
+ disabled: resolvedDisabled,
404
+ shortcut: "M",
405
+ tooltip: "Volume",
406
+ onClick: (event) => {
407
+ onClick?.(event);
408
+ if (event.defaultPrevented) return;
409
+ dispatch({ type: muted ? MediaActionTypes.MEDIA_UNMUTE_REQUEST : MediaActionTypes.MEDIA_MUTE_REQUEST });
410
+ },
411
+ ...props,
412
+ children: /* @__PURE__ */ jsx(Icon, {})
413
+ }), /* @__PURE__ */ jsx(Slider.Root, {
414
+ "data-slot": "media-player-volume-slider",
415
+ className: cn("flex h-8 w-20 min-w-20 touch-none items-center", resolvedDisabled && "pointer-events-none opacity-45", expandable && "pointer-events-none opacity-0 transition-opacity group-hover/volume:pointer-events-auto group-hover/volume:opacity-100 group-focus-within/volume:pointer-events-auto group-focus-within/volume:opacity-100", rangeClassName),
416
+ disabled: resolvedDisabled,
417
+ max: 1,
418
+ min: 0,
419
+ onValueChange: (value) => {
420
+ const nextValue = Array.isArray(value) ? value[0] ?? 0 : value;
421
+ dispatch({
422
+ type: MediaActionTypes.MEDIA_VOLUME_REQUEST,
423
+ detail: nextValue
424
+ });
425
+ if (muted && nextValue > 0) dispatch({ type: MediaActionTypes.MEDIA_UNMUTE_REQUEST });
426
+ },
427
+ step: .02,
428
+ value: muted ? 0 : volume,
429
+ children: /* @__PURE__ */ jsx(Slider.Control, {
430
+ className: "flex h-8 w-full items-center",
431
+ children: /* @__PURE__ */ jsxs(Slider.Track, {
432
+ className: "h-1.5 w-full rounded-full bg-fg-on-brand/30",
433
+ children: [/* @__PURE__ */ jsx(Slider.Indicator, { className: "rounded-full bg-fg-on-brand" }), /* @__PURE__ */ jsx(Slider.Thumb, {
434
+ getAriaLabel: () => "Media volume",
435
+ className: "size-3 rounded-full bg-fg-on-brand shadow-1 outline-none focus-visible:[filter:drop-shadow(0_0_0.25rem_var(--color-line-focus))]"
436
+ })]
437
+ })
438
+ })
439
+ })]
440
+ });
441
+ }
442
+ function MediaPlayerPlaybackSpeed({ className, rates = DEFAULT_PLAYBACK_RATES, ...props }) {
443
+ const dispatch = useMediaDispatch();
444
+ const { onMenuOpenChange, portalContainer } = React.useContext(MediaPlayerContext);
445
+ const playbackRate = useMediaSelector((state) => state.mediaPlaybackRate ?? 1);
446
+ const rateLabel = formatRate(playbackRate);
447
+ return /* @__PURE__ */ jsxs(DropdownMenu, {
448
+ size: "sm",
449
+ onOpenChange: onMenuOpenChange,
450
+ children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, { render: /* @__PURE__ */ jsx(MediaControlButton, {
451
+ "aria-label": `Playback speed ${rateLabel}`,
452
+ "data-slot": "media-player-playback-speed",
453
+ className: cn("w-auto px-3 text-gdt-xs font-bold tabular-nums", className),
454
+ tooltip: "Playback speed",
455
+ ...props,
456
+ children: rateLabel
457
+ }) }), /* @__PURE__ */ jsx(DropdownMenuContent, {
458
+ align: "end",
459
+ className: "min-w-24",
460
+ container: portalContainer ?? void 0,
461
+ side: "top",
462
+ sideOffset: 8,
463
+ children: /* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
464
+ value: String(playbackRate),
465
+ onValueChange: (value) => {
466
+ dispatch({
467
+ type: MediaActionTypes.MEDIA_PLAYBACK_RATE_REQUEST,
468
+ detail: Number(value)
469
+ });
470
+ },
471
+ children: rates.map((rate) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
472
+ closeOnClick: true,
473
+ value: String(rate),
474
+ children: formatRate(rate)
475
+ }, rate))
476
+ })
477
+ })]
478
+ });
479
+ }
480
+ function MediaPlayerCaptions({ className, disabled, onClick, ...props }) {
481
+ const dispatch = useMediaDispatch();
482
+ const subtitlesList = useMediaSelector((state) => state.mediaSubtitlesList);
483
+ const subtitlesShowing = useMediaSelector((state) => state.mediaSubtitlesShowing);
484
+ const hasSubtitles = (subtitlesList?.length ?? 0) > 0;
485
+ const showing = (subtitlesShowing?.length ?? 0) > 0;
486
+ const Icon = showing ? Captions : CaptionsOff;
487
+ return /* @__PURE__ */ jsx(MediaControlButton, {
488
+ "aria-label": showing ? "Disable captions" : "Enable captions",
489
+ "aria-pressed": showing,
490
+ "data-slot": "media-player-captions",
491
+ className,
492
+ disabled: disabled || !hasSubtitles,
493
+ shortcut: "C",
494
+ tooltip: "Captions",
495
+ onClick: (event) => {
496
+ onClick?.(event);
497
+ if (event.defaultPrevented) return;
498
+ dispatch({ type: MediaActionTypes.MEDIA_TOGGLE_SUBTITLES_REQUEST });
499
+ },
500
+ ...props,
501
+ children: /* @__PURE__ */ jsx(Icon, {})
502
+ });
503
+ }
504
+ function MediaPlayerSettings({ className, rates = DEFAULT_PLAYBACK_RATES, ...props }) {
505
+ const dispatch = useMediaDispatch();
506
+ const { onMenuOpenChange, portalContainer } = React.useContext(MediaPlayerContext);
507
+ const playbackRate = useMediaSelector((state) => state.mediaPlaybackRate ?? 1);
508
+ const subtitlesList = useMediaSelector((state) => state.mediaSubtitlesList);
509
+ const subtitlesShowing = useMediaSelector((state) => state.mediaSubtitlesShowing);
510
+ const subtitles = subtitlesList ?? [];
511
+ const showingTrack = subtitlesShowing?.[0];
512
+ const captionsValue = showingTrack ? subtitleTrackId(showingTrack) : "off";
513
+ const captionsLabel = showingTrack ? subtitleTrackLabel(showingTrack) : "Off";
514
+ return /* @__PURE__ */ jsxs(DropdownMenu, {
515
+ size: "sm",
516
+ onOpenChange: onMenuOpenChange,
517
+ children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, { render: /* @__PURE__ */ jsx(MediaControlButton, {
518
+ "aria-label": "Player settings",
519
+ "data-slot": "media-player-settings",
520
+ className,
521
+ tooltip: "Settings",
522
+ ...props,
523
+ children: /* @__PURE__ */ jsx(Settings, {})
524
+ }) }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
525
+ align: "end",
526
+ className: "min-w-48",
527
+ container: portalContainer ?? void 0,
528
+ side: "top",
529
+ sideOffset: 8,
530
+ children: [/* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: ["Speed", /* @__PURE__ */ jsx("span", {
531
+ className: "ml-auto rounded-md bg-surface px-1.5 py-0.5 text-gdt-xs font-bold tabular-nums",
532
+ children: formatRate(playbackRate)
533
+ })] }), /* @__PURE__ */ jsx(DropdownMenuSubContent, {
534
+ container: portalContainer ?? void 0,
535
+ children: /* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
536
+ value: String(playbackRate),
537
+ onValueChange: (value) => {
538
+ dispatch({
539
+ type: MediaActionTypes.MEDIA_PLAYBACK_RATE_REQUEST,
540
+ detail: Number(value)
541
+ });
542
+ },
543
+ children: rates.map((rate) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
544
+ closeOnClick: true,
545
+ value: String(rate),
546
+ children: formatRate(rate)
547
+ }, rate))
548
+ })
549
+ })] }), subtitles.length > 0 ? /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: ["Captions", /* @__PURE__ */ jsx("span", {
550
+ className: "ml-auto rounded-md bg-surface px-1.5 py-0.5 text-gdt-xs font-bold",
551
+ children: captionsLabel
552
+ })] }), /* @__PURE__ */ jsx(DropdownMenuSubContent, {
553
+ container: portalContainer ?? void 0,
554
+ children: /* @__PURE__ */ jsxs(DropdownMenuRadioGroup, {
555
+ value: captionsValue,
556
+ onValueChange: (value) => {
557
+ if (subtitlesShowing?.length) dispatch({
558
+ type: MediaActionTypes.MEDIA_DISABLE_SUBTITLES_REQUEST,
559
+ detail: subtitlesShowing
560
+ });
561
+ const track = subtitles.find((candidate) => subtitleTrackId(candidate) === value);
562
+ if (track) dispatch({
563
+ type: MediaActionTypes.MEDIA_SHOW_SUBTITLES_REQUEST,
564
+ detail: [track]
565
+ });
566
+ },
567
+ children: [/* @__PURE__ */ jsx(DropdownMenuRadioItem, {
568
+ closeOnClick: true,
569
+ value: "off",
570
+ children: "Off"
571
+ }), subtitles.map((track) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
572
+ closeOnClick: true,
573
+ value: subtitleTrackId(track),
574
+ children: subtitleTrackLabel(track)
575
+ }, subtitleTrackId(track)))]
576
+ })
577
+ })] }) : null]
578
+ })]
579
+ });
580
+ }
581
+ function MediaPlayerTime({ className, showDuration = true, ...props }) {
582
+ const currentTime = useMediaSelector((state) => state.mediaCurrentTime ?? 0);
583
+ const duration = useMediaSelector((state) => state.mediaDuration);
584
+ return /* @__PURE__ */ jsx("span", {
585
+ "data-slot": "media-player-time",
586
+ className: cn("inline-flex shrink-0 items-center px-1 text-gdt-sm font-medium tabular-nums text-fg-on-brand/80", className),
587
+ ...props,
588
+ children: showDuration ? `${formatTime(currentTime)} / ${formatTime(duration)}` : formatTime(currentTime)
589
+ });
590
+ }
591
+ function MediaPlayerSeek({ className, disabled, onValueChange, "aria-label": ariaLabel, step = 5, withTime = true, ...props }) {
592
+ const dispatch = useMediaDispatch();
593
+ const { portalContainer } = React.useContext(MediaPlayerContext);
594
+ const controlRef = React.useRef(null);
595
+ const [dragValue, setDragValue] = React.useState(null);
596
+ const [hoverValue, setHoverValue] = React.useState(null);
597
+ const [pendingSeek, setPendingSeek] = React.useState(null);
598
+ const currentTime = useMediaSelector((state) => state.mediaCurrentTime ?? 0);
599
+ const duration = useMediaSelector((state) => state.mediaDuration);
600
+ const seekable = useMediaSelector((state) => state.mediaSeekable);
601
+ const buffered = useMediaSelector((state) => state.mediaBuffered);
602
+ const previewImage = useMediaSelector((state) => state.mediaPreviewImage);
603
+ const previewCoords = useMediaSelector((state) => state.mediaPreviewCoords);
604
+ const chapterCues = useMediaSelector((state) => state.mediaChaptersCues);
605
+ const { min, max } = getSeekBounds(seekable, duration);
606
+ const canSeek = max > min;
607
+ const resolvedDisabled = disabled || !canSeek;
608
+ const seekRafRef = React.useRef(null);
609
+ const latestSeekValueRef = React.useRef(0);
610
+ const displayTime = clamp(dragValue ?? pendingSeek ?? currentTime, min, max);
611
+ const sliderStep = canSeek ? clamp((max - min) / 500, .05, 1) : 1;
612
+ React.useEffect(() => {
613
+ if (pendingSeek !== null && Math.abs(currentTime - pendingSeek) < .5) setPendingSeek(null);
614
+ }, [currentTime, pendingSeek]);
615
+ React.useEffect(() => {
616
+ return () => {
617
+ if (seekRafRef.current !== null) cancelAnimationFrame(seekRafRef.current);
618
+ };
619
+ }, []);
620
+ const queueSeek = (value) => {
621
+ latestSeekValueRef.current = value;
622
+ if (seekRafRef.current !== null) return;
623
+ seekRafRef.current = requestAnimationFrame(() => {
624
+ seekRafRef.current = null;
625
+ dispatch({
626
+ type: MediaActionTypes.MEDIA_SEEK_REQUEST,
627
+ detail: latestSeekValueRef.current
628
+ });
629
+ });
630
+ };
631
+ const previewAt = (value) => {
632
+ dispatch({
633
+ type: MediaActionTypes.MEDIA_PREVIEW_REQUEST,
634
+ detail: clamp(value, min, max)
635
+ });
636
+ };
637
+ const clearPreview = () => {
638
+ setHoverValue(null);
639
+ dispatch({
640
+ type: MediaActionTypes.MEDIA_PREVIEW_REQUEST,
641
+ detail: void 0
642
+ });
643
+ };
644
+ const previewValue = dragValue ?? hoverValue;
645
+ const previewPercent = previewValue !== null && canSeek ? (clamp(previewValue, min, max) - min) / (max - min) * 100 : 0;
646
+ const previewChapter = previewValue !== null ? chapterCues?.find((cue) => previewValue >= cue.startTime && previewValue < cue.endTime) : void 0;
647
+ const previewCoordValues = previewCoords?.map(Number);
648
+ const hasSpriteCoords = previewCoordValues?.length === 4 && (previewCoordValues[2] ?? 0) > 0;
649
+ const previewScale = hasSpriteCoords ? SEEK_PREVIEW_WIDTH / (previewCoordValues?.[2] ?? 1) : 1;
650
+ const previewClampPx = previewImage ? 84 : 32;
651
+ const controlRect = withTime && previewValue !== null ? controlRef.current?.getBoundingClientRect() : void 0;
652
+ const previewLeft = controlRect ? clamp(controlRect.left + previewPercent / 100 * controlRect.width, controlRect.left + previewClampPx, controlRect.right - previewClampPx) : 0;
653
+ const segmentBounds = canSeek ? [
654
+ min,
655
+ ...(chapterCues ?? []).map((cue) => cue.startTime).filter((time) => time > min && time < max).sort((a, b) => a - b),
656
+ max
657
+ ] : [0, 1];
658
+ const segments = segmentBounds.slice(0, -1).map((start, index) => ({
659
+ start,
660
+ end: segmentBounds[index + 1] ?? start
661
+ }));
662
+ return /* @__PURE__ */ jsxs(Slider.Root, {
663
+ "data-slot": "media-player-seek",
664
+ className: cn("relative flex h-8 w-full min-w-0 touch-none items-center", resolvedDisabled && "pointer-events-none opacity-45", className),
665
+ disabled: resolvedDisabled,
666
+ largeStep: step,
667
+ max: canSeek ? max : 100,
668
+ min,
669
+ onValueChange: (value) => {
670
+ const nextValue = Array.isArray(value) ? value[0] ?? 0 : value;
671
+ setDragValue(nextValue);
672
+ previewAt(nextValue);
673
+ queueSeek(nextValue);
674
+ onValueChange?.(nextValue);
675
+ },
676
+ onValueCommitted: (value) => {
677
+ const nextValue = clamp(Array.isArray(value) ? value[0] ?? 0 : value, min, max);
678
+ if (seekRafRef.current !== null) {
679
+ cancelAnimationFrame(seekRafRef.current);
680
+ seekRafRef.current = null;
681
+ }
682
+ dispatch({
683
+ type: MediaActionTypes.MEDIA_SEEK_REQUEST,
684
+ detail: nextValue
685
+ });
686
+ setPendingSeek(nextValue);
687
+ setDragValue(null);
688
+ clearPreview();
689
+ },
690
+ step: sliderStep,
691
+ value: displayTime,
692
+ ...props,
693
+ children: [withTime && previewValue !== null && controlRect && ReactDOM.createPortal(/* @__PURE__ */ jsxs("div", {
694
+ className: "pointer-events-none fixed z-50 flex -translate-x-1/2 -translate-y-full flex-col items-center overflow-hidden rounded-lg bg-surface-overlay text-fg-primary shadow-2",
695
+ "data-slot": "media-player-seek-preview",
696
+ style: {
697
+ left: previewLeft,
698
+ top: controlRect.top - 6
699
+ },
700
+ children: [
701
+ previewImage ? hasSpriteCoords ? /* @__PURE__ */ jsx("span", {
702
+ className: "relative block overflow-hidden bg-line-subtle/50",
703
+ style: {
704
+ width: SEEK_PREVIEW_WIDTH,
705
+ height: (previewCoordValues?.[3] ?? 0) * previewScale
706
+ },
707
+ children: /* @__PURE__ */ jsx("img", {
708
+ src: previewImage,
709
+ alt: "",
710
+ draggable: false,
711
+ className: "absolute top-0 left-0 max-w-none origin-top-left",
712
+ style: { transform: `scale(${previewScale}) translate(${-(previewCoordValues?.[0] ?? 0)}px, ${-(previewCoordValues?.[1] ?? 0)}px)` }
713
+ })
714
+ }) : /* @__PURE__ */ jsx("img", {
715
+ src: previewImage,
716
+ alt: "",
717
+ draggable: false,
718
+ className: "block aspect-video bg-line-subtle/50 object-cover",
719
+ style: { width: SEEK_PREVIEW_WIDTH }
720
+ }) : null,
721
+ previewChapter?.text ? /* @__PURE__ */ jsx("span", {
722
+ className: "max-w-40 truncate px-2.5 pt-1 text-gdt-xs font-bold",
723
+ children: previewChapter.text
724
+ }) : null,
725
+ /* @__PURE__ */ jsx("span", {
726
+ className: cn("px-2.5 py-1 text-gdt-xs font-bold tabular-nums", (previewImage || previewChapter?.text) && "pt-0.5 pb-1.5"),
727
+ children: formatTime(previewValue)
728
+ })
729
+ ]
730
+ }), portalContainer ?? document.body), /* @__PURE__ */ jsxs(Slider.Control, {
731
+ ref: controlRef,
732
+ className: "flex h-8 w-full items-center",
733
+ onPointerLeave: () => {
734
+ if (dragValue === null) clearPreview();
735
+ },
736
+ onPointerMove: (event) => {
737
+ if (resolvedDisabled || dragValue !== null) return;
738
+ const next = getSeekValueFromPointer(event, min, max);
739
+ setHoverValue(next.value);
740
+ previewAt(next.value);
741
+ },
742
+ children: [/* @__PURE__ */ jsx(Slider.Track, {
743
+ className: "relative flex h-2 w-full items-center gap-[3px]",
744
+ children: segments.map(({ start, end }) => {
745
+ const span = end - start || 1;
746
+ const fraction = (value) => clamp((clamp(value, start, end) - start) / span, 0, 1);
747
+ const playedWidth = fraction(displayTime) * 100;
748
+ return /* @__PURE__ */ jsxs("span", {
749
+ "data-slot": "media-player-seek-segment",
750
+ className: "relative h-full overflow-hidden rounded-full bg-line-subtle/65",
751
+ style: { width: `${span / (max - min || 1) * 100}%` },
752
+ children: [canSeek && buffered?.map(([bufferStart, bufferEnd]) => bufferEnd > start && bufferStart < end ? /* @__PURE__ */ jsx("span", {
753
+ "data-slot": "media-player-seek-buffered",
754
+ className: "absolute inset-y-0 bg-fg-on-brand/25",
755
+ style: {
756
+ left: `${fraction(bufferStart) * 100}%`,
757
+ width: `${(fraction(bufferEnd) - fraction(bufferStart)) * 100}%`
758
+ }
759
+ }, `${bufferStart}-${bufferEnd}`) : null), /* @__PURE__ */ jsx("span", {
760
+ "data-slot": "media-player-seek-progress",
761
+ className: cn("absolute inset-y-0 left-0 bg-surface-brand", dragValue === null && "transition-[width] duration-200 ease-linear"),
762
+ style: { width: `${playedWidth}%` }
763
+ })]
764
+ }, start);
765
+ })
766
+ }), /* @__PURE__ */ jsx(Slider.Thumb, {
767
+ getAriaLabel: () => ariaLabel ?? "Media timeline",
768
+ getAriaValueText: (_formatted, value) => `${formatTime(value)} of ${formatTime(max)}`,
769
+ className: cn("size-3.5 -translate-x-1/2 rounded-full bg-surface-overlay shadow-2 ring-2 ring-surface-brand outline-none", "transition-[inset-inline-start,transform] duration-200 ease-linear data-[dragging]:scale-125 data-[dragging]:transition-transform", "focus-visible:[filter:drop-shadow(0_0_0.25rem_var(--color-line-focus))]")
770
+ })]
771
+ })]
772
+ });
773
+ }
774
+ function MediaPlayerLoop({ className, onClick, ...props }) {
775
+ const dispatch = useMediaDispatch();
776
+ const loop = useMediaSelector((state) => state.mediaLoop === true);
777
+ return /* @__PURE__ */ jsx(MediaControlButton, {
778
+ "aria-label": loop ? "Disable loop" : "Enable loop",
779
+ "aria-pressed": loop,
780
+ "data-slot": "media-player-loop",
781
+ className,
782
+ tooltip: loop ? "Disable loop" : "Enable loop",
783
+ onClick: (event) => {
784
+ onClick?.(event);
785
+ if (event.defaultPrevented) return;
786
+ dispatch({
787
+ type: MediaActionTypes.MEDIA_LOOP_REQUEST,
788
+ detail: !loop
789
+ });
790
+ },
791
+ ...props,
792
+ children: /* @__PURE__ */ jsx(Repeat2, {})
793
+ });
794
+ }
795
+ function MediaPlayerPiP({ className, disabled, onClick, ...props }) {
796
+ const dispatch = useMediaDispatch();
797
+ const isPip = useMediaSelector((state) => state.mediaIsPip === true);
798
+ const unavailable = useMediaSelector((state) => !!state.mediaPipUnavailable);
799
+ return /* @__PURE__ */ jsx(MediaControlButton, {
800
+ "aria-label": isPip ? "Exit picture in picture" : "Enter picture in picture",
801
+ "aria-pressed": isPip,
802
+ "data-slot": "media-player-pip",
803
+ className,
804
+ disabled: disabled || unavailable,
805
+ tooltip: isPip ? "Exit picture in picture" : "Picture in picture",
806
+ onClick: (event) => {
807
+ onClick?.(event);
808
+ if (event.defaultPrevented) return;
809
+ dispatch({ type: isPip ? MediaActionTypes.MEDIA_EXIT_PIP_REQUEST : MediaActionTypes.MEDIA_ENTER_PIP_REQUEST });
810
+ },
811
+ ...props,
812
+ children: /* @__PURE__ */ jsx(PictureInPicture2, {})
813
+ });
814
+ }
815
+ function MediaPlayerFullscreen({ className, disabled, onClick, ...props }) {
816
+ const dispatch = useMediaDispatch();
817
+ const isFullscreen = useMediaSelector((state) => state.mediaIsFullscreen === true);
818
+ const unavailable = useMediaSelector((state) => !!state.mediaFullscreenUnavailable);
819
+ const Icon = isFullscreen ? Minimize : Maximize;
820
+ return /* @__PURE__ */ jsx(MediaControlButton, {
821
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
822
+ "aria-pressed": isFullscreen,
823
+ "data-slot": "media-player-fullscreen",
824
+ className,
825
+ disabled: disabled || unavailable,
826
+ shortcut: "F",
827
+ tooltip: isFullscreen ? "Exit fullscreen" : "Fullscreen",
828
+ onClick: (event) => {
829
+ onClick?.(event);
830
+ if (event.defaultPrevented) return;
831
+ dispatch({ type: isFullscreen ? MediaActionTypes.MEDIA_EXIT_FULLSCREEN_REQUEST : MediaActionTypes.MEDIA_ENTER_FULLSCREEN_REQUEST });
832
+ },
833
+ ...props,
834
+ children: /* @__PURE__ */ jsx(Icon, {})
835
+ });
836
+ }
837
+ const MEDIA_ERROR_CONTENT = {
838
+ 1: {
839
+ title: "Playback interrupted",
840
+ description: "Loading the media was interrupted."
841
+ },
842
+ 2: {
843
+ title: "Connection error",
844
+ description: "A network error kept the media from loading."
845
+ },
846
+ 3: {
847
+ title: "Playback error",
848
+ description: "The media could not be decoded."
849
+ },
850
+ 4: {
851
+ title: "Unsupported format",
852
+ description: "The media format is not supported."
853
+ }
854
+ };
855
+ function MediaPlayerError({ children, className, ...props }) {
856
+ const { rootNode } = React.useContext(MediaPlayerContext);
857
+ const mediaError = useMediaSelector((state) => state.mediaError);
858
+ const [sourceFailed, setSourceFailed] = React.useState(false);
859
+ React.useEffect(() => {
860
+ if (!rootNode) return;
861
+ const update = () => {
862
+ const media = rootNode.querySelector("video, audio");
863
+ setSourceFailed(!!media && media.networkState === HTMLMediaElement.NETWORK_NO_SOURCE && media.readyState === HTMLMediaElement.HAVE_NOTHING);
864
+ };
865
+ update();
866
+ rootNode.addEventListener("error", update, true);
867
+ rootNode.addEventListener("loadstart", update, true);
868
+ rootNode.addEventListener("loadedmetadata", update, true);
869
+ return () => {
870
+ rootNode.removeEventListener("error", update, true);
871
+ rootNode.removeEventListener("loadstart", update, true);
872
+ rootNode.removeEventListener("loadedmetadata", update, true);
873
+ };
874
+ }, [rootNode]);
875
+ const error = mediaError ?? (sourceFailed ? { code: 4 } : null);
876
+ if (!error) return null;
877
+ const content = MEDIA_ERROR_CONTENT[error.code ?? 0] ?? {
878
+ title: "Something went wrong",
879
+ description: "The media could not be played."
880
+ };
881
+ return /* @__PURE__ */ jsx("div", {
882
+ "data-slot": "media-player-error",
883
+ role: "alert",
884
+ className: cn("absolute inset-0 z-20 flex flex-col items-center justify-center gap-1 bg-canvas/85 px-6 text-center text-fg-primary backdrop-blur-sm rounded-2xl", className),
885
+ ...props,
886
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
887
+ /* @__PURE__ */ jsx(TriangleAlert, {
888
+ "aria-hidden": "true",
889
+ className: "mb-2 size-9 text-fg-danger"
890
+ }),
891
+ /* @__PURE__ */ jsx("p", {
892
+ className: "text-gdt-lg font-bold",
893
+ children: content.title
894
+ }),
895
+ /* @__PURE__ */ jsx("p", {
896
+ className: "text-gdt-sm text-fg-secondary",
897
+ children: content.description
898
+ }),
899
+ /* @__PURE__ */ jsxs("div", {
900
+ className: "mt-4 flex items-center gap-2",
901
+ children: [/* @__PURE__ */ jsxs(MediaControlButton, {
902
+ className: "w-auto gap-1.5 px-4 text-gdt-xs font-bold [&_svg]:size-3.5",
903
+ onClick: () => {
904
+ rootNode?.querySelector("video, audio")?.load();
905
+ },
906
+ children: [/* @__PURE__ */ jsx(RefreshCcw, {}), "Try again"]
907
+ }), /* @__PURE__ */ jsxs(MediaControlButton, {
908
+ className: "w-auto gap-1.5 px-4 text-gdt-xs font-bold [&_svg]:size-3.5",
909
+ onClick: () => {
910
+ window.location.reload();
911
+ },
912
+ children: [/* @__PURE__ */ jsx(RotateCcw, {}), "Reload page"]
913
+ })]
914
+ })
915
+ ] })
916
+ });
917
+ }
918
+ //#endregion
919
+ export { MediaPlayer, MediaPlayerAudio, MediaPlayerCaptions, MediaPlayerControls, MediaPlayerControlsOverlay, MediaPlayerError, MediaPlayerFullscreen, MediaPlayerLoop, MediaPlayerPiP, MediaPlayerPlay, MediaPlayerPlaybackSpeed, MediaPlayerSeek, MediaPlayerSeekBackward, MediaPlayerSeekForward, MediaPlayerSettings, MediaPlayerTime, MediaPlayerVideo, MediaPlayerVolume };