@hanifhan1f/vidstack 1.12.34 → 1.12.35

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 (210) hide show
  1. package/cdn/with-layouts/chunks/{vidstack-BCLumCST.js → vidstack-BEgmmcDO.js} +56 -52
  2. package/cdn/with-layouts/chunks/{vidstack-DJyGEdCH.js → vidstack-t3PBZMbl.js} +1 -1
  3. package/cdn/with-layouts/vidstack.js +1 -1
  4. package/dev/chunks/{vidstack-B__DfQsT.js → vidstack-BReSQAMt.js} +10 -6
  5. package/dev/define/templates/vidstack-audio-layout.js +1 -1
  6. package/dev/define/templates/vidstack-video-layout.js +7 -5
  7. package/dev/define/vidstack-player-default-layout.js +1 -1
  8. package/dev/define/vidstack-player-layouts.js +1 -1
  9. package/dev/vidstack-elements.js +1 -1
  10. package/dev/vidstack.js +0 -4
  11. package/elements.d.ts +1 -1
  12. package/global/player.d.ts +1 -1
  13. package/global/plyr.d.ts +2 -2
  14. package/index.d.ts +2 -2
  15. package/package.json +1 -1
  16. package/prod/chunks/{vidstack-BnEo_Sla.js → vidstack-B0glDgAI.js} +10 -6
  17. package/prod/define/templates/vidstack-audio-layout.js +1 -1
  18. package/prod/define/templates/vidstack-video-layout.js +7 -5
  19. package/prod/define/vidstack-player-default-layout.js +1 -1
  20. package/prod/define/vidstack-player-layouts.js +1 -1
  21. package/prod/vidstack-elements.js +1 -1
  22. package/server/chunks/{vidstack-BIGdJnUK.js → vidstack-nANS1jfu.js} +16 -10
  23. package/server/define/vidstack-player-default-layout.js +1 -1
  24. package/server/define/vidstack-player-layouts.js +1 -1
  25. package/server/vidstack-elements.js +1 -1
  26. package/types/{vidstack-DYLKXUvI.d.ts → vidstack-CZqFq0VF.d.ts} +13 -1
  27. package/cdn/chunks/vidstack-8JHLDxl5.js +0 -1
  28. package/cdn/chunks/vidstack-BF7lZRtq.js +0 -3
  29. package/cdn/chunks/vidstack-BYgY9wmd.js +0 -1
  30. package/cdn/chunks/vidstack-BYpysj84.js +0 -1
  31. package/cdn/chunks/vidstack-Bjo5esRp.js +0 -1
  32. package/cdn/chunks/vidstack-BkxGdzTJ.js +0 -16
  33. package/cdn/chunks/vidstack-BuL67v3q.js +0 -1
  34. package/cdn/chunks/vidstack-Bzk6lVKb.js +0 -1
  35. package/cdn/chunks/vidstack-C0msPRTd.js +0 -3
  36. package/cdn/chunks/vidstack-C1FlyyzK.js +0 -1
  37. package/cdn/chunks/vidstack-CIjxJCz3.js +0 -1
  38. package/cdn/chunks/vidstack-CioT3Yw2.js +0 -1
  39. package/cdn/chunks/vidstack-Cj0I-Rec.js +0 -1
  40. package/cdn/chunks/vidstack-CmpbA3Yd.js +0 -16
  41. package/cdn/chunks/vidstack-CnWKPIKT.js +0 -16
  42. package/cdn/chunks/vidstack-CrqkytHl.js +0 -1
  43. package/cdn/chunks/vidstack-D0M8R0ZU.js +0 -1
  44. package/cdn/chunks/vidstack-D40FSa5B.js +0 -3
  45. package/cdn/chunks/vidstack-D84Fzc__.js +0 -16
  46. package/cdn/chunks/vidstack-DD2JwFVU.js +0 -1
  47. package/cdn/chunks/vidstack-DQvyz7Mm.js +0 -1
  48. package/cdn/chunks/vidstack-Dd9fqVv6.js +0 -1
  49. package/cdn/chunks/vidstack-DfDZuHNP.js +0 -1
  50. package/cdn/chunks/vidstack-uMxrPflF.js +0 -1
  51. package/cdn/chunks/vidstack-xjJ-ui_l.js +0 -1
  52. package/cdn/chunks/vidstack-zemsqC5d.js +0 -1
  53. package/cdn/providers/vidstack-audio-2Dt_Ivbp.js +0 -1
  54. package/cdn/providers/vidstack-audio-BOGYlExy.js +0 -1
  55. package/cdn/providers/vidstack-dash-CUtD4e6q.js +0 -1
  56. package/cdn/providers/vidstack-dash-D4ZARr66.js +0 -1
  57. package/cdn/providers/vidstack-google-cast-BdORATUX.js +0 -1
  58. package/cdn/providers/vidstack-hls-8-552IuX.js +0 -1
  59. package/cdn/providers/vidstack-hls-R25Kb6DP.js +0 -1
  60. package/cdn/providers/vidstack-html-BvVaN2VT.js +0 -1
  61. package/cdn/providers/vidstack-html-DaAUJYsD.js +0 -1
  62. package/cdn/providers/vidstack-video-BnwQZKER.js +0 -1
  63. package/cdn/providers/vidstack-video-Csvox7SO.js +0 -1
  64. package/cdn/providers/vidstack-vimeo-D4Z96kg2.js +0 -1
  65. package/cdn/providers/vidstack-vimeo-gJmBqtLK.js +0 -1
  66. package/cdn/providers/vidstack-youtube-Chl_dTAz.js +0 -1
  67. package/cdn/providers/vidstack-youtube-DiND6h3s.js +0 -1
  68. package/cdn/with-layouts/chunks/vidstack-4liSokT6.js +0 -1
  69. package/cdn/with-layouts/chunks/vidstack-B97B8XDc.js +0 -3
  70. package/cdn/with-layouts/chunks/vidstack-BD5YoTt5.js +0 -937
  71. package/cdn/with-layouts/chunks/vidstack-BGhRKayG.js +0 -914
  72. package/cdn/with-layouts/chunks/vidstack-BL_lNyW_.js +0 -1
  73. package/cdn/with-layouts/chunks/vidstack-BMhNagfl.js +0 -1
  74. package/cdn/with-layouts/chunks/vidstack-BP3ybDy9.js +0 -912
  75. package/cdn/with-layouts/chunks/vidstack-BbFHhcVG.js +0 -1
  76. package/cdn/with-layouts/chunks/vidstack-BjOOdDcQ.js +0 -1
  77. package/cdn/with-layouts/chunks/vidstack-C5AP9wid.js +0 -1
  78. package/cdn/with-layouts/chunks/vidstack-CS2aNc61.js +0 -1
  79. package/cdn/with-layouts/chunks/vidstack-CXEcXyBI.js +0 -1
  80. package/cdn/with-layouts/chunks/vidstack-Ciq-n5rg.js +0 -1
  81. package/cdn/with-layouts/chunks/vidstack-CmuGllcj.js +0 -1
  82. package/cdn/with-layouts/chunks/vidstack-CyNByJUW.js +0 -912
  83. package/cdn/with-layouts/chunks/vidstack-D-3_fAsK.js +0 -1
  84. package/cdn/with-layouts/chunks/vidstack-DCaNJN4T.js +0 -1
  85. package/cdn/with-layouts/chunks/vidstack-DKqYI_HJ.js +0 -1
  86. package/cdn/with-layouts/chunks/vidstack-DLGH9jfs.js +0 -1
  87. package/cdn/with-layouts/chunks/vidstack-DLVdcWrK.js +0 -3
  88. package/cdn/with-layouts/chunks/vidstack-DPO7J4-v.js +0 -3
  89. package/cdn/with-layouts/chunks/vidstack-DWjB11vV.js +0 -1
  90. package/cdn/with-layouts/chunks/vidstack-Dd3L-eQj.js +0 -1
  91. package/cdn/with-layouts/chunks/vidstack-Dh2GOjra.js +0 -1
  92. package/cdn/with-layouts/chunks/vidstack-DhNpv7SU.js +0 -1
  93. package/cdn/with-layouts/chunks/vidstack-QW5tTAS4.js +0 -897
  94. package/cdn/with-layouts/chunks/vidstack-T2rZVigk.js +0 -912
  95. package/cdn/with-layouts/chunks/vidstack-Xe_d7ovA.js +0 -1
  96. package/cdn/with-layouts/chunks/vidstack-wt2OT4N7.js +0 -1
  97. package/cdn/with-layouts/providers/vidstack-audio-Bw1csc6N.js +0 -1
  98. package/cdn/with-layouts/providers/vidstack-audio-CwoQJvl2.js +0 -1
  99. package/cdn/with-layouts/providers/vidstack-dash-CJsKJfLI.js +0 -1
  100. package/cdn/with-layouts/providers/vidstack-dash-DHRMFG4Y.js +0 -1
  101. package/cdn/with-layouts/providers/vidstack-google-cast-BSYJYn-o.js +0 -1
  102. package/cdn/with-layouts/providers/vidstack-hls-DG1rTEqu.js +0 -1
  103. package/cdn/with-layouts/providers/vidstack-hls-ji26kFdQ.js +0 -1
  104. package/cdn/with-layouts/providers/vidstack-html-BvHMxtoe.js +0 -1
  105. package/cdn/with-layouts/providers/vidstack-html-CoKFAYW5.js +0 -1
  106. package/cdn/with-layouts/providers/vidstack-video-1Uj5cNP2.js +0 -1
  107. package/cdn/with-layouts/providers/vidstack-video-CIxFJ9Z1.js +0 -1
  108. package/cdn/with-layouts/providers/vidstack-vimeo-CNLKOGMa.js +0 -1
  109. package/cdn/with-layouts/providers/vidstack-vimeo-DACTbJaQ.js +0 -1
  110. package/cdn/with-layouts/providers/vidstack-youtube-D1e-LE-8.js +0 -1
  111. package/cdn/with-layouts/providers/vidstack-youtube-RoLp-I6u.js +0 -1
  112. package/dev/chunks/vidstack-03oQOdB7.js +0 -58
  113. package/dev/chunks/vidstack-0XhA3AD_.js +0 -5181
  114. package/dev/chunks/vidstack-44ILR0Cb.js +0 -1521
  115. package/dev/chunks/vidstack-B4XOm7dP.js +0 -104
  116. package/dev/chunks/vidstack-BJsZjPkB.js +0 -204
  117. package/dev/chunks/vidstack-BXSB7eI9.js +0 -58
  118. package/dev/chunks/vidstack-BaGbgcvz.js +0 -107
  119. package/dev/chunks/vidstack-Blfm1k-4.js +0 -1520
  120. package/dev/chunks/vidstack-Bo8BNFJ2.js +0 -2986
  121. package/dev/chunks/vidstack-Bs54kFSz.js +0 -66
  122. package/dev/chunks/vidstack-C3N4zIuV.js +0 -254
  123. package/dev/chunks/vidstack-C4aPQ7hZ.js +0 -1482
  124. package/dev/chunks/vidstack-C6OqdJO7.js +0 -114
  125. package/dev/chunks/vidstack-CAL4iu_K.js +0 -1482
  126. package/dev/chunks/vidstack-CEjYxSqZ.js +0 -297
  127. package/dev/chunks/vidstack-CJCnHmKE.js +0 -104
  128. package/dev/chunks/vidstack-CQdFhXSo.js +0 -204
  129. package/dev/chunks/vidstack-CSryZFvY.js +0 -1521
  130. package/dev/chunks/vidstack-C_rvOKWp.js +0 -33
  131. package/dev/chunks/vidstack-CaudO1jl.js +0 -109
  132. package/dev/chunks/vidstack-CcQdBWil.js +0 -58
  133. package/dev/chunks/vidstack-Cky9ors4.js +0 -297
  134. package/dev/chunks/vidstack-DAOcbKGP.js +0 -254
  135. package/dev/chunks/vidstack-DD_3HszA.js +0 -1520
  136. package/dev/chunks/vidstack-DKaohJzR.js +0 -5181
  137. package/dev/chunks/vidstack-DLXCqdYV.js +0 -3010
  138. package/dev/chunks/vidstack-DS7nRfge.js +0 -204
  139. package/dev/chunks/vidstack-DWtK42Sh.js +0 -1483
  140. package/dev/chunks/vidstack-D_LvMxPr.js +0 -204
  141. package/dev/chunks/vidstack-Db1-Hg_U.js +0 -297
  142. package/dev/chunks/vidstack-DrczgsqN.js +0 -297
  143. package/dev/chunks/vidstack-EoLRQZbs.js +0 -2986
  144. package/dev/chunks/vidstack-FKkY62Dr.js +0 -104
  145. package/dev/chunks/vidstack-el2dbO0m.js +0 -5181
  146. package/dev/chunks/vidstack-rvhuswgi.js +0 -2986
  147. package/prod/chunks/vidstack-BAqdCFIm.js +0 -4771
  148. package/prod/chunks/vidstack-BHqGlnGz.js +0 -1482
  149. package/prod/chunks/vidstack-BP49Gz0m.js +0 -58
  150. package/prod/chunks/vidstack-BRZe2BNi.js +0 -107
  151. package/prod/chunks/vidstack-BRnfTkxi.js +0 -297
  152. package/prod/chunks/vidstack-BaaRY-9x.js +0 -201
  153. package/prod/chunks/vidstack-BexQYZop.js +0 -2976
  154. package/prod/chunks/vidstack-BpLd9ASW.js +0 -246
  155. package/prod/chunks/vidstack-C-yd_bAJ.js +0 -4771
  156. package/prod/chunks/vidstack-C05ipjAK.js +0 -1520
  157. package/prod/chunks/vidstack-CA4tDJdF.js +0 -33
  158. package/prod/chunks/vidstack-CFXAYpuh.js +0 -1521
  159. package/prod/chunks/vidstack-CIvL96_j.js +0 -297
  160. package/prod/chunks/vidstack-CYVCrFjx.js +0 -201
  161. package/prod/chunks/vidstack-Cs0fH84E.js +0 -1521
  162. package/prod/chunks/vidstack-D7hJcnN-.js +0 -297
  163. package/prod/chunks/vidstack-DDePVDjt.js +0 -2976
  164. package/prod/chunks/vidstack-DESBVLFp.js +0 -104
  165. package/prod/chunks/vidstack-DMDDSV3t.js +0 -104
  166. package/prod/chunks/vidstack-DXfGRhxZ.js +0 -201
  167. package/prod/chunks/vidstack-D_atbNqH.js +0 -3000
  168. package/prod/chunks/vidstack-DcMkaIHJ.js +0 -2976
  169. package/prod/chunks/vidstack-DnRxQoqP.js +0 -104
  170. package/prod/chunks/vidstack-DwenML7x.js +0 -4771
  171. package/prod/chunks/vidstack-IDWYvfna.js +0 -58
  172. package/prod/chunks/vidstack-Ko2EJadT.js +0 -1483
  173. package/prod/chunks/vidstack-MbEMbVfP.js +0 -109
  174. package/prod/chunks/vidstack-ShUhyBfI.js +0 -201
  175. package/prod/chunks/vidstack-SnIdjCkV.js +0 -58
  176. package/prod/chunks/vidstack-V1jwkH0s.js +0 -66
  177. package/prod/chunks/vidstack-V9U6gsde.js +0 -1482
  178. package/prod/chunks/vidstack-XA3zT5W9.js +0 -297
  179. package/prod/chunks/vidstack-bdt7uOlN.js +0 -114
  180. package/prod/chunks/vidstack-kdaDngIm.js +0 -1520
  181. package/prod/chunks/vidstack-oNEzlviH.js +0 -246
  182. package/server/chunks/vidstack-B2Bc9g7_.js +0 -2000
  183. package/server/chunks/vidstack-B4CWj0Hp.js +0 -381
  184. package/server/chunks/vidstack-B8P1aUCK.js +0 -1503
  185. package/server/chunks/vidstack-B8_v1VQn.js +0 -3059
  186. package/server/chunks/vidstack-BGgfNYAH.js +0 -141
  187. package/server/chunks/vidstack-BGmwlunt.js +0 -3035
  188. package/server/chunks/vidstack-BO8FLks6.js +0 -295
  189. package/server/chunks/vidstack-BosyhF3p.js +0 -207
  190. package/server/chunks/vidstack-C19bj3Wq.js +0 -307
  191. package/server/chunks/vidstack-C8F1EUBn.js +0 -104
  192. package/server/chunks/vidstack-CFTkUXGK.js +0 -295
  193. package/server/chunks/vidstack-CQMB7Msg.js +0 -1502
  194. package/server/chunks/vidstack-CWho6PlG.js +0 -141
  195. package/server/chunks/vidstack-CdBfecZT.js +0 -205
  196. package/server/chunks/vidstack-Cv_Art04.js +0 -4635
  197. package/server/chunks/vidstack-DE4b5Bgx.js +0 -2002
  198. package/server/chunks/vidstack-Db22EuE_.js +0 -207
  199. package/server/chunks/vidstack-DbvCOsqU.js +0 -107
  200. package/server/chunks/vidstack-DgHfFDiw.js +0 -1962
  201. package/server/chunks/vidstack-DhF59-Up.js +0 -4635
  202. package/server/chunks/vidstack-DnkB7eGO.js +0 -207
  203. package/server/chunks/vidstack-DoHmOxNm.js +0 -295
  204. package/server/chunks/vidstack-DsnTqzpL.js +0 -29
  205. package/server/chunks/vidstack-DzWvfg1d.js +0 -1503
  206. package/server/chunks/vidstack-FHGkN5xj.js +0 -566
  207. package/server/chunks/vidstack-PnFpou7g.js +0 -3035
  208. package/server/chunks/vidstack-f5-aflD2.js +0 -104
  209. package/server/chunks/vidstack-gEJMQpTE.js +0 -2001
  210. package/server/chunks/vidstack-n4zAyLEV.js +0 -2139
@@ -1,2986 +0,0 @@
1
- import { ViewController, onDispose, isArray, isString, isNull, effect, peek, listenEvent, ariaBool, isWriteSignal, Component, State, createContext, hasProvidedContext, useContext, EventsController, isNumber, functionThrottle, signal, provideContext, animationFrameThrottle, isObject, method, useState, computed, setAttribute, r, wasEnterKeyPressed, setStyle, isKeyboardEvent, tick, prop, scoped } from './vidstack-C1PwJD_4.js';
2
- import { useMediaContext } from './vidstack-BjxlZzGu.js';
3
- import { hasAnimation, setAttributeIfEmpty, onPress, $ariaBool, setARIALabel, isTouchPinchEvent, observeVisibility, isHTMLElement, isElementParent, isEventInside, isElementVisible, requestScopedAnimationFrame, autoPlacement } from './vidstack-C0SWkbs7.js';
4
- import { isTrackCaptionKind } from './vidstack-C3N4zIuV.js';
5
- import { FocusVisibleController } from './vidstack-Bur7I7Vy.js';
6
- import { clampNumber, round, getNumberOfDecimalPlaces } from './vidstack-Dihypf8P.js';
7
- import { assert } from './vidstack-DbBJlz7I.js';
8
- import { getRequestCredentials } from './vidstack-DdTXMZro.js';
9
- import { watchActiveTextTrack } from './vidstack-CcQdBWil.js';
10
- import { sortVideoQualities } from './vidstack-BTM4ERc7.js';
11
-
12
- class ARIAKeyShortcuts extends ViewController {
13
- #shortcut;
14
- constructor(shortcut) {
15
- super();
16
- this.#shortcut = shortcut;
17
- }
18
- onAttach(el) {
19
- const { $props, ariaKeys } = useMediaContext(), keys = el.getAttribute("aria-keyshortcuts");
20
- if (keys) {
21
- ariaKeys[this.#shortcut] = keys;
22
- {
23
- onDispose(() => {
24
- delete ariaKeys[this.#shortcut];
25
- });
26
- }
27
- return;
28
- }
29
- const shortcuts = $props.keyShortcuts()[this.#shortcut];
30
- if (shortcuts) {
31
- const keys2 = isArray(shortcuts) ? shortcuts.join(" ") : isString(shortcuts) ? shortcuts : shortcuts?.keys;
32
- el.setAttribute("aria-keyshortcuts", isArray(keys2) ? keys2.join(" ") : keys2);
33
- }
34
- }
35
- }
36
-
37
- function padNumberWithZeroes(num, expectedLength) {
38
- const str = String(num);
39
- const actualLength = str.length;
40
- const shouldPad = actualLength < expectedLength;
41
- if (shouldPad) {
42
- const padLength = expectedLength - actualLength;
43
- const padding = `0`.repeat(padLength);
44
- return `${padding}${num}`;
45
- }
46
- return str;
47
- }
48
- function parseTime(duration) {
49
- const hours = Math.trunc(duration / 3600);
50
- const minutes = Math.trunc(duration % 3600 / 60);
51
- const seconds = Math.trunc(duration % 60);
52
- const fraction = Number((duration - Math.trunc(duration)).toPrecision(3));
53
- return {
54
- hours,
55
- minutes,
56
- seconds,
57
- fraction
58
- };
59
- }
60
- function formatTime(duration, { padHrs = null, padMins = null, showHrs = false, showMs = false } = {}) {
61
- const { hours, minutes, seconds, fraction } = parseTime(duration), paddedHours = padHrs ? padNumberWithZeroes(hours, 2) : hours, paddedMinutes = padMins || isNull(padMins) && duration >= 3600 ? padNumberWithZeroes(minutes, 2) : minutes, paddedSeconds = padNumberWithZeroes(seconds, 2), paddedMs = showMs && fraction > 0 ? `.${String(fraction).replace(/^0?\./, "")}` : "", time = `${paddedMinutes}:${paddedSeconds}${paddedMs}`;
62
- return hours > 0 || showHrs ? `${paddedHours}:${time}` : time;
63
- }
64
- function formatSpokenTime(duration) {
65
- const spokenParts = [];
66
- const { hours, minutes, seconds } = parseTime(duration);
67
- if (hours > 0) {
68
- spokenParts.push(`${hours} hour`);
69
- }
70
- if (minutes > 0) {
71
- spokenParts.push(`${minutes} min`);
72
- }
73
- if (seconds > 0 || spokenParts.length === 0) {
74
- spokenParts.push(`${seconds} sec`);
75
- }
76
- return spokenParts.join(" ");
77
- }
78
-
79
- class Popper extends ViewController {
80
- #delegate;
81
- constructor(delegate) {
82
- super();
83
- this.#delegate = delegate;
84
- effect(this.#watchTrigger.bind(this));
85
- }
86
- onDestroy() {
87
- this.#stopAnimationEndListener?.();
88
- this.#stopAnimationEndListener = null;
89
- }
90
- #watchTrigger() {
91
- const trigger = this.#delegate.trigger();
92
- if (!trigger) {
93
- this.hide();
94
- return;
95
- }
96
- const show = this.show.bind(this), hide = this.hide.bind(this);
97
- this.#delegate.listen(trigger, show, hide);
98
- }
99
- #showTimerId = -1;
100
- #hideRafId = -1;
101
- #stopAnimationEndListener = null;
102
- show(trigger) {
103
- this.#cancelShowing();
104
- window.cancelAnimationFrame(this.#hideRafId);
105
- this.#hideRafId = -1;
106
- this.#stopAnimationEndListener?.();
107
- this.#stopAnimationEndListener = null;
108
- this.#showTimerId = window.setTimeout(() => {
109
- this.#showTimerId = -1;
110
- const content = this.#delegate.content();
111
- if (content) content.style.removeProperty("display");
112
- peek(() => this.#delegate.onChange(true, trigger));
113
- }, this.#delegate.showDelay?.() ?? 0);
114
- }
115
- hide(trigger) {
116
- this.#cancelShowing();
117
- peek(() => this.#delegate.onChange(false, trigger));
118
- this.#hideRafId = requestAnimationFrame(() => {
119
- this.#cancelShowing();
120
- this.#hideRafId = -1;
121
- const content = this.#delegate.content();
122
- if (content) {
123
- const onHide = () => {
124
- content.style.display = "none";
125
- this.#stopAnimationEndListener = null;
126
- };
127
- const isAnimated = hasAnimation(content);
128
- if (isAnimated) {
129
- this.#stopAnimationEndListener?.();
130
- const stop = listenEvent(content, "animationend", onHide, { once: true });
131
- this.#stopAnimationEndListener = stop;
132
- } else {
133
- onHide();
134
- }
135
- }
136
- });
137
- }
138
- #cancelShowing() {
139
- window.clearTimeout(this.#showTimerId);
140
- this.#showTimerId = -1;
141
- }
142
- }
143
-
144
- class ToggleButtonController extends ViewController {
145
- static props = {
146
- disabled: false
147
- };
148
- #delegate;
149
- constructor(delegate) {
150
- super();
151
- this.#delegate = delegate;
152
- new FocusVisibleController();
153
- if (delegate.keyShortcut) {
154
- new ARIAKeyShortcuts(delegate.keyShortcut);
155
- }
156
- }
157
- onSetup() {
158
- const { disabled } = this.$props;
159
- this.setAttributes({
160
- "data-pressed": this.#delegate.isPresssed,
161
- "aria-pressed": this.#isARIAPressed.bind(this),
162
- "aria-disabled": () => disabled() ? "true" : null
163
- });
164
- }
165
- onAttach(el) {
166
- setAttributeIfEmpty(el, "tabindex", "0");
167
- setAttributeIfEmpty(el, "role", "button");
168
- setAttributeIfEmpty(el, "type", "button");
169
- }
170
- onConnect(el) {
171
- const events = onPress(el, this.#onMaybePress.bind(this));
172
- for (const type of ["click", "touchstart"]) {
173
- events.add(type, this.#onInteraction.bind(this), {
174
- passive: true
175
- });
176
- }
177
- }
178
- #isARIAPressed() {
179
- return ariaBool(this.#delegate.isPresssed());
180
- }
181
- #onPressed(event) {
182
- if (isWriteSignal(this.#delegate.isPresssed)) {
183
- this.#delegate.isPresssed.set((p) => !p);
184
- }
185
- }
186
- #onMaybePress(event) {
187
- const disabled = this.$props.disabled() || this.el.hasAttribute("data-disabled");
188
- if (disabled) {
189
- event.preventDefault();
190
- event.stopImmediatePropagation();
191
- return;
192
- }
193
- event.preventDefault();
194
- (this.#delegate.onPress ?? this.#onPressed).call(this, event);
195
- }
196
- #onInteraction(event) {
197
- if (this.$props.disabled()) {
198
- event.preventDefault();
199
- event.stopImmediatePropagation();
200
- }
201
- }
202
- }
203
-
204
- class AirPlayButton extends Component {
205
- static props = ToggleButtonController.props;
206
- #media;
207
- constructor() {
208
- super();
209
- new ToggleButtonController({
210
- isPresssed: this.#isPressed.bind(this),
211
- onPress: this.#onPress.bind(this)
212
- });
213
- }
214
- onSetup() {
215
- this.#media = useMediaContext();
216
- const { canAirPlay, isAirPlayConnected } = this.#media.$state;
217
- this.setAttributes({
218
- "data-active": isAirPlayConnected,
219
- "data-supported": canAirPlay,
220
- "data-state": this.#getState.bind(this),
221
- "aria-hidden": $ariaBool(() => !canAirPlay())
222
- });
223
- }
224
- onAttach(el) {
225
- el.setAttribute("data-media-tooltip", "airplay");
226
- setARIALabel(el, this.#getDefaultLabel.bind(this));
227
- }
228
- #onPress(event) {
229
- const remote = this.#media.remote;
230
- remote.requestAirPlay(event);
231
- }
232
- #isPressed() {
233
- const { remotePlaybackType, remotePlaybackState } = this.#media.$state;
234
- return remotePlaybackType() === "airplay" && remotePlaybackState() !== "disconnected";
235
- }
236
- #getState() {
237
- const { remotePlaybackType, remotePlaybackState } = this.#media.$state;
238
- return remotePlaybackType() === "airplay" && remotePlaybackState();
239
- }
240
- #getDefaultLabel() {
241
- const { remotePlaybackState } = this.#media.$state;
242
- return `AirPlay ${remotePlaybackState()}`;
243
- }
244
- }
245
-
246
- class PlayButton extends Component {
247
- static props = ToggleButtonController.props;
248
- #media;
249
- constructor() {
250
- super();
251
- new ToggleButtonController({
252
- isPresssed: this.#isPressed.bind(this),
253
- keyShortcut: "togglePaused",
254
- onPress: this.#onPress.bind(this)
255
- });
256
- }
257
- onSetup() {
258
- this.#media = useMediaContext();
259
- const { paused, ended } = this.#media.$state;
260
- this.setAttributes({
261
- "data-paused": paused,
262
- "data-ended": ended
263
- });
264
- }
265
- onAttach(el) {
266
- el.setAttribute("data-media-tooltip", "play");
267
- setARIALabel(el, "Play");
268
- }
269
- #onPress(event) {
270
- const remote = this.#media.remote;
271
- this.#isPressed() ? remote.pause(event) : remote.play(event);
272
- }
273
- #isPressed() {
274
- const { paused } = this.#media.$state;
275
- return !paused();
276
- }
277
- }
278
-
279
- class CaptionButton extends Component {
280
- static props = ToggleButtonController.props;
281
- #media;
282
- constructor() {
283
- super();
284
- new ToggleButtonController({
285
- isPresssed: this.#isPressed.bind(this),
286
- keyShortcut: "toggleCaptions",
287
- onPress: this.#onPress.bind(this)
288
- });
289
- }
290
- onSetup() {
291
- this.#media = useMediaContext();
292
- this.setAttributes({
293
- "data-active": this.#isPressed.bind(this),
294
- "data-supported": () => !this.#isHidden(),
295
- "aria-hidden": $ariaBool(this.#isHidden.bind(this))
296
- });
297
- }
298
- onAttach(el) {
299
- el.setAttribute("data-media-tooltip", "caption");
300
- setARIALabel(el, "Captions");
301
- }
302
- #onPress(event) {
303
- this.#media.remote.toggleCaptions(event);
304
- }
305
- #isPressed() {
306
- const { textTrack } = this.#media.$state, track = textTrack();
307
- return !!track && isTrackCaptionKind(track);
308
- }
309
- #isHidden() {
310
- const { hasCaptions } = this.#media.$state;
311
- return !hasCaptions();
312
- }
313
- }
314
-
315
- class FullscreenButton extends Component {
316
- static props = {
317
- ...ToggleButtonController.props,
318
- target: "prefer-media"
319
- };
320
- #media;
321
- constructor() {
322
- super();
323
- new ToggleButtonController({
324
- isPresssed: this.#isPressed.bind(this),
325
- keyShortcut: "toggleFullscreen",
326
- onPress: this.#onPress.bind(this)
327
- });
328
- }
329
- onSetup() {
330
- this.#media = useMediaContext();
331
- const { fullscreen } = this.#media.$state, isSupported = this.#isSupported.bind(this);
332
- this.setAttributes({
333
- "data-active": fullscreen,
334
- "data-supported": isSupported,
335
- "aria-hidden": $ariaBool(() => !isSupported())
336
- });
337
- }
338
- onAttach(el) {
339
- el.setAttribute("data-media-tooltip", "fullscreen");
340
- setARIALabel(el, "Fullscreen");
341
- }
342
- #onPress(event) {
343
- const remote = this.#media.remote, target = this.$props.target();
344
- this.#isPressed() ? remote.exitFullscreen(target, event) : remote.enterFullscreen(target, event);
345
- }
346
- #isPressed() {
347
- const { fullscreen } = this.#media.$state;
348
- return fullscreen();
349
- }
350
- #isSupported() {
351
- const { canFullscreen } = this.#media.$state;
352
- return canFullscreen();
353
- }
354
- }
355
-
356
- class MuteButton extends Component {
357
- static props = ToggleButtonController.props;
358
- #media;
359
- constructor() {
360
- super();
361
- new ToggleButtonController({
362
- isPresssed: this.#isPressed.bind(this),
363
- keyShortcut: "toggleMuted",
364
- onPress: this.#onPress.bind(this)
365
- });
366
- }
367
- onSetup() {
368
- this.#media = useMediaContext();
369
- this.setAttributes({
370
- "data-muted": this.#isPressed.bind(this),
371
- "data-state": this.#getState.bind(this)
372
- });
373
- }
374
- onAttach(el) {
375
- el.setAttribute("data-media-mute-button", "");
376
- el.setAttribute("data-media-tooltip", "mute");
377
- setARIALabel(el, "Mute");
378
- }
379
- #onPress(event) {
380
- const remote = this.#media.remote;
381
- this.#isPressed() ? remote.unmute(event) : remote.mute(event);
382
- }
383
- #isPressed() {
384
- const { muted, volume } = this.#media.$state;
385
- return muted() || volume() === 0;
386
- }
387
- #getState() {
388
- const { muted, volume } = this.#media.$state, $volume = volume();
389
- if (muted() || $volume === 0) return "muted";
390
- else if ($volume >= 0.5) return "high";
391
- else if ($volume < 0.5) return "low";
392
- }
393
- }
394
-
395
- class PIPButton extends Component {
396
- static props = ToggleButtonController.props;
397
- #media;
398
- constructor() {
399
- super();
400
- new ToggleButtonController({
401
- isPresssed: this.#isPressed.bind(this),
402
- keyShortcut: "togglePictureInPicture",
403
- onPress: this.#onPress.bind(this)
404
- });
405
- }
406
- onSetup() {
407
- this.#media = useMediaContext();
408
- const { pictureInPicture } = this.#media.$state, isSupported = this.#isSupported.bind(this);
409
- this.setAttributes({
410
- "data-active": pictureInPicture,
411
- "data-supported": isSupported,
412
- "aria-hidden": $ariaBool(() => !isSupported())
413
- });
414
- }
415
- onAttach(el) {
416
- el.setAttribute("data-media-tooltip", "pip");
417
- setARIALabel(el, "PiP");
418
- }
419
- #onPress(event) {
420
- const remote = this.#media.remote;
421
- this.#isPressed() ? remote.exitPictureInPicture(event) : remote.enterPictureInPicture(event);
422
- }
423
- #isPressed() {
424
- const { pictureInPicture } = this.#media.$state;
425
- return pictureInPicture();
426
- }
427
- #isSupported() {
428
- const { canPictureInPicture } = this.#media.$state;
429
- return canPictureInPicture();
430
- }
431
- }
432
-
433
- class SeekButton extends Component {
434
- static props = {
435
- disabled: false,
436
- seconds: 30
437
- };
438
- #media;
439
- constructor() {
440
- super();
441
- new FocusVisibleController();
442
- }
443
- onSetup() {
444
- this.#media = useMediaContext();
445
- const { seeking } = this.#media.$state, { seconds } = this.$props, isSupported = this.#isSupported.bind(this);
446
- this.setAttributes({
447
- seconds,
448
- "data-seeking": seeking,
449
- "data-supported": isSupported,
450
- "aria-hidden": $ariaBool(() => !isSupported())
451
- });
452
- }
453
- onAttach(el) {
454
- setAttributeIfEmpty(el, "tabindex", "0");
455
- setAttributeIfEmpty(el, "role", "button");
456
- setAttributeIfEmpty(el, "type", "button");
457
- el.setAttribute("data-media-tooltip", "seek");
458
- setARIALabel(el, this.#getDefaultLabel.bind(this));
459
- }
460
- onConnect(el) {
461
- onPress(el, this.#onPress.bind(this));
462
- }
463
- #isSupported() {
464
- const { canSeek } = this.#media.$state;
465
- return canSeek();
466
- }
467
- #getDefaultLabel() {
468
- const { seconds } = this.$props;
469
- return `Seek ${seconds() > 0 ? "forward" : "backward"} ${seconds()} seconds`;
470
- }
471
- #onPress(event) {
472
- const { seconds, disabled } = this.$props;
473
- if (disabled()) return;
474
- const { currentTime } = this.#media.$state, seekTo = currentTime() + seconds();
475
- this.#media.remote.seek(seekTo, event);
476
- }
477
- }
478
-
479
- class LiveButton extends Component {
480
- static props = {
481
- disabled: false
482
- };
483
- #media;
484
- constructor() {
485
- super();
486
- new FocusVisibleController();
487
- }
488
- onSetup() {
489
- this.#media = useMediaContext();
490
- const { disabled } = this.$props, { live, liveEdge } = this.#media.$state, isHidden = () => !live();
491
- this.setAttributes({
492
- "data-edge": liveEdge,
493
- "data-hidden": isHidden,
494
- "aria-disabled": $ariaBool(() => disabled() || liveEdge()),
495
- "aria-hidden": $ariaBool(isHidden)
496
- });
497
- }
498
- onAttach(el) {
499
- setAttributeIfEmpty(el, "tabindex", "0");
500
- setAttributeIfEmpty(el, "role", "button");
501
- setAttributeIfEmpty(el, "type", "button");
502
- el.setAttribute("data-media-tooltip", "live");
503
- }
504
- onConnect(el) {
505
- onPress(el, this.#onPress.bind(this));
506
- }
507
- #onPress(event) {
508
- const { disabled } = this.$props, { liveEdge } = this.#media.$state;
509
- if (disabled() || liveEdge()) return;
510
- this.#media.remote.seekToLiveEdge(event);
511
- }
512
- }
513
-
514
- const sliderState = new State({
515
- min: 0,
516
- max: 100,
517
- value: 0,
518
- step: 1,
519
- pointerValue: 0,
520
- focused: false,
521
- dragging: false,
522
- pointing: false,
523
- hidden: false,
524
- get active() {
525
- return this.dragging || this.focused || this.pointing;
526
- },
527
- get fillRate() {
528
- return calcRate(this.min, this.max, this.value);
529
- },
530
- get fillPercent() {
531
- return this.fillRate * 100;
532
- },
533
- get pointerRate() {
534
- return calcRate(this.min, this.max, this.pointerValue);
535
- },
536
- get pointerPercent() {
537
- return this.pointerRate * 100;
538
- }
539
- });
540
- function calcRate(min, max, value) {
541
- const range = max - min, offset = value - min;
542
- return range > 0 ? offset / range : 0;
543
- }
544
-
545
- class IntersectionObserverController extends ViewController {
546
- #init;
547
- #observer;
548
- constructor(init) {
549
- super();
550
- this.#init = init;
551
- }
552
- onConnect(el) {
553
- this.#observer = new IntersectionObserver((entries) => {
554
- this.#init.callback?.(entries, this.#observer);
555
- }, this.#init);
556
- this.#observer.observe(el);
557
- onDispose(this.#onDisconnect.bind(this));
558
- }
559
- /**
560
- * Disconnect any active intersection observers.
561
- */
562
- #onDisconnect() {
563
- this.#observer?.disconnect();
564
- this.#observer = void 0;
565
- }
566
- }
567
-
568
- const sliderContext = createContext();
569
- const sliderObserverContext = createContext();
570
-
571
- function getClampedValue(min, max, value, step) {
572
- return clampNumber(min, round(value, getNumberOfDecimalPlaces(step)), max);
573
- }
574
- function getValueFromRate(min, max, rate, step) {
575
- const boundRate = clampNumber(0, rate, 1), range = max - min, fill = range * boundRate, stepRatio = fill / step, steps = step * Math.round(stepRatio);
576
- return min + steps;
577
- }
578
-
579
- const SliderKeyDirection = {
580
- Left: -1,
581
- ArrowLeft: -1,
582
- Up: 1,
583
- ArrowUp: 1,
584
- Right: 1,
585
- ArrowRight: 1,
586
- Down: -1,
587
- ArrowDown: -1
588
- };
589
- class SliderEventsController extends ViewController {
590
- #delegate;
591
- #media;
592
- #observer;
593
- constructor(delegate, media) {
594
- super();
595
- this.#delegate = delegate;
596
- this.#media = media;
597
- }
598
- onSetup() {
599
- if (hasProvidedContext(sliderObserverContext)) {
600
- this.#observer = useContext(sliderObserverContext);
601
- }
602
- }
603
- onConnect(el) {
604
- effect(this.#attachEventListeners.bind(this, el));
605
- effect(this.#attachPointerListeners.bind(this, el));
606
- if (this.#delegate.swipeGesture) effect(this.#watchSwipeGesture.bind(this));
607
- }
608
- #watchSwipeGesture() {
609
- const { pointer } = this.#media.$state;
610
- if (pointer() !== "coarse" || !this.#delegate.swipeGesture()) {
611
- this.#provider = null;
612
- return;
613
- }
614
- this.#provider = this.#media.player.el?.querySelector(
615
- "media-provider,[data-media-provider]"
616
- );
617
- if (!this.#provider) return;
618
- new EventsController(this.#provider).add("touchstart", this.#onTouchStart.bind(this), {
619
- passive: true
620
- }).add("touchmove", this.#onTouchMove.bind(this), { passive: false });
621
- }
622
- #provider = null;
623
- #touch = null;
624
- #touchStartValue = null;
625
- #onTouchStart(event) {
626
- this.#touch = event.touches[0];
627
- }
628
- #onTouchMove(event) {
629
- if (isNull(this.#touch) || isTouchPinchEvent(event)) return;
630
- const touch = event.touches[0], xDiff = touch.clientX - this.#touch.clientX, yDiff = touch.clientY - this.#touch.clientY, isDragging = this.$state.dragging();
631
- if (!isDragging && Math.abs(yDiff) > 5) {
632
- return;
633
- }
634
- if (isDragging) return;
635
- event.preventDefault();
636
- if (Math.abs(xDiff) > 20) {
637
- this.#touch = touch;
638
- this.#touchStartValue = this.$state.value();
639
- this.#onStartDragging(this.#touchStartValue, event);
640
- }
641
- }
642
- #attachEventListeners(el) {
643
- const { hidden } = this.$props;
644
- listenEvent(el, "focus", this.#onFocus.bind(this));
645
- if (hidden() || this.#delegate.isDisabled()) return;
646
- new EventsController(el).add("keyup", this.#onKeyUp.bind(this)).add("keydown", this.#onKeyDown.bind(this)).add("pointerenter", this.#onPointerEnter.bind(this)).add("pointermove", this.#onPointerMove.bind(this)).add("pointerleave", this.#onPointerLeave.bind(this)).add("pointerdown", this.#onPointerDown.bind(this));
647
- }
648
- #attachPointerListeners(el) {
649
- if (this.#delegate.isDisabled() || !this.$state.dragging()) return;
650
- new EventsController(document).add("pointerup", this.#onDocumentPointerUp.bind(this), { capture: true }).add("pointermove", this.#onDocumentPointerMove.bind(this)).add("touchmove", this.#onDocumentTouchMove.bind(this), {
651
- passive: false
652
- });
653
- }
654
- #onFocus() {
655
- this.#updatePointerValue(this.$state.value());
656
- }
657
- #updateValue(newValue, trigger) {
658
- const { value, min, max, dragging } = this.$state;
659
- const clampedValue = Math.max(min(), Math.min(newValue, max()));
660
- value.set(clampedValue);
661
- const event = this.createEvent("value-change", { detail: clampedValue, trigger });
662
- this.dispatch(event);
663
- this.#delegate.onValueChange?.(event);
664
- if (dragging()) {
665
- const event2 = this.createEvent("drag-value-change", { detail: clampedValue, trigger });
666
- this.dispatch(event2);
667
- this.#delegate.onDragValueChange?.(event2);
668
- }
669
- }
670
- #updatePointerValue(value, trigger) {
671
- const { pointerValue, dragging } = this.$state;
672
- pointerValue.set(value);
673
- this.dispatch("pointer-value-change", { detail: value, trigger });
674
- if (dragging()) {
675
- this.#updateValue(value, trigger);
676
- }
677
- }
678
- #getPointerValue(event) {
679
- let thumbPositionRate, rect = this.el.getBoundingClientRect(), { min, max } = this.$state;
680
- if (this.$props.orientation() === "vertical") {
681
- const { bottom: trackBottom, height: trackHeight } = rect;
682
- thumbPositionRate = (trackBottom - event.clientY) / trackHeight;
683
- } else {
684
- if (this.#touch && isNumber(this.#touchStartValue)) {
685
- const { width } = this.#provider.getBoundingClientRect(), rate = (event.clientX - this.#touch.clientX) / width, range = max() - min(), diff = range * Math.abs(rate);
686
- thumbPositionRate = (rate < 0 ? this.#touchStartValue - diff : this.#touchStartValue + diff) / range;
687
- } else {
688
- const { left: trackLeft, width: trackWidth } = rect;
689
- thumbPositionRate = (event.clientX - trackLeft) / trackWidth;
690
- }
691
- }
692
- return Math.max(
693
- min(),
694
- Math.min(
695
- max(),
696
- this.#delegate.roundValue(
697
- getValueFromRate(min(), max(), thumbPositionRate, this.#delegate.getStep())
698
- )
699
- )
700
- );
701
- }
702
- #onPointerEnter(event) {
703
- this.$state.pointing.set(true);
704
- }
705
- #onPointerMove(event) {
706
- const { dragging } = this.$state;
707
- if (dragging()) return;
708
- this.#updatePointerValue(this.#getPointerValue(event), event);
709
- }
710
- #onPointerLeave(event) {
711
- this.$state.pointing.set(false);
712
- }
713
- #onPointerDown(event) {
714
- if (event.button !== 0) return;
715
- const value = this.#getPointerValue(event);
716
- this.#onStartDragging(value, event);
717
- this.#updatePointerValue(value, event);
718
- }
719
- #onStartDragging(value, trigger) {
720
- const { dragging } = this.$state;
721
- if (dragging()) return;
722
- dragging.set(true);
723
- this.#media.remote.pauseControls(trigger);
724
- const event = this.createEvent("drag-start", { detail: value, trigger });
725
- this.dispatch(event);
726
- this.#delegate.onDragStart?.(event);
727
- this.#observer?.onDragStart?.();
728
- }
729
- #onStopDragging(value, trigger) {
730
- const { dragging } = this.$state;
731
- if (!dragging()) return;
732
- dragging.set(false);
733
- this.#media.remote.resumeControls(trigger);
734
- const event = this.createEvent("drag-end", { detail: value, trigger });
735
- this.dispatch(event);
736
- this.#delegate.onDragEnd?.(event);
737
- this.#touch = null;
738
- this.#touchStartValue = null;
739
- this.#observer?.onDragEnd?.();
740
- }
741
- // -------------------------------------------------------------------------------------------
742
- // Keyboard Events
743
- // -------------------------------------------------------------------------------------------
744
- #lastDownKey;
745
- #repeatedKeys = false;
746
- #onKeyDown(event) {
747
- const isValidKey = Object.keys(SliderKeyDirection).includes(event.key);
748
- if (!isValidKey) return;
749
- const { key } = event, jumpValue = this.#calcJumpValue(event);
750
- if (!isNull(jumpValue)) {
751
- this.#updatePointerValue(jumpValue, event);
752
- this.#updateValue(jumpValue, event);
753
- return;
754
- }
755
- const newValue = this.#calcNewKeyValue(event);
756
- if (!this.#repeatedKeys) {
757
- this.#repeatedKeys = key === this.#lastDownKey;
758
- if (!this.$state.dragging() && this.#repeatedKeys) {
759
- this.#onStartDragging(newValue, event);
760
- }
761
- }
762
- this.#updatePointerValue(newValue, event);
763
- this.#lastDownKey = key;
764
- }
765
- #onKeyUp(event) {
766
- const isValidKey = Object.keys(SliderKeyDirection).includes(event.key);
767
- if (!isValidKey || !isNull(this.#calcJumpValue(event))) return;
768
- const newValue = this.#repeatedKeys ? this.$state.pointerValue() : this.#calcNewKeyValue(event);
769
- this.#updateValue(newValue, event);
770
- this.#onStopDragging(newValue, event);
771
- this.#lastDownKey = "";
772
- this.#repeatedKeys = false;
773
- }
774
- #calcJumpValue(event) {
775
- let key = event.key, { min, max } = this.$state;
776
- if (key === "Home" || key === "PageUp") {
777
- return min();
778
- } else if (key === "End" || key === "PageDown") {
779
- return max();
780
- } else if (!event.metaKey && /^[0-9]$/.test(key)) {
781
- return (max() - min()) / 10 * Number(key);
782
- }
783
- return null;
784
- }
785
- #calcNewKeyValue(event) {
786
- const { key, shiftKey } = event;
787
- event.preventDefault();
788
- event.stopPropagation();
789
- const { shiftKeyMultiplier } = this.$props;
790
- const { min, max, value, pointerValue } = this.$state, step = this.#delegate.getStep(), keyStep = this.#delegate.getKeyStep();
791
- const modifiedStep = !shiftKey ? keyStep : keyStep * shiftKeyMultiplier(), direction = Number(SliderKeyDirection[key]), diff = modifiedStep * direction, currentValue = this.#repeatedKeys ? pointerValue() : this.#delegate.getValue?.() ?? value(), steps = (currentValue + diff) / step;
792
- return Math.max(min(), Math.min(max(), Number((step * steps).toFixed(3))));
793
- }
794
- // -------------------------------------------------------------------------------------------
795
- // Document (Pointer Events)
796
- // -------------------------------------------------------------------------------------------
797
- #onDocumentPointerUp(event) {
798
- if (event.button !== 0) return;
799
- event.preventDefault();
800
- event.stopImmediatePropagation();
801
- const value = this.#getPointerValue(event);
802
- this.#updatePointerValue(value, event);
803
- this.#onStopDragging(value, event);
804
- }
805
- #onDocumentTouchMove(event) {
806
- event.preventDefault();
807
- }
808
- #onDocumentPointerMove = functionThrottle(
809
- (event) => {
810
- this.#updatePointerValue(this.#getPointerValue(event), event);
811
- },
812
- 20,
813
- { leading: true }
814
- );
815
- }
816
-
817
- const sliderValueFormatContext = createContext(() => ({}));
818
-
819
- class SliderController extends ViewController {
820
- static props = {
821
- hidden: false,
822
- disabled: false,
823
- step: 1,
824
- keyStep: 1,
825
- orientation: "horizontal",
826
- shiftKeyMultiplier: 5
827
- };
828
- #media;
829
- #delegate;
830
- #isVisible = signal(true);
831
- #isIntersecting = signal(true);
832
- constructor(delegate) {
833
- super();
834
- this.#delegate = delegate;
835
- }
836
- onSetup() {
837
- this.#media = useMediaContext();
838
- const focus = new FocusVisibleController();
839
- focus.attach(this);
840
- this.$state.focused = focus.focused.bind(focus);
841
- if (!hasProvidedContext(sliderValueFormatContext)) {
842
- provideContext(sliderValueFormatContext, {
843
- default: "value"
844
- });
845
- }
846
- provideContext(sliderContext, {
847
- orientation: this.$props.orientation,
848
- disabled: this.#delegate.isDisabled,
849
- preview: signal(null)
850
- });
851
- effect(this.#watchValue.bind(this));
852
- effect(this.#watchStep.bind(this));
853
- effect(this.#watchDisabled.bind(this));
854
- this.#setupAttrs();
855
- new SliderEventsController(this.#delegate, this.#media).attach(this);
856
- new IntersectionObserverController({
857
- callback: this.#onIntersectionChange.bind(this)
858
- }).attach(this);
859
- }
860
- onAttach(el) {
861
- setAttributeIfEmpty(el, "role", "slider");
862
- setAttributeIfEmpty(el, "tabindex", "0");
863
- setAttributeIfEmpty(el, "autocomplete", "off");
864
- effect(this.#watchCSSVars.bind(this));
865
- }
866
- onConnect(el) {
867
- onDispose(observeVisibility(el, this.#isVisible.set));
868
- effect(this.#watchHidden.bind(this));
869
- }
870
- #onIntersectionChange(entries) {
871
- this.#isIntersecting.set(entries[0].isIntersecting);
872
- }
873
- // -------------------------------------------------------------------------------------------
874
- // Watch
875
- // -------------------------------------------------------------------------------------------
876
- #watchHidden() {
877
- const { hidden } = this.$props;
878
- this.$state.hidden.set(hidden() || !this.#isVisible() || !this.#isIntersecting.bind(this));
879
- }
880
- #watchValue() {
881
- const { dragging, value, min, max } = this.$state;
882
- if (peek(dragging)) return;
883
- value.set(getClampedValue(min(), max(), value(), this.#delegate.getStep()));
884
- }
885
- #watchStep() {
886
- this.$state.step.set(this.#delegate.getStep());
887
- }
888
- #watchDisabled() {
889
- if (!this.#delegate.isDisabled()) return;
890
- const { dragging, pointing } = this.$state;
891
- dragging.set(false);
892
- pointing.set(false);
893
- }
894
- // -------------------------------------------------------------------------------------------
895
- // ARIA
896
- // -------------------------------------------------------------------------------------------
897
- #getARIADisabled() {
898
- return ariaBool(this.#delegate.isDisabled());
899
- }
900
- // -------------------------------------------------------------------------------------------
901
- // Attributes
902
- // -------------------------------------------------------------------------------------------
903
- #setupAttrs() {
904
- const { orientation } = this.$props, { dragging, active, pointing } = this.$state;
905
- this.setAttributes({
906
- "data-dragging": dragging,
907
- "data-pointing": pointing,
908
- "data-active": active,
909
- "aria-disabled": this.#getARIADisabled.bind(this),
910
- "aria-valuemin": this.#delegate.aria.valueMin ?? this.$state.min,
911
- "aria-valuemax": this.#delegate.aria.valueMax ?? this.$state.max,
912
- "aria-valuenow": this.#delegate.aria.valueNow,
913
- "aria-valuetext": this.#delegate.aria.valueText,
914
- "aria-orientation": orientation
915
- });
916
- }
917
- #watchCSSVars() {
918
- const { fillPercent, pointerPercent } = this.$state;
919
- this.#updateSliderVars(round(fillPercent(), 3), round(pointerPercent(), 3));
920
- }
921
- #updateSliderVars = animationFrameThrottle((fillPercent, pointerPercent) => {
922
- this.el?.style.setProperty("--slider-fill", fillPercent + "%");
923
- this.el?.style.setProperty("--slider-pointer", pointerPercent + "%");
924
- });
925
- }
926
-
927
- class Slider extends Component {
928
- static props = {
929
- ...SliderController.props,
930
- min: 0,
931
- max: 100,
932
- value: 0
933
- };
934
- static state = sliderState;
935
- constructor() {
936
- super();
937
- new SliderController({
938
- getStep: this.$props.step,
939
- getKeyStep: this.$props.keyStep,
940
- roundValue: Math.round,
941
- isDisabled: this.$props.disabled,
942
- aria: {
943
- valueNow: this.#getARIAValueNow.bind(this),
944
- valueText: this.#getARIAValueText.bind(this)
945
- }
946
- });
947
- }
948
- onSetup() {
949
- effect(this.#watchValue.bind(this));
950
- effect(this.#watchMinMax.bind(this));
951
- }
952
- // -------------------------------------------------------------------------------------------
953
- // Props
954
- // -------------------------------------------------------------------------------------------
955
- #getARIAValueNow() {
956
- const { value } = this.$state;
957
- return Math.round(value());
958
- }
959
- #getARIAValueText() {
960
- const { value, max } = this.$state;
961
- return round(value() / max() * 100, 2) + "%";
962
- }
963
- // -------------------------------------------------------------------------------------------
964
- // Watch
965
- // -------------------------------------------------------------------------------------------
966
- #watchValue() {
967
- const { value } = this.$props;
968
- this.$state.value.set(value());
969
- }
970
- #watchMinMax() {
971
- const { min, max } = this.$props;
972
- this.$state.min.set(min());
973
- this.$state.max.set(max());
974
- }
975
- }
976
-
977
- const cache = /* @__PURE__ */ new Map(), pending = /* @__PURE__ */ new Map(), warned = /* @__PURE__ */ new Set() ;
978
- class ThumbnailsLoader {
979
- #media;
980
- #src;
981
- #crossOrigin;
982
- $images = signal([]);
983
- static create(src, crossOrigin) {
984
- const media = useMediaContext();
985
- return new ThumbnailsLoader(src, crossOrigin, media);
986
- }
987
- constructor(src, crossOrigin, media) {
988
- this.#src = src;
989
- this.#crossOrigin = crossOrigin;
990
- this.#media = media;
991
- effect(this.#onLoadCues.bind(this));
992
- }
993
- #onLoadCues() {
994
- const { canLoad } = this.#media.$state;
995
- if (!canLoad()) return;
996
- const src = this.#src();
997
- if (!src) return;
998
- if (isString(src) && cache.has(src)) {
999
- const cues = cache.get(src);
1000
- cache.delete(src);
1001
- cache.set(src, cues);
1002
- if (cache.size > 99) {
1003
- const firstKey = cache.keys().next().value;
1004
- cache.delete(firstKey);
1005
- }
1006
- this.$images.set(cache.get(src));
1007
- } else if (isString(src)) {
1008
- const crossOrigin = this.#crossOrigin(), currentKey = src + "::" + crossOrigin;
1009
- if (!pending.has(currentKey)) {
1010
- const promise = new Promise(async (resolve, reject) => {
1011
- try {
1012
- const response = await fetch(src, {
1013
- credentials: getRequestCredentials(crossOrigin)
1014
- }), isJSON = response.headers.get("content-type") === "application/json";
1015
- if (isJSON) {
1016
- const json = await response.json();
1017
- if (isArray(json)) {
1018
- if (json[0] && "text" in json[0]) {
1019
- resolve(this.#processVTTCues(json));
1020
- } else {
1021
- for (let i = 0; i < json.length; i++) {
1022
- const image = json[i];
1023
- assert(isObject(image), `Item not an object at index ${i}`);
1024
- assert(
1025
- "url" in image && isString(image.url),
1026
- `Invalid or missing \`url\` property at index ${i}`
1027
- );
1028
- assert(
1029
- "startTime" in image && isNumber(image.startTime),
1030
- `Invalid or missing \`startTime\` property at index ${i}`
1031
- );
1032
- }
1033
- resolve(json);
1034
- }
1035
- } else {
1036
- resolve(this.#processStoryboard(json));
1037
- }
1038
- return;
1039
- }
1040
- import('media-captions').then(async ({ parseResponse }) => {
1041
- try {
1042
- const { cues } = await parseResponse(response);
1043
- resolve(this.#processVTTCues(cues));
1044
- } catch (e) {
1045
- reject(e);
1046
- }
1047
- });
1048
- } catch (e) {
1049
- reject(e);
1050
- }
1051
- }).then((images) => {
1052
- cache.set(currentKey, images);
1053
- return images;
1054
- }).catch((error) => {
1055
- this.#onError(src, error);
1056
- }).finally(() => {
1057
- if (isString(currentKey)) pending.delete(currentKey);
1058
- });
1059
- pending.set(currentKey, promise);
1060
- }
1061
- pending.get(currentKey)?.then((images) => {
1062
- this.$images.set(images || []);
1063
- });
1064
- } else if (isArray(src)) {
1065
- try {
1066
- this.$images.set(this.#processImages(src));
1067
- } catch (error) {
1068
- this.#onError(src, error);
1069
- }
1070
- } else {
1071
- try {
1072
- this.$images.set(this.#processStoryboard(src));
1073
- } catch (error) {
1074
- this.#onError(src, error);
1075
- }
1076
- }
1077
- return () => {
1078
- this.$images.set([]);
1079
- };
1080
- }
1081
- #processImages(images) {
1082
- const baseURL = this.#resolveBaseUrl();
1083
- return images.map((img, i) => {
1084
- assert(
1085
- img.url && isString(img.url),
1086
- `Invalid or missing \`url\` property at index ${i}`
1087
- );
1088
- assert(
1089
- "startTime" in img && isNumber(img.startTime),
1090
- `Invalid or missing \`startTime\` property at index ${i}`
1091
- );
1092
- return {
1093
- ...img,
1094
- url: isString(img.url) ? this.#resolveURL(img.url, baseURL) : img.url
1095
- };
1096
- });
1097
- }
1098
- #processStoryboard(board) {
1099
- assert(isString(board.url), "Missing `url` in storyboard object");
1100
- assert(isArray(board.tiles) && board.tiles?.length, `Empty tiles in storyboard`);
1101
- const url = new URL(board.url), images = [];
1102
- const tileWidth = "tile_width" in board ? board.tile_width : board.tileWidth, tileHeight = "tile_height" in board ? board.tile_height : board.tileHeight;
1103
- for (const tile of board.tiles) {
1104
- images.push({
1105
- url,
1106
- startTime: "start" in tile ? tile.start : tile.startTime,
1107
- width: tileWidth,
1108
- height: tileHeight,
1109
- coords: { x: tile.x, y: tile.y }
1110
- });
1111
- }
1112
- return images;
1113
- }
1114
- #processVTTCues(cues) {
1115
- for (let i = 0; i < cues.length; i++) {
1116
- const cue = cues[i];
1117
- assert(
1118
- "startTime" in cue && isNumber(cue.startTime),
1119
- `Invalid or missing \`startTime\` property at index ${i}`
1120
- );
1121
- assert(
1122
- "text" in cue && isString(cue.text),
1123
- `Invalid or missing \`text\` property at index ${i}`
1124
- );
1125
- }
1126
- const images = [], baseURL = this.#resolveBaseUrl();
1127
- for (const cue of cues) {
1128
- const [url, hash] = cue.text.split("#"), data = this.#resolveData(hash);
1129
- images.push({
1130
- url: this.#resolveURL(url, baseURL),
1131
- startTime: cue.startTime,
1132
- endTime: cue.endTime,
1133
- width: data?.w,
1134
- height: data?.h,
1135
- coords: data && isNumber(data.x) && isNumber(data.y) ? { x: data.x, y: data.y } : void 0
1136
- });
1137
- }
1138
- return images;
1139
- }
1140
- #resolveBaseUrl() {
1141
- let baseURL = peek(this.#src);
1142
- if (!isString(baseURL) || !/^https?:/.test(baseURL)) {
1143
- return location.href;
1144
- }
1145
- return baseURL;
1146
- }
1147
- #resolveURL(src, baseURL) {
1148
- return /^https?:/.test(src) ? new URL(src) : new URL(src, baseURL);
1149
- }
1150
- #resolveData(hash) {
1151
- if (!hash) return {};
1152
- const [hashProps, values] = hash.split("="), hashValues = values?.split(","), data = {};
1153
- if (!hashProps || !hashValues) {
1154
- return null;
1155
- }
1156
- for (let i = 0; i < hashProps.length; i++) {
1157
- const value = +hashValues[i];
1158
- if (!isNaN(value)) data[hashProps[i]] = value;
1159
- }
1160
- return data;
1161
- }
1162
- #onError(src, error) {
1163
- if (warned?.has(src)) return;
1164
- this.#media.logger?.errorGroup("[vidstack] failed to load thumbnails").labelledLog("Src", src).labelledLog("Error", error).dispatch();
1165
- warned?.add(src);
1166
- }
1167
- }
1168
-
1169
- class Thumbnail extends Component {
1170
- static props = {
1171
- src: null,
1172
- time: 0,
1173
- crossOrigin: null
1174
- };
1175
- static state = new State({
1176
- src: "",
1177
- img: null,
1178
- thumbnails: [],
1179
- activeThumbnail: null,
1180
- crossOrigin: null,
1181
- loading: false,
1182
- error: null,
1183
- hidden: false
1184
- });
1185
- media;
1186
- #loader;
1187
- #styleResets = [];
1188
- onSetup() {
1189
- this.media = useMediaContext();
1190
- this.#loader = ThumbnailsLoader.create(this.$props.src, this.$state.crossOrigin);
1191
- this.#watchCrossOrigin();
1192
- this.setAttributes({
1193
- "data-loading": this.#isLoading.bind(this),
1194
- "data-error": this.#hasError.bind(this),
1195
- "data-hidden": this.$state.hidden,
1196
- "aria-hidden": $ariaBool(this.$state.hidden)
1197
- });
1198
- }
1199
- onConnect(el) {
1200
- effect(this.#watchImg.bind(this));
1201
- effect(this.#watchHidden.bind(this));
1202
- effect(this.#watchCrossOrigin.bind(this));
1203
- effect(this.#onLoadStart.bind(this));
1204
- effect(this.#onFindActiveThumbnail.bind(this));
1205
- effect(this.#resize.bind(this));
1206
- }
1207
- #watchImg() {
1208
- const img = this.$state.img();
1209
- if (!img) return;
1210
- new EventsController(img).add("load", this.#onLoaded.bind(this)).add("error", this.#onError.bind(this));
1211
- }
1212
- #watchCrossOrigin() {
1213
- const { crossOrigin: crossOriginProp } = this.$props, { crossOrigin: crossOriginState } = this.$state, { crossOrigin: mediaCrossOrigin } = this.media.$state, crossOrigin = crossOriginProp() !== null ? crossOriginProp() : mediaCrossOrigin();
1214
- crossOriginState.set(crossOrigin === true ? "anonymous" : crossOrigin);
1215
- }
1216
- #onLoadStart() {
1217
- const { src, loading, error } = this.$state;
1218
- if (src()) {
1219
- loading.set(true);
1220
- error.set(null);
1221
- }
1222
- return () => {
1223
- this.#resetStyles();
1224
- loading.set(false);
1225
- error.set(null);
1226
- };
1227
- }
1228
- #onLoaded() {
1229
- const { loading, error } = this.$state;
1230
- this.#resize();
1231
- loading.set(false);
1232
- error.set(null);
1233
- }
1234
- #onError(event) {
1235
- const { loading, error } = this.$state;
1236
- loading.set(false);
1237
- error.set(event);
1238
- }
1239
- #isLoading() {
1240
- const { loading, hidden } = this.$state;
1241
- return !hidden() && loading();
1242
- }
1243
- #hasError() {
1244
- const { error } = this.$state;
1245
- return !isNull(error());
1246
- }
1247
- #watchHidden() {
1248
- const { hidden } = this.$state, { duration } = this.media.$state, images = this.#loader.$images();
1249
- hidden.set(this.#hasError() || !Number.isFinite(duration()) || images.length === 0);
1250
- }
1251
- getTime() {
1252
- return this.$props.time();
1253
- }
1254
- #onFindActiveThumbnail() {
1255
- let images = this.#loader.$images();
1256
- if (!images.length) return;
1257
- let time = this.getTime(), { src, activeThumbnail } = this.$state, activeIndex = -1, activeImage = null;
1258
- for (let i = images.length - 1; i >= 0; i--) {
1259
- const image = images[i];
1260
- if (time >= image.startTime && (!image.endTime || time < image.endTime)) {
1261
- activeIndex = i;
1262
- break;
1263
- }
1264
- }
1265
- if (images[activeIndex]) {
1266
- activeImage = images[activeIndex];
1267
- }
1268
- activeThumbnail.set(activeImage);
1269
- src.set(activeImage?.url.href || "");
1270
- }
1271
- #resize() {
1272
- if (!this.scope || this.$state.hidden()) return;
1273
- const rootEl = this.el, imgEl = this.$state.img(), thumbnail = this.$state.activeThumbnail();
1274
- if (!imgEl || !thumbnail || !rootEl) return;
1275
- let width = thumbnail.width ?? imgEl.naturalWidth, height = thumbnail?.height ?? imgEl.naturalHeight, {
1276
- maxWidth,
1277
- maxHeight,
1278
- minWidth,
1279
- minHeight,
1280
- width: elWidth,
1281
- height: elHeight
1282
- } = getComputedStyle(this.el);
1283
- if (minWidth === "100%") minWidth = parseFloat(elWidth) + "";
1284
- if (minHeight === "100%") minHeight = parseFloat(elHeight) + "";
1285
- let minRatio = Math.max(parseInt(minWidth) / width, parseInt(minHeight) / height), maxRatio = Math.min(
1286
- Math.max(parseInt(minWidth), parseInt(maxWidth)) / width,
1287
- Math.max(parseInt(minHeight), parseInt(maxHeight)) / height
1288
- ), scale = !isNaN(maxRatio) && maxRatio < 1 ? maxRatio : minRatio > 1 ? minRatio : 1;
1289
- this.#style(rootEl, "--thumbnail-width", `${width * scale}px`);
1290
- this.#style(rootEl, "--thumbnail-height", `${height * scale}px`);
1291
- this.#style(rootEl, "--thumbnail-aspect-ratio", String(round(width / height, 5)));
1292
- this.#style(imgEl, "width", `${imgEl.naturalWidth * scale}px`);
1293
- this.#style(imgEl, "height", `${imgEl.naturalHeight * scale}px`);
1294
- this.#style(
1295
- imgEl,
1296
- "transform",
1297
- thumbnail.coords ? `translate(-${thumbnail.coords.x * scale}px, -${thumbnail.coords.y * scale}px)` : ""
1298
- );
1299
- this.#style(imgEl, "max-width", "none");
1300
- }
1301
- #style(el, name, value) {
1302
- el.style.setProperty(name, value);
1303
- this.#styleResets.push(() => el.style.removeProperty(name));
1304
- }
1305
- #resetStyles() {
1306
- for (const reset of this.#styleResets) reset();
1307
- this.#styleResets = [];
1308
- }
1309
- }
1310
-
1311
- class SliderValue extends Component {
1312
- static props = {
1313
- type: "pointer",
1314
- format: null,
1315
- showHours: false,
1316
- showMs: false,
1317
- padHours: null,
1318
- padMinutes: null,
1319
- decimalPlaces: 2
1320
- };
1321
- #format;
1322
- #text;
1323
- #slider;
1324
- onSetup() {
1325
- this.#slider = useState(Slider.state);
1326
- this.#format = useContext(sliderValueFormatContext);
1327
- this.#text = computed(this.getValueText.bind(this));
1328
- }
1329
- /**
1330
- * Returns the current value formatted as text based on prop settings.
1331
- */
1332
- getValueText() {
1333
- const {
1334
- type,
1335
- format: $format,
1336
- decimalPlaces,
1337
- padHours,
1338
- padMinutes,
1339
- showHours,
1340
- showMs
1341
- } = this.$props, { value: sliderValue, pointerValue, min, max } = this.#slider, format = $format?.() ?? this.#format.default;
1342
- const value = type() === "current" ? sliderValue() : pointerValue();
1343
- if (format === "percent") {
1344
- const range = max() - min();
1345
- const percent = value / range * 100;
1346
- return (this.#format.percent ?? round)(percent, decimalPlaces()) + "%";
1347
- } else if (format === "time") {
1348
- return (this.#format.time ?? formatTime)(value, {
1349
- padHrs: padHours(),
1350
- padMins: padMinutes(),
1351
- showHrs: showHours(),
1352
- showMs: showMs()
1353
- });
1354
- } else {
1355
- return (this.#format.value?.(value) ?? value.toFixed(2)) + "";
1356
- }
1357
- }
1358
- }
1359
- const slidervalue__proto = SliderValue.prototype;
1360
- method(slidervalue__proto, "getValueText");
1361
-
1362
- class SliderPreview extends Component {
1363
- static props = {
1364
- offset: 0,
1365
- noClamp: false
1366
- };
1367
- #slider;
1368
- onSetup() {
1369
- this.#slider = useContext(sliderContext);
1370
- const { active } = useState(Slider.state);
1371
- this.setAttributes({
1372
- "data-visible": active
1373
- });
1374
- }
1375
- onAttach(el) {
1376
- Object.assign(el.style, {
1377
- position: "absolute",
1378
- top: 0,
1379
- left: 0,
1380
- width: "max-content"
1381
- });
1382
- }
1383
- onConnect(el) {
1384
- const { preview } = this.#slider;
1385
- preview.set(el);
1386
- onDispose(() => preview.set(null));
1387
- effect(this.#updatePlacement.bind(this));
1388
- const resize = new ResizeObserver(this.#updatePlacement.bind(this));
1389
- resize.observe(el);
1390
- onDispose(() => resize.disconnect());
1391
- }
1392
- #updatePlacement = animationFrameThrottle(() => {
1393
- const { disabled, orientation } = this.#slider;
1394
- if (disabled()) return;
1395
- const el = this.el, { offset, noClamp } = this.$props;
1396
- if (!el) return;
1397
- updateSliderPreviewPlacement(el, {
1398
- clamp: !noClamp(),
1399
- offset: offset(),
1400
- orientation: orientation()
1401
- });
1402
- });
1403
- }
1404
- function updateSliderPreviewPlacement(el, {
1405
- clamp,
1406
- offset,
1407
- orientation
1408
- }) {
1409
- const computedStyle = getComputedStyle(el), width = parseFloat(computedStyle.width), height = parseFloat(computedStyle.height), styles = {
1410
- top: null,
1411
- right: null,
1412
- bottom: null,
1413
- left: null
1414
- };
1415
- styles[orientation === "horizontal" ? "bottom" : "left"] = `calc(100% + var(--media-slider-preview-offset, ${offset}px))`;
1416
- if (orientation === "horizontal") {
1417
- const widthHalf = width / 2;
1418
- if (!clamp) {
1419
- styles.left = `calc(var(--slider-pointer) - ${widthHalf}px)`;
1420
- } else {
1421
- const leftClamp = `max(0px, calc(var(--slider-pointer) - ${widthHalf}px))`, rightClamp = `calc(100% - ${width}px)`;
1422
- styles.left = `min(${leftClamp}, ${rightClamp})`;
1423
- }
1424
- } else {
1425
- const heightHalf = height / 2;
1426
- if (!clamp) {
1427
- styles.bottom = `calc(var(--slider-pointer) - ${heightHalf}px)`;
1428
- } else {
1429
- const topClamp = `max(${heightHalf}px, calc(var(--slider-pointer) - ${heightHalf}px))`, bottomClamp = `calc(100% - ${height}px)`;
1430
- styles.bottom = `min(${topClamp}, ${bottomClamp})`;
1431
- }
1432
- }
1433
- Object.assign(el.style, styles);
1434
- }
1435
-
1436
- class VolumeSlider extends Component {
1437
- static props = {
1438
- ...SliderController.props,
1439
- keyStep: 5,
1440
- shiftKeyMultiplier: 2
1441
- };
1442
- static state = sliderState;
1443
- #media;
1444
- onSetup() {
1445
- this.#media = useMediaContext();
1446
- const { audioGain } = this.#media.$state;
1447
- provideContext(sliderValueFormatContext, {
1448
- default: "percent",
1449
- value(value) {
1450
- return (value * (audioGain() ?? 1)).toFixed(2);
1451
- },
1452
- percent(value) {
1453
- return Math.round(value * (audioGain() ?? 1));
1454
- }
1455
- });
1456
- new SliderController({
1457
- getStep: this.$props.step,
1458
- getKeyStep: this.$props.keyStep,
1459
- roundValue: Math.round,
1460
- isDisabled: this.#isDisabled.bind(this),
1461
- aria: {
1462
- valueMax: this.#getARIAValueMax.bind(this),
1463
- valueNow: this.#getARIAValueNow.bind(this),
1464
- valueText: this.#getARIAValueText.bind(this)
1465
- },
1466
- onDragValueChange: this.#onDragValueChange.bind(this),
1467
- onValueChange: this.#onValueChange.bind(this)
1468
- }).attach(this);
1469
- effect(this.#watchVolume.bind(this));
1470
- }
1471
- onAttach(el) {
1472
- el.setAttribute("data-media-volume-slider", "");
1473
- setAttributeIfEmpty(el, "aria-label", "Volume");
1474
- const { canSetVolume } = this.#media.$state;
1475
- this.setAttributes({
1476
- "data-supported": canSetVolume,
1477
- "aria-hidden": $ariaBool(() => !canSetVolume())
1478
- });
1479
- }
1480
- #getARIAValueNow() {
1481
- const { value } = this.$state, { audioGain } = this.#media.$state;
1482
- return Math.round(value() * (audioGain() ?? 1));
1483
- }
1484
- #getARIAValueText() {
1485
- const { value, max } = this.$state, { audioGain } = this.#media.$state;
1486
- return round(value() / max() * (audioGain() ?? 1) * 100, 2) + "%";
1487
- }
1488
- #getARIAValueMax() {
1489
- const { audioGain } = this.#media.$state;
1490
- return this.$state.max() * (audioGain() ?? 1);
1491
- }
1492
- #isDisabled() {
1493
- const { disabled } = this.$props, { canSetVolume } = this.#media.$state;
1494
- return disabled() || !canSetVolume();
1495
- }
1496
- #watchVolume() {
1497
- const { muted, volume } = this.#media.$state;
1498
- const newValue = muted() ? 0 : volume() * 100;
1499
- this.$state.value.set(newValue);
1500
- this.dispatch("value-change", { detail: newValue });
1501
- }
1502
- #throttleVolumeChange = functionThrottle(this.#onVolumeChange.bind(this), 25);
1503
- #onVolumeChange(event) {
1504
- if (!event.trigger) return;
1505
- const mediaVolume = round(event.detail / 100, 3);
1506
- this.#media.remote.changeVolume(mediaVolume, event);
1507
- }
1508
- #onValueChange(event) {
1509
- this.#throttleVolumeChange(event);
1510
- }
1511
- #onDragValueChange(event) {
1512
- this.#throttleVolumeChange(event);
1513
- }
1514
- }
1515
-
1516
- class TimeSlider extends Component {
1517
- static props = {
1518
- ...SliderController.props,
1519
- step: 0.1,
1520
- keyStep: 5,
1521
- shiftKeyMultiplier: 2,
1522
- pauseWhileDragging: false,
1523
- noSwipeGesture: false,
1524
- seekingRequestThrottle: 100
1525
- };
1526
- static state = sliderState;
1527
- #media;
1528
- #dispatchSeeking;
1529
- #chapter = signal(null);
1530
- constructor() {
1531
- super();
1532
- const { noSwipeGesture } = this.$props;
1533
- new SliderController({
1534
- swipeGesture: () => !noSwipeGesture(),
1535
- getValue: this.#getValue.bind(this),
1536
- getStep: this.#getStep.bind(this),
1537
- getKeyStep: this.#getKeyStep.bind(this),
1538
- roundValue: this.#roundValue,
1539
- isDisabled: this.#isDisabled.bind(this),
1540
- aria: {
1541
- valueNow: this.#getARIAValueNow.bind(this),
1542
- valueText: this.#getARIAValueText.bind(this)
1543
- },
1544
- onDragStart: this.#onDragStart.bind(this),
1545
- onDragValueChange: this.#onDragValueChange.bind(this),
1546
- onDragEnd: this.#onDragEnd.bind(this),
1547
- onValueChange: this.#onValueChange.bind(this)
1548
- });
1549
- }
1550
- onSetup() {
1551
- this.#media = useMediaContext();
1552
- provideContext(sliderValueFormatContext, {
1553
- default: "time",
1554
- value: this.#formatValue.bind(this),
1555
- time: this.#formatTime.bind(this)
1556
- });
1557
- this.setAttributes({
1558
- "data-chapters": this.#hasChapters.bind(this)
1559
- });
1560
- this.setStyles({
1561
- "--slider-progress": this.#calcBufferedPercent.bind(this)
1562
- });
1563
- effect(this.#watchCurrentTime.bind(this));
1564
- effect(this.#watchSeekingThrottle.bind(this));
1565
- }
1566
- onAttach(el) {
1567
- el.setAttribute("data-media-time-slider", "");
1568
- setAttributeIfEmpty(el, "aria-label", "Seek");
1569
- }
1570
- onConnect(el) {
1571
- effect(this.#watchPreviewing.bind(this));
1572
- watchActiveTextTrack(this.#media.textTracks, "chapters", this.#chapter.set);
1573
- }
1574
- #calcBufferedPercent() {
1575
- const { bufferedEnd, duration } = this.#media.$state;
1576
- return round(Math.min(bufferedEnd() / Math.max(duration(), 1), 1) * 100, 3) + "%";
1577
- }
1578
- #hasChapters() {
1579
- const { duration } = this.#media.$state;
1580
- return this.#chapter()?.cues.length && Number.isFinite(duration()) && duration() > 0;
1581
- }
1582
- #watchSeekingThrottle() {
1583
- this.#dispatchSeeking = functionThrottle(
1584
- this.#seeking.bind(this),
1585
- this.$props.seekingRequestThrottle()
1586
- );
1587
- }
1588
- #watchCurrentTime() {
1589
- if (this.$state.hidden()) return;
1590
- const { value, dragging } = this.$state, newValue = this.#getValue();
1591
- if (!peek(dragging)) {
1592
- value.set(newValue);
1593
- this.dispatch("value-change", { detail: newValue });
1594
- }
1595
- }
1596
- #watchPreviewing() {
1597
- const player = this.#media.player.el, { preview } = useContext(sliderContext);
1598
- player && preview() && setAttribute(player, "data-preview", this.$state.active());
1599
- }
1600
- #seeking(time, event) {
1601
- this.#media.remote.seeking(time, event);
1602
- }
1603
- #seek(time, percent, event) {
1604
- this.#dispatchSeeking.cancel();
1605
- const { live } = this.#media.$state;
1606
- if (live() && percent >= 99) {
1607
- this.#media.remote.seekToLiveEdge(event);
1608
- return;
1609
- }
1610
- this.#media.remote.seek(time, event);
1611
- }
1612
- #playingBeforeDragStart = false;
1613
- #onDragStart(event) {
1614
- const { pauseWhileDragging } = this.$props;
1615
- if (pauseWhileDragging()) {
1616
- const { paused } = this.#media.$state;
1617
- this.#playingBeforeDragStart = !paused();
1618
- this.#media.remote.pause(event);
1619
- }
1620
- }
1621
- #onDragValueChange(event) {
1622
- this.#dispatchSeeking(this.#percentToTime(event.detail), event);
1623
- }
1624
- #onDragEnd(event) {
1625
- const { seeking } = this.#media.$state;
1626
- if (!peek(seeking)) this.#seeking(this.#percentToTime(event.detail), event);
1627
- const percent = event.detail;
1628
- this.#seek(this.#percentToTime(percent), percent, event);
1629
- const { pauseWhileDragging } = this.$props;
1630
- if (pauseWhileDragging() && this.#playingBeforeDragStart) {
1631
- this.#media.remote.play(event);
1632
- this.#playingBeforeDragStart = false;
1633
- }
1634
- }
1635
- #onValueChange(event) {
1636
- const { dragging } = this.$state;
1637
- if (dragging() || !event.trigger) return;
1638
- this.#onDragEnd(event);
1639
- }
1640
- // -------------------------------------------------------------------------------------------
1641
- // Props
1642
- // -------------------------------------------------------------------------------------------
1643
- #getValue() {
1644
- const { currentTime } = this.#media.$state;
1645
- return this.#timeToPercent(currentTime());
1646
- }
1647
- #getStep() {
1648
- const value = this.$props.step() / this.#media.$state.duration() * 100;
1649
- return Number.isFinite(value) ? value : 1;
1650
- }
1651
- #getKeyStep() {
1652
- const value = this.$props.keyStep() / this.#media.$state.duration() * 100;
1653
- return Number.isFinite(value) ? value : 1;
1654
- }
1655
- #roundValue(value) {
1656
- return round(value, 3);
1657
- }
1658
- #isDisabled() {
1659
- const { disabled } = this.$props, { canSeek } = this.#media.$state;
1660
- return disabled() || !canSeek();
1661
- }
1662
- // -------------------------------------------------------------------------------------------
1663
- // ARIA
1664
- // -------------------------------------------------------------------------------------------
1665
- #getARIAValueNow() {
1666
- const { value } = this.$state;
1667
- return Math.round(value());
1668
- }
1669
- #getARIAValueText() {
1670
- const time = this.#percentToTime(this.$state.value()), { duration } = this.#media.$state;
1671
- return Number.isFinite(time) ? `${formatSpokenTime(time)} out of ${formatSpokenTime(duration())}` : "live";
1672
- }
1673
- // -------------------------------------------------------------------------------------------
1674
- // Format
1675
- // -------------------------------------------------------------------------------------------
1676
- #percentToTime(percent) {
1677
- const { duration } = this.#media.$state;
1678
- return round(percent / 100 * duration(), 5);
1679
- }
1680
- #timeToPercent(time) {
1681
- const { liveEdge, duration } = this.#media.$state, rate = Math.max(0, Math.min(1, liveEdge() ? 1 : Math.min(time, duration()) / duration()));
1682
- return Number.isNaN(rate) ? 0 : Number.isFinite(rate) ? rate * 100 : 100;
1683
- }
1684
- #formatValue(percent) {
1685
- const time = this.#percentToTime(percent), { live, duration } = this.#media.$state;
1686
- return Number.isFinite(time) ? (live() ? time - duration() : time).toFixed(0) : "LIVE";
1687
- }
1688
- #formatTime(percent, options) {
1689
- const time = this.#percentToTime(percent), { live, duration } = this.#media.$state, value = live() ? time - duration() : time;
1690
- return Number.isFinite(time) ? `${value < 0 ? "-" : ""}${formatTime(Math.abs(value), options)}` : "LIVE";
1691
- }
1692
- }
1693
-
1694
- const menuContext = createContext();
1695
-
1696
- function scrollIntoView(el, options) {
1697
- const scrolls = r(el, options);
1698
- for (const { el: el2, top, left } of scrolls) {
1699
- el2.scroll({ top, left, behavior: options.behavior });
1700
- }
1701
- }
1702
- function scrollIntoCenter(el, options = {}) {
1703
- scrollIntoView(el, {
1704
- scrollMode: "if-needed",
1705
- block: "center",
1706
- inline: "center",
1707
- ...options
1708
- });
1709
- }
1710
-
1711
- const FOCUSABLE_ELEMENTS_SELECTOR = /* @__PURE__ */ [
1712
- "a[href]",
1713
- "[tabindex]",
1714
- "input",
1715
- "select",
1716
- "button"
1717
- ].map((selector) => `${selector}:not([aria-hidden='true'])`).join(",");
1718
- const VALID_KEYS = /* @__PURE__ */ new Set([
1719
- "Escape",
1720
- "Tab",
1721
- "ArrowUp",
1722
- "ArrowDown",
1723
- "Home",
1724
- "PageUp",
1725
- "End",
1726
- "PageDown",
1727
- "Enter",
1728
- " "
1729
- ]);
1730
- class MenuFocusController {
1731
- #index = -1;
1732
- #el = null;
1733
- #elements = [];
1734
- #delegate;
1735
- get items() {
1736
- return this.#elements;
1737
- }
1738
- constructor(delegate) {
1739
- this.#delegate = delegate;
1740
- }
1741
- attachMenu(el) {
1742
- listenEvent(el, "focus", this.#onFocus.bind(this));
1743
- this.#el = el;
1744
- onDispose(() => {
1745
- this.#el = null;
1746
- });
1747
- }
1748
- listen() {
1749
- if (!this.#el) return;
1750
- this.update();
1751
- new EventsController(this.#el).add("keyup", this.#onKeyUp.bind(this)).add("keydown", this.#onKeyDown.bind(this));
1752
- onDispose(() => {
1753
- this.#index = -1;
1754
- this.#elements = [];
1755
- });
1756
- }
1757
- update() {
1758
- this.#index = 0;
1759
- this.#elements = this.#getFocusableElements();
1760
- }
1761
- scroll(index = this.#findActiveIndex()) {
1762
- const element = this.#elements[index];
1763
- if (element) {
1764
- requestAnimationFrame(() => {
1765
- requestAnimationFrame(() => {
1766
- scrollIntoCenter(element, {
1767
- behavior: "smooth",
1768
- boundary: (el) => {
1769
- return !el.hasAttribute("data-root");
1770
- }
1771
- });
1772
- });
1773
- });
1774
- }
1775
- }
1776
- focusActive(scroll = true) {
1777
- const index = this.#findActiveIndex();
1778
- this.#focusAt(index >= 0 ? index : 0, scroll);
1779
- }
1780
- #focusAt(index, scroll = true) {
1781
- this.#index = index;
1782
- if (this.#elements[index]) {
1783
- this.#elements[index].focus({ preventScroll: true });
1784
- if (scroll) this.scroll(index);
1785
- } else {
1786
- this.#el?.focus({ preventScroll: true });
1787
- }
1788
- }
1789
- #findActiveIndex() {
1790
- return this.#elements.findIndex(
1791
- (el) => document.activeElement === el || el.getAttribute("role") === "menuitemradio" && el.getAttribute("aria-checked") === "true"
1792
- );
1793
- }
1794
- #onFocus() {
1795
- if (this.#index >= 0) return;
1796
- this.update();
1797
- this.focusActive();
1798
- }
1799
- #validateKeyEvent(event) {
1800
- const el = event.target;
1801
- if (wasEnterKeyPressed(event) && el instanceof Element) {
1802
- const role = el.getAttribute("role");
1803
- return !/a|input|select|button/.test(el.localName) && !role;
1804
- }
1805
- return VALID_KEYS.has(event.key);
1806
- }
1807
- #onKeyUp(event) {
1808
- if (!this.#validateKeyEvent(event)) return;
1809
- event.stopPropagation();
1810
- event.preventDefault();
1811
- }
1812
- #onKeyDown(event) {
1813
- if (!this.#validateKeyEvent(event)) return;
1814
- event.stopPropagation();
1815
- event.preventDefault();
1816
- switch (event.key) {
1817
- case "Escape":
1818
- this.#delegate.closeMenu(event);
1819
- break;
1820
- case "Tab":
1821
- this.#focusAt(this.#nextIndex(event.shiftKey ? -1 : 1));
1822
- break;
1823
- case "ArrowUp":
1824
- this.#focusAt(this.#nextIndex(-1));
1825
- break;
1826
- case "ArrowDown":
1827
- this.#focusAt(this.#nextIndex(1));
1828
- break;
1829
- case "Home":
1830
- case "PageUp":
1831
- this.#focusAt(0);
1832
- break;
1833
- case "End":
1834
- case "PageDown":
1835
- this.#focusAt(this.#elements.length - 1);
1836
- break;
1837
- }
1838
- }
1839
- #nextIndex(delta) {
1840
- let index = this.#index;
1841
- do {
1842
- index = (index + delta + this.#elements.length) % this.#elements.length;
1843
- } while (this.#elements[index]?.offsetParent === null);
1844
- return index;
1845
- }
1846
- #getFocusableElements() {
1847
- if (!this.#el) return [];
1848
- const focusableElements = this.#el.querySelectorAll(FOCUSABLE_ELEMENTS_SELECTOR), elements = [];
1849
- const is = (node) => {
1850
- return node.getAttribute("role") === "menu";
1851
- };
1852
- for (const el of focusableElements) {
1853
- if (isHTMLElement(el) && el.offsetParent !== null && // does not have display: none
1854
- isElementParent(this.#el, el, is)) {
1855
- elements.push(el);
1856
- }
1857
- }
1858
- return elements;
1859
- }
1860
- }
1861
-
1862
- var __defProp = Object.defineProperty;
1863
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
1864
- var __decorateClass = (decorators, target, key, kind) => {
1865
- var result = __getOwnPropDesc(target, key) ;
1866
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
1867
- if (decorator = decorators[i])
1868
- result = (decorator(target, key, result) ) || result;
1869
- if (result) __defProp(target, key, result);
1870
- return result;
1871
- };
1872
- let idCount = 0;
1873
- class Menu extends Component {
1874
- static props = {
1875
- showDelay: 0
1876
- };
1877
- #media;
1878
- #menuId;
1879
- #menuButtonId;
1880
- #expanded = signal(false);
1881
- #disabled = signal(false);
1882
- #trigger = signal(null);
1883
- #content = signal(null);
1884
- #parentMenu;
1885
- #submenus = /* @__PURE__ */ new Set();
1886
- #menuObserver = null;
1887
- #popper;
1888
- #focus;
1889
- #isSliderActive = false;
1890
- #isTriggerDisabled = signal(false);
1891
- #transitionCallbacks = /* @__PURE__ */ new Set();
1892
- get triggerElement() {
1893
- return this.#trigger();
1894
- }
1895
- get contentElement() {
1896
- return this.#content();
1897
- }
1898
- get isSubmenu() {
1899
- return !!this.#parentMenu;
1900
- }
1901
- constructor() {
1902
- super();
1903
- const { showDelay } = this.$props;
1904
- this.#popper = new Popper({
1905
- trigger: this.#trigger,
1906
- content: this.#content,
1907
- showDelay,
1908
- listen: (trigger, show, hide) => {
1909
- onPress(trigger, (event) => {
1910
- if (this.#expanded()) hide(event);
1911
- else show(event);
1912
- });
1913
- const closeTarget = this.#getCloseTarget();
1914
- if (closeTarget) {
1915
- onPress(closeTarget, (event) => {
1916
- event.stopPropagation();
1917
- hide(event);
1918
- });
1919
- }
1920
- },
1921
- onChange: this.#onExpandedChange.bind(this)
1922
- });
1923
- }
1924
- onSetup() {
1925
- this.#media = useMediaContext();
1926
- const currentIdCount = ++idCount;
1927
- this.#menuId = `media-menu-${currentIdCount}`;
1928
- this.#menuButtonId = `media-menu-button-${currentIdCount}`;
1929
- this.#focus = new MenuFocusController({
1930
- closeMenu: this.close.bind(this)
1931
- });
1932
- if (hasProvidedContext(menuContext)) {
1933
- this.#parentMenu = useContext(menuContext);
1934
- }
1935
- this.#observeSliders();
1936
- this.setAttributes({
1937
- "data-open": this.#expanded,
1938
- "data-root": !this.isSubmenu,
1939
- "data-submenu": this.isSubmenu,
1940
- "data-disabled": this.#isDisabled.bind(this)
1941
- });
1942
- provideContext(menuContext, {
1943
- button: this.#trigger,
1944
- content: this.#content,
1945
- expanded: this.#expanded,
1946
- hint: signal(""),
1947
- submenu: !!this.#parentMenu,
1948
- disable: this.#disable.bind(this),
1949
- attachMenuButton: this.#attachMenuButton.bind(this),
1950
- attachMenuItems: this.#attachMenuItems.bind(this),
1951
- attachObserver: this.#attachObserver.bind(this),
1952
- disableMenuButton: this.#disableMenuButton.bind(this),
1953
- addSubmenu: this.#addSubmenu.bind(this),
1954
- onTransitionEvent: (callback) => {
1955
- this.#transitionCallbacks.add(callback);
1956
- onDispose(() => {
1957
- this.#transitionCallbacks.delete(callback);
1958
- });
1959
- }
1960
- });
1961
- }
1962
- onAttach(el) {
1963
- el.style.setProperty("display", "contents");
1964
- }
1965
- onConnect(el) {
1966
- effect(this.#watchExpanded.bind(this));
1967
- if (this.isSubmenu) {
1968
- this.#parentMenu?.addSubmenu(this);
1969
- }
1970
- }
1971
- onDestroy() {
1972
- this.#trigger.set(null);
1973
- this.#content.set(null);
1974
- this.#menuObserver = null;
1975
- this.#transitionCallbacks.clear();
1976
- }
1977
- #observeSliders() {
1978
- let sliderActiveTimer = -1, parentSliderObserver = hasProvidedContext(sliderObserverContext) ? useContext(sliderObserverContext) : null;
1979
- provideContext(sliderObserverContext, {
1980
- onDragStart: () => {
1981
- parentSliderObserver?.onDragStart?.();
1982
- window.clearTimeout(sliderActiveTimer);
1983
- sliderActiveTimer = -1;
1984
- this.#isSliderActive = true;
1985
- },
1986
- onDragEnd: () => {
1987
- parentSliderObserver?.onDragEnd?.();
1988
- sliderActiveTimer = window.setTimeout(() => {
1989
- this.#isSliderActive = false;
1990
- sliderActiveTimer = -1;
1991
- }, 300);
1992
- }
1993
- });
1994
- }
1995
- #watchExpanded() {
1996
- const expanded = this.#isExpanded();
1997
- if (!this.isSubmenu) this.#onResize();
1998
- this.#updateMenuItemsHidden(expanded);
1999
- if (!expanded) return;
2000
- effect(() => {
2001
- const { height } = this.#media.$state, content = this.#content();
2002
- content && setStyle(content, "--player-height", height() + "px");
2003
- });
2004
- this.#focus.listen();
2005
- this.listen("pointerup", this.#onPointerUp.bind(this));
2006
- listenEvent(window, "pointerup", this.#onWindowPointerUp.bind(this));
2007
- }
2008
- #attachMenuButton(button) {
2009
- const el = button.el, isMenuItem = this.isSubmenu, isARIADisabled = $ariaBool(this.#isDisabled.bind(this));
2010
- setAttributeIfEmpty(el, "tabindex", isMenuItem ? "-1" : "0");
2011
- setAttributeIfEmpty(el, "role", isMenuItem ? "menuitem" : "button");
2012
- setAttribute(el, "id", this.#menuButtonId);
2013
- setAttribute(el, "aria-haspopup", "menu");
2014
- setAttribute(el, "aria-expanded", "false");
2015
- setAttribute(el, "data-root", !this.isSubmenu);
2016
- setAttribute(el, "data-submenu", this.isSubmenu);
2017
- const watchAttrs = () => {
2018
- setAttribute(el, "data-open", this.#expanded());
2019
- setAttribute(el, "aria-disabled", isARIADisabled());
2020
- };
2021
- effect(watchAttrs);
2022
- this.#trigger.set(el);
2023
- onDispose(() => {
2024
- this.#trigger.set(null);
2025
- });
2026
- }
2027
- #attachMenuItems(items) {
2028
- const el = items.el;
2029
- el.style.setProperty("display", "none");
2030
- setAttribute(el, "id", this.#menuId);
2031
- setAttributeIfEmpty(el, "role", "menu");
2032
- setAttributeIfEmpty(el, "tabindex", "-1");
2033
- setAttribute(el, "data-root", !this.isSubmenu);
2034
- setAttribute(el, "data-submenu", this.isSubmenu);
2035
- this.#content.set(el);
2036
- onDispose(() => this.#content.set(null));
2037
- const watchAttrs = () => setAttribute(el, "data-open", this.#expanded());
2038
- effect(watchAttrs);
2039
- this.#focus.attachMenu(el);
2040
- this.#updateMenuItemsHidden(false);
2041
- const onTransition = this.#onResizeTransition.bind(this);
2042
- if (!this.isSubmenu) {
2043
- items.listen("transitionstart", onTransition);
2044
- items.listen("transitionend", onTransition);
2045
- items.listen("animationend", this.#onResize);
2046
- items.listen("vds-menu-resize", this.#onResize);
2047
- } else {
2048
- this.#parentMenu?.onTransitionEvent(onTransition);
2049
- }
2050
- }
2051
- #attachObserver(observer) {
2052
- this.#menuObserver = observer;
2053
- }
2054
- #updateMenuItemsHidden(expanded) {
2055
- const content = peek(this.#content);
2056
- if (content) setAttribute(content, "aria-hidden", ariaBool(!expanded));
2057
- }
2058
- #disableMenuButton(disabled) {
2059
- this.#isTriggerDisabled.set(disabled);
2060
- }
2061
- #wasKeyboardExpand = false;
2062
- #onExpandedChange(isExpanded, event) {
2063
- this.#wasKeyboardExpand = isKeyboardEvent(event);
2064
- event?.stopPropagation();
2065
- if (this.#expanded() === isExpanded) return;
2066
- if (this.#isDisabled()) {
2067
- if (isExpanded) this.#popper.hide(event);
2068
- return;
2069
- }
2070
- this.el?.dispatchEvent(
2071
- new Event("vds-menu-resize", {
2072
- bubbles: true,
2073
- composed: true
2074
- })
2075
- );
2076
- const trigger = this.#trigger(), content = this.#content();
2077
- if (trigger) {
2078
- setAttribute(trigger, "aria-controls", isExpanded && this.#menuId);
2079
- setAttribute(trigger, "aria-expanded", ariaBool(isExpanded));
2080
- }
2081
- if (content) setAttribute(content, "aria-labelledby", isExpanded && this.#menuButtonId);
2082
- this.#expanded.set(isExpanded);
2083
- this.#toggleMediaControls(event);
2084
- tick();
2085
- if (this.#wasKeyboardExpand) {
2086
- if (isExpanded) content?.focus();
2087
- else trigger?.focus();
2088
- for (const el of [this.el, content]) {
2089
- el && el.setAttribute("data-keyboard", "");
2090
- }
2091
- } else {
2092
- for (const el of [this.el, content]) {
2093
- el && el.removeAttribute("data-keyboard");
2094
- }
2095
- }
2096
- this.dispatch(isExpanded ? "open" : "close", { trigger: event });
2097
- if (isExpanded) {
2098
- if (!this.isSubmenu && this.#media.activeMenu !== this) {
2099
- this.#media.activeMenu?.close(event);
2100
- this.#media.activeMenu = this;
2101
- }
2102
- this.#menuObserver?.onOpen?.(event);
2103
- } else {
2104
- if (this.isSubmenu) {
2105
- for (const el of this.#submenus) el.close(event);
2106
- } else {
2107
- this.#media.activeMenu = null;
2108
- }
2109
- this.#menuObserver?.onClose?.(event);
2110
- }
2111
- if (isExpanded) {
2112
- requestAnimationFrame(this.#updateFocus.bind(this));
2113
- }
2114
- }
2115
- #updateFocus() {
2116
- if (this.#isTransitionActive || this.#isSubmenuOpen) return;
2117
- this.#focus.update();
2118
- requestAnimationFrame(() => {
2119
- if (this.#wasKeyboardExpand) {
2120
- this.#focus.focusActive();
2121
- } else {
2122
- this.#focus.scroll();
2123
- }
2124
- });
2125
- }
2126
- #isExpanded() {
2127
- return !this.#isDisabled() && this.#expanded();
2128
- }
2129
- #isDisabled() {
2130
- return this.#disabled() || this.#isTriggerDisabled();
2131
- }
2132
- #disable(disabled) {
2133
- this.#disabled.set(disabled);
2134
- }
2135
- #onPointerUp(event) {
2136
- const content = this.#content();
2137
- if (this.#isSliderActive || content && isEventInside(content, event)) {
2138
- return;
2139
- }
2140
- event.stopPropagation();
2141
- }
2142
- #onWindowPointerUp(event) {
2143
- const content = this.#content();
2144
- if (this.#isSliderActive || content && isEventInside(content, event)) {
2145
- return;
2146
- }
2147
- this.close(event);
2148
- }
2149
- #getCloseTarget() {
2150
- const target = this.el?.querySelector('[data-part="close-target"]');
2151
- return this.el && target && isElementParent(this.el, target, (node) => node.getAttribute("role") === "menu") ? target : null;
2152
- }
2153
- #toggleMediaControls(trigger) {
2154
- if (this.isSubmenu) return;
2155
- if (this.#expanded()) this.#media.remote.pauseControls(trigger);
2156
- else this.#media.remote.resumeControls(trigger);
2157
- }
2158
- #addSubmenu(menu) {
2159
- this.#submenus.add(menu);
2160
- new EventsController(menu).add("open", this.#onSubmenuOpenBind).add("close", this.#onSubmenuCloseBind);
2161
- onDispose(this.#removeSubmenuBind);
2162
- }
2163
- #removeSubmenuBind = this.#removeSubmenu.bind(this);
2164
- #removeSubmenu(menu) {
2165
- this.#submenus.delete(menu);
2166
- }
2167
- #isSubmenuOpen = false;
2168
- #onSubmenuOpenBind = this.#onSubmenuOpen.bind(this);
2169
- #onSubmenuOpen(event) {
2170
- this.#isSubmenuOpen = true;
2171
- const content = this.#content();
2172
- if (this.isSubmenu) {
2173
- this.triggerElement?.setAttribute("aria-hidden", "true");
2174
- }
2175
- for (const target of this.#submenus) {
2176
- if (target !== event.target) {
2177
- for (const el of [target.el, target.triggerElement]) {
2178
- el?.setAttribute("aria-hidden", "true");
2179
- }
2180
- }
2181
- }
2182
- if (content) {
2183
- const el = event.target.el;
2184
- for (const child of content.children) {
2185
- if (child.contains(el)) {
2186
- child.setAttribute("data-open", "");
2187
- } else if (child !== el) {
2188
- child.setAttribute("data-hidden", "");
2189
- }
2190
- }
2191
- }
2192
- }
2193
- #onSubmenuCloseBind = this.#onSubmenuClose.bind(this);
2194
- #onSubmenuClose(event) {
2195
- this.#isSubmenuOpen = false;
2196
- const content = this.#content();
2197
- if (this.isSubmenu) {
2198
- this.triggerElement?.setAttribute("aria-hidden", "false");
2199
- }
2200
- for (const target of this.#submenus) {
2201
- for (const el of [target.el, target.triggerElement]) {
2202
- el?.setAttribute("aria-hidden", "false");
2203
- }
2204
- }
2205
- if (content) {
2206
- for (const child of content.children) {
2207
- child.removeAttribute("data-open");
2208
- child.removeAttribute("data-hidden");
2209
- }
2210
- }
2211
- }
2212
- #onResize = animationFrameThrottle(() => {
2213
- const content = peek(this.#content);
2214
- if (!content || false) return;
2215
- let height = 0, styles = getComputedStyle(content), children = [...content.children];
2216
- for (const prop2 of ["paddingTop", "paddingBottom", "borderTopWidth", "borderBottomWidth"]) {
2217
- height += parseFloat(styles[prop2]) || 0;
2218
- }
2219
- for (const child of children) {
2220
- if (isHTMLElement(child) && child.style.display === "contents") {
2221
- children.push(...child.children);
2222
- } else if (child.nodeType === 3) {
2223
- height += parseFloat(getComputedStyle(child).fontSize);
2224
- } else if (isHTMLElement(child)) {
2225
- if (!isElementVisible(child)) continue;
2226
- const style = getComputedStyle(child);
2227
- height += child.offsetHeight + (parseFloat(style.marginTop) || 0) + (parseFloat(style.marginBottom) || 0);
2228
- }
2229
- }
2230
- setStyle(content, "--menu-height", height + "px");
2231
- });
2232
- #isTransitionActive = false;
2233
- #onResizeTransition(event) {
2234
- const content = this.#content();
2235
- if (content && event.propertyName === "height") {
2236
- this.#isTransitionActive = event.type === "transitionstart";
2237
- setAttribute(content, "data-transition", this.#isTransitionActive ? "height" : null);
2238
- if (this.#expanded()) this.#updateFocus();
2239
- }
2240
- for (const callback of this.#transitionCallbacks) callback(event);
2241
- }
2242
- open(trigger) {
2243
- if (peek(this.#expanded)) return;
2244
- this.#popper.show(trigger);
2245
- tick();
2246
- }
2247
- close(trigger) {
2248
- if (!peek(this.#expanded)) return;
2249
- this.#popper.hide(trigger);
2250
- tick();
2251
- }
2252
- }
2253
- __decorateClass([
2254
- prop
2255
- ], Menu.prototype, "triggerElement");
2256
- __decorateClass([
2257
- prop
2258
- ], Menu.prototype, "contentElement");
2259
- __decorateClass([
2260
- prop
2261
- ], Menu.prototype, "isSubmenu");
2262
- __decorateClass([
2263
- method
2264
- ], Menu.prototype, "open");
2265
- __decorateClass([
2266
- method
2267
- ], Menu.prototype, "close");
2268
-
2269
- class MenuButton extends Component {
2270
- static props = {
2271
- disabled: false
2272
- };
2273
- #menu;
2274
- #hintEl = signal(null);
2275
- get expanded() {
2276
- return this.#menu?.expanded() ?? false;
2277
- }
2278
- constructor() {
2279
- super();
2280
- new FocusVisibleController();
2281
- }
2282
- onSetup() {
2283
- this.#menu = useContext(menuContext);
2284
- }
2285
- onAttach(el) {
2286
- this.#menu.attachMenuButton(this);
2287
- effect(this.#watchDisabled.bind(this));
2288
- setAttributeIfEmpty(el, "type", "button");
2289
- }
2290
- onConnect(el) {
2291
- effect(this.#watchHintEl.bind(this));
2292
- this.#onMutation();
2293
- const mutations = new MutationObserver(this.#onMutation.bind(this));
2294
- mutations.observe(el, { attributeFilter: ["data-part"], childList: true, subtree: true });
2295
- onDispose(() => mutations.disconnect());
2296
- onPress(el, (trigger) => {
2297
- this.dispatch("select", { trigger });
2298
- });
2299
- }
2300
- #watchDisabled() {
2301
- this.#menu.disableMenuButton(this.$props.disabled());
2302
- }
2303
- #watchHintEl() {
2304
- const el = this.#hintEl();
2305
- if (!el) return;
2306
- effect(() => {
2307
- const text = this.#menu.hint();
2308
- if (text) el.textContent = text;
2309
- });
2310
- }
2311
- #onMutation() {
2312
- const hintEl = this.el?.querySelector('[data-part="hint"]');
2313
- this.#hintEl.set(hintEl ?? null);
2314
- }
2315
- }
2316
- const menubutton__proto = MenuButton.prototype;
2317
- prop(menubutton__proto, "expanded");
2318
-
2319
- class MenuItem extends MenuButton {
2320
- }
2321
-
2322
- class MenuPortal extends Component {
2323
- static props = {
2324
- container: null,
2325
- disabled: false
2326
- };
2327
- #target = null;
2328
- #media;
2329
- onSetup() {
2330
- this.#media = useMediaContext();
2331
- provideContext(menuPortalContext, {
2332
- attach: this.#attachElement.bind(this)
2333
- });
2334
- }
2335
- onAttach(el) {
2336
- el.style.setProperty("display", "contents");
2337
- }
2338
- // Need this so connect scope is defined.
2339
- onConnect(el) {
2340
- }
2341
- onDestroy() {
2342
- this.#target?.remove();
2343
- this.#target = null;
2344
- }
2345
- #attachElement(el) {
2346
- this.#portal(false);
2347
- this.#target = el;
2348
- requestScopedAnimationFrame(() => {
2349
- requestScopedAnimationFrame(() => {
2350
- if (!this.connectScope) return;
2351
- effect(this.#watchDisabled.bind(this));
2352
- });
2353
- });
2354
- }
2355
- #watchDisabled() {
2356
- const { fullscreen } = this.#media.$state, { disabled } = this.$props;
2357
- this.#portal(disabled() === "fullscreen" ? !fullscreen() : !disabled());
2358
- }
2359
- #portal(shouldPortal) {
2360
- if (!this.#target) return;
2361
- let container = this.#getContainer(this.$props.container());
2362
- if (!container) return;
2363
- const isPortalled = this.#target.parentElement === container;
2364
- setAttribute(this.#target, "data-portal", shouldPortal);
2365
- if (shouldPortal) {
2366
- if (!isPortalled) {
2367
- this.#target.remove();
2368
- container.append(this.#target);
2369
- }
2370
- } else if (isPortalled && this.#target.parentElement === container) {
2371
- this.#target.remove();
2372
- this.el?.append(this.#target);
2373
- }
2374
- }
2375
- #getContainer(selector) {
2376
- if (isHTMLElement(selector)) return selector;
2377
- return selector ? document.querySelector(selector) : document.body;
2378
- }
2379
- }
2380
- const menuPortalContext = createContext();
2381
-
2382
- class MenuItems extends Component {
2383
- static props = {
2384
- placement: null,
2385
- offset: 0,
2386
- alignOffset: 0
2387
- };
2388
- #menu;
2389
- constructor() {
2390
- super();
2391
- new FocusVisibleController();
2392
- const { placement } = this.$props;
2393
- this.setAttributes({
2394
- "data-placement": placement
2395
- });
2396
- }
2397
- onAttach(el) {
2398
- this.#menu = useContext(menuContext);
2399
- this.#menu.attachMenuItems(this);
2400
- if (hasProvidedContext(menuPortalContext)) {
2401
- const portal = useContext(menuPortalContext);
2402
- if (portal) {
2403
- provideContext(menuPortalContext, null);
2404
- portal.attach(el);
2405
- onDispose(() => portal.attach(null));
2406
- }
2407
- }
2408
- }
2409
- onConnect(el) {
2410
- effect(this.#watchPlacement.bind(this));
2411
- }
2412
- #watchPlacement() {
2413
- const { expanded } = this.#menu;
2414
- if (!this.el || !expanded()) return;
2415
- const placement = this.$props.placement();
2416
- if (!placement) return;
2417
- Object.assign(this.el.style, {
2418
- position: "absolute",
2419
- top: 0,
2420
- left: 0,
2421
- width: "max-content"
2422
- });
2423
- const { offset: mainOffset, alignOffset } = this.$props;
2424
- onDispose(
2425
- autoPlacement(this.el, this.#getButton(), placement, {
2426
- offsetVarName: "media-menu",
2427
- xOffset: alignOffset(),
2428
- yOffset: mainOffset()
2429
- })
2430
- );
2431
- onDispose(this.#hide.bind(this));
2432
- }
2433
- #hide() {
2434
- if (!this.el) return;
2435
- this.el.removeAttribute("style");
2436
- this.el.style.display = "none";
2437
- }
2438
- #getButton() {
2439
- return this.#menu.button();
2440
- }
2441
- }
2442
-
2443
- const radioControllerContext = createContext();
2444
-
2445
- class RadioGroupController extends ViewController {
2446
- #group = /* @__PURE__ */ new Set();
2447
- #value = signal("");
2448
- #controller = null;
2449
- onValueChange;
2450
- get values() {
2451
- return Array.from(this.#group).map((radio) => radio.value());
2452
- }
2453
- get value() {
2454
- return this.#value();
2455
- }
2456
- set value(value) {
2457
- this.#onChange(value);
2458
- }
2459
- onSetup() {
2460
- provideContext(radioControllerContext, {
2461
- add: this.#addRadio.bind(this),
2462
- remove: this.#removeRadio.bind(this)
2463
- });
2464
- }
2465
- onAttach(el) {
2466
- const isMenuItem = hasProvidedContext(menuContext);
2467
- if (!isMenuItem) setAttributeIfEmpty(el, "role", "radiogroup");
2468
- this.setAttributes({ value: this.#value });
2469
- }
2470
- onDestroy() {
2471
- this.#group.clear();
2472
- }
2473
- #addRadio(radio) {
2474
- if (this.#group.has(radio)) return;
2475
- this.#group.add(radio);
2476
- radio.onCheck = this.#onChangeBind;
2477
- radio.check(radio.value() === this.#value());
2478
- }
2479
- #removeRadio(radio) {
2480
- radio.onCheck = null;
2481
- this.#group.delete(radio);
2482
- }
2483
- #onChangeBind = this.#onChange.bind(this);
2484
- #onChange(newValue, trigger) {
2485
- const currentValue = peek(this.#value);
2486
- if (!newValue || newValue === currentValue) return;
2487
- const currentRadio = this.#findRadio(currentValue), newRadio = this.#findRadio(newValue);
2488
- currentRadio?.check(false, trigger);
2489
- newRadio?.check(true, trigger);
2490
- this.#value.set(newValue);
2491
- this.onValueChange?.(newValue, trigger);
2492
- }
2493
- #findRadio(newValue) {
2494
- for (const radio of this.#group) {
2495
- if (newValue === peek(radio.value)) return radio;
2496
- }
2497
- return null;
2498
- }
2499
- }
2500
-
2501
- class Radio extends Component {
2502
- static props = {
2503
- value: ""
2504
- };
2505
- #checked = signal(false);
2506
- #controller = {
2507
- value: this.$props.value,
2508
- check: this.#check.bind(this),
2509
- onCheck: null
2510
- };
2511
- /**
2512
- * Whether this radio is currently checked.
2513
- */
2514
- get checked() {
2515
- return this.#checked();
2516
- }
2517
- constructor() {
2518
- super();
2519
- new FocusVisibleController();
2520
- }
2521
- onSetup() {
2522
- this.setAttributes({
2523
- value: this.$props.value,
2524
- "data-checked": this.#checked,
2525
- "aria-checked": $ariaBool(this.#checked)
2526
- });
2527
- }
2528
- onAttach(el) {
2529
- const isMenuItem = hasProvidedContext(menuContext);
2530
- setAttributeIfEmpty(el, "tabindex", isMenuItem ? "-1" : "0");
2531
- setAttributeIfEmpty(el, "role", isMenuItem ? "menuitemradio" : "radio");
2532
- effect(this.#watchValue.bind(this));
2533
- }
2534
- onConnect(el) {
2535
- this.#addToGroup();
2536
- onPress(el, this.#onPress.bind(this));
2537
- onDispose(this.#onDisconnect.bind(this));
2538
- }
2539
- #onDisconnect() {
2540
- scoped(() => {
2541
- const group = useContext(radioControllerContext);
2542
- group.remove(this.#controller);
2543
- }, this.connectScope);
2544
- }
2545
- #addToGroup() {
2546
- const group = useContext(radioControllerContext);
2547
- group.add(this.#controller);
2548
- }
2549
- #watchValue() {
2550
- const { value } = this.$props, newValue = value();
2551
- if (peek(this.#checked)) {
2552
- this.#controller.onCheck?.(newValue);
2553
- }
2554
- }
2555
- #onPress(event) {
2556
- if (peek(this.#checked)) return;
2557
- this.#onChange(true, event);
2558
- this.#onSelect(event);
2559
- this.#controller.onCheck?.(peek(this.$props.value), event);
2560
- }
2561
- #check(value, trigger) {
2562
- if (peek(this.#checked) === value) return;
2563
- this.#onChange(value, trigger);
2564
- }
2565
- #onChange(value, trigger) {
2566
- this.#checked.set(value);
2567
- this.dispatch("change", { detail: value, trigger });
2568
- }
2569
- #onSelect(trigger) {
2570
- this.dispatch("select", { trigger });
2571
- }
2572
- }
2573
- const radio__proto = Radio.prototype;
2574
- prop(radio__proto, "checked");
2575
-
2576
- class AudioRadioGroup extends Component {
2577
- static props = {
2578
- emptyLabel: "Default"
2579
- };
2580
- #menu;
2581
- #media;
2582
- #controller;
2583
- get value() {
2584
- return this.#controller.value;
2585
- }
2586
- get disabled() {
2587
- const { audioTracks } = this.#media.$state;
2588
- return audioTracks().length <= 1;
2589
- }
2590
- constructor() {
2591
- super();
2592
- this.#controller = new RadioGroupController();
2593
- this.#controller.onValueChange = this.#onValueChange.bind(this);
2594
- }
2595
- onSetup() {
2596
- this.#media = useMediaContext();
2597
- if (hasProvidedContext(menuContext)) {
2598
- this.#menu = useContext(menuContext);
2599
- }
2600
- }
2601
- onConnect(el) {
2602
- effect(this.#watchValue.bind(this));
2603
- effect(this.#watchControllerDisabled.bind(this));
2604
- effect(this.#watchHintText.bind(this));
2605
- }
2606
- getOptions() {
2607
- const { audioTracks } = this.#media.$state;
2608
- return audioTracks().map((track) => ({
2609
- track,
2610
- label: track.label,
2611
- value: track.label.toLowerCase()
2612
- }));
2613
- }
2614
- #watchValue() {
2615
- this.#controller.value = this.#getValue();
2616
- }
2617
- #watchHintText() {
2618
- const { emptyLabel } = this.$props, { audioTrack } = this.#media.$state, track = audioTrack();
2619
- this.#menu?.hint.set(track?.label ?? emptyLabel());
2620
- }
2621
- #watchControllerDisabled() {
2622
- this.#menu?.disable(this.disabled);
2623
- }
2624
- #getValue() {
2625
- const { audioTrack } = this.#media.$state;
2626
- const track = audioTrack();
2627
- return track ? track.label.toLowerCase() : "";
2628
- }
2629
- #onValueChange(value, trigger) {
2630
- if (this.disabled) return;
2631
- const index = this.#media.audioTracks.toArray().findIndex((track) => track.label.toLowerCase() === value);
2632
- if (index >= 0) {
2633
- const track = this.#media.audioTracks[index];
2634
- this.#media.remote.changeAudioTrack(index, trigger);
2635
- this.dispatch("change", { detail: track, trigger });
2636
- }
2637
- }
2638
- }
2639
- const audioradiogroup__proto = AudioRadioGroup.prototype;
2640
- prop(audioradiogroup__proto, "value");
2641
- prop(audioradiogroup__proto, "disabled");
2642
- method(audioradiogroup__proto, "getOptions");
2643
-
2644
- class CaptionsRadioGroup extends Component {
2645
- static props = {
2646
- offLabel: "Off"
2647
- };
2648
- #media;
2649
- #menu;
2650
- #controller;
2651
- get value() {
2652
- return this.#controller.value;
2653
- }
2654
- get disabled() {
2655
- const { hasCaptions } = this.#media.$state;
2656
- return !hasCaptions();
2657
- }
2658
- constructor() {
2659
- super();
2660
- this.#controller = new RadioGroupController();
2661
- this.#controller.onValueChange = this.#onValueChange.bind(this);
2662
- }
2663
- onSetup() {
2664
- this.#media = useMediaContext();
2665
- if (hasProvidedContext(menuContext)) {
2666
- this.#menu = useContext(menuContext);
2667
- }
2668
- }
2669
- onConnect(el) {
2670
- super.onConnect?.(el);
2671
- effect(this.#watchValue.bind(this));
2672
- effect(this.#watchControllerDisabled.bind(this));
2673
- effect(this.#watchHintText.bind(this));
2674
- }
2675
- getOptions() {
2676
- const { offLabel } = this.$props, { textTracks } = this.#media.$state;
2677
- return [
2678
- { value: "off", label: offLabel },
2679
- ...textTracks().filter(isTrackCaptionKind).map((track) => ({
2680
- track,
2681
- label: track.label,
2682
- value: this.#getTrackValue(track)
2683
- }))
2684
- ];
2685
- }
2686
- #watchValue() {
2687
- this.#controller.value = this.#getValue();
2688
- }
2689
- #watchHintText() {
2690
- const { offLabel } = this.$props, { textTrack } = this.#media.$state, track = textTrack();
2691
- this.#menu?.hint.set(
2692
- track && isTrackCaptionKind(track) && track.mode === "showing" ? track.label : offLabel()
2693
- );
2694
- }
2695
- #watchControllerDisabled() {
2696
- this.#menu?.disable(this.disabled);
2697
- }
2698
- #getValue() {
2699
- const { textTrack } = this.#media.$state, track = textTrack();
2700
- return track && isTrackCaptionKind(track) && track.mode === "showing" ? this.#getTrackValue(track) : "off";
2701
- }
2702
- #onValueChange(value, trigger) {
2703
- if (this.disabled) return;
2704
- if (value === "off") {
2705
- const track = this.#media.textTracks.selected;
2706
- if (track) {
2707
- const index2 = this.#media.textTracks.indexOf(track);
2708
- this.#media.remote.changeTextTrackMode(index2, "disabled", trigger);
2709
- this.dispatch("change", { detail: null, trigger });
2710
- }
2711
- return;
2712
- }
2713
- const index = this.#media.textTracks.toArray().findIndex((track) => this.#getTrackValue(track) === value);
2714
- if (index >= 0) {
2715
- const track = this.#media.textTracks[index];
2716
- this.#media.remote.changeTextTrackMode(index, "showing", trigger);
2717
- this.dispatch("change", { detail: track, trigger });
2718
- }
2719
- }
2720
- #getTrackValue(track) {
2721
- return track.id + ":" + track.kind + "-" + track.label.toLowerCase();
2722
- }
2723
- }
2724
- const captionsradiogroup__proto = CaptionsRadioGroup.prototype;
2725
- prop(captionsradiogroup__proto, "value");
2726
- prop(captionsradiogroup__proto, "disabled");
2727
- method(captionsradiogroup__proto, "getOptions");
2728
-
2729
- const DEFAULT_PLAYBACK_RATES = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
2730
- class SpeedRadioGroup extends Component {
2731
- static props = {
2732
- normalLabel: "Normal",
2733
- rates: DEFAULT_PLAYBACK_RATES
2734
- };
2735
- #media;
2736
- #menu;
2737
- #controller;
2738
- get value() {
2739
- return this.#controller.value;
2740
- }
2741
- get disabled() {
2742
- const { rates } = this.$props, { canSetPlaybackRate } = this.#media.$state;
2743
- return !canSetPlaybackRate() || rates().length === 0;
2744
- }
2745
- constructor() {
2746
- super();
2747
- this.#controller = new RadioGroupController();
2748
- this.#controller.onValueChange = this.#onValueChange.bind(this);
2749
- }
2750
- onSetup() {
2751
- this.#media = useMediaContext();
2752
- if (hasProvidedContext(menuContext)) {
2753
- this.#menu = useContext(menuContext);
2754
- }
2755
- }
2756
- onConnect(el) {
2757
- effect(this.#watchValue.bind(this));
2758
- effect(this.#watchHintText.bind(this));
2759
- effect(this.#watchControllerDisabled.bind(this));
2760
- }
2761
- getOptions() {
2762
- const { rates, normalLabel } = this.$props;
2763
- return rates().map((rate) => ({
2764
- label: rate === 1 ? normalLabel : rate + "\xD7",
2765
- value: rate.toString()
2766
- }));
2767
- }
2768
- #watchValue() {
2769
- this.#controller.value = this.#getValue();
2770
- }
2771
- #watchHintText() {
2772
- const { normalLabel } = this.$props, { playbackRate } = this.#media.$state, rate = playbackRate();
2773
- this.#menu?.hint.set(rate === 1 ? normalLabel() : rate + "\xD7");
2774
- }
2775
- #watchControllerDisabled() {
2776
- this.#menu?.disable(this.disabled);
2777
- }
2778
- #getValue() {
2779
- const { playbackRate } = this.#media.$state;
2780
- return playbackRate().toString();
2781
- }
2782
- #onValueChange(value, trigger) {
2783
- if (this.disabled) return;
2784
- const rate = +value;
2785
- this.#media.remote.changePlaybackRate(rate, trigger);
2786
- this.dispatch("change", { detail: rate, trigger });
2787
- }
2788
- }
2789
- const speedradiogroup__proto = SpeedRadioGroup.prototype;
2790
- prop(speedradiogroup__proto, "value");
2791
- prop(speedradiogroup__proto, "disabled");
2792
- method(speedradiogroup__proto, "getOptions");
2793
-
2794
- class QualityRadioGroup extends Component {
2795
- static props = {
2796
- autoLabel: "Auto",
2797
- hideBitrate: false,
2798
- sort: "descending"
2799
- };
2800
- #media;
2801
- #menu;
2802
- #controller;
2803
- get value() {
2804
- return this.#controller.value;
2805
- }
2806
- get disabled() {
2807
- const { canSetQuality, qualities } = this.#media.$state;
2808
- return !canSetQuality() || qualities().length <= 1;
2809
- }
2810
- #sortedQualities = computed(() => {
2811
- const { sort } = this.$props, { qualities } = this.#media.$state;
2812
- return sortVideoQualities(qualities(), sort() === "descending");
2813
- });
2814
- constructor() {
2815
- super();
2816
- this.#controller = new RadioGroupController();
2817
- this.#controller.onValueChange = this.#onValueChange.bind(this);
2818
- }
2819
- onSetup() {
2820
- this.#media = useMediaContext();
2821
- if (hasProvidedContext(menuContext)) {
2822
- this.#menu = useContext(menuContext);
2823
- }
2824
- }
2825
- onConnect(el) {
2826
- effect(this.#watchValue.bind(this));
2827
- effect(this.#watchControllerDisabled.bind(this));
2828
- effect(this.#watchHintText.bind(this));
2829
- }
2830
- getOptions() {
2831
- const { autoLabel, hideBitrate } = this.$props;
2832
- return [
2833
- { value: "auto", label: autoLabel },
2834
- ...this.#sortedQualities().map((quality) => {
2835
- const bitrate = quality.bitrate && quality.bitrate >= 0 ? `${round(quality.bitrate / 1e6, 2)} Mbps` : null;
2836
- return {
2837
- quality,
2838
- label: quality.height + "p",
2839
- value: this.#getQualityId(quality),
2840
- bitrate: () => !hideBitrate() ? bitrate : null
2841
- };
2842
- })
2843
- ];
2844
- }
2845
- #watchValue() {
2846
- this.#controller.value = this.#getValue();
2847
- }
2848
- #watchHintText() {
2849
- const { autoLabel } = this.$props, { autoQuality, quality } = this.#media.$state, qualityText = quality() ? quality().height + "p" : "";
2850
- this.#menu?.hint.set(
2851
- !autoQuality() ? qualityText : autoLabel() + (qualityText ? ` (${qualityText})` : "")
2852
- );
2853
- }
2854
- #watchControllerDisabled() {
2855
- this.#menu?.disable(this.disabled);
2856
- }
2857
- #onValueChange(value, trigger) {
2858
- if (this.disabled) return;
2859
- if (value === "auto") {
2860
- this.#media.remote.changeQuality(-1, trigger);
2861
- this.dispatch("change", { detail: "auto", trigger });
2862
- return;
2863
- }
2864
- const { qualities } = this.#media.$state, index = peek(qualities).findIndex((quality) => this.#getQualityId(quality) === value);
2865
- if (index >= 0) {
2866
- const quality = peek(qualities)[index];
2867
- this.#media.remote.changeQuality(index, trigger);
2868
- this.dispatch("change", { detail: quality, trigger });
2869
- }
2870
- }
2871
- #getValue() {
2872
- const { quality, autoQuality } = this.#media.$state;
2873
- if (autoQuality()) return "auto";
2874
- const currentQuality = quality();
2875
- return currentQuality ? this.#getQualityId(currentQuality) : "auto";
2876
- }
2877
- #getQualityId(quality) {
2878
- return quality.height + "_" + quality.bitrate;
2879
- }
2880
- }
2881
- const qualityradiogroup__proto = QualityRadioGroup.prototype;
2882
- prop(qualityradiogroup__proto, "value");
2883
- prop(qualityradiogroup__proto, "disabled");
2884
- method(qualityradiogroup__proto, "getOptions");
2885
-
2886
- class Time extends Component {
2887
- static props = {
2888
- type: "current",
2889
- showHours: false,
2890
- padHours: null,
2891
- padMinutes: null,
2892
- remainder: false,
2893
- toggle: false,
2894
- hidden: false
2895
- };
2896
- static state = new State({
2897
- timeText: "",
2898
- hidden: false
2899
- });
2900
- #media;
2901
- #invert = signal(null);
2902
- #isVisible = signal(true);
2903
- #isIntersecting = signal(true);
2904
- onSetup() {
2905
- this.#media = useMediaContext();
2906
- this.#watchTime();
2907
- const { type } = this.$props;
2908
- this.setAttributes({
2909
- "data-type": type,
2910
- "data-remainder": this.#shouldInvert.bind(this)
2911
- });
2912
- new IntersectionObserverController({
2913
- callback: this.#onIntersectionChange.bind(this)
2914
- }).attach(this);
2915
- }
2916
- onAttach(el) {
2917
- if (!el.hasAttribute("role")) effect(this.#watchRole.bind(this));
2918
- effect(this.#watchTime.bind(this));
2919
- }
2920
- onConnect(el) {
2921
- onDispose(observeVisibility(el, this.#isVisible.set));
2922
- effect(this.#watchHidden.bind(this));
2923
- effect(this.#watchToggle.bind(this));
2924
- }
2925
- #onIntersectionChange(entries) {
2926
- this.#isIntersecting.set(entries[0].isIntersecting);
2927
- }
2928
- #watchHidden() {
2929
- const { hidden } = this.$props;
2930
- this.$state.hidden.set(hidden() || !this.#isVisible() || !this.#isIntersecting());
2931
- }
2932
- #watchToggle() {
2933
- if (!this.$props.toggle()) {
2934
- this.#invert.set(null);
2935
- return;
2936
- }
2937
- if (this.el) {
2938
- onPress(this.el, this.#onToggle.bind(this));
2939
- }
2940
- }
2941
- #watchTime() {
2942
- const { hidden, timeText } = this.$state, { duration } = this.#media.$state;
2943
- if (hidden()) return;
2944
- const { type, padHours, padMinutes, showHours } = this.$props, seconds = this.#getSeconds(type()), $duration = duration(), shouldInvert = this.#shouldInvert();
2945
- if (!Number.isFinite(seconds + $duration)) {
2946
- timeText.set("LIVE");
2947
- return;
2948
- }
2949
- const time = shouldInvert ? Math.max(0, $duration - seconds) : seconds, formattedTime = formatTime(time, {
2950
- padHrs: padHours(),
2951
- padMins: padMinutes(),
2952
- showHrs: showHours()
2953
- });
2954
- timeText.set((shouldInvert ? "-" : "") + formattedTime);
2955
- }
2956
- #watchRole() {
2957
- if (!this.el) return;
2958
- const { toggle } = this.$props;
2959
- setAttribute(this.el, "role", toggle() ? "timer" : null);
2960
- setAttribute(this.el, "tabindex", toggle() ? 0 : null);
2961
- }
2962
- #getSeconds(type) {
2963
- const { bufferedEnd, duration, currentTime } = this.#media.$state;
2964
- switch (type) {
2965
- case "buffered":
2966
- return bufferedEnd();
2967
- case "duration":
2968
- return duration();
2969
- default:
2970
- return currentTime();
2971
- }
2972
- }
2973
- #shouldInvert() {
2974
- return this.$props.remainder() && this.#invert() !== false;
2975
- }
2976
- #onToggle(event) {
2977
- event.preventDefault();
2978
- if (this.#invert() === null) {
2979
- this.#invert.set(!this.$props.remainder());
2980
- return;
2981
- }
2982
- this.#invert.set((v) => !v);
2983
- }
2984
- }
2985
-
2986
- export { ARIAKeyShortcuts, AirPlayButton, AudioRadioGroup, CaptionButton, CaptionsRadioGroup, DEFAULT_PLAYBACK_RATES, FullscreenButton, LiveButton, Menu, MenuButton, MenuItem, MenuItems, MenuPortal, MuteButton, PIPButton, PlayButton, Popper, QualityRadioGroup, Radio, RadioGroupController, SeekButton, Slider, SliderController, SliderPreview, SliderValue, SpeedRadioGroup, Thumbnail, ThumbnailsLoader, Time, TimeSlider, ToggleButtonController, VolumeSlider, formatSpokenTime, formatTime, menuContext, menuPortalContext, sliderContext, sliderState, sliderValueFormatContext, updateSliderPreviewPlacement };