@signal9/era-ui 1.1.2

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 (656) hide show
  1. package/dist/dev/audit/audits/_helpers.d.ts +5 -0
  2. package/dist/dev/audit/audits/_helpers.js +11 -0
  3. package/dist/dev/audit/audits/concentric-inset.d.ts +2 -0
  4. package/dist/dev/audit/audits/concentric-inset.js +33 -0
  5. package/dist/dev/audit/audits/index.d.ts +4 -0
  6. package/dist/dev/audit/audits/index.js +9 -0
  7. package/dist/dev/audit/audits/uniform-sibling-gaps.d.ts +2 -0
  8. package/dist/dev/audit/audits/uniform-sibling-gaps.js +29 -0
  9. package/dist/dev/audit/audits/wall-matches-gap.d.ts +2 -0
  10. package/dist/dev/audit/audits/wall-matches-gap.js +30 -0
  11. package/dist/dev/audit/index.d.ts +4 -0
  12. package/dist/dev/audit/index.js +3 -0
  13. package/dist/dev/audit/registry.d.ts +13 -0
  14. package/dist/dev/audit/registry.js +50 -0
  15. package/dist/dev/audit/runner.d.ts +3 -0
  16. package/dist/dev/audit/runner.js +27 -0
  17. package/dist/dev/audit/types.d.ts +28 -0
  18. package/dist/dev/audit/types.js +7 -0
  19. package/dist/dev/audit-overlay.svelte +125 -0
  20. package/dist/dev/audit-overlay.svelte.d.ts +9 -0
  21. package/dist/dev/index.d.ts +2 -0
  22. package/dist/dev/index.js +2 -0
  23. package/dist/docs/badge.md +18 -0
  24. package/dist/docs/bits-ui-audit.md +58 -0
  25. package/dist/docs/button.md +18 -0
  26. package/dist/docs/chip.md +20 -0
  27. package/dist/docs/component-docs-core.d.ts +8 -0
  28. package/dist/docs/component-docs-core.js +72 -0
  29. package/dist/docs/component-docs.d.ts +2 -0
  30. package/dist/docs/component-docs.js +25 -0
  31. package/dist/docs/component-source-scanners.d.ts +18 -0
  32. package/dist/docs/component-source-scanners.js +46 -0
  33. package/dist/docs/extract-props.d.ts +13 -0
  34. package/dist/docs/extract-props.js +324 -0
  35. package/dist/docs/generated-docs.d.ts +4 -0
  36. package/dist/docs/generated-docs.js +23 -0
  37. package/dist/docs/index.d.ts +5 -0
  38. package/dist/docs/index.js +62 -0
  39. package/dist/docs/llms-txt.d.ts +2 -0
  40. package/dist/docs/llms-txt.js +84 -0
  41. package/dist/docs/quickstart.md +43 -0
  42. package/dist/docs/separator.md +20 -0
  43. package/dist/docs/table.md +35 -0
  44. package/dist/era-ui.css +2 -0
  45. package/dist/generated-docs/accordion.md +57 -0
  46. package/dist/generated-docs/alert-dialog.md +81 -0
  47. package/dist/generated-docs/aspect-ratio.md +17 -0
  48. package/dist/generated-docs/avatar.md +39 -0
  49. package/dist/generated-docs/badge.md +38 -0
  50. package/dist/generated-docs/bar.md +19 -0
  51. package/dist/generated-docs/button-group.md +19 -0
  52. package/dist/generated-docs/button.md +46 -0
  53. package/dist/generated-docs/calendar.md +126 -0
  54. package/dist/generated-docs/card.md +26 -0
  55. package/dist/generated-docs/checkbox.md +42 -0
  56. package/dist/generated-docs/chip.md +40 -0
  57. package/dist/generated-docs/code-block.md +20 -0
  58. package/dist/generated-docs/collapsible.md +30 -0
  59. package/dist/generated-docs/combobox.md +74 -0
  60. package/dist/generated-docs/command.md +123 -0
  61. package/dist/generated-docs/context-menu.md +94 -0
  62. package/dist/generated-docs/date-field.md +49 -0
  63. package/dist/generated-docs/date-picker.md +180 -0
  64. package/dist/generated-docs/date-range-field.md +50 -0
  65. package/dist/generated-docs/date-range-picker.md +180 -0
  66. package/dist/generated-docs/dialog.md +70 -0
  67. package/dist/generated-docs/draggable.md +3 -0
  68. package/dist/generated-docs/dropdown-menu.md +94 -0
  69. package/dist/generated-docs/file-upload.md +18 -0
  70. package/dist/generated-docs/input.md +18 -0
  71. package/dist/generated-docs/kv.md +20 -0
  72. package/dist/generated-docs/label.md +17 -0
  73. package/dist/generated-docs/link-preview.md +30 -0
  74. package/dist/generated-docs/llms-full.txt +3104 -0
  75. package/dist/generated-docs/llms.txt +34 -0
  76. package/dist/generated-docs/manifest.json +812 -0
  77. package/dist/generated-docs/menu.md +11 -0
  78. package/dist/generated-docs/menubar.md +103 -0
  79. package/dist/generated-docs/meter.md +17 -0
  80. package/dist/generated-docs/mode.md +24 -0
  81. package/dist/generated-docs/navigation-menu.md +11 -0
  82. package/dist/generated-docs/pagination.md +55 -0
  83. package/dist/generated-docs/pane.md +30 -0
  84. package/dist/generated-docs/pin-input.md +31 -0
  85. package/dist/generated-docs/popover.md +43 -0
  86. package/dist/generated-docs/progress.md +17 -0
  87. package/dist/generated-docs/quickstart.md +47 -0
  88. package/dist/generated-docs/radio-group.md +31 -0
  89. package/dist/generated-docs/range-calendar.md +11 -0
  90. package/dist/generated-docs/rating-group.md +31 -0
  91. package/dist/generated-docs/scroll-area.md +57 -0
  92. package/dist/generated-docs/select.md +49 -0
  93. package/dist/generated-docs/separator.md +39 -0
  94. package/dist/generated-docs/sheet.md +11 -0
  95. package/dist/generated-docs/skeleton.md +18 -0
  96. package/dist/generated-docs/slider.md +71 -0
  97. package/dist/generated-docs/surfaces.md +3 -0
  98. package/dist/generated-docs/switch.md +18 -0
  99. package/dist/generated-docs/table.md +113 -0
  100. package/dist/generated-docs/tabs.md +48 -0
  101. package/dist/generated-docs/time-field.md +49 -0
  102. package/dist/generated-docs/time-range-field.md +50 -0
  103. package/dist/generated-docs/toggle-group.md +30 -0
  104. package/dist/generated-docs/toggle.md +20 -0
  105. package/dist/generated-docs/toolbar.md +11 -0
  106. package/dist/generated-docs/tooltip.md +30 -0
  107. package/dist/generated-docs/video-player.md +73 -0
  108. package/dist/index.d.ts +2 -0
  109. package/dist/index.js +2 -0
  110. package/dist/styles/density.css +274 -0
  111. package/dist/styles/index.css +77 -0
  112. package/dist/styles/motion.css +56 -0
  113. package/dist/styles/surfaces/base.css +103 -0
  114. package/dist/styles/surfaces/bevel.css +71 -0
  115. package/dist/styles/surfaces/cartoon.css +27 -0
  116. package/dist/styles/surfaces/glass.css +127 -0
  117. package/dist/styles/themes.css +351 -0
  118. package/dist/styles/typography.css +28 -0
  119. package/dist/ui/accordion/accordion-content.svelte +15 -0
  120. package/dist/ui/accordion/accordion-content.svelte.d.ts +4 -0
  121. package/dist/ui/accordion/accordion-header.svelte +8 -0
  122. package/dist/ui/accordion/accordion-header.svelte.d.ts +4 -0
  123. package/dist/ui/accordion/accordion-item.svelte +12 -0
  124. package/dist/ui/accordion/accordion-item.svelte.d.ts +4 -0
  125. package/dist/ui/accordion/accordion-trigger.svelte +24 -0
  126. package/dist/ui/accordion/accordion-trigger.svelte.d.ts +5 -0
  127. package/dist/ui/accordion/accordion.svelte +8 -0
  128. package/dist/ui/accordion/accordion.svelte.d.ts +4 -0
  129. package/dist/ui/accordion/index.d.ts +5 -0
  130. package/dist/ui/accordion/index.js +5 -0
  131. package/dist/ui/alert-dialog/alert-dialog-action.svelte +41 -0
  132. package/dist/ui/alert-dialog/alert-dialog-action.svelte.d.ts +9 -0
  133. package/dist/ui/alert-dialog/alert-dialog-cancel.svelte +39 -0
  134. package/dist/ui/alert-dialog/alert-dialog-cancel.svelte.d.ts +9 -0
  135. package/dist/ui/alert-dialog/alert-dialog-content.svelte +25 -0
  136. package/dist/ui/alert-dialog/alert-dialog-content.svelte.d.ts +4 -0
  137. package/dist/ui/alert-dialog/alert-dialog-description.svelte +16 -0
  138. package/dist/ui/alert-dialog/alert-dialog-description.svelte.d.ts +4 -0
  139. package/dist/ui/alert-dialog/alert-dialog-overlay.svelte +16 -0
  140. package/dist/ui/alert-dialog/alert-dialog-overlay.svelte.d.ts +4 -0
  141. package/dist/ui/alert-dialog/alert-dialog-title.svelte +12 -0
  142. package/dist/ui/alert-dialog/alert-dialog-title.svelte.d.ts +4 -0
  143. package/dist/ui/alert-dialog/alert-dialog-trigger.svelte +28 -0
  144. package/dist/ui/alert-dialog/alert-dialog-trigger.svelte.d.ts +9 -0
  145. package/dist/ui/alert-dialog/index.d.ts +10 -0
  146. package/dist/ui/alert-dialog/index.js +10 -0
  147. package/dist/ui/aspect-ratio/aspect-ratio.svelte +19 -0
  148. package/dist/ui/aspect-ratio/aspect-ratio.svelte.d.ts +5 -0
  149. package/dist/ui/aspect-ratio/index.d.ts +1 -0
  150. package/dist/ui/aspect-ratio/index.js +1 -0
  151. package/dist/ui/avatar/avatar-fallback.svelte +22 -0
  152. package/dist/ui/avatar/avatar-fallback.svelte.d.ts +5 -0
  153. package/dist/ui/avatar/avatar-image.svelte +8 -0
  154. package/dist/ui/avatar/avatar-image.svelte.d.ts +4 -0
  155. package/dist/ui/avatar/avatar-root.svelte +22 -0
  156. package/dist/ui/avatar/avatar-root.svelte.d.ts +5 -0
  157. package/dist/ui/avatar/index.d.ts +3 -0
  158. package/dist/ui/avatar/index.js +3 -0
  159. package/dist/ui/badge/badge.svelte +40 -0
  160. package/dist/ui/badge/badge.svelte.d.ts +32 -0
  161. package/dist/ui/badge/index.d.ts +1 -0
  162. package/dist/ui/badge/index.js +1 -0
  163. package/dist/ui/bar/bar.svelte +45 -0
  164. package/dist/ui/bar/bar.svelte.d.ts +30 -0
  165. package/dist/ui/bar/index.d.ts +1 -0
  166. package/dist/ui/bar/index.js +1 -0
  167. package/dist/ui/button/button.svelte +53 -0
  168. package/dist/ui/button/button.svelte.d.ts +21 -0
  169. package/dist/ui/button/index.d.ts +12 -0
  170. package/dist/ui/button/index.js +3 -0
  171. package/dist/ui/button/variants.d.ts +70 -0
  172. package/dist/ui/button/variants.js +97 -0
  173. package/dist/ui/button-group/button-group.svelte +31 -0
  174. package/dist/ui/button-group/button-group.svelte.d.ts +10 -0
  175. package/dist/ui/button-group/index.d.ts +1 -0
  176. package/dist/ui/button-group/index.js +1 -0
  177. package/dist/ui/calendar/calendar-cell.svelte +8 -0
  178. package/dist/ui/calendar/calendar-cell.svelte.d.ts +4 -0
  179. package/dist/ui/calendar/calendar-day.svelte +15 -0
  180. package/dist/ui/calendar/calendar-day.svelte.d.ts +4 -0
  181. package/dist/ui/calendar/calendar-grid-body.svelte +7 -0
  182. package/dist/ui/calendar/calendar-grid-body.svelte.d.ts +4 -0
  183. package/dist/ui/calendar/calendar-grid-head.svelte +7 -0
  184. package/dist/ui/calendar/calendar-grid-head.svelte.d.ts +4 -0
  185. package/dist/ui/calendar/calendar-grid-row.svelte +8 -0
  186. package/dist/ui/calendar/calendar-grid-row.svelte.d.ts +4 -0
  187. package/dist/ui/calendar/calendar-grid.svelte +8 -0
  188. package/dist/ui/calendar/calendar-grid.svelte.d.ts +4 -0
  189. package/dist/ui/calendar/calendar-head-cell.svelte +15 -0
  190. package/dist/ui/calendar/calendar-head-cell.svelte.d.ts +4 -0
  191. package/dist/ui/calendar/calendar-header.svelte +12 -0
  192. package/dist/ui/calendar/calendar-header.svelte.d.ts +4 -0
  193. package/dist/ui/calendar/calendar-heading.svelte +8 -0
  194. package/dist/ui/calendar/calendar-heading.svelte.d.ts +4 -0
  195. package/dist/ui/calendar/calendar-next-button.svelte +30 -0
  196. package/dist/ui/calendar/calendar-next-button.svelte.d.ts +9 -0
  197. package/dist/ui/calendar/calendar-prev-button.svelte +30 -0
  198. package/dist/ui/calendar/calendar-prev-button.svelte.d.ts +9 -0
  199. package/dist/ui/calendar/calendar-root.svelte +30 -0
  200. package/dist/ui/calendar/calendar-root.svelte.d.ts +4 -0
  201. package/dist/ui/calendar/index.d.ts +12 -0
  202. package/dist/ui/calendar/index.js +12 -0
  203. package/dist/ui/card/card.svelte +159 -0
  204. package/dist/ui/card/card.svelte.d.ts +63 -0
  205. package/dist/ui/card/index.d.ts +1 -0
  206. package/dist/ui/card/index.js +1 -0
  207. package/dist/ui/checkbox/checkbox-group-label.svelte +12 -0
  208. package/dist/ui/checkbox/checkbox-group-label.svelte.d.ts +4 -0
  209. package/dist/ui/checkbox/checkbox-group.svelte +18 -0
  210. package/dist/ui/checkbox/checkbox-group.svelte.d.ts +4 -0
  211. package/dist/ui/checkbox/checkbox.svelte +33 -0
  212. package/dist/ui/checkbox/checkbox.svelte.d.ts +4 -0
  213. package/dist/ui/checkbox/index.d.ts +3 -0
  214. package/dist/ui/checkbox/index.js +3 -0
  215. package/dist/ui/chip/chip.svelte +66 -0
  216. package/dist/ui/chip/chip.svelte.d.ts +26 -0
  217. package/dist/ui/chip/index.d.ts +1 -0
  218. package/dist/ui/chip/index.js +1 -0
  219. package/dist/ui/code-block/code-block.svelte +58 -0
  220. package/dist/ui/code-block/code-block.svelte.d.ts +11 -0
  221. package/dist/ui/code-block/index.d.ts +1 -0
  222. package/dist/ui/code-block/index.js +1 -0
  223. package/dist/ui/collapsible/collapsible-content.svelte +15 -0
  224. package/dist/ui/collapsible/collapsible-content.svelte.d.ts +5 -0
  225. package/dist/ui/collapsible/collapsible-trigger.svelte +22 -0
  226. package/dist/ui/collapsible/collapsible-trigger.svelte.d.ts +5 -0
  227. package/dist/ui/collapsible/index.d.ts +4 -0
  228. package/dist/ui/collapsible/index.js +4 -0
  229. package/dist/ui/combobox/combobox-content.svelte +25 -0
  230. package/dist/ui/combobox/combobox-content.svelte.d.ts +5 -0
  231. package/dist/ui/combobox/combobox-empty.svelte +22 -0
  232. package/dist/ui/combobox/combobox-empty.svelte.d.ts +8 -0
  233. package/dist/ui/combobox/combobox-group-heading.svelte +19 -0
  234. package/dist/ui/combobox/combobox-group-heading.svelte.d.ts +5 -0
  235. package/dist/ui/combobox/combobox-group.svelte +8 -0
  236. package/dist/ui/combobox/combobox-group.svelte.d.ts +4 -0
  237. package/dist/ui/combobox/combobox-input.svelte +15 -0
  238. package/dist/ui/combobox/combobox-input.svelte.d.ts +4 -0
  239. package/dist/ui/combobox/combobox-item.svelte +17 -0
  240. package/dist/ui/combobox/combobox-item.svelte.d.ts +4 -0
  241. package/dist/ui/combobox/combobox-trigger.svelte +27 -0
  242. package/dist/ui/combobox/combobox-trigger.svelte.d.ts +5 -0
  243. package/dist/ui/combobox/index.d.ts +10 -0
  244. package/dist/ui/combobox/index.js +10 -0
  245. package/dist/ui/command/command-empty.svelte +15 -0
  246. package/dist/ui/command/command-empty.svelte.d.ts +4 -0
  247. package/dist/ui/command/command-group-heading.svelte +16 -0
  248. package/dist/ui/command/command-group-heading.svelte.d.ts +4 -0
  249. package/dist/ui/command/command-group-items.svelte +12 -0
  250. package/dist/ui/command/command-group-items.svelte.d.ts +4 -0
  251. package/dist/ui/command/command-group.svelte +7 -0
  252. package/dist/ui/command/command-group.svelte.d.ts +4 -0
  253. package/dist/ui/command/command-input.svelte +45 -0
  254. package/dist/ui/command/command-input.svelte.d.ts +10 -0
  255. package/dist/ui/command/command-item.svelte +15 -0
  256. package/dist/ui/command/command-item.svelte.d.ts +4 -0
  257. package/dist/ui/command/command-link-item.svelte +15 -0
  258. package/dist/ui/command/command-link-item.svelte.d.ts +4 -0
  259. package/dist/ui/command/command-list.svelte +12 -0
  260. package/dist/ui/command/command-list.svelte.d.ts +4 -0
  261. package/dist/ui/command/command-loading.svelte +15 -0
  262. package/dist/ui/command/command-loading.svelte.d.ts +4 -0
  263. package/dist/ui/command/command-root.svelte +21 -0
  264. package/dist/ui/command/command-root.svelte.d.ts +4 -0
  265. package/dist/ui/command/command-separator.svelte +12 -0
  266. package/dist/ui/command/command-separator.svelte.d.ts +4 -0
  267. package/dist/ui/command/command-viewport.svelte +8 -0
  268. package/dist/ui/command/command-viewport.svelte.d.ts +4 -0
  269. package/dist/ui/command/index.d.ts +12 -0
  270. package/dist/ui/command/index.js +12 -0
  271. package/dist/ui/context-menu/context-menu-checkbox-item.svelte +21 -0
  272. package/dist/ui/context-menu/context-menu-checkbox-item.svelte.d.ts +4 -0
  273. package/dist/ui/context-menu/context-menu-content.svelte +27 -0
  274. package/dist/ui/context-menu/context-menu-content.svelte.d.ts +4 -0
  275. package/dist/ui/context-menu/context-menu-group-heading.svelte +16 -0
  276. package/dist/ui/context-menu/context-menu-group-heading.svelte.d.ts +4 -0
  277. package/dist/ui/context-menu/context-menu-item.svelte +22 -0
  278. package/dist/ui/context-menu/context-menu-item.svelte.d.ts +5 -0
  279. package/dist/ui/context-menu/context-menu-radio-item.svelte +19 -0
  280. package/dist/ui/context-menu/context-menu-radio-item.svelte.d.ts +4 -0
  281. package/dist/ui/context-menu/context-menu-separator.svelte +16 -0
  282. package/dist/ui/context-menu/context-menu-separator.svelte.d.ts +4 -0
  283. package/dist/ui/context-menu/context-menu-sub-content.svelte +26 -0
  284. package/dist/ui/context-menu/context-menu-sub-content.svelte.d.ts +5 -0
  285. package/dist/ui/context-menu/context-menu-sub-trigger.svelte +24 -0
  286. package/dist/ui/context-menu/context-menu-sub-trigger.svelte.d.ts +5 -0
  287. package/dist/ui/context-menu/context-menu-trigger.svelte +12 -0
  288. package/dist/ui/context-menu/context-menu-trigger.svelte.d.ts +4 -0
  289. package/dist/ui/context-menu/index.d.ts +14 -0
  290. package/dist/ui/context-menu/index.js +14 -0
  291. package/dist/ui/date-field/date-field-input.svelte +15 -0
  292. package/dist/ui/date-field/date-field-input.svelte.d.ts +4 -0
  293. package/dist/ui/date-field/date-field-label.svelte +8 -0
  294. package/dist/ui/date-field/date-field-label.svelte.d.ts +4 -0
  295. package/dist/ui/date-field/date-field-root.svelte +11 -0
  296. package/dist/ui/date-field/date-field-root.svelte.d.ts +3 -0
  297. package/dist/ui/date-field/date-field-segment.svelte +15 -0
  298. package/dist/ui/date-field/date-field-segment.svelte.d.ts +4 -0
  299. package/dist/ui/date-field/index.d.ts +4 -0
  300. package/dist/ui/date-field/index.js +4 -0
  301. package/dist/ui/date-picker/date-picker-calendar.svelte +16 -0
  302. package/dist/ui/date-picker/date-picker-calendar.svelte.d.ts +4 -0
  303. package/dist/ui/date-picker/date-picker-cell.svelte +8 -0
  304. package/dist/ui/date-picker/date-picker-cell.svelte.d.ts +4 -0
  305. package/dist/ui/date-picker/date-picker-content.svelte +25 -0
  306. package/dist/ui/date-picker/date-picker-content.svelte.d.ts +5 -0
  307. package/dist/ui/date-picker/date-picker-day.svelte +15 -0
  308. package/dist/ui/date-picker/date-picker-day.svelte.d.ts +4 -0
  309. package/dist/ui/date-picker/date-picker-grid-body.svelte +11 -0
  310. package/dist/ui/date-picker/date-picker-grid-body.svelte.d.ts +4 -0
  311. package/dist/ui/date-picker/date-picker-grid-head.svelte +11 -0
  312. package/dist/ui/date-picker/date-picker-grid-head.svelte.d.ts +4 -0
  313. package/dist/ui/date-picker/date-picker-grid-row.svelte +8 -0
  314. package/dist/ui/date-picker/date-picker-grid-row.svelte.d.ts +4 -0
  315. package/dist/ui/date-picker/date-picker-grid.svelte +8 -0
  316. package/dist/ui/date-picker/date-picker-grid.svelte.d.ts +4 -0
  317. package/dist/ui/date-picker/date-picker-head-cell.svelte +19 -0
  318. package/dist/ui/date-picker/date-picker-head-cell.svelte.d.ts +4 -0
  319. package/dist/ui/date-picker/date-picker-header.svelte +12 -0
  320. package/dist/ui/date-picker/date-picker-header.svelte.d.ts +4 -0
  321. package/dist/ui/date-picker/date-picker-heading.svelte +12 -0
  322. package/dist/ui/date-picker/date-picker-heading.svelte.d.ts +4 -0
  323. package/dist/ui/date-picker/date-picker-input.svelte +15 -0
  324. package/dist/ui/date-picker/date-picker-input.svelte.d.ts +4 -0
  325. package/dist/ui/date-picker/date-picker-label.svelte +8 -0
  326. package/dist/ui/date-picker/date-picker-label.svelte.d.ts +4 -0
  327. package/dist/ui/date-picker/date-picker-next-button.svelte +30 -0
  328. package/dist/ui/date-picker/date-picker-next-button.svelte.d.ts +9 -0
  329. package/dist/ui/date-picker/date-picker-prev-button.svelte +30 -0
  330. package/dist/ui/date-picker/date-picker-prev-button.svelte.d.ts +9 -0
  331. package/dist/ui/date-picker/date-picker-root.svelte +19 -0
  332. package/dist/ui/date-picker/date-picker-root.svelte.d.ts +3 -0
  333. package/dist/ui/date-picker/date-picker-segment.svelte +15 -0
  334. package/dist/ui/date-picker/date-picker-segment.svelte.d.ts +4 -0
  335. package/dist/ui/date-picker/date-picker-trigger.svelte +27 -0
  336. package/dist/ui/date-picker/date-picker-trigger.svelte.d.ts +5 -0
  337. package/dist/ui/date-picker/index.d.ts +18 -0
  338. package/dist/ui/date-picker/index.js +18 -0
  339. package/dist/ui/date-range-field/date-range-field-input.svelte +19 -0
  340. package/dist/ui/date-range-field/date-range-field-input.svelte.d.ts +4 -0
  341. package/dist/ui/date-range-field/date-range-field-label.svelte +12 -0
  342. package/dist/ui/date-range-field/date-range-field-label.svelte.d.ts +4 -0
  343. package/dist/ui/date-range-field/date-range-field-root.svelte +13 -0
  344. package/dist/ui/date-range-field/date-range-field-root.svelte.d.ts +4 -0
  345. package/dist/ui/date-range-field/date-range-field-segment.svelte +19 -0
  346. package/dist/ui/date-range-field/date-range-field-segment.svelte.d.ts +3 -0
  347. package/dist/ui/date-range-field/index.d.ts +4 -0
  348. package/dist/ui/date-range-field/index.js +4 -0
  349. package/dist/ui/date-range-picker/date-range-picker-calendar.svelte +16 -0
  350. package/dist/ui/date-range-picker/date-range-picker-calendar.svelte.d.ts +4 -0
  351. package/dist/ui/date-range-picker/date-range-picker-cell.svelte +12 -0
  352. package/dist/ui/date-range-picker/date-range-picker-cell.svelte.d.ts +4 -0
  353. package/dist/ui/date-range-picker/date-range-picker-content.svelte +25 -0
  354. package/dist/ui/date-range-picker/date-range-picker-content.svelte.d.ts +5 -0
  355. package/dist/ui/date-range-picker/date-range-picker-day.svelte +19 -0
  356. package/dist/ui/date-range-picker/date-range-picker-day.svelte.d.ts +4 -0
  357. package/dist/ui/date-range-picker/date-range-picker-grid-body.svelte +12 -0
  358. package/dist/ui/date-range-picker/date-range-picker-grid-body.svelte.d.ts +4 -0
  359. package/dist/ui/date-range-picker/date-range-picker-grid-head.svelte +12 -0
  360. package/dist/ui/date-range-picker/date-range-picker-grid-head.svelte.d.ts +4 -0
  361. package/dist/ui/date-range-picker/date-range-picker-grid-row.svelte +12 -0
  362. package/dist/ui/date-range-picker/date-range-picker-grid-row.svelte.d.ts +4 -0
  363. package/dist/ui/date-range-picker/date-range-picker-grid.svelte +16 -0
  364. package/dist/ui/date-range-picker/date-range-picker-grid.svelte.d.ts +4 -0
  365. package/dist/ui/date-range-picker/date-range-picker-head-cell.svelte +19 -0
  366. package/dist/ui/date-range-picker/date-range-picker-head-cell.svelte.d.ts +4 -0
  367. package/dist/ui/date-range-picker/date-range-picker-header.svelte +16 -0
  368. package/dist/ui/date-range-picker/date-range-picker-header.svelte.d.ts +4 -0
  369. package/dist/ui/date-range-picker/date-range-picker-heading.svelte +16 -0
  370. package/dist/ui/date-range-picker/date-range-picker-heading.svelte.d.ts +4 -0
  371. package/dist/ui/date-range-picker/date-range-picker-input.svelte +19 -0
  372. package/dist/ui/date-range-picker/date-range-picker-input.svelte.d.ts +4 -0
  373. package/dist/ui/date-range-picker/date-range-picker-label.svelte +12 -0
  374. package/dist/ui/date-range-picker/date-range-picker-label.svelte.d.ts +4 -0
  375. package/dist/ui/date-range-picker/date-range-picker-next-button.svelte +30 -0
  376. package/dist/ui/date-range-picker/date-range-picker-next-button.svelte.d.ts +9 -0
  377. package/dist/ui/date-range-picker/date-range-picker-prev-button.svelte +30 -0
  378. package/dist/ui/date-range-picker/date-range-picker-prev-button.svelte.d.ts +9 -0
  379. package/dist/ui/date-range-picker/date-range-picker-root.svelte +19 -0
  380. package/dist/ui/date-range-picker/date-range-picker-root.svelte.d.ts +4 -0
  381. package/dist/ui/date-range-picker/date-range-picker-segment.svelte +19 -0
  382. package/dist/ui/date-range-picker/date-range-picker-segment.svelte.d.ts +3 -0
  383. package/dist/ui/date-range-picker/date-range-picker-trigger.svelte +27 -0
  384. package/dist/ui/date-range-picker/date-range-picker-trigger.svelte.d.ts +5 -0
  385. package/dist/ui/date-range-picker/index.d.ts +18 -0
  386. package/dist/ui/date-range-picker/index.js +18 -0
  387. package/dist/ui/dialog/dialog-close.svelte +39 -0
  388. package/dist/ui/dialog/dialog-close.svelte.d.ts +9 -0
  389. package/dist/ui/dialog/dialog-content.svelte +25 -0
  390. package/dist/ui/dialog/dialog-content.svelte.d.ts +5 -0
  391. package/dist/ui/dialog/dialog-description.svelte +12 -0
  392. package/dist/ui/dialog/dialog-description.svelte.d.ts +4 -0
  393. package/dist/ui/dialog/dialog-overlay.svelte +12 -0
  394. package/dist/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
  395. package/dist/ui/dialog/dialog-title.svelte +8 -0
  396. package/dist/ui/dialog/dialog-title.svelte.d.ts +4 -0
  397. package/dist/ui/dialog/dialog-trigger.svelte +28 -0
  398. package/dist/ui/dialog/dialog-trigger.svelte.d.ts +9 -0
  399. package/dist/ui/dialog/index.d.ts +9 -0
  400. package/dist/ui/dialog/index.js +9 -0
  401. package/dist/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +21 -0
  402. package/dist/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +4 -0
  403. package/dist/ui/dropdown-menu/dropdown-menu-content.svelte +27 -0
  404. package/dist/ui/dropdown-menu/dropdown-menu-content.svelte.d.ts +5 -0
  405. package/dist/ui/dropdown-menu/dropdown-menu-group-heading.svelte +16 -0
  406. package/dist/ui/dropdown-menu/dropdown-menu-group-heading.svelte.d.ts +4 -0
  407. package/dist/ui/dropdown-menu/dropdown-menu-item.svelte +22 -0
  408. package/dist/ui/dropdown-menu/dropdown-menu-item.svelte.d.ts +5 -0
  409. package/dist/ui/dropdown-menu/dropdown-menu-radio-item.svelte +19 -0
  410. package/dist/ui/dropdown-menu/dropdown-menu-radio-item.svelte.d.ts +4 -0
  411. package/dist/ui/dropdown-menu/dropdown-menu-separator.svelte +16 -0
  412. package/dist/ui/dropdown-menu/dropdown-menu-separator.svelte.d.ts +4 -0
  413. package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte +26 -0
  414. package/dist/ui/dropdown-menu/dropdown-menu-sub-content.svelte.d.ts +5 -0
  415. package/dist/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +24 -0
  416. package/dist/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte.d.ts +5 -0
  417. package/dist/ui/dropdown-menu/dropdown-menu-trigger.svelte +24 -0
  418. package/dist/ui/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +5 -0
  419. package/dist/ui/dropdown-menu/index.d.ts +14 -0
  420. package/dist/ui/dropdown-menu/index.js +14 -0
  421. package/dist/ui/file-upload/file-upload.svelte +98 -0
  422. package/dist/ui/file-upload/file-upload.svelte.d.ts +8 -0
  423. package/dist/ui/file-upload/index.d.ts +1 -0
  424. package/dist/ui/file-upload/index.js +1 -0
  425. package/dist/ui/index.d.ts +58 -0
  426. package/dist/ui/index.js +62 -0
  427. package/dist/ui/input/index.d.ts +2 -0
  428. package/dist/ui/input/index.js +2 -0
  429. package/dist/ui/input/input.svelte +23 -0
  430. package/dist/ui/input/input.svelte.d.ts +7 -0
  431. package/dist/ui/input/textarea.svelte +23 -0
  432. package/dist/ui/input/textarea.svelte.d.ts +7 -0
  433. package/dist/ui/kv/index.d.ts +1 -0
  434. package/dist/ui/kv/index.js +1 -0
  435. package/dist/ui/kv/kv.svelte +33 -0
  436. package/dist/ui/kv/kv.svelte.d.ts +10 -0
  437. package/dist/ui/label/index.d.ts +1 -0
  438. package/dist/ui/label/index.js +1 -0
  439. package/dist/ui/label/label.svelte +15 -0
  440. package/dist/ui/label/label.svelte.d.ts +5 -0
  441. package/dist/ui/link-preview/index.d.ts +7 -0
  442. package/dist/ui/link-preview/index.js +7 -0
  443. package/dist/ui/link-preview/link-preview-content.svelte +26 -0
  444. package/dist/ui/link-preview/link-preview-content.svelte.d.ts +5 -0
  445. package/dist/ui/link-preview/link-preview-trigger.svelte +19 -0
  446. package/dist/ui/link-preview/link-preview-trigger.svelte.d.ts +5 -0
  447. package/dist/ui/menu/index.d.ts +1 -0
  448. package/dist/ui/menu/index.js +3 -0
  449. package/dist/ui/menubar/index.d.ts +16 -0
  450. package/dist/ui/menubar/index.js +16 -0
  451. package/dist/ui/menubar/menubar-checkbox-item.svelte +21 -0
  452. package/dist/ui/menubar/menubar-checkbox-item.svelte.d.ts +4 -0
  453. package/dist/ui/menubar/menubar-content.svelte +27 -0
  454. package/dist/ui/menubar/menubar-content.svelte.d.ts +5 -0
  455. package/dist/ui/menubar/menubar-group-heading.svelte +16 -0
  456. package/dist/ui/menubar/menubar-group-heading.svelte.d.ts +4 -0
  457. package/dist/ui/menubar/menubar-item.svelte +22 -0
  458. package/dist/ui/menubar/menubar-item.svelte.d.ts +5 -0
  459. package/dist/ui/menubar/menubar-radio-item.svelte +15 -0
  460. package/dist/ui/menubar/menubar-radio-item.svelte.d.ts +4 -0
  461. package/dist/ui/menubar/menubar-root.svelte +15 -0
  462. package/dist/ui/menubar/menubar-root.svelte.d.ts +4 -0
  463. package/dist/ui/menubar/menubar-separator.svelte +12 -0
  464. package/dist/ui/menubar/menubar-separator.svelte.d.ts +4 -0
  465. package/dist/ui/menubar/menubar-sub-content.svelte +26 -0
  466. package/dist/ui/menubar/menubar-sub-content.svelte.d.ts +5 -0
  467. package/dist/ui/menubar/menubar-sub-trigger.svelte +24 -0
  468. package/dist/ui/menubar/menubar-sub-trigger.svelte.d.ts +5 -0
  469. package/dist/ui/menubar/menubar-trigger.svelte +22 -0
  470. package/dist/ui/menubar/menubar-trigger.svelte.d.ts +5 -0
  471. package/dist/ui/meter/index.d.ts +1 -0
  472. package/dist/ui/meter/index.js +1 -0
  473. package/dist/ui/meter/meter.svelte +32 -0
  474. package/dist/ui/meter/meter.svelte.d.ts +4 -0
  475. package/dist/ui/mode/index.d.ts +1 -0
  476. package/dist/ui/mode/index.js +1 -0
  477. package/dist/ui/mode/mode.svelte +61 -0
  478. package/dist/ui/mode/mode.svelte.d.ts +18 -0
  479. package/dist/ui/navigation-menu/index.d.ts +10 -0
  480. package/dist/ui/navigation-menu/index.js +10 -0
  481. package/dist/ui/pagination/index.d.ts +4 -0
  482. package/dist/ui/pagination/index.js +4 -0
  483. package/dist/ui/pagination/pagination-next-button.svelte +30 -0
  484. package/dist/ui/pagination/pagination-next-button.svelte.d.ts +9 -0
  485. package/dist/ui/pagination/pagination-page.svelte +32 -0
  486. package/dist/ui/pagination/pagination-page.svelte.d.ts +9 -0
  487. package/dist/ui/pagination/pagination-prev-button.svelte +30 -0
  488. package/dist/ui/pagination/pagination-prev-button.svelte.d.ts +9 -0
  489. package/dist/ui/pagination/pagination-root.svelte +18 -0
  490. package/dist/ui/pagination/pagination-root.svelte.d.ts +4 -0
  491. package/dist/ui/pane/index.d.ts +1 -0
  492. package/dist/ui/pane/index.js +1 -0
  493. package/dist/ui/pane/pane.svelte +145 -0
  494. package/dist/ui/pane/pane.svelte.d.ts +34 -0
  495. package/dist/ui/pin-input/index.d.ts +2 -0
  496. package/dist/ui/pin-input/index.js +2 -0
  497. package/dist/ui/pin-input/pin-input-cell.svelte +30 -0
  498. package/dist/ui/pin-input/pin-input-cell.svelte.d.ts +4 -0
  499. package/dist/ui/pin-input/pin-input-root.svelte +18 -0
  500. package/dist/ui/pin-input/pin-input-root.svelte.d.ts +4 -0
  501. package/dist/ui/popover/index.d.ts +7 -0
  502. package/dist/ui/popover/index.js +7 -0
  503. package/dist/ui/popover/popover-close.svelte +27 -0
  504. package/dist/ui/popover/popover-close.svelte.d.ts +9 -0
  505. package/dist/ui/popover/popover-content.svelte +24 -0
  506. package/dist/ui/popover/popover-content.svelte.d.ts +5 -0
  507. package/dist/ui/popover/popover-trigger.svelte +41 -0
  508. package/dist/ui/popover/popover-trigger.svelte.d.ts +9 -0
  509. package/dist/ui/progress/index.d.ts +1 -0
  510. package/dist/ui/progress/index.js +1 -0
  511. package/dist/ui/progress/progress.svelte +47 -0
  512. package/dist/ui/progress/progress.svelte.d.ts +4 -0
  513. package/dist/ui/provider/axes.d.ts +74 -0
  514. package/dist/ui/provider/axes.js +70 -0
  515. package/dist/ui/provider/index.d.ts +6 -0
  516. package/dist/ui/provider/index.js +6 -0
  517. package/dist/ui/provider/modes.d.ts +12 -0
  518. package/dist/ui/provider/modes.js +14 -0
  519. package/dist/ui/provider/surfaces.d.ts +45 -0
  520. package/dist/ui/provider/surfaces.js +24 -0
  521. package/dist/ui/radio-group/index.d.ts +2 -0
  522. package/dist/ui/radio-group/index.js +2 -0
  523. package/dist/ui/radio-group/radio-group-item.svelte +19 -0
  524. package/dist/ui/radio-group/radio-group-item.svelte.d.ts +4 -0
  525. package/dist/ui/radio-group/radio-group-root.svelte +18 -0
  526. package/dist/ui/radio-group/radio-group-root.svelte.d.ts +4 -0
  527. package/dist/ui/range-calendar/index.d.ts +15 -0
  528. package/dist/ui/range-calendar/index.js +15 -0
  529. package/dist/ui/rating-group/index.d.ts +2 -0
  530. package/dist/ui/rating-group/index.js +2 -0
  531. package/dist/ui/rating-group/rating-group-item.svelte +17 -0
  532. package/dist/ui/rating-group/rating-group-item.svelte.d.ts +4 -0
  533. package/dist/ui/rating-group/rating-group-root.svelte +20 -0
  534. package/dist/ui/rating-group/rating-group-root.svelte.d.ts +4 -0
  535. package/dist/ui/scroll-area/index.d.ts +5 -0
  536. package/dist/ui/scroll-area/index.js +5 -0
  537. package/dist/ui/scroll-area/scroll-area-corner.svelte +7 -0
  538. package/dist/ui/scroll-area/scroll-area-corner.svelte.d.ts +4 -0
  539. package/dist/ui/scroll-area/scroll-area-scrollbar.svelte +19 -0
  540. package/dist/ui/scroll-area/scroll-area-scrollbar.svelte.d.ts +4 -0
  541. package/dist/ui/scroll-area/scroll-area-thumb.svelte +12 -0
  542. package/dist/ui/scroll-area/scroll-area-thumb.svelte.d.ts +4 -0
  543. package/dist/ui/scroll-area/scroll-area-viewport.svelte +12 -0
  544. package/dist/ui/scroll-area/scroll-area-viewport.svelte.d.ts +4 -0
  545. package/dist/ui/scroll-area/scroll-area.svelte +18 -0
  546. package/dist/ui/scroll-area/scroll-area.svelte.d.ts +4 -0
  547. package/dist/ui/select/index.d.ts +6 -0
  548. package/dist/ui/select/index.js +6 -0
  549. package/dist/ui/select/select-content.svelte +25 -0
  550. package/dist/ui/select/select-content.svelte.d.ts +5 -0
  551. package/dist/ui/select/select-item.svelte +17 -0
  552. package/dist/ui/select/select-item.svelte.d.ts +4 -0
  553. package/dist/ui/select/select-trigger.svelte +24 -0
  554. package/dist/ui/select/select-trigger.svelte.d.ts +5 -0
  555. package/dist/ui/select/select-value.svelte +71 -0
  556. package/dist/ui/select/select-value.svelte.d.ts +9 -0
  557. package/dist/ui/separator/index.d.ts +1 -0
  558. package/dist/ui/separator/index.js +1 -0
  559. package/dist/ui/separator/separator.svelte +31 -0
  560. package/dist/ui/separator/separator.svelte.d.ts +8 -0
  561. package/dist/ui/sheet/index.d.ts +2 -0
  562. package/dist/ui/sheet/index.js +5 -0
  563. package/dist/ui/sheet/sheet-content.svelte +41 -0
  564. package/dist/ui/sheet/sheet-content.svelte.d.ts +31 -0
  565. package/dist/ui/skeleton/index.d.ts +1 -0
  566. package/dist/ui/skeleton/index.js +1 -0
  567. package/dist/ui/skeleton/skeleton.svelte +43 -0
  568. package/dist/ui/skeleton/skeleton.svelte.d.ts +27 -0
  569. package/dist/ui/slider/index.d.ts +6 -0
  570. package/dist/ui/slider/index.js +6 -0
  571. package/dist/ui/slider/slider-range.svelte +40 -0
  572. package/dist/ui/slider/slider-range.svelte.d.ts +13 -0
  573. package/dist/ui/slider/slider-root.svelte +29 -0
  574. package/dist/ui/slider/slider-root.svelte.d.ts +4 -0
  575. package/dist/ui/slider/slider-thumb-label.svelte +12 -0
  576. package/dist/ui/slider/slider-thumb-label.svelte.d.ts +4 -0
  577. package/dist/ui/slider/slider-thumb.svelte +15 -0
  578. package/dist/ui/slider/slider-thumb.svelte.d.ts +4 -0
  579. package/dist/ui/slider/slider-tick-label.svelte +12 -0
  580. package/dist/ui/slider/slider-tick-label.svelte.d.ts +4 -0
  581. package/dist/ui/slider/slider-tick.svelte +15 -0
  582. package/dist/ui/slider/slider-tick.svelte.d.ts +4 -0
  583. package/dist/ui/switch/index.d.ts +1 -0
  584. package/dist/ui/switch/index.js +1 -0
  585. package/dist/ui/switch/switch.svelte +36 -0
  586. package/dist/ui/switch/switch.svelte.d.ts +4 -0
  587. package/dist/ui/table/index.d.ts +6 -0
  588. package/dist/ui/table/index.js +6 -0
  589. package/dist/ui/table/table-body.svelte +18 -0
  590. package/dist/ui/table/table-body.svelte.d.ts +9 -0
  591. package/dist/ui/table/table-cell.svelte +26 -0
  592. package/dist/ui/table/table-cell.svelte.d.ts +9 -0
  593. package/dist/ui/table/table-head-cell.svelte +26 -0
  594. package/dist/ui/table/table-head-cell.svelte.d.ts +9 -0
  595. package/dist/ui/table/table-head.svelte +18 -0
  596. package/dist/ui/table/table-head.svelte.d.ts +9 -0
  597. package/dist/ui/table/table-root.svelte +36 -0
  598. package/dist/ui/table/table-root.svelte.d.ts +17 -0
  599. package/dist/ui/table/table-row.svelte +26 -0
  600. package/dist/ui/table/table-row.svelte.d.ts +9 -0
  601. package/dist/ui/tabs/index.d.ts +4 -0
  602. package/dist/ui/tabs/index.js +4 -0
  603. package/dist/ui/tabs/tabs-content.svelte +7 -0
  604. package/dist/ui/tabs/tabs-content.svelte.d.ts +4 -0
  605. package/dist/ui/tabs/tabs-list.svelte +7 -0
  606. package/dist/ui/tabs/tabs-list.svelte.d.ts +4 -0
  607. package/dist/ui/tabs/tabs-trigger.svelte +7 -0
  608. package/dist/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
  609. package/dist/ui/tabs/tabs.svelte +7 -0
  610. package/dist/ui/tabs/tabs.svelte.d.ts +4 -0
  611. package/dist/ui/time-field/index.d.ts +4 -0
  612. package/dist/ui/time-field/index.js +4 -0
  613. package/dist/ui/time-field/time-field-input.svelte +15 -0
  614. package/dist/ui/time-field/time-field-input.svelte.d.ts +4 -0
  615. package/dist/ui/time-field/time-field-label.svelte +8 -0
  616. package/dist/ui/time-field/time-field-label.svelte.d.ts +4 -0
  617. package/dist/ui/time-field/time-field-root.svelte +11 -0
  618. package/dist/ui/time-field/time-field-root.svelte.d.ts +4 -0
  619. package/dist/ui/time-field/time-field-segment.svelte +15 -0
  620. package/dist/ui/time-field/time-field-segment.svelte.d.ts +4 -0
  621. package/dist/ui/time-range-field/index.d.ts +4 -0
  622. package/dist/ui/time-range-field/index.js +4 -0
  623. package/dist/ui/time-range-field/time-range-field-input.svelte +19 -0
  624. package/dist/ui/time-range-field/time-range-field-input.svelte.d.ts +4 -0
  625. package/dist/ui/time-range-field/time-range-field-label.svelte +12 -0
  626. package/dist/ui/time-range-field/time-range-field-label.svelte.d.ts +4 -0
  627. package/dist/ui/time-range-field/time-range-field-root.svelte +13 -0
  628. package/dist/ui/time-range-field/time-range-field-root.svelte.d.ts +4 -0
  629. package/dist/ui/time-range-field/time-range-field-segment.svelte +19 -0
  630. package/dist/ui/time-range-field/time-range-field-segment.svelte.d.ts +3 -0
  631. package/dist/ui/toggle/index.d.ts +1 -0
  632. package/dist/ui/toggle/index.js +1 -0
  633. package/dist/ui/toggle/toggle.svelte +27 -0
  634. package/dist/ui/toggle/toggle.svelte.d.ts +9 -0
  635. package/dist/ui/toggle-group/index.d.ts +2 -0
  636. package/dist/ui/toggle-group/index.js +2 -0
  637. package/dist/ui/toggle-group/toggle-group-item.svelte +17 -0
  638. package/dist/ui/toggle-group/toggle-group-item.svelte.d.ts +4 -0
  639. package/dist/ui/toggle-group/toggle-group-root.svelte +19 -0
  640. package/dist/ui/toggle-group/toggle-group-root.svelte.d.ts +5 -0
  641. package/dist/ui/toolbar/index.d.ts +6 -0
  642. package/dist/ui/toolbar/index.js +6 -0
  643. package/dist/ui/tooltip/index.d.ts +7 -0
  644. package/dist/ui/tooltip/index.js +7 -0
  645. package/dist/ui/tooltip/tooltip-content.svelte +26 -0
  646. package/dist/ui/tooltip/tooltip-content.svelte.d.ts +5 -0
  647. package/dist/ui/tooltip/tooltip-trigger.svelte +7 -0
  648. package/dist/ui/tooltip/tooltip-trigger.svelte.d.ts +4 -0
  649. package/dist/ui/video-player/index.d.ts +1 -0
  650. package/dist/ui/video-player/index.js +1 -0
  651. package/dist/ui/video-player/video-player.svelte +1377 -0
  652. package/dist/ui/video-player/video-player.svelte.d.ts +107 -0
  653. package/dist/utils/index.d.ts +15 -0
  654. package/dist/utils/index.js +24 -0
  655. package/package.json +115 -0
  656. package/readme +127 -0
@@ -0,0 +1,1377 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { untrack } from 'svelte';
4
+ import { browser } from '$app/environment';
5
+ import {
6
+ ContextMenu as BitsContextMenu,
7
+ DropdownMenu as BitsDropdownMenu,
8
+ Slider as BitsSlider
9
+ } from 'bits-ui';
10
+ import {
11
+ MediaRemoteControl,
12
+ type MediaPlayerElement,
13
+ type MediaFullscreenRequestTarget,
14
+ type MediaState
15
+ } from 'vidstack';
16
+ import { Bar } from '../bar';
17
+ import { Badge } from '../badge';
18
+ import { Button } from '../button';
19
+ import * as ContextMenu from '../context-menu';
20
+ import * as DropdownMenu from '../dropdown-menu';
21
+ import Play from '@lucide/svelte/icons/play';
22
+ import Pause from '@lucide/svelte/icons/pause';
23
+ import RotateCcw from '@lucide/svelte/icons/rotate-ccw';
24
+ import Rewind from '@lucide/svelte/icons/rewind';
25
+ import FastForward from '@lucide/svelte/icons/fast-forward';
26
+ import Volume2 from '@lucide/svelte/icons/volume-2';
27
+ import Volume1 from '@lucide/svelte/icons/volume-1';
28
+ import VolumeX from '@lucide/svelte/icons/volume-x';
29
+ import Gauge from '@lucide/svelte/icons/gauge';
30
+ import Settings from '@lucide/svelte/icons/settings';
31
+ import PictureInPicture from '@lucide/svelte/icons/picture-in-picture';
32
+ import PictureInPicture2 from '@lucide/svelte/icons/picture-in-picture-2';
33
+ import Maximize from '@lucide/svelte/icons/maximize';
34
+ import Minimize from '@lucide/svelte/icons/minimize';
35
+ import LoaderCircle from '@lucide/svelte/icons/loader-circle';
36
+ import Check from '@lucide/svelte/icons/check';
37
+ import Download from '@lucide/svelte/icons/download';
38
+ import { cn } from '../../utils/index.js';
39
+
40
+ // Only the engine + outlet are registered, so vidstack ships no UI chrome
41
+ // or default stylesheet — every control is built from this library.
42
+ // Wait for both custom elements to be defined before wiring remote control
43
+ // and subscriptions; otherwise, mount can race and leave controls inert.
44
+ let elementsDefined = $state(!browser);
45
+ if (browser) {
46
+ void Promise.all([
47
+ import('vidstack/define/media-player'),
48
+ import('vidstack/define/media-outlet'),
49
+ import('vidstack/define/media-gesture')
50
+ ]).then(() => {
51
+ elementsDefined = true;
52
+ });
53
+ }
54
+
55
+ const FULLSCREEN_TARGET: MediaFullscreenRequestTarget = 'prefer-media';
56
+ const toScalar = (v: number | number[]) => (Array.isArray(v) ? (v[0] ?? 0) : v);
57
+
58
+ type MediaEventWithDetail<T> = CustomEvent<T> & { detail: T };
59
+ type ChapterTrack = {
60
+ src: string;
61
+ label?: string;
62
+ srclang?: string;
63
+ default?: boolean;
64
+ };
65
+ type ChapterCue = {
66
+ start: number;
67
+ end: number;
68
+ text: string;
69
+ };
70
+
71
+ let {
72
+ ref = $bindable(null),
73
+ src,
74
+ poster,
75
+ chapters,
76
+ preload = 'metadata',
77
+ load = 'visible',
78
+ muted = false,
79
+ autoplay = false,
80
+ loop = false,
81
+ playsinline = true,
82
+ crossorigin,
83
+ initialVolume,
84
+ initialPlaybackRate,
85
+ aspectRatio,
86
+ viewType,
87
+ streamType,
88
+ fullscreenOrientation,
89
+ controls = false,
90
+ play: showPlay = true,
91
+ time: showTime = true,
92
+ skip = false,
93
+ volume: showVolume = false,
94
+ volumeOrientation = 'horizontal',
95
+ speed: showSpeed = false,
96
+ quality: showQuality = false,
97
+ pip: showPip = false,
98
+ fullscreen: showFullscreen = false,
99
+ seekPreview: showSeekPreview = false,
100
+ contextMenu: showContextMenu = false,
101
+ autoHide = true,
102
+ idleDelay = 500,
103
+ idleWhilePaused = false,
104
+ mobileTimelineOnly = true,
105
+ keyDisabled = false,
106
+ keyTarget,
107
+ keyShortcuts,
108
+ onplay,
109
+ onpause,
110
+ onended,
111
+ onplaying,
112
+ onseeked,
113
+ onseeking,
114
+ oncanplay,
115
+ ontimeupdate,
116
+ onvolumechange,
117
+ onratechange,
118
+ ondurationchange,
119
+ onloadedmetadata,
120
+ onfullscreenchange,
121
+ onpipchange,
122
+ onerror,
123
+ children,
124
+ class: className
125
+ }: {
126
+ ref?: HTMLElement | null;
127
+ src: string;
128
+ poster?: string;
129
+ /** Optional WebVTT chapters track. Pass a URL or full track descriptor. */
130
+ chapters?: string | ChapterTrack;
131
+ preload?: 'none' | 'metadata' | 'auto';
132
+ load?: 'eager' | 'idle' | 'visible' | 'custom';
133
+ muted?: boolean;
134
+ autoplay?: boolean;
135
+ loop?: boolean;
136
+ playsinline?: boolean;
137
+ crossorigin?: 'anonymous' | 'use-credentials';
138
+ /** Initial volume (0–1). */
139
+ initialVolume?: number;
140
+ /** Initial playback rate (e.g. 1, 1.5, 2). */
141
+ initialPlaybackRate?: number;
142
+ /** Force a fixed aspect ratio as width/height, e.g. 16/9. */
143
+ aspectRatio?: number;
144
+ viewType?: 'unknown' | 'audio' | 'video';
145
+ streamType?: 'unknown' | 'on-demand' | 'live' | 'live:dvr' | 'll-live' | 'll-live:dvr';
146
+ fullscreenOrientation?:
147
+ | 'any'
148
+ | 'natural'
149
+ | 'landscape'
150
+ | 'portrait'
151
+ | 'portrait-primary'
152
+ | 'portrait-secondary'
153
+ | 'landscape-primary'
154
+ | 'landscape-secondary';
155
+ /** Fall back to the provider's native controls instead of the custom chrome. */
156
+ controls?: boolean;
157
+ /** Show the play / pause button in the custom chrome. */
158
+ play?: boolean;
159
+ /** Show the elapsed and duration time badges. */
160
+ time?: boolean;
161
+ /** Show ±N-second skip buttons. `true` defaults to 10s; pass a number to override. */
162
+ skip?: boolean | number;
163
+ /** Show the mute toggle and volume slider. */
164
+ volume?: boolean;
165
+ /** Layout direction for the volume slider. */
166
+ volumeOrientation?: 'horizontal' | 'vertical';
167
+ /** Show the playback-speed dropdown. */
168
+ speed?: boolean;
169
+ /** Show the quality dropdown. Auto-hidden when the source exposes no quality options (e.g. plain MP4). */
170
+ quality?: boolean;
171
+ /** Show the picture-in-picture toggle. */
172
+ pip?: boolean;
173
+ /** Show the fullscreen toggle. */
174
+ fullscreen?: boolean;
175
+ /** Show a hover preview above the seek bar with the cursor's time and (if available) the chapter at that point. */
176
+ seekPreview?: boolean;
177
+ /** Enable a right-click context menu mirroring the enabled chrome plus a download shortcut. */
178
+ contextMenu?: boolean;
179
+ /** Whether the controls bar fades when the user is idle during playback. Set `false` to keep it visible. */
180
+ autoHide?: boolean;
181
+ /**
182
+ * Delay (ms) of inactivity during playback before vidstack flips `userIdle` to true.
183
+ * Maps to the player's `user-idle-delay` attribute. Defaults to 500 ms (vidstack's
184
+ * own default is 2000 ms).
185
+ */
186
+ idleDelay?: number;
187
+ /**
188
+ * Vidstack only fades the bar while playing (deliberately — its `MediaUserController`
189
+ * bails out of idle tracking on `pause`). Set this to `true` to also hide the bar
190
+ * while paused after `idleDelay` ms of pointer/keyboard inactivity.
191
+ */
192
+ idleWhilePaused?: boolean;
193
+ /**
194
+ * On narrow viewports / coarse-pointer devices, auto-collapse to a "timeline-only"
195
+ * layout — every chrome control suppresses regardless of its individual prop, leaving
196
+ * just the seek bar. Set `false` to honor the chrome props as-is on mobile too.
197
+ */
198
+ mobileTimelineOnly?: boolean;
199
+ keyDisabled?: boolean;
200
+ keyTarget?: 'document' | 'player';
201
+ keyShortcuts?: Record<string, string>;
202
+ onplay?: (e: Event) => void;
203
+ onpause?: (e: Event) => void;
204
+ onended?: (e: Event) => void;
205
+ onplaying?: (e: Event) => void;
206
+ onseeked?: (e: MediaEventWithDetail<number>) => void;
207
+ onseeking?: (e: MediaEventWithDetail<number>) => void;
208
+ oncanplay?: (e: Event) => void;
209
+ ontimeupdate?: (e: MediaEventWithDetail<{ currentTime: number; played: TimeRanges }>) => void;
210
+ onvolumechange?: (e: MediaEventWithDetail<{ volume: number; muted: boolean }>) => void;
211
+ onratechange?: (e: MediaEventWithDetail<number>) => void;
212
+ ondurationchange?: (e: MediaEventWithDetail<number>) => void;
213
+ onloadedmetadata?: (e: Event) => void;
214
+ onfullscreenchange?: (e: MediaEventWithDetail<boolean>) => void;
215
+ onpipchange?: (e: MediaEventWithDetail<boolean>) => void;
216
+ onerror?: (e: Event) => void;
217
+ /** Optional override for the entire controls layer — replaces the default chrome. */
218
+ children?: Snippet;
219
+ class?: string;
220
+ } = $props();
221
+
222
+ let paused = $state(true);
223
+ let ended = $state(false);
224
+ let started = $state(false);
225
+ let currentTime = $state(0);
226
+ let duration = $state(0);
227
+ let volumeState = $state(1);
228
+ let mutedState = $state(false);
229
+ let rateState = $state(1);
230
+ let waiting = $state(false);
231
+ let fullscreenState = $state(false);
232
+ let pipState = $state(false);
233
+ let live = $state(false);
234
+ let canSeek = $state(true);
235
+ let qualities = $state<MediaState['qualities']>([]);
236
+ let currentQuality = $state<MediaState['quality']>(null);
237
+ let autoQuality = $state(false);
238
+ let canSetQuality = $state(false);
239
+ // Mirrored from vidstack's subscribe — container-relative breakpoint is
240
+ // what makes an embedded 400 px player collapse to mobile mode too,
241
+ // not just phone-width viewports.
242
+ let touchPointer = $state(false);
243
+ let breakpointX = $state<MediaState['breakpointX']>('lg');
244
+ // Vidstack reports whether the current provider/browser combo actually
245
+ // supports fullscreen and PiP — auto-hide those buttons rather than
246
+ // rendering ones that silently fail when clicked.
247
+ let canEnterFullscreen = $state(false);
248
+ let canEnterPip = $state(false);
249
+ const mobileMode = $derived(mobileTimelineOnly && (touchPointer || breakpointX === 'sm'));
250
+ // vidstack flips `userIdle` to true while the engine is playing and the
251
+ // pointer hasn't moved over the player for ~2.5s (and back to false on
252
+ // any interaction or while paused). We use it to fade the controls bar.
253
+ let userIdle = $state(false);
254
+ // Vidstack's userIdle never fires while paused (its MediaUserController
255
+ // bails out on `pause`). When `idleWhilePaused` is on, run our own timer
256
+ // and OR the result into the bar's fade.
257
+ let pausedIdle = $state(false);
258
+ // Mirror the end of vidstack's buffered / seekable ranges. Used for the
259
+ // seek bar's progress underlay and to clamp the slider max below
260
+ // `duration` when the engine reports a smaller seekable window
261
+ // (e.g. HLS live).
262
+ let bufferedEnd = $state(0);
263
+ let seekableEnd = $state(0);
264
+ let seekTimelineRef = $state<HTMLElement | null>(null);
265
+ let seekInteracted = $state(false);
266
+ let chapterCues = $state<ChapterCue[]>([]);
267
+
268
+ // While scrubbing, scrubTime overrides currentTime so the label and thumb
269
+ // track the pointer immediately instead of waiting for the media engine.
270
+ const SEEK_SETTLE_EPSILON = 0.08;
271
+ const SEEK_DRIFT_SNAP = 0.35;
272
+ const SEEK_DRIFT_CORRECTION = 0.14;
273
+ const SEEK_KEY_STEP = 5;
274
+ const SEEK_PAGE_STEP = 30;
275
+ let scrubbing = $state(false);
276
+ let scrubTime = $state(0);
277
+ // Hover preview: only the *whole-second* time and active flag drive
278
+ // re-renders. The exact pointer x position is written straight to a CSS
279
+ // variable on the timeline (no Svelte invalidation per pointermove tick).
280
+ let hoverActive = $state(false);
281
+ let hoverSeconds = $state(0);
282
+ let visualTime = 0;
283
+ let pendingSeekTime: number | null = null;
284
+ let resumeAfterScrub = false;
285
+ // fmt() ran on the time-text node every time-update (~60×/s). Deriving
286
+ // whole-second labels means it only re-runs when the visible string changes.
287
+ const shownTimeLabel = $derived(fmt(Math.floor(scrubbing ? scrubTime : currentTime)));
288
+ const durationLabel = $derived(fmt(Math.floor(duration)));
289
+ const seekAriaValue = $derived(Math.round((scrubbing ? scrubTime : currentTime) * 100) / 100);
290
+ const seekValueText = $derived(`${shownTimeLabel} of ${durationLabel}`);
291
+ const chapterTrack = $derived(
292
+ typeof chapters === 'string' ? { src: chapters, srclang: 'en-US', default: true } : chapters
293
+ );
294
+ const chapterMarkers = $derived(
295
+ chapterCues
296
+ .map((cue) => ({
297
+ ...cue,
298
+ left: timeMax > 0 ? Math.max(0, Math.min(100, (cue.start / timeMax) * 100)) : 0
299
+ }))
300
+ .filter((cue) => cue.left > 0 && cue.left < 100)
301
+ );
302
+ const hoverTimeLabel = $derived(fmt(hoverSeconds));
303
+ const hoverChapter = $derived(
304
+ hoverActive
305
+ ? chapterCues.find((c) => hoverSeconds >= c.start && hoverSeconds < c.end)
306
+ : undefined
307
+ );
308
+ const seekPreviewVisible = $derived(showSeekPreview && hoverActive && !mobileMode);
309
+
310
+ const playerEl = $derived(ref as (HTMLElement & MediaPlayerElement) | null);
311
+ // Construct only in the browser — Cloudflare worker SSR resolves vidstack's
312
+ // CJS build, where `MediaRemoteControl` lands as a non-constructor value
313
+ // and `new` throws. Templates only invoke `remote` from event handlers, so
314
+ // it never runs during server render.
315
+ let remote!: MediaRemoteControl;
316
+ if (browser) remote = new MediaRemoteControl();
317
+
318
+ $effect(() => {
319
+ if (!elementsDefined) return;
320
+ if (!playerEl) return;
321
+ remote.setPlayer(playerEl);
322
+ remote.setTarget(playerEl);
323
+ // initialVolume / initialPlaybackRate are documented as initial values.
324
+ // Untrack them so a parent updating those props after mount doesn't
325
+ // re-run this effect and tear down setPlayer / setTarget.
326
+ untrack(() => {
327
+ if (typeof initialVolume === 'number') remote.changeVolume(initialVolume);
328
+ if (typeof initialPlaybackRate === 'number') remote.changePlaybackRate(initialPlaybackRate);
329
+ });
330
+ return () => {
331
+ remote.setPlayer(null);
332
+ remote.setTarget(null);
333
+ };
334
+ });
335
+
336
+ const rateOptions = [0.5, 0.75, 1, 1.25, 1.5, 2];
337
+
338
+ const skipBy = $derived(typeof skip === 'number' ? skip : skip ? 10 : 0);
339
+ const showSkip = $derived(skipBy > 0);
340
+
341
+ // Sort highest-resolution first; bitrate breaks ties for same-height
342
+ // renditions. We keep the original array index because that's what
343
+ // `remote.changeQuality(index)` expects — sorting is purely visual.
344
+ const qualityOptions = $derived(
345
+ qualities
346
+ .map((q, index) => ({ q, index }))
347
+ .sort((a, b) =>
348
+ b.q.height !== a.q.height ? b.q.height - a.q.height : b.q.bitrate - a.q.bitrate
349
+ )
350
+ );
351
+ const showQualityMenu = $derived(showQuality && canSetQuality && qualities.length > 0);
352
+ const qualityValue = $derived(
353
+ autoQuality || !currentQuality ? 'auto' : String(qualities.indexOf(currentQuality))
354
+ );
355
+ const qualityHint = $derived(
356
+ autoQuality
357
+ ? currentQuality
358
+ ? `Auto (${currentQuality.height}p)`
359
+ : 'Auto'
360
+ : currentQuality
361
+ ? `${currentQuality.height}p`
362
+ : '—'
363
+ );
364
+
365
+ function changeQuality(value: string, e?: Event) {
366
+ if (value === 'auto') remote.changeQuality(-1, e);
367
+ else {
368
+ const idx = Number(value);
369
+ if (Number.isFinite(idx)) remote.changeQuality(idx, e);
370
+ }
371
+ }
372
+
373
+ // When autoHide is false, force vidstack out of idle for the entire mount so
374
+ // the controls bar never fades. We can't just set a huge user-idle-delay —
375
+ // vidstack would still flip idle on `pause → play` if left long enough — so
376
+ // we tell the controller to pause idle tracking and resume on cleanup.
377
+ $effect(() => {
378
+ if (!browser || !elementsDefined || !playerEl) return;
379
+ if (autoHide) return;
380
+ remote.pauseUserIdle();
381
+ return () => remote.resumeUserIdle();
382
+ });
383
+
384
+ // Custom idle timer that fills vidstack's gap: track inactivity while
385
+ // PAUSED and fade the bar after `idleDelay`. Only runs when `paused` is
386
+ // true and the consumer has opted in via `idleWhilePaused`. Reset on any
387
+ // pointer or keyboard activity inside the player element.
388
+ $effect(() => {
389
+ if (!browser || !elementsDefined || !playerEl) return;
390
+ if (!autoHide || !idleWhilePaused || !paused) {
391
+ pausedIdle = false;
392
+ return;
393
+ }
394
+ const delay = typeof idleDelay === 'number' ? idleDelay : 500;
395
+ let timer = window.setTimeout(() => {
396
+ pausedIdle = true;
397
+ }, delay);
398
+ const reset = () => {
399
+ pausedIdle = false;
400
+ window.clearTimeout(timer);
401
+ timer = window.setTimeout(() => {
402
+ pausedIdle = true;
403
+ }, delay);
404
+ };
405
+ playerEl.addEventListener('pointermove', reset);
406
+ playerEl.addEventListener('pointerdown', reset);
407
+ playerEl.addEventListener('keydown', reset);
408
+ return () => {
409
+ window.clearTimeout(timer);
410
+ playerEl.removeEventListener('pointermove', reset);
411
+ playerEl.removeEventListener('pointerdown', reset);
412
+ playerEl.removeEventListener('keydown', reset);
413
+ pausedIdle = false;
414
+ };
415
+ });
416
+
417
+ function downloadVideo() {
418
+ if (!browser) return;
419
+ const link = document.createElement('a');
420
+ link.href = src;
421
+ // Same-origin sources honor the download attribute and save the file.
422
+ // Cross-origin servers without CORS download headers will navigate
423
+ // instead — opening in a new tab is the friendliest fallback.
424
+ link.download = '';
425
+ link.target = '_blank';
426
+ link.rel = 'noopener';
427
+ link.click();
428
+ }
429
+
430
+ // Vidstack's `_play()` already seeks to start when play is requested on
431
+ // an ended player, so we just delegate.
432
+ function togglePlay(e: Event) {
433
+ remote.togglePaused(e);
434
+ }
435
+ function seekBy(delta: number, e: Event) {
436
+ seekInteracted = true;
437
+ const next = (readMediaTime() ?? currentTime) + delta;
438
+ remote.seek(Math.max(0, duration ? Math.min(duration, next) : next), e);
439
+ }
440
+
441
+ function readMediaTime(el = playerEl) {
442
+ const time = (el as unknown as { currentTime?: number } | null)?.currentTime;
443
+ return typeof time === 'number' && Number.isFinite(time) ? time : null;
444
+ }
445
+
446
+ function clampTime(t: number) {
447
+ if (!Number.isFinite(t)) return 0;
448
+ return Math.max(0, timeMax > 0 ? Math.min(timeMax, t) : t);
449
+ }
450
+
451
+ function setSeekVisual(time: number) {
452
+ const el = seekTimelineRef;
453
+ if (!el) return;
454
+ visualTime = clampTime(started || seekInteracted ? time : 0);
455
+ const fraction = timeMax > 0 ? visualTime / timeMax : 0;
456
+ const clamped = Math.max(0, Math.min(1, fraction));
457
+ el.style.setProperty('--era-video-played-scale', String(clamped));
458
+ }
459
+
460
+ function setBufferedVisual() {
461
+ seekTimelineRef?.style.setProperty(
462
+ '--era-video-buffered-scale',
463
+ String(Math.max(0, Math.min(1, bufferedFraction)))
464
+ );
465
+ }
466
+
467
+ function pointerTime(e: PointerEvent) {
468
+ const rect = seekTimelineRef?.getBoundingClientRect();
469
+ if (!rect || rect.width <= 0) return currentTime;
470
+ const fraction = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
471
+ return clampTime(fraction * timeMax);
472
+ }
473
+
474
+ function scrubTo(time: number) {
475
+ scrubTime = clampTime(time);
476
+ setSeekVisual(scrubTime);
477
+ }
478
+
479
+ function commitSeek(time: number, e?: Event) {
480
+ const next = clampTime(time);
481
+ const atLiveEdge = live && timeMax > 0 && next / timeMax >= 0.99;
482
+ if (atLiveEdge) remote.seekToLiveEdge();
483
+ else remote.seek(next, e);
484
+ pendingSeekTime = next;
485
+ currentTime = next;
486
+ setSeekVisual(next);
487
+ scrubbing = false;
488
+ if (resumeAfterScrub) {
489
+ resumeAfterScrub = false;
490
+ remote.play();
491
+ }
492
+ }
493
+
494
+ function handleSeekPointerDown(e: PointerEvent) {
495
+ if (seekDisabled) return;
496
+ e.preventDefault();
497
+ const target = e.currentTarget as HTMLElement;
498
+ target.setPointerCapture(e.pointerId);
499
+ seekInteracted = true;
500
+ scrubbing = true;
501
+ scrubTo(pointerTime(e));
502
+ if (!paused) {
503
+ resumeAfterScrub = true;
504
+ remote.pause(e);
505
+ }
506
+ }
507
+
508
+ function handleSeekPointerMove(e: PointerEvent) {
509
+ if (showSeekPreview) updateHover(e);
510
+ if (!scrubbing || seekDisabled) return;
511
+ scrubTo(pointerTime(e));
512
+ }
513
+
514
+ function updateHover(e: PointerEvent) {
515
+ const time = pointerTime(e);
516
+ const fraction = timeMax > 0 ? Math.max(0, Math.min(1, time / timeMax)) : 0;
517
+ seekTimelineRef?.style.setProperty('--era-video-hover-pct', `${fraction * 100}%`);
518
+ const sec = Math.floor(time);
519
+ if (sec !== hoverSeconds) hoverSeconds = sec;
520
+ }
521
+
522
+ function handleSeekPointerEnter(e: PointerEvent) {
523
+ if (!showSeekPreview) return;
524
+ hoverActive = true;
525
+ updateHover(e);
526
+ }
527
+
528
+ function handleSeekPointerLeave() {
529
+ if (hoverActive) hoverActive = false;
530
+ }
531
+
532
+ function handleSeekPointerUp(e: PointerEvent) {
533
+ if (!scrubbing) return;
534
+ const target = e.currentTarget as HTMLElement;
535
+ if (target.hasPointerCapture(e.pointerId)) target.releasePointerCapture(e.pointerId);
536
+ commitSeek(pointerTime(e), e);
537
+ }
538
+
539
+ function handleSeekPointerCancel(e: PointerEvent) {
540
+ if (!scrubbing) return;
541
+ const target = e.currentTarget as HTMLElement;
542
+ if (target.hasPointerCapture(e.pointerId)) target.releasePointerCapture(e.pointerId);
543
+ scrubbing = false;
544
+ resumeAfterScrub = false;
545
+ setSeekVisual(readMediaTime() ?? currentTime);
546
+ }
547
+
548
+ function handleSeekKeydown(e: KeyboardEvent) {
549
+ if (seekDisabled) return;
550
+ const base = readMediaTime() ?? currentTime;
551
+ let next: number | null = null;
552
+ switch (e.key) {
553
+ case 'ArrowLeft':
554
+ case 'ArrowDown':
555
+ next = base - SEEK_KEY_STEP;
556
+ break;
557
+ case 'ArrowRight':
558
+ case 'ArrowUp':
559
+ next = base + SEEK_KEY_STEP;
560
+ break;
561
+ case 'PageDown':
562
+ next = base - SEEK_PAGE_STEP;
563
+ break;
564
+ case 'PageUp':
565
+ next = base + SEEK_PAGE_STEP;
566
+ break;
567
+ case 'Home':
568
+ next = 0;
569
+ break;
570
+ case 'End':
571
+ next = timeMax;
572
+ break;
573
+ default:
574
+ return;
575
+ }
576
+ e.preventDefault();
577
+ seekInteracted = true;
578
+ commitSeek(next, e);
579
+ }
580
+
581
+ function fmt(t: number) {
582
+ if (!Number.isFinite(t) || t < 0) return '0:00';
583
+ const total = Math.floor(t);
584
+ const h = Math.floor(total / 3600);
585
+ const m = Math.floor((total % 3600) / 60);
586
+ const s = total % 60;
587
+ const pad = (n: number) => String(n).padStart(2, '0');
588
+ if (h > 0) return `${h}:${pad(m)}:${pad(s)}`;
589
+ return `${m}:${pad(s)}`;
590
+ }
591
+
592
+ function parseVttTime(time: string) {
593
+ const parts = time.trim().split(':');
594
+ const seconds = Number(parts.pop());
595
+ const minutes = Number(parts.pop() ?? 0);
596
+ const hours = Number(parts.pop() ?? 0);
597
+ if (![seconds, minutes, hours].every(Number.isFinite)) return null;
598
+ return hours * 3600 + minutes * 60 + seconds;
599
+ }
600
+
601
+ function parseChaptersVtt(vtt: string) {
602
+ return vtt
603
+ .replace(/\r/g, '')
604
+ .split(/\n{2,}/)
605
+ .map((block) => {
606
+ const lines = block
607
+ .split('\n')
608
+ .map((line) => line.trim())
609
+ .filter(Boolean);
610
+ const timingIndex = lines.findIndex((line) => line.includes('-->'));
611
+ if (timingIndex === -1) return null;
612
+ const [rawStart, rawEnd] = lines[timingIndex].split('-->');
613
+ const start = parseVttTime(rawStart);
614
+ const end = parseVttTime(rawEnd?.trim().split(/\s+/)[0] ?? '');
615
+ if (start === null || end === null) return null;
616
+ return {
617
+ start,
618
+ end,
619
+ text: lines.slice(timingIndex + 1).join(' ')
620
+ };
621
+ })
622
+ .filter((cue): cue is ChapterCue => cue !== null);
623
+ }
624
+
625
+ $effect(() => {
626
+ if (!browser) return;
627
+ const src = chapterTrack?.src;
628
+ if (!src) {
629
+ chapterCues = [];
630
+ return;
631
+ }
632
+ let cancelled = false;
633
+ fetch(src)
634
+ .then((res) => (res.ok ? res.text() : ''))
635
+ .then((text) => {
636
+ if (!cancelled) chapterCues = parseChaptersVtt(text);
637
+ })
638
+ .catch(() => {
639
+ if (!cancelled) chapterCues = [];
640
+ });
641
+ return () => {
642
+ cancelled = true;
643
+ };
644
+ });
645
+
646
+ $effect(() => {
647
+ if (!elementsDefined) return;
648
+ if (!playerEl) return;
649
+ const el = playerEl;
650
+
651
+ // vidstack drives every piece of media state. Subscribe once and let
652
+ // the engine's signals overwrite our local mirrors — source changes,
653
+ // buffering, seek throttling, and replay are all already handled
654
+ // inside vidstack, so there's nothing for us to track or invalidate.
655
+ // The cast works around a TS quirk: `MediaPlayerElement` is an
656
+ // interface that extends a type-alias intersection, and TS doesn't
657
+ // reliably surface the conditional `subscribe` member that way.
658
+ type PlayerWithStore = { subscribe: (cb: (s: Readonly<MediaState>) => void) => () => void };
659
+ const dispose = (el as unknown as PlayerWithStore).subscribe((state) => {
660
+ paused = state.paused;
661
+ ended = state.ended;
662
+ started = state.started;
663
+ // Mirror `<media-buffering-indicator>`: spinner shows while the
664
+ // resource isn't ready to play or while the engine is genuinely
665
+ // waiting for data — never during user-initiated seeks (the moving
666
+ // thumb is the feedback). Folding `state.seeking` in here was the
667
+ // source of "spinner stuck after paused-seek to a buffered region".
668
+ waiting = !state.canPlay || state.waiting;
669
+ // vidstack's `subscribe` is a maverick `effect()`, so this callback
670
+ // only re-runs for state fields it actually reads. We must read
671
+ // `state.currentTime` to keep `time-update` ticks driving the
672
+ // timeline — but `state.currentTime` itself mirrors that throttled
673
+ // (~4 Hz) event and lags the engine by up to ~250 ms, which is
674
+ // visible as a thumb jump on pause/resume edges. The element's
675
+ // `currentTime` getter (via the underlying provider) is precise
676
+ // and unthrottled, so use it for the actual value and keep the
677
+ // `state.currentTime` read solely for dependency tracking.
678
+ const tracked = state.currentTime;
679
+ const mediaTime = readMediaTime(el) ?? tracked;
680
+ if (
681
+ pendingSeekTime !== null &&
682
+ state.seeking &&
683
+ Math.abs(mediaTime - pendingSeekTime) > SEEK_SETTLE_EPSILON
684
+ ) {
685
+ currentTime = pendingSeekTime;
686
+ } else {
687
+ pendingSeekTime = null;
688
+ currentTime = mediaTime;
689
+ }
690
+ duration = state.duration;
691
+ bufferedEnd = state.bufferedEnd;
692
+ seekableEnd = state.seekableEnd;
693
+ volumeState = state.volume;
694
+ mutedState = state.muted;
695
+ rateState = state.playbackRate;
696
+ fullscreenState = state.fullscreen;
697
+ pipState = state.pictureInPicture;
698
+ live = state.live;
699
+ canSeek = state.canSeek;
700
+ userIdle = state.userIdle;
701
+ // Copy the qualities array so Svelte's reactivity sees the change —
702
+ // vidstack mutates this list in place as the engine learns about
703
+ // renditions, and assigning the same reference would skip updates.
704
+ qualities = [...state.qualities];
705
+ currentQuality = state.quality;
706
+ autoQuality = state.autoQuality;
707
+ canSetQuality = state.canSetQuality;
708
+ touchPointer = state.touchPointer;
709
+ breakpointX = state.breakpointX;
710
+ canEnterFullscreen = state.canFullscreen;
711
+ canEnterPip = state.canPictureInPicture;
712
+ });
713
+
714
+ // Forward selected events to consumer callbacks. The thunks read
715
+ // each prop fresh at fire time so a parent updating a callback
716
+ // after mount is honored without re-attaching listeners.
717
+ const forward = <T extends Event>(name: string, get: () => ((e: T) => void) | undefined) => {
718
+ const fn = (e: Event) => get()?.(e as T);
719
+ el.addEventListener(name, fn);
720
+ return () => el.removeEventListener(name, fn);
721
+ };
722
+ const cleanups = [
723
+ forward('play', () => onplay),
724
+ forward('pause', () => onpause),
725
+ forward('ended', () => onended),
726
+ forward('playing', () => onplaying),
727
+ forward('seeked', () => onseeked),
728
+ forward('seeking', () => onseeking),
729
+ forward('can-play', () => oncanplay),
730
+ forward('time-update', () => ontimeupdate),
731
+ forward('volume-change', () => onvolumechange),
732
+ forward('rate-change', () => onratechange),
733
+ forward('duration-change', () => ondurationchange),
734
+ forward('loaded-metadata', () => onloadedmetadata),
735
+ forward('fullscreen-change', () => onfullscreenchange),
736
+ forward('picture-in-picture-change', () => onpipchange),
737
+ forward('error', () => onerror)
738
+ ];
739
+
740
+ return () => {
741
+ dispose();
742
+ for (const fn of cleanups) fn();
743
+ };
744
+ });
745
+
746
+ // Keep the visible seek bar off Svelte's render path during playback. The
747
+ // timeline is a dedicated control instead of a generic form slider, so the
748
+ // fill and thumb can move with CSS variables without value reconciliation
749
+ // fighting rAF. On resume we start from the last visual time and ease small
750
+ // media-clock drift back toward the provider, masking the common one-frame
751
+ // jump without hiding real seeks or stalls.
752
+ $effect(() => {
753
+ const el = playerEl;
754
+ const timeline = seekTimelineRef;
755
+ const max = timeMax;
756
+ const rate = rateState;
757
+ if (!el || !timeline || max <= 0) return;
758
+
759
+ setBufferedVisual();
760
+
761
+ if (scrubbing) {
762
+ setSeekVisual(scrubTime);
763
+ return;
764
+ }
765
+
766
+ if (paused) {
767
+ const t = readMediaTime(el) ?? currentTime;
768
+ currentTime = t;
769
+ setSeekVisual(t);
770
+ return;
771
+ }
772
+
773
+ const mediaTime = readMediaTime(el) ?? currentTime;
774
+ if (Math.abs(visualTime - mediaTime) > SEEK_DRIFT_SNAP) setSeekVisual(mediaTime);
775
+
776
+ let originTime = visualTime;
777
+ let originNow = performance.now();
778
+ let raf = requestAnimationFrame(function tick(now) {
779
+ const predicted = originTime + ((now - originNow) / 1000) * rate;
780
+ const actual = readMediaTime(el) ?? predicted;
781
+ const drift = actual - predicted;
782
+ const next =
783
+ Math.abs(drift) > SEEK_DRIFT_SNAP ? actual : predicted + drift * SEEK_DRIFT_CORRECTION;
784
+ setSeekVisual(next);
785
+ originTime = visualTime;
786
+ originNow = now;
787
+ raf = requestAnimationFrame(tick);
788
+ });
789
+ return () => cancelAnimationFrame(raf);
790
+ });
791
+
792
+ const playLabel = $derived(ended ? 'Replay' : paused ? 'Play' : 'Pause');
793
+ const muteLabel = $derived(mutedState || volumeState === 0 ? 'Unmute' : 'Mute');
794
+ // Prefer seekable.end when smaller than duration (HLS live windows); fall
795
+ // back to duration for VOD where the engine may report seekable === duration.
796
+ const timeMax = $derived(
797
+ seekableEnd > 0 && seekableEnd < (duration || Infinity)
798
+ ? seekableEnd
799
+ : duration > 0
800
+ ? duration
801
+ : 1
802
+ );
803
+ const bufferedFraction = $derived(timeMax > 0 ? bufferedEnd / timeMax : 0);
804
+ // Snap volumeState to the slider's step before passing it as `value`, so
805
+ // bits-ui's snap-to-step watcher doesn't fire onValueChange (with a
806
+ // rounded value) on every mount and external volume update.
807
+ const VOLUME_STEP = 0.01;
808
+ const shownVolume = $derived(
809
+ mutedState ? 0 : Math.round(volumeState / VOLUME_STEP) * VOLUME_STEP
810
+ );
811
+ const resolvedVolumeOrientation = $derived(
812
+ volumeOrientation === 'vertical' ? 'vertical' : 'horizontal'
813
+ );
814
+ // In vertical mode the popup is anchored to the mute icon — same width
815
+ // (h-md), shared bg, rounded top + flat bottom — so it reads as the icon
816
+ // extending upward (vidstack has no native API for this). The sleeve's
817
+ // box runs all the way down to the button (`bottom-full`, no offset) so
818
+ // the cursor crossing the visual gap stays inside the cluster's hover
819
+ // region; the gap is recreated by the slider's inset within the sleeve.
820
+ const volumeClusterClass = $derived(
821
+ resolvedVolumeOrientation === 'vertical'
822
+ ? 'group relative shrink-0'
823
+ : 'relative flex shrink-0 items-center'
824
+ );
825
+ // `--era-shadow` (inset only) — `--era-shadow-lg`'s drop shadow would seam
826
+ // against the mute button below. Visibility is in the scoped style block.
827
+ const volumeSleeveClass = $derived(
828
+ resolvedVolumeOrientation === 'vertical'
829
+ ? 'absolute bottom-full left-1/2 z-20 h-[calc(var(--era-h-lg)*3)] w-(--era-h-md) -translate-x-1/2 rounded-t-(--era-rd-md) bg-hover shadow-(--era-shadow) glass-blur'
830
+ : 'relative h-(--era-h-sm) w-[calc(var(--era-h-md)*3)]'
831
+ );
832
+ // Slider IS the visible track. Range and Thumb (both bg-fg) live inside
833
+ // and blend into one filled column.
834
+ const volumeSliderClass = $derived(
835
+ `absolute touch-none select-none overflow-hidden rounded-(--era-rd-xs) bg-3 shadow-(--era-shadow) focus:outline-none ${
836
+ resolvedVolumeOrientation === 'vertical'
837
+ ? 'top-(--era-gap) bottom-(--era-gap) left-1/2 w-(--era-h-xs) -translate-x-1/2'
838
+ : 'inset-x-(--era-xs-inset-sm) top-1/2 h-(--era-h-xs) -translate-y-1/2'
839
+ }`
840
+ );
841
+ // Bits-ui's slider needs a thumb element to handle pointer events, but
842
+ // the visible position indicator is the Range fill alone — no separate
843
+ // thumb to align. The thumb stays in the DOM at zero size, invisible.
844
+ const volumeThumbClass = 'block size-0 cursor-pointer focus:outline-none';
845
+ const volumeRangeClass = $derived(
846
+ resolvedVolumeOrientation === 'vertical' ? 'absolute w-full bg-fg' : 'absolute h-full bg-fg'
847
+ );
848
+ // vidstack's `canSeek` flips false during initial metadata load on VOD
849
+ // (because seekableWindow isn't finite yet) — disabling the slider then
850
+ // is just visual noise, the engine queues the seek as soon as metadata
851
+ // arrives. The genuine no-seek case is a live stream without a DVR
852
+ // window, which is what we actually want to gate on.
853
+ const seekDisabled = $derived(live && !canSeek);
854
+ // Don't fade while the cursor is hovering the bar itself — even when
855
+ // vidstack's userIdle (or our paused-idle) flips true because the user
856
+ // has held still over the bar for a while.
857
+ let barHovered = $state(false);
858
+ const barClass = $derived(
859
+ `absolute inset-x-(--era-gap) bottom-(--era-gap) z-10 bg-2 shadow-(--era-shadow) transition-opacity duration-(--era-duration) ${
860
+ (userIdle || pausedIdle) && !barHovered ? 'pointer-events-none opacity-0' : ''
861
+ }`
862
+ );
863
+ </script>
864
+
865
+ <!-- Snippet so the right-click branch can spread bits-ui's trigger props
866
+ onto the SAME `<media-player>` (a wrapper would break vidstack's
867
+ fullscreen target). -->
868
+ {#snippet rootMediaPlayer(extraProps: Record<string, unknown>)}
869
+ <media-player
870
+ bind:this={ref}
871
+ class={cn(
872
+ 'relative block w-full overflow-hidden [border-radius:calc(var(--era-rd-lg)+var(--era-gap))] bg-black',
873
+ className
874
+ )}
875
+ {src}
876
+ {poster}
877
+ {preload}
878
+ {load}
879
+ {crossorigin}
880
+ muted={muted ? '' : undefined}
881
+ autoplay={autoplay ? '' : undefined}
882
+ loop={loop ? '' : undefined}
883
+ view-type={viewType}
884
+ stream-type={streamType}
885
+ aspect-ratio={aspectRatio}
886
+ fullscreen-orientation={fullscreenOrientation}
887
+ controls={controls ? '' : undefined}
888
+ playsinline={playsinline ? '' : undefined}
889
+ user-idle-delay={typeof idleDelay === 'number' ? idleDelay : undefined}
890
+ key-disabled={keyDisabled ? '' : undefined}
891
+ key-target={keyTarget}
892
+ key-shortcuts={keyShortcuts ? JSON.stringify(keyShortcuts) : undefined}
893
+ {...extraProps}
894
+ >
895
+ <media-outlet>
896
+ {#if !controls && !children && mobileMode}
897
+ <!-- Mobile only: vidstack's gesture component carries an inherent
898
+ 250 ms delay on single-tap (waits to disambiguate from a
899
+ double-tap), which is fine here because the dbl-tap-seek
900
+ halves justify the wait. Desktop uses a plain <button>
901
+ overlay below for instant click response. -->
902
+ <media-gesture
903
+ event="pointerup"
904
+ action="toggle:paused"
905
+ class="absolute inset-0"
906
+ aria-label={playLabel}
907
+ ></media-gesture>
908
+ <media-gesture
909
+ event="dblpointerup"
910
+ action="seek:-10"
911
+ class="absolute top-0 bottom-0 left-0 w-1/2"
912
+ aria-label="Back 10 seconds"
913
+ ></media-gesture>
914
+ <media-gesture
915
+ event="dblpointerup"
916
+ action="seek:10"
917
+ class="absolute top-0 right-0 bottom-0 w-1/2"
918
+ aria-label="Forward 10 seconds"
919
+ ></media-gesture>
920
+ {/if}
921
+ {#if chapterTrack}
922
+ <track
923
+ src={chapterTrack.src}
924
+ label={chapterTrack.label}
925
+ srclang={chapterTrack.srclang ?? 'en-US'}
926
+ kind="chapters"
927
+ default={chapterTrack.default ?? true}
928
+ />
929
+ {/if}
930
+ </media-outlet>
931
+
932
+ {#if children}
933
+ {@render children()}
934
+ {:else if !controls}
935
+ {#if poster && !started}
936
+ <img
937
+ src={poster}
938
+ alt=""
939
+ aria-hidden="true"
940
+ class="pointer-events-none absolute inset-0 z-0 size-full object-cover"
941
+ />
942
+ {/if}
943
+
944
+ {#if !mobileMode}
945
+ <!-- Desktop: instant click-to-toggle. Mobile uses media-gesture
946
+ elements inside the outlet (above) for dbl-tap-seek. -->
947
+ <button
948
+ type="button"
949
+ aria-label={playLabel}
950
+ class="absolute inset-0 z-0 cursor-pointer bg-transparent"
951
+ onclick={togglePlay}
952
+ ></button>
953
+ {/if}
954
+
955
+ {#if waiting}
956
+ <span
957
+ aria-hidden="true"
958
+ class="pointer-events-none absolute inset-0 z-10 flex items-center justify-center"
959
+ >
960
+ <LoaderCircle class="size-(--era-h-lg) animate-spin text-fg" />
961
+ </span>
962
+ {/if}
963
+
964
+ <Bar
965
+ class={barClass}
966
+ onpointerenter={() => (barHovered = true)}
967
+ onpointerleave={() => (barHovered = false)}
968
+ >
969
+ {#if showSkip && !mobileMode}
970
+ <Button
971
+ icon
972
+ aria-label={`Back ${skipBy} seconds`}
973
+ onclick={(e: MouseEvent) => seekBy(-skipBy, e)}
974
+ >
975
+ <Rewind class="size-(--era-h-xs)" aria-hidden="true" />
976
+ </Button>
977
+ {/if}
978
+
979
+ {#if showPlay && !mobileMode}
980
+ <Button icon aria-label={playLabel} onclick={togglePlay}>
981
+ {#if ended}
982
+ <RotateCcw class="size-(--era-h-xs)" aria-hidden="true" />
983
+ {:else if paused}
984
+ <Play class="size-(--era-h-xs)" aria-hidden="true" />
985
+ {:else}
986
+ <Pause class="size-(--era-h-xs)" aria-hidden="true" />
987
+ {/if}
988
+ </Button>
989
+ {/if}
990
+
991
+ {#if showSkip && !mobileMode}
992
+ <Button
993
+ icon
994
+ aria-label={`Forward ${skipBy} seconds`}
995
+ onclick={(e: MouseEvent) => seekBy(skipBy, e)}
996
+ >
997
+ <FastForward class="size-(--era-h-xs)" aria-hidden="true" />
998
+ </Button>
999
+ {/if}
1000
+
1001
+ {#if showTime && !mobileMode}
1002
+ <Badge class="shrink-0 bg-transparent tabular-nums">
1003
+ {shownTimeLabel}
1004
+ </Badge>
1005
+ {/if}
1006
+
1007
+ <div
1008
+ bind:this={seekTimelineRef}
1009
+ role="slider"
1010
+ tabindex={seekDisabled ? -1 : 0}
1011
+ aria-label="Seek"
1012
+ aria-valuemin={0}
1013
+ aria-valuemax={Math.round(timeMax * 100) / 100}
1014
+ aria-valuenow={seekAriaValue}
1015
+ aria-valuetext={seekValueText}
1016
+ aria-disabled={seekDisabled}
1017
+ class="relative h-(--era-h-sm) min-w-0 flex-1 touch-none select-none focus:outline-none focus-visible:outline focus-visible:outline-offset-2 focus-visible:outline-fg data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50"
1018
+ style="--era-video-played-scale: 0; --era-video-buffered-scale: 0; --era-video-hover-pct: 0%;"
1019
+ data-seek-root
1020
+ data-disabled={seekDisabled}
1021
+ onpointerdown={handleSeekPointerDown}
1022
+ onpointermove={handleSeekPointerMove}
1023
+ onpointerup={handleSeekPointerUp}
1024
+ onpointercancel={handleSeekPointerCancel}
1025
+ onpointerenter={handleSeekPointerEnter}
1026
+ onpointerleave={handleSeekPointerLeave}
1027
+ onkeydown={handleSeekKeydown}
1028
+ >
1029
+ <span
1030
+ aria-hidden="true"
1031
+ class="pointer-events-none absolute inset-0 rounded-(--era-rd-sm)"
1032
+ >
1033
+ <span
1034
+ class="absolute inset-(--era-xs-inset-sm) overflow-hidden rounded-(--era-rd-xs) bg-3 shadow-(--era-shadow)"
1035
+ >
1036
+ <span
1037
+ class="absolute inset-y-0 left-0 w-full origin-left scale-x-(--era-video-buffered-scale) bg-4"
1038
+ ></span>
1039
+ <span
1040
+ class="absolute inset-y-0 left-0 w-full origin-left scale-x-(--era-video-played-scale) bg-fg"
1041
+ ></span>
1042
+ {#each chapterMarkers as chapter (chapter.start)}
1043
+ <span
1044
+ aria-hidden="true"
1045
+ title={chapter.text}
1046
+ class="absolute inset-y-0 w-px bg-2"
1047
+ style="left: {chapter.left}%"
1048
+ ></span>
1049
+ {/each}
1050
+ </span>
1051
+ </span>
1052
+ {#if seekPreviewVisible}
1053
+ <!--
1054
+ Anchored to the seek-timeline div, but the timeline sits
1055
+ centered inside the taller Bar (h-lg around h-sm). To
1056
+ keep a `--era-gap`-sized clearance above the visible Bar
1057
+ surface — not just above the timeline div's top — we
1058
+ add half the bar/timeline height delta to the offset.
1059
+ -->
1060
+ <span
1061
+ aria-hidden="true"
1062
+ class="pointer-events-none absolute bottom-[calc(100%+(var(--era-h-lg)-var(--era-h-sm))/2+var(--era-gap))] left-(--era-video-hover-pct) inline-flex h-(--era-h-xxs) -translate-x-1/2 items-center gap-(--era-gap) rounded-(--era-rd-xxs) bg-(--era-surface-bg-elevated) px-(--era-inset-xxs) font-mono text-body leading-none whitespace-nowrap text-fg shadow-(--era-shadow-lg) glass-blur"
1063
+ >
1064
+ <span class="tabular-nums">{hoverTimeLabel}</span>
1065
+ {#if hoverChapter}
1066
+ <span class="text-muted">{hoverChapter.text}</span>
1067
+ {/if}
1068
+ </span>
1069
+ {/if}
1070
+ </div>
1071
+
1072
+ {#if showTime && !mobileMode}
1073
+ <Badge class="shrink-0 bg-transparent tabular-nums">
1074
+ {durationLabel}
1075
+ </Badge>
1076
+ {/if}
1077
+
1078
+ {#if showVolume && !mobileMode}
1079
+ <div
1080
+ class={volumeClusterClass}
1081
+ data-volume-cluster={resolvedVolumeOrientation === 'vertical' ? '' : undefined}
1082
+ >
1083
+ <Button
1084
+ icon
1085
+ aria-label={muteLabel}
1086
+ data-mute-button={resolvedVolumeOrientation === 'vertical' ? '' : undefined}
1087
+ onclick={(e: MouseEvent) => remote.toggleMuted(e)}
1088
+ >
1089
+ {#if mutedState || volumeState === 0}
1090
+ <VolumeX class="size-(--era-h-xs)" aria-hidden="true" />
1091
+ {:else if volumeState < 0.5}
1092
+ <Volume1 class="size-(--era-h-xs)" aria-hidden="true" />
1093
+ {:else}
1094
+ <Volume2 class="size-(--era-h-xs)" aria-hidden="true" />
1095
+ {/if}
1096
+ </Button>
1097
+
1098
+ <div
1099
+ class={volumeSleeveClass}
1100
+ data-volume-sleeve={resolvedVolumeOrientation === 'vertical' ? '' : undefined}
1101
+ >
1102
+ <BitsSlider.Root
1103
+ type="single"
1104
+ value={shownVolume}
1105
+ min={0}
1106
+ max={1}
1107
+ step={VOLUME_STEP}
1108
+ orientation={resolvedVolumeOrientation}
1109
+ aria-label="Volume"
1110
+ class={volumeSliderClass}
1111
+ onValueChange={(v: number | number[]) => remote.changeVolume(toScalar(v))}
1112
+ >
1113
+ {#snippet children({ thumbs })}
1114
+ <BitsSlider.Range class={volumeRangeClass} />
1115
+ {#each thumbs as index (index)}
1116
+ <BitsSlider.Thumb {index} class={volumeThumbClass} />
1117
+ {/each}
1118
+ {/snippet}
1119
+ </BitsSlider.Root>
1120
+ </div>
1121
+ </div>
1122
+ {/if}
1123
+
1124
+ {#if showSpeed && !mobileMode}
1125
+ <DropdownMenu.Root
1126
+ onOpenChange={(open: boolean) => {
1127
+ if (open) remote.pauseUserIdle();
1128
+ else remote.resumeUserIdle();
1129
+ }}
1130
+ >
1131
+ <BitsDropdownMenu.Trigger>
1132
+ {#snippet child({ props })}
1133
+ <Button icon aria-label="Playback speed" {...props}>
1134
+ <Gauge class="size-(--era-h-xs)" aria-hidden="true" />
1135
+ </Button>
1136
+ {/snippet}
1137
+ </BitsDropdownMenu.Trigger>
1138
+ <DropdownMenu.Content sideOffset={4}>
1139
+ <DropdownMenu.RadioGroup
1140
+ value={String(rateState)}
1141
+ onValueChange={(v: string) => remote.changePlaybackRate(Number(v))}
1142
+ >
1143
+ {#each rateOptions as r (r)}
1144
+ <DropdownMenu.RadioItem value={String(r)}>
1145
+ {#snippet children({ checked })}
1146
+ <span>{r === 1 ? 'Normal' : `${r}x`}</span>
1147
+ {#if checked}
1148
+ <Check class="ml-auto size-(--era-h-xs) text-muted" aria-hidden="true" />
1149
+ {/if}
1150
+ {/snippet}
1151
+ </DropdownMenu.RadioItem>
1152
+ {/each}
1153
+ </DropdownMenu.RadioGroup>
1154
+ </DropdownMenu.Content>
1155
+ </DropdownMenu.Root>
1156
+ {/if}
1157
+
1158
+ {#if showQualityMenu && !mobileMode}
1159
+ <DropdownMenu.Root
1160
+ onOpenChange={(open: boolean) => {
1161
+ if (open) remote.pauseUserIdle();
1162
+ else remote.resumeUserIdle();
1163
+ }}
1164
+ >
1165
+ <BitsDropdownMenu.Trigger>
1166
+ {#snippet child({ props })}
1167
+ <Button icon aria-label="Quality" {...props}>
1168
+ <Settings class="size-(--era-h-xs)" aria-hidden="true" />
1169
+ </Button>
1170
+ {/snippet}
1171
+ </BitsDropdownMenu.Trigger>
1172
+ <DropdownMenu.Content sideOffset={4}>
1173
+ <DropdownMenu.RadioGroup
1174
+ value={qualityValue}
1175
+ onValueChange={(v: string) => changeQuality(v)}
1176
+ >
1177
+ <DropdownMenu.RadioItem value="auto">
1178
+ {#snippet children({ checked })}
1179
+ <span>Auto</span>
1180
+ {#if checked}
1181
+ <Check class="ml-auto size-(--era-h-xs) text-muted" aria-hidden="true" />
1182
+ {/if}
1183
+ {/snippet}
1184
+ </DropdownMenu.RadioItem>
1185
+ {#each qualityOptions as { q, index } (index)}
1186
+ <DropdownMenu.RadioItem value={String(index)}>
1187
+ {#snippet children({ checked })}
1188
+ <span>{q.height}p</span>
1189
+ {#if checked}
1190
+ <Check class="ml-auto size-(--era-h-xs) text-muted" aria-hidden="true" />
1191
+ {/if}
1192
+ {/snippet}
1193
+ </DropdownMenu.RadioItem>
1194
+ {/each}
1195
+ </DropdownMenu.RadioGroup>
1196
+ </DropdownMenu.Content>
1197
+ </DropdownMenu.Root>
1198
+ {/if}
1199
+
1200
+ {#if showPip && canEnterPip && !mobileMode}
1201
+ <Button
1202
+ icon
1203
+ aria-label="Picture-in-Picture"
1204
+ onclick={(e: MouseEvent) => remote.togglePictureInPicture(e)}
1205
+ >
1206
+ {#if pipState}
1207
+ <PictureInPicture2 class="size-(--era-h-xs)" aria-hidden="true" />
1208
+ {:else}
1209
+ <PictureInPicture class="size-(--era-h-xs)" aria-hidden="true" />
1210
+ {/if}
1211
+ </Button>
1212
+ {/if}
1213
+
1214
+ {#if showFullscreen && canEnterFullscreen && !mobileMode}
1215
+ <Button
1216
+ icon
1217
+ aria-label={fullscreenState ? 'Exit fullscreen' : 'Fullscreen'}
1218
+ onclick={(e: MouseEvent) => remote.toggleFullscreen(FULLSCREEN_TARGET, e)}
1219
+ >
1220
+ {#if fullscreenState}
1221
+ <Minimize class="size-(--era-h-xs)" aria-hidden="true" />
1222
+ {:else}
1223
+ <Maximize class="size-(--era-h-xs)" aria-hidden="true" />
1224
+ {/if}
1225
+ </Button>
1226
+ {/if}
1227
+ </Bar>
1228
+ {/if}
1229
+ </media-player>
1230
+ {/snippet}
1231
+
1232
+ {#if showContextMenu && !mobileMode}
1233
+ <ContextMenu.Root
1234
+ onOpenChange={(open: boolean) => {
1235
+ if (open) remote.pauseUserIdle();
1236
+ else remote.resumeUserIdle();
1237
+ }}
1238
+ >
1239
+ <BitsContextMenu.Trigger>
1240
+ {#snippet child({ props: ctxProps }: { props: Record<string, unknown> })}
1241
+ {@render rootMediaPlayer(ctxProps)}
1242
+ {/snippet}
1243
+ </BitsContextMenu.Trigger>
1244
+ <ContextMenu.Content onOpenAutoFocus={(e: Event) => e.preventDefault()}>
1245
+ <ContextMenu.Item onSelect={togglePlay}>
1246
+ {#if ended}
1247
+ <RotateCcw class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1248
+ {:else if paused}
1249
+ <Play class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1250
+ {:else}
1251
+ <Pause class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1252
+ {/if}
1253
+ <span>{playLabel}</span>
1254
+ </ContextMenu.Item>
1255
+
1256
+ {#if showVolume && !mobileMode}
1257
+ <ContextMenu.Item onSelect={(e: Event) => remote.toggleMuted(e)}>
1258
+ {#if mutedState || volumeState === 0}
1259
+ <VolumeX class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1260
+ {:else if volumeState < 0.5}
1261
+ <Volume1 class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1262
+ {:else}
1263
+ <Volume2 class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1264
+ {/if}
1265
+ <span>{muteLabel}</span>
1266
+ </ContextMenu.Item>
1267
+ {/if}
1268
+
1269
+ {#if showSpeed && !mobileMode}
1270
+ <ContextMenu.Sub>
1271
+ <ContextMenu.SubTrigger>
1272
+ <Gauge class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1273
+ <span class="flex-1">Speed</span>
1274
+ <span class="ml-auto pl-(--era-gap) text-muted">
1275
+ {rateState === 1 ? '1x' : `${rateState}x`}
1276
+ </span>
1277
+ </ContextMenu.SubTrigger>
1278
+ <ContextMenu.SubContent>
1279
+ <ContextMenu.RadioGroup
1280
+ value={String(rateState)}
1281
+ onValueChange={(v: string) => remote.changePlaybackRate(Number(v))}
1282
+ >
1283
+ {#each rateOptions as r (r)}
1284
+ <ContextMenu.RadioItem value={String(r)}>
1285
+ <span>{r === 1 ? 'Normal' : `${r}x`}</span>
1286
+ </ContextMenu.RadioItem>
1287
+ {/each}
1288
+ </ContextMenu.RadioGroup>
1289
+ </ContextMenu.SubContent>
1290
+ </ContextMenu.Sub>
1291
+ {/if}
1292
+
1293
+ {#if showQualityMenu && !mobileMode}
1294
+ <ContextMenu.Sub>
1295
+ <ContextMenu.SubTrigger>
1296
+ <Settings class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1297
+ <span class="flex-1">Quality</span>
1298
+ <span class="ml-auto pl-(--era-gap) text-muted">{qualityHint}</span>
1299
+ </ContextMenu.SubTrigger>
1300
+ <ContextMenu.SubContent>
1301
+ <ContextMenu.RadioGroup
1302
+ value={qualityValue}
1303
+ onValueChange={(v: string) => changeQuality(v)}
1304
+ >
1305
+ <ContextMenu.RadioItem value="auto">Auto</ContextMenu.RadioItem>
1306
+ {#each qualityOptions as { q, index } (index)}
1307
+ <ContextMenu.RadioItem value={String(index)}>{q.height}p</ContextMenu.RadioItem>
1308
+ {/each}
1309
+ </ContextMenu.RadioGroup>
1310
+ </ContextMenu.SubContent>
1311
+ </ContextMenu.Sub>
1312
+ {/if}
1313
+
1314
+ {#if showPip && canEnterPip && !mobileMode}
1315
+ <ContextMenu.Item onSelect={(e: Event) => remote.togglePictureInPicture(e)}>
1316
+ {#if pipState}
1317
+ <PictureInPicture2 class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1318
+ {:else}
1319
+ <PictureInPicture class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1320
+ {/if}
1321
+ <span>{pipState ? 'Exit picture-in-picture' : 'Picture-in-picture'}</span>
1322
+ </ContextMenu.Item>
1323
+ {/if}
1324
+
1325
+ {#if canEnterFullscreen}
1326
+ <ContextMenu.Item onSelect={(e: Event) => remote.toggleFullscreen(FULLSCREEN_TARGET, e)}>
1327
+ {#if fullscreenState}
1328
+ <Minimize class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1329
+ {:else}
1330
+ <Maximize class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1331
+ {/if}
1332
+ <span>{fullscreenState ? 'Exit fullscreen' : 'Fullscreen'}</span>
1333
+ </ContextMenu.Item>
1334
+ {/if}
1335
+
1336
+ <ContextMenu.Separator />
1337
+
1338
+ <ContextMenu.Item onSelect={downloadVideo}>
1339
+ <Download class="mr-(--era-gap) size-(--era-h-xs)" aria-hidden="true" />
1340
+ <span>Download video</span>
1341
+ </ContextMenu.Item>
1342
+ </ContextMenu.Content>
1343
+ </ContextMenu.Root>
1344
+ {:else}
1345
+ {@render rootMediaPlayer({})}
1346
+ {/if}
1347
+
1348
+ <style>
1349
+ media-player :global(media-outlet) {
1350
+ display: block;
1351
+ width: 100%;
1352
+ }
1353
+ media-player :global(media-outlet video) {
1354
+ display: block;
1355
+ width: 100%;
1356
+ height: auto;
1357
+ }
1358
+ /* Hover keeps it open; `:has([data-active])` keeps it open while the
1359
+ bits-ui slider thumb is being dragged (its `data-active` is set on
1360
+ pointerdown and cleared on pointerup, even if the cursor strays
1361
+ outside the cluster). Plain CSS rather than Tailwind variants on the
1362
+ button sidesteps cascade ambiguity with `rounded-(--era-rd-md)`. */
1363
+ :global([data-volume-cluster] [data-volume-sleeve]) {
1364
+ visibility: hidden;
1365
+ }
1366
+ :global([data-volume-cluster]:hover [data-volume-sleeve]),
1367
+ :global([data-volume-cluster]:has([data-active]) [data-volume-sleeve]) {
1368
+ visibility: visible;
1369
+ }
1370
+ :global([data-volume-cluster]:hover [data-mute-button]),
1371
+ :global([data-volume-cluster]:has([data-active]) [data-mute-button]) {
1372
+ border-top-left-radius: 0;
1373
+ border-top-right-radius: 0;
1374
+ background-color: var(--color-hover);
1375
+ color: var(--color-bright);
1376
+ }
1377
+ </style>