@hanifhan1f/vidstack 1.12.33 → 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,2976 +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, isKeyboardEvent, tick, setStyle, prop, scoped } from './vidstack-BNpgCJJ1.js';
2
- import { useMediaContext } from './vidstack-tt3O1zL6.js';
3
- import { hasAnimation, setAttributeIfEmpty, onPress, $ariaBool, setARIALabel, isTouchPinchEvent, observeVisibility, isHTMLElement, isElementParent, isEventInside, isElementVisible, requestScopedAnimationFrame, autoPlacement } from './vidstack-DB9WDRL5.js';
4
- import { isTrackCaptionKind } from './vidstack-oNEzlviH.js';
5
- import { FocusVisibleController } from './vidstack-COLU-zPZ.js';
6
- import { clampNumber, round, getNumberOfDecimalPlaces } from './vidstack-Dihypf8P.js';
7
- import { assert } from './vidstack-C9vIqaYT.js';
8
- import { getRequestCredentials } from './vidstack-KShKSmYu.js';
9
- import { watchActiveTextTrack } from './vidstack-BP49Gz0m.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();
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), false);
1024
- assert(
1025
- "url" in image && isString(image.url),
1026
- false
1027
- );
1028
- assert(
1029
- "startTime" in image && isNumber(image.startTime),
1030
- false
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
- assert(
1087
- "startTime" in img && isNumber(img.startTime));
1088
- return {
1089
- ...img,
1090
- url: isString(img.url) ? this.#resolveURL(img.url, baseURL) : img.url
1091
- };
1092
- });
1093
- }
1094
- #processStoryboard(board) {
1095
- assert(isString(board.url));
1096
- assert(isArray(board.tiles) && board.tiles?.length);
1097
- const url = new URL(board.url), images = [];
1098
- const tileWidth = "tile_width" in board ? board.tile_width : board.tileWidth, tileHeight = "tile_height" in board ? board.tile_height : board.tileHeight;
1099
- for (const tile of board.tiles) {
1100
- images.push({
1101
- url,
1102
- startTime: "start" in tile ? tile.start : tile.startTime,
1103
- width: tileWidth,
1104
- height: tileHeight,
1105
- coords: { x: tile.x, y: tile.y }
1106
- });
1107
- }
1108
- return images;
1109
- }
1110
- #processVTTCues(cues) {
1111
- for (let i = 0; i < cues.length; i++) {
1112
- const cue = cues[i];
1113
- assert(
1114
- "startTime" in cue && isNumber(cue.startTime));
1115
- assert(
1116
- "text" in cue && isString(cue.text));
1117
- }
1118
- const images = [], baseURL = this.#resolveBaseUrl();
1119
- for (const cue of cues) {
1120
- const [url, hash] = cue.text.split("#"), data = this.#resolveData(hash);
1121
- images.push({
1122
- url: this.#resolveURL(url, baseURL),
1123
- startTime: cue.startTime,
1124
- endTime: cue.endTime,
1125
- width: data?.w,
1126
- height: data?.h,
1127
- coords: data && isNumber(data.x) && isNumber(data.y) ? { x: data.x, y: data.y } : void 0
1128
- });
1129
- }
1130
- return images;
1131
- }
1132
- #resolveBaseUrl() {
1133
- let baseURL = peek(this.#src);
1134
- if (!isString(baseURL) || !/^https?:/.test(baseURL)) {
1135
- return location.href;
1136
- }
1137
- return baseURL;
1138
- }
1139
- #resolveURL(src, baseURL) {
1140
- return /^https?:/.test(src) ? new URL(src) : new URL(src, baseURL);
1141
- }
1142
- #resolveData(hash) {
1143
- if (!hash) return {};
1144
- const [hashProps, values] = hash.split("="), hashValues = values?.split(","), data = {};
1145
- if (!hashProps || !hashValues) {
1146
- return null;
1147
- }
1148
- for (let i = 0; i < hashProps.length; i++) {
1149
- const value = +hashValues[i];
1150
- if (!isNaN(value)) data[hashProps[i]] = value;
1151
- }
1152
- return data;
1153
- }
1154
- #onError(src, error) {
1155
- return;
1156
- }
1157
- }
1158
-
1159
- class Thumbnail extends Component {
1160
- static props = {
1161
- src: null,
1162
- time: 0,
1163
- crossOrigin: null
1164
- };
1165
- static state = new State({
1166
- src: "",
1167
- img: null,
1168
- thumbnails: [],
1169
- activeThumbnail: null,
1170
- crossOrigin: null,
1171
- loading: false,
1172
- error: null,
1173
- hidden: false
1174
- });
1175
- media;
1176
- #loader;
1177
- #styleResets = [];
1178
- onSetup() {
1179
- this.media = useMediaContext();
1180
- this.#loader = ThumbnailsLoader.create(this.$props.src, this.$state.crossOrigin);
1181
- this.#watchCrossOrigin();
1182
- this.setAttributes({
1183
- "data-loading": this.#isLoading.bind(this),
1184
- "data-error": this.#hasError.bind(this),
1185
- "data-hidden": this.$state.hidden,
1186
- "aria-hidden": $ariaBool(this.$state.hidden)
1187
- });
1188
- }
1189
- onConnect(el) {
1190
- effect(this.#watchImg.bind(this));
1191
- effect(this.#watchHidden.bind(this));
1192
- effect(this.#watchCrossOrigin.bind(this));
1193
- effect(this.#onLoadStart.bind(this));
1194
- effect(this.#onFindActiveThumbnail.bind(this));
1195
- effect(this.#resize.bind(this));
1196
- }
1197
- #watchImg() {
1198
- const img = this.$state.img();
1199
- if (!img) return;
1200
- new EventsController(img).add("load", this.#onLoaded.bind(this)).add("error", this.#onError.bind(this));
1201
- }
1202
- #watchCrossOrigin() {
1203
- const { crossOrigin: crossOriginProp } = this.$props, { crossOrigin: crossOriginState } = this.$state, { crossOrigin: mediaCrossOrigin } = this.media.$state, crossOrigin = crossOriginProp() !== null ? crossOriginProp() : mediaCrossOrigin();
1204
- crossOriginState.set(crossOrigin === true ? "anonymous" : crossOrigin);
1205
- }
1206
- #onLoadStart() {
1207
- const { src, loading, error } = this.$state;
1208
- if (src()) {
1209
- loading.set(true);
1210
- error.set(null);
1211
- }
1212
- return () => {
1213
- this.#resetStyles();
1214
- loading.set(false);
1215
- error.set(null);
1216
- };
1217
- }
1218
- #onLoaded() {
1219
- const { loading, error } = this.$state;
1220
- this.#resize();
1221
- loading.set(false);
1222
- error.set(null);
1223
- }
1224
- #onError(event) {
1225
- const { loading, error } = this.$state;
1226
- loading.set(false);
1227
- error.set(event);
1228
- }
1229
- #isLoading() {
1230
- const { loading, hidden } = this.$state;
1231
- return !hidden() && loading();
1232
- }
1233
- #hasError() {
1234
- const { error } = this.$state;
1235
- return !isNull(error());
1236
- }
1237
- #watchHidden() {
1238
- const { hidden } = this.$state, { duration } = this.media.$state, images = this.#loader.$images();
1239
- hidden.set(this.#hasError() || !Number.isFinite(duration()) || images.length === 0);
1240
- }
1241
- getTime() {
1242
- return this.$props.time();
1243
- }
1244
- #onFindActiveThumbnail() {
1245
- let images = this.#loader.$images();
1246
- if (!images.length) return;
1247
- let time = this.getTime(), { src, activeThumbnail } = this.$state, activeIndex = -1, activeImage = null;
1248
- for (let i = images.length - 1; i >= 0; i--) {
1249
- const image = images[i];
1250
- if (time >= image.startTime && (!image.endTime || time < image.endTime)) {
1251
- activeIndex = i;
1252
- break;
1253
- }
1254
- }
1255
- if (images[activeIndex]) {
1256
- activeImage = images[activeIndex];
1257
- }
1258
- activeThumbnail.set(activeImage);
1259
- src.set(activeImage?.url.href || "");
1260
- }
1261
- #resize() {
1262
- if (!this.scope || this.$state.hidden()) return;
1263
- const rootEl = this.el, imgEl = this.$state.img(), thumbnail = this.$state.activeThumbnail();
1264
- if (!imgEl || !thumbnail || !rootEl) return;
1265
- let width = thumbnail.width ?? imgEl.naturalWidth, height = thumbnail?.height ?? imgEl.naturalHeight, {
1266
- maxWidth,
1267
- maxHeight,
1268
- minWidth,
1269
- minHeight,
1270
- width: elWidth,
1271
- height: elHeight
1272
- } = getComputedStyle(this.el);
1273
- if (minWidth === "100%") minWidth = parseFloat(elWidth) + "";
1274
- if (minHeight === "100%") minHeight = parseFloat(elHeight) + "";
1275
- let minRatio = Math.max(parseInt(minWidth) / width, parseInt(minHeight) / height), maxRatio = Math.min(
1276
- Math.max(parseInt(minWidth), parseInt(maxWidth)) / width,
1277
- Math.max(parseInt(minHeight), parseInt(maxHeight)) / height
1278
- ), scale = !isNaN(maxRatio) && maxRatio < 1 ? maxRatio : minRatio > 1 ? minRatio : 1;
1279
- this.#style(rootEl, "--thumbnail-width", `${width * scale}px`);
1280
- this.#style(rootEl, "--thumbnail-height", `${height * scale}px`);
1281
- this.#style(rootEl, "--thumbnail-aspect-ratio", String(round(width / height, 5)));
1282
- this.#style(imgEl, "width", `${imgEl.naturalWidth * scale}px`);
1283
- this.#style(imgEl, "height", `${imgEl.naturalHeight * scale}px`);
1284
- this.#style(
1285
- imgEl,
1286
- "transform",
1287
- thumbnail.coords ? `translate(-${thumbnail.coords.x * scale}px, -${thumbnail.coords.y * scale}px)` : ""
1288
- );
1289
- this.#style(imgEl, "max-width", "none");
1290
- }
1291
- #style(el, name, value) {
1292
- el.style.setProperty(name, value);
1293
- this.#styleResets.push(() => el.style.removeProperty(name));
1294
- }
1295
- #resetStyles() {
1296
- for (const reset of this.#styleResets) reset();
1297
- this.#styleResets = [];
1298
- }
1299
- }
1300
-
1301
- class SliderValue extends Component {
1302
- static props = {
1303
- type: "pointer",
1304
- format: null,
1305
- showHours: false,
1306
- showMs: false,
1307
- padHours: null,
1308
- padMinutes: null,
1309
- decimalPlaces: 2
1310
- };
1311
- #format;
1312
- #text;
1313
- #slider;
1314
- onSetup() {
1315
- this.#slider = useState(Slider.state);
1316
- this.#format = useContext(sliderValueFormatContext);
1317
- this.#text = computed(this.getValueText.bind(this));
1318
- }
1319
- /**
1320
- * Returns the current value formatted as text based on prop settings.
1321
- */
1322
- getValueText() {
1323
- const {
1324
- type,
1325
- format: $format,
1326
- decimalPlaces,
1327
- padHours,
1328
- padMinutes,
1329
- showHours,
1330
- showMs
1331
- } = this.$props, { value: sliderValue, pointerValue, min, max } = this.#slider, format = $format?.() ?? this.#format.default;
1332
- const value = type() === "current" ? sliderValue() : pointerValue();
1333
- if (format === "percent") {
1334
- const range = max() - min();
1335
- const percent = value / range * 100;
1336
- return (this.#format.percent ?? round)(percent, decimalPlaces()) + "%";
1337
- } else if (format === "time") {
1338
- return (this.#format.time ?? formatTime)(value, {
1339
- padHrs: padHours(),
1340
- padMins: padMinutes(),
1341
- showHrs: showHours(),
1342
- showMs: showMs()
1343
- });
1344
- } else {
1345
- return (this.#format.value?.(value) ?? value.toFixed(2)) + "";
1346
- }
1347
- }
1348
- }
1349
- const slidervalue__proto = SliderValue.prototype;
1350
- method(slidervalue__proto, "getValueText");
1351
-
1352
- class SliderPreview extends Component {
1353
- static props = {
1354
- offset: 0,
1355
- noClamp: false
1356
- };
1357
- #slider;
1358
- onSetup() {
1359
- this.#slider = useContext(sliderContext);
1360
- const { active } = useState(Slider.state);
1361
- this.setAttributes({
1362
- "data-visible": active
1363
- });
1364
- }
1365
- onAttach(el) {
1366
- Object.assign(el.style, {
1367
- position: "absolute",
1368
- top: 0,
1369
- left: 0,
1370
- width: "max-content"
1371
- });
1372
- }
1373
- onConnect(el) {
1374
- const { preview } = this.#slider;
1375
- preview.set(el);
1376
- onDispose(() => preview.set(null));
1377
- effect(this.#updatePlacement.bind(this));
1378
- const resize = new ResizeObserver(this.#updatePlacement.bind(this));
1379
- resize.observe(el);
1380
- onDispose(() => resize.disconnect());
1381
- }
1382
- #updatePlacement = animationFrameThrottle(() => {
1383
- const { disabled, orientation } = this.#slider;
1384
- if (disabled()) return;
1385
- const el = this.el, { offset, noClamp } = this.$props;
1386
- if (!el) return;
1387
- updateSliderPreviewPlacement(el, {
1388
- clamp: !noClamp(),
1389
- offset: offset(),
1390
- orientation: orientation()
1391
- });
1392
- });
1393
- }
1394
- function updateSliderPreviewPlacement(el, {
1395
- clamp,
1396
- offset,
1397
- orientation
1398
- }) {
1399
- const computedStyle = getComputedStyle(el), width = parseFloat(computedStyle.width), height = parseFloat(computedStyle.height), styles = {
1400
- top: null,
1401
- right: null,
1402
- bottom: null,
1403
- left: null
1404
- };
1405
- styles[orientation === "horizontal" ? "bottom" : "left"] = `calc(100% + var(--media-slider-preview-offset, ${offset}px))`;
1406
- if (orientation === "horizontal") {
1407
- const widthHalf = width / 2;
1408
- if (!clamp) {
1409
- styles.left = `calc(var(--slider-pointer) - ${widthHalf}px)`;
1410
- } else {
1411
- const leftClamp = `max(0px, calc(var(--slider-pointer) - ${widthHalf}px))`, rightClamp = `calc(100% - ${width}px)`;
1412
- styles.left = `min(${leftClamp}, ${rightClamp})`;
1413
- }
1414
- } else {
1415
- const heightHalf = height / 2;
1416
- if (!clamp) {
1417
- styles.bottom = `calc(var(--slider-pointer) - ${heightHalf}px)`;
1418
- } else {
1419
- const topClamp = `max(${heightHalf}px, calc(var(--slider-pointer) - ${heightHalf}px))`, bottomClamp = `calc(100% - ${height}px)`;
1420
- styles.bottom = `min(${topClamp}, ${bottomClamp})`;
1421
- }
1422
- }
1423
- Object.assign(el.style, styles);
1424
- }
1425
-
1426
- class VolumeSlider extends Component {
1427
- static props = {
1428
- ...SliderController.props,
1429
- keyStep: 5,
1430
- shiftKeyMultiplier: 2
1431
- };
1432
- static state = sliderState;
1433
- #media;
1434
- onSetup() {
1435
- this.#media = useMediaContext();
1436
- const { audioGain } = this.#media.$state;
1437
- provideContext(sliderValueFormatContext, {
1438
- default: "percent",
1439
- value(value) {
1440
- return (value * (audioGain() ?? 1)).toFixed(2);
1441
- },
1442
- percent(value) {
1443
- return Math.round(value * (audioGain() ?? 1));
1444
- }
1445
- });
1446
- new SliderController({
1447
- getStep: this.$props.step,
1448
- getKeyStep: this.$props.keyStep,
1449
- roundValue: Math.round,
1450
- isDisabled: this.#isDisabled.bind(this),
1451
- aria: {
1452
- valueMax: this.#getARIAValueMax.bind(this),
1453
- valueNow: this.#getARIAValueNow.bind(this),
1454
- valueText: this.#getARIAValueText.bind(this)
1455
- },
1456
- onDragValueChange: this.#onDragValueChange.bind(this),
1457
- onValueChange: this.#onValueChange.bind(this)
1458
- }).attach(this);
1459
- effect(this.#watchVolume.bind(this));
1460
- }
1461
- onAttach(el) {
1462
- el.setAttribute("data-media-volume-slider", "");
1463
- setAttributeIfEmpty(el, "aria-label", "Volume");
1464
- const { canSetVolume } = this.#media.$state;
1465
- this.setAttributes({
1466
- "data-supported": canSetVolume,
1467
- "aria-hidden": $ariaBool(() => !canSetVolume())
1468
- });
1469
- }
1470
- #getARIAValueNow() {
1471
- const { value } = this.$state, { audioGain } = this.#media.$state;
1472
- return Math.round(value() * (audioGain() ?? 1));
1473
- }
1474
- #getARIAValueText() {
1475
- const { value, max } = this.$state, { audioGain } = this.#media.$state;
1476
- return round(value() / max() * (audioGain() ?? 1) * 100, 2) + "%";
1477
- }
1478
- #getARIAValueMax() {
1479
- const { audioGain } = this.#media.$state;
1480
- return this.$state.max() * (audioGain() ?? 1);
1481
- }
1482
- #isDisabled() {
1483
- const { disabled } = this.$props, { canSetVolume } = this.#media.$state;
1484
- return disabled() || !canSetVolume();
1485
- }
1486
- #watchVolume() {
1487
- const { muted, volume } = this.#media.$state;
1488
- const newValue = muted() ? 0 : volume() * 100;
1489
- this.$state.value.set(newValue);
1490
- this.dispatch("value-change", { detail: newValue });
1491
- }
1492
- #throttleVolumeChange = functionThrottle(this.#onVolumeChange.bind(this), 25);
1493
- #onVolumeChange(event) {
1494
- if (!event.trigger) return;
1495
- const mediaVolume = round(event.detail / 100, 3);
1496
- this.#media.remote.changeVolume(mediaVolume, event);
1497
- }
1498
- #onValueChange(event) {
1499
- this.#throttleVolumeChange(event);
1500
- }
1501
- #onDragValueChange(event) {
1502
- this.#throttleVolumeChange(event);
1503
- }
1504
- }
1505
-
1506
- class TimeSlider extends Component {
1507
- static props = {
1508
- ...SliderController.props,
1509
- step: 0.1,
1510
- keyStep: 5,
1511
- shiftKeyMultiplier: 2,
1512
- pauseWhileDragging: false,
1513
- noSwipeGesture: false,
1514
- seekingRequestThrottle: 100
1515
- };
1516
- static state = sliderState;
1517
- #media;
1518
- #dispatchSeeking;
1519
- #chapter = signal(null);
1520
- constructor() {
1521
- super();
1522
- const { noSwipeGesture } = this.$props;
1523
- new SliderController({
1524
- swipeGesture: () => !noSwipeGesture(),
1525
- getValue: this.#getValue.bind(this),
1526
- getStep: this.#getStep.bind(this),
1527
- getKeyStep: this.#getKeyStep.bind(this),
1528
- roundValue: this.#roundValue,
1529
- isDisabled: this.#isDisabled.bind(this),
1530
- aria: {
1531
- valueNow: this.#getARIAValueNow.bind(this),
1532
- valueText: this.#getARIAValueText.bind(this)
1533
- },
1534
- onDragStart: this.#onDragStart.bind(this),
1535
- onDragValueChange: this.#onDragValueChange.bind(this),
1536
- onDragEnd: this.#onDragEnd.bind(this),
1537
- onValueChange: this.#onValueChange.bind(this)
1538
- });
1539
- }
1540
- onSetup() {
1541
- this.#media = useMediaContext();
1542
- provideContext(sliderValueFormatContext, {
1543
- default: "time",
1544
- value: this.#formatValue.bind(this),
1545
- time: this.#formatTime.bind(this)
1546
- });
1547
- this.setAttributes({
1548
- "data-chapters": this.#hasChapters.bind(this)
1549
- });
1550
- this.setStyles({
1551
- "--slider-progress": this.#calcBufferedPercent.bind(this)
1552
- });
1553
- effect(this.#watchCurrentTime.bind(this));
1554
- effect(this.#watchSeekingThrottle.bind(this));
1555
- }
1556
- onAttach(el) {
1557
- el.setAttribute("data-media-time-slider", "");
1558
- setAttributeIfEmpty(el, "aria-label", "Seek");
1559
- }
1560
- onConnect(el) {
1561
- effect(this.#watchPreviewing.bind(this));
1562
- watchActiveTextTrack(this.#media.textTracks, "chapters", this.#chapter.set);
1563
- }
1564
- #calcBufferedPercent() {
1565
- const { bufferedEnd, duration } = this.#media.$state;
1566
- return round(Math.min(bufferedEnd() / Math.max(duration(), 1), 1) * 100, 3) + "%";
1567
- }
1568
- #hasChapters() {
1569
- const { duration } = this.#media.$state;
1570
- return this.#chapter()?.cues.length && Number.isFinite(duration()) && duration() > 0;
1571
- }
1572
- #watchSeekingThrottle() {
1573
- this.#dispatchSeeking = functionThrottle(
1574
- this.#seeking.bind(this),
1575
- this.$props.seekingRequestThrottle()
1576
- );
1577
- }
1578
- #watchCurrentTime() {
1579
- if (this.$state.hidden()) return;
1580
- const { value, dragging } = this.$state, newValue = this.#getValue();
1581
- if (!peek(dragging)) {
1582
- value.set(newValue);
1583
- this.dispatch("value-change", { detail: newValue });
1584
- }
1585
- }
1586
- #watchPreviewing() {
1587
- const player = this.#media.player.el, { preview } = useContext(sliderContext);
1588
- player && preview() && setAttribute(player, "data-preview", this.$state.active());
1589
- }
1590
- #seeking(time, event) {
1591
- this.#media.remote.seeking(time, event);
1592
- }
1593
- #seek(time, percent, event) {
1594
- this.#dispatchSeeking.cancel();
1595
- const { live } = this.#media.$state;
1596
- if (live() && percent >= 99) {
1597
- this.#media.remote.seekToLiveEdge(event);
1598
- return;
1599
- }
1600
- this.#media.remote.seek(time, event);
1601
- }
1602
- #playingBeforeDragStart = false;
1603
- #onDragStart(event) {
1604
- const { pauseWhileDragging } = this.$props;
1605
- if (pauseWhileDragging()) {
1606
- const { paused } = this.#media.$state;
1607
- this.#playingBeforeDragStart = !paused();
1608
- this.#media.remote.pause(event);
1609
- }
1610
- }
1611
- #onDragValueChange(event) {
1612
- this.#dispatchSeeking(this.#percentToTime(event.detail), event);
1613
- }
1614
- #onDragEnd(event) {
1615
- const { seeking } = this.#media.$state;
1616
- if (!peek(seeking)) this.#seeking(this.#percentToTime(event.detail), event);
1617
- const percent = event.detail;
1618
- this.#seek(this.#percentToTime(percent), percent, event);
1619
- const { pauseWhileDragging } = this.$props;
1620
- if (pauseWhileDragging() && this.#playingBeforeDragStart) {
1621
- this.#media.remote.play(event);
1622
- this.#playingBeforeDragStart = false;
1623
- }
1624
- }
1625
- #onValueChange(event) {
1626
- const { dragging } = this.$state;
1627
- if (dragging() || !event.trigger) return;
1628
- this.#onDragEnd(event);
1629
- }
1630
- // -------------------------------------------------------------------------------------------
1631
- // Props
1632
- // -------------------------------------------------------------------------------------------
1633
- #getValue() {
1634
- const { currentTime } = this.#media.$state;
1635
- return this.#timeToPercent(currentTime());
1636
- }
1637
- #getStep() {
1638
- const value = this.$props.step() / this.#media.$state.duration() * 100;
1639
- return Number.isFinite(value) ? value : 1;
1640
- }
1641
- #getKeyStep() {
1642
- const value = this.$props.keyStep() / this.#media.$state.duration() * 100;
1643
- return Number.isFinite(value) ? value : 1;
1644
- }
1645
- #roundValue(value) {
1646
- return round(value, 3);
1647
- }
1648
- #isDisabled() {
1649
- const { disabled } = this.$props, { canSeek } = this.#media.$state;
1650
- return disabled() || !canSeek();
1651
- }
1652
- // -------------------------------------------------------------------------------------------
1653
- // ARIA
1654
- // -------------------------------------------------------------------------------------------
1655
- #getARIAValueNow() {
1656
- const { value } = this.$state;
1657
- return Math.round(value());
1658
- }
1659
- #getARIAValueText() {
1660
- const time = this.#percentToTime(this.$state.value()), { duration } = this.#media.$state;
1661
- return Number.isFinite(time) ? `${formatSpokenTime(time)} out of ${formatSpokenTime(duration())}` : "live";
1662
- }
1663
- // -------------------------------------------------------------------------------------------
1664
- // Format
1665
- // -------------------------------------------------------------------------------------------
1666
- #percentToTime(percent) {
1667
- const { duration } = this.#media.$state;
1668
- return round(percent / 100 * duration(), 5);
1669
- }
1670
- #timeToPercent(time) {
1671
- const { liveEdge, duration } = this.#media.$state, rate = Math.max(0, Math.min(1, liveEdge() ? 1 : Math.min(time, duration()) / duration()));
1672
- return Number.isNaN(rate) ? 0 : Number.isFinite(rate) ? rate * 100 : 100;
1673
- }
1674
- #formatValue(percent) {
1675
- const time = this.#percentToTime(percent), { live, duration } = this.#media.$state;
1676
- return Number.isFinite(time) ? (live() ? time - duration() : time).toFixed(0) : "LIVE";
1677
- }
1678
- #formatTime(percent, options) {
1679
- const time = this.#percentToTime(percent), { live, duration } = this.#media.$state, value = live() ? time - duration() : time;
1680
- return Number.isFinite(time) ? `${value < 0 ? "-" : ""}${formatTime(Math.abs(value), options)}` : "LIVE";
1681
- }
1682
- }
1683
-
1684
- const menuContext = createContext();
1685
-
1686
- function scrollIntoView(el, options) {
1687
- const scrolls = r(el, options);
1688
- for (const { el: el2, top, left } of scrolls) {
1689
- el2.scroll({ top, left, behavior: options.behavior });
1690
- }
1691
- }
1692
- function scrollIntoCenter(el, options = {}) {
1693
- scrollIntoView(el, {
1694
- scrollMode: "if-needed",
1695
- block: "center",
1696
- inline: "center",
1697
- ...options
1698
- });
1699
- }
1700
-
1701
- const FOCUSABLE_ELEMENTS_SELECTOR = /* @__PURE__ */ [
1702
- "a[href]",
1703
- "[tabindex]",
1704
- "input",
1705
- "select",
1706
- "button"
1707
- ].map((selector) => `${selector}:not([aria-hidden='true'])`).join(",");
1708
- const VALID_KEYS = /* @__PURE__ */ new Set([
1709
- "Escape",
1710
- "Tab",
1711
- "ArrowUp",
1712
- "ArrowDown",
1713
- "Home",
1714
- "PageUp",
1715
- "End",
1716
- "PageDown",
1717
- "Enter",
1718
- " "
1719
- ]);
1720
- class MenuFocusController {
1721
- #index = -1;
1722
- #el = null;
1723
- #elements = [];
1724
- #delegate;
1725
- get items() {
1726
- return this.#elements;
1727
- }
1728
- constructor(delegate) {
1729
- this.#delegate = delegate;
1730
- }
1731
- attachMenu(el) {
1732
- listenEvent(el, "focus", this.#onFocus.bind(this));
1733
- this.#el = el;
1734
- onDispose(() => {
1735
- this.#el = null;
1736
- });
1737
- }
1738
- listen() {
1739
- if (!this.#el) return;
1740
- this.update();
1741
- new EventsController(this.#el).add("keyup", this.#onKeyUp.bind(this)).add("keydown", this.#onKeyDown.bind(this));
1742
- onDispose(() => {
1743
- this.#index = -1;
1744
- this.#elements = [];
1745
- });
1746
- }
1747
- update() {
1748
- this.#index = 0;
1749
- this.#elements = this.#getFocusableElements();
1750
- }
1751
- scroll(index = this.#findActiveIndex()) {
1752
- const element = this.#elements[index];
1753
- if (element) {
1754
- requestAnimationFrame(() => {
1755
- requestAnimationFrame(() => {
1756
- scrollIntoCenter(element, {
1757
- behavior: "smooth",
1758
- boundary: (el) => {
1759
- return !el.hasAttribute("data-root");
1760
- }
1761
- });
1762
- });
1763
- });
1764
- }
1765
- }
1766
- focusActive(scroll = true) {
1767
- const index = this.#findActiveIndex();
1768
- this.#focusAt(index >= 0 ? index : 0, scroll);
1769
- }
1770
- #focusAt(index, scroll = true) {
1771
- this.#index = index;
1772
- if (this.#elements[index]) {
1773
- this.#elements[index].focus({ preventScroll: true });
1774
- if (scroll) this.scroll(index);
1775
- } else {
1776
- this.#el?.focus({ preventScroll: true });
1777
- }
1778
- }
1779
- #findActiveIndex() {
1780
- return this.#elements.findIndex(
1781
- (el) => document.activeElement === el || el.getAttribute("role") === "menuitemradio" && el.getAttribute("aria-checked") === "true"
1782
- );
1783
- }
1784
- #onFocus() {
1785
- if (this.#index >= 0) return;
1786
- this.update();
1787
- this.focusActive();
1788
- }
1789
- #validateKeyEvent(event) {
1790
- const el = event.target;
1791
- if (wasEnterKeyPressed(event) && el instanceof Element) {
1792
- const role = el.getAttribute("role");
1793
- return !/a|input|select|button/.test(el.localName) && !role;
1794
- }
1795
- return VALID_KEYS.has(event.key);
1796
- }
1797
- #onKeyUp(event) {
1798
- if (!this.#validateKeyEvent(event)) return;
1799
- event.stopPropagation();
1800
- event.preventDefault();
1801
- }
1802
- #onKeyDown(event) {
1803
- if (!this.#validateKeyEvent(event)) return;
1804
- event.stopPropagation();
1805
- event.preventDefault();
1806
- switch (event.key) {
1807
- case "Escape":
1808
- this.#delegate.closeMenu(event);
1809
- break;
1810
- case "Tab":
1811
- this.#focusAt(this.#nextIndex(event.shiftKey ? -1 : 1));
1812
- break;
1813
- case "ArrowUp":
1814
- this.#focusAt(this.#nextIndex(-1));
1815
- break;
1816
- case "ArrowDown":
1817
- this.#focusAt(this.#nextIndex(1));
1818
- break;
1819
- case "Home":
1820
- case "PageUp":
1821
- this.#focusAt(0);
1822
- break;
1823
- case "End":
1824
- case "PageDown":
1825
- this.#focusAt(this.#elements.length - 1);
1826
- break;
1827
- }
1828
- }
1829
- #nextIndex(delta) {
1830
- let index = this.#index;
1831
- do {
1832
- index = (index + delta + this.#elements.length) % this.#elements.length;
1833
- } while (this.#elements[index]?.offsetParent === null);
1834
- return index;
1835
- }
1836
- #getFocusableElements() {
1837
- if (!this.#el) return [];
1838
- const focusableElements = this.#el.querySelectorAll(FOCUSABLE_ELEMENTS_SELECTOR), elements = [];
1839
- const is = (node) => {
1840
- return node.getAttribute("role") === "menu";
1841
- };
1842
- for (const el of focusableElements) {
1843
- if (isHTMLElement(el) && el.offsetParent !== null && // does not have display: none
1844
- isElementParent(this.#el, el, is)) {
1845
- elements.push(el);
1846
- }
1847
- }
1848
- return elements;
1849
- }
1850
- }
1851
-
1852
- var __defProp = Object.defineProperty;
1853
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
1854
- var __decorateClass = (decorators, target, key, kind) => {
1855
- var result = __getOwnPropDesc(target, key) ;
1856
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
1857
- if (decorator = decorators[i])
1858
- result = (decorator(target, key, result) ) || result;
1859
- if (result) __defProp(target, key, result);
1860
- return result;
1861
- };
1862
- let idCount = 0;
1863
- class Menu extends Component {
1864
- static props = {
1865
- showDelay: 0
1866
- };
1867
- #media;
1868
- #menuId;
1869
- #menuButtonId;
1870
- #expanded = signal(false);
1871
- #disabled = signal(false);
1872
- #trigger = signal(null);
1873
- #content = signal(null);
1874
- #parentMenu;
1875
- #submenus = /* @__PURE__ */ new Set();
1876
- #menuObserver = null;
1877
- #popper;
1878
- #focus;
1879
- #isSliderActive = false;
1880
- #isTriggerDisabled = signal(false);
1881
- #transitionCallbacks = /* @__PURE__ */ new Set();
1882
- get triggerElement() {
1883
- return this.#trigger();
1884
- }
1885
- get contentElement() {
1886
- return this.#content();
1887
- }
1888
- get isSubmenu() {
1889
- return !!this.#parentMenu;
1890
- }
1891
- constructor() {
1892
- super();
1893
- const { showDelay } = this.$props;
1894
- this.#popper = new Popper({
1895
- trigger: this.#trigger,
1896
- content: this.#content,
1897
- showDelay,
1898
- listen: (trigger, show, hide) => {
1899
- onPress(trigger, (event) => {
1900
- if (this.#expanded()) hide(event);
1901
- else show(event);
1902
- });
1903
- const closeTarget = this.#getCloseTarget();
1904
- if (closeTarget) {
1905
- onPress(closeTarget, (event) => {
1906
- event.stopPropagation();
1907
- hide(event);
1908
- });
1909
- }
1910
- },
1911
- onChange: this.#onExpandedChange.bind(this)
1912
- });
1913
- }
1914
- onSetup() {
1915
- this.#media = useMediaContext();
1916
- const currentIdCount = ++idCount;
1917
- this.#menuId = `media-menu-${currentIdCount}`;
1918
- this.#menuButtonId = `media-menu-button-${currentIdCount}`;
1919
- this.#focus = new MenuFocusController({
1920
- closeMenu: this.close.bind(this)
1921
- });
1922
- if (hasProvidedContext(menuContext)) {
1923
- this.#parentMenu = useContext(menuContext);
1924
- }
1925
- this.#observeSliders();
1926
- this.setAttributes({
1927
- "data-open": this.#expanded,
1928
- "data-root": !this.isSubmenu,
1929
- "data-submenu": this.isSubmenu,
1930
- "data-disabled": this.#isDisabled.bind(this)
1931
- });
1932
- provideContext(menuContext, {
1933
- button: this.#trigger,
1934
- content: this.#content,
1935
- expanded: this.#expanded,
1936
- hint: signal(""),
1937
- submenu: !!this.#parentMenu,
1938
- disable: this.#disable.bind(this),
1939
- attachMenuButton: this.#attachMenuButton.bind(this),
1940
- attachMenuItems: this.#attachMenuItems.bind(this),
1941
- attachObserver: this.#attachObserver.bind(this),
1942
- disableMenuButton: this.#disableMenuButton.bind(this),
1943
- addSubmenu: this.#addSubmenu.bind(this),
1944
- onTransitionEvent: (callback) => {
1945
- this.#transitionCallbacks.add(callback);
1946
- onDispose(() => {
1947
- this.#transitionCallbacks.delete(callback);
1948
- });
1949
- }
1950
- });
1951
- }
1952
- onAttach(el) {
1953
- el.style.setProperty("display", "contents");
1954
- }
1955
- onConnect(el) {
1956
- effect(this.#watchExpanded.bind(this));
1957
- if (this.isSubmenu) {
1958
- this.#parentMenu?.addSubmenu(this);
1959
- }
1960
- }
1961
- onDestroy() {
1962
- this.#trigger.set(null);
1963
- this.#content.set(null);
1964
- this.#menuObserver = null;
1965
- this.#transitionCallbacks.clear();
1966
- }
1967
- #observeSliders() {
1968
- let sliderActiveTimer = -1, parentSliderObserver = hasProvidedContext(sliderObserverContext) ? useContext(sliderObserverContext) : null;
1969
- provideContext(sliderObserverContext, {
1970
- onDragStart: () => {
1971
- parentSliderObserver?.onDragStart?.();
1972
- window.clearTimeout(sliderActiveTimer);
1973
- sliderActiveTimer = -1;
1974
- this.#isSliderActive = true;
1975
- },
1976
- onDragEnd: () => {
1977
- parentSliderObserver?.onDragEnd?.();
1978
- sliderActiveTimer = window.setTimeout(() => {
1979
- this.#isSliderActive = false;
1980
- sliderActiveTimer = -1;
1981
- }, 300);
1982
- }
1983
- });
1984
- }
1985
- #watchExpanded() {
1986
- const expanded = this.#isExpanded();
1987
- if (!this.isSubmenu) this.#onResize();
1988
- this.#updateMenuItemsHidden(expanded);
1989
- if (!expanded) return;
1990
- effect(() => {
1991
- const { height } = this.#media.$state, content = this.#content();
1992
- content && setStyle(content, "--player-height", height() + "px");
1993
- });
1994
- this.#focus.listen();
1995
- this.listen("pointerup", this.#onPointerUp.bind(this));
1996
- listenEvent(window, "pointerup", this.#onWindowPointerUp.bind(this));
1997
- }
1998
- #attachMenuButton(button) {
1999
- const el = button.el, isMenuItem = this.isSubmenu, isARIADisabled = $ariaBool(this.#isDisabled.bind(this));
2000
- setAttributeIfEmpty(el, "tabindex", isMenuItem ? "-1" : "0");
2001
- setAttributeIfEmpty(el, "role", isMenuItem ? "menuitem" : "button");
2002
- setAttribute(el, "id", this.#menuButtonId);
2003
- setAttribute(el, "aria-haspopup", "menu");
2004
- setAttribute(el, "aria-expanded", "false");
2005
- setAttribute(el, "data-root", !this.isSubmenu);
2006
- setAttribute(el, "data-submenu", this.isSubmenu);
2007
- const watchAttrs = () => {
2008
- setAttribute(el, "data-open", this.#expanded());
2009
- setAttribute(el, "aria-disabled", isARIADisabled());
2010
- };
2011
- effect(watchAttrs);
2012
- this.#trigger.set(el);
2013
- onDispose(() => {
2014
- this.#trigger.set(null);
2015
- });
2016
- }
2017
- #attachMenuItems(items) {
2018
- const el = items.el;
2019
- el.style.setProperty("display", "none");
2020
- setAttribute(el, "id", this.#menuId);
2021
- setAttributeIfEmpty(el, "role", "menu");
2022
- setAttributeIfEmpty(el, "tabindex", "-1");
2023
- setAttribute(el, "data-root", !this.isSubmenu);
2024
- setAttribute(el, "data-submenu", this.isSubmenu);
2025
- this.#content.set(el);
2026
- onDispose(() => this.#content.set(null));
2027
- const watchAttrs = () => setAttribute(el, "data-open", this.#expanded());
2028
- effect(watchAttrs);
2029
- this.#focus.attachMenu(el);
2030
- this.#updateMenuItemsHidden(false);
2031
- const onTransition = this.#onResizeTransition.bind(this);
2032
- if (!this.isSubmenu) {
2033
- items.listen("transitionstart", onTransition);
2034
- items.listen("transitionend", onTransition);
2035
- items.listen("animationend", this.#onResize);
2036
- items.listen("vds-menu-resize", this.#onResize);
2037
- } else {
2038
- this.#parentMenu?.onTransitionEvent(onTransition);
2039
- }
2040
- }
2041
- #attachObserver(observer) {
2042
- this.#menuObserver = observer;
2043
- }
2044
- #updateMenuItemsHidden(expanded) {
2045
- const content = peek(this.#content);
2046
- if (content) setAttribute(content, "aria-hidden", ariaBool(!expanded));
2047
- }
2048
- #disableMenuButton(disabled) {
2049
- this.#isTriggerDisabled.set(disabled);
2050
- }
2051
- #wasKeyboardExpand = false;
2052
- #onExpandedChange(isExpanded, event) {
2053
- this.#wasKeyboardExpand = isKeyboardEvent(event);
2054
- event?.stopPropagation();
2055
- if (this.#expanded() === isExpanded) return;
2056
- if (this.#isDisabled()) {
2057
- if (isExpanded) this.#popper.hide(event);
2058
- return;
2059
- }
2060
- this.el?.dispatchEvent(
2061
- new Event("vds-menu-resize", {
2062
- bubbles: true,
2063
- composed: true
2064
- })
2065
- );
2066
- const trigger = this.#trigger(), content = this.#content();
2067
- if (trigger) {
2068
- setAttribute(trigger, "aria-controls", isExpanded && this.#menuId);
2069
- setAttribute(trigger, "aria-expanded", ariaBool(isExpanded));
2070
- }
2071
- if (content) setAttribute(content, "aria-labelledby", isExpanded && this.#menuButtonId);
2072
- this.#expanded.set(isExpanded);
2073
- this.#toggleMediaControls(event);
2074
- tick();
2075
- if (this.#wasKeyboardExpand) {
2076
- if (isExpanded) content?.focus();
2077
- else trigger?.focus();
2078
- for (const el of [this.el, content]) {
2079
- el && el.setAttribute("data-keyboard", "");
2080
- }
2081
- } else {
2082
- for (const el of [this.el, content]) {
2083
- el && el.removeAttribute("data-keyboard");
2084
- }
2085
- }
2086
- this.dispatch(isExpanded ? "open" : "close", { trigger: event });
2087
- if (isExpanded) {
2088
- if (!this.isSubmenu && this.#media.activeMenu !== this) {
2089
- this.#media.activeMenu?.close(event);
2090
- this.#media.activeMenu = this;
2091
- }
2092
- this.#menuObserver?.onOpen?.(event);
2093
- } else {
2094
- if (this.isSubmenu) {
2095
- for (const el of this.#submenus) el.close(event);
2096
- } else {
2097
- this.#media.activeMenu = null;
2098
- }
2099
- this.#menuObserver?.onClose?.(event);
2100
- }
2101
- if (isExpanded) {
2102
- requestAnimationFrame(this.#updateFocus.bind(this));
2103
- }
2104
- }
2105
- #updateFocus() {
2106
- if (this.#isTransitionActive || this.#isSubmenuOpen) return;
2107
- this.#focus.update();
2108
- requestAnimationFrame(() => {
2109
- if (this.#wasKeyboardExpand) {
2110
- this.#focus.focusActive();
2111
- } else {
2112
- this.#focus.scroll();
2113
- }
2114
- });
2115
- }
2116
- #isExpanded() {
2117
- return !this.#isDisabled() && this.#expanded();
2118
- }
2119
- #isDisabled() {
2120
- return this.#disabled() || this.#isTriggerDisabled();
2121
- }
2122
- #disable(disabled) {
2123
- this.#disabled.set(disabled);
2124
- }
2125
- #onPointerUp(event) {
2126
- const content = this.#content();
2127
- if (this.#isSliderActive || content && isEventInside(content, event)) {
2128
- return;
2129
- }
2130
- event.stopPropagation();
2131
- }
2132
- #onWindowPointerUp(event) {
2133
- const content = this.#content();
2134
- if (this.#isSliderActive || content && isEventInside(content, event)) {
2135
- return;
2136
- }
2137
- this.close(event);
2138
- }
2139
- #getCloseTarget() {
2140
- const target = this.el?.querySelector('[data-part="close-target"]');
2141
- return this.el && target && isElementParent(this.el, target, (node) => node.getAttribute("role") === "menu") ? target : null;
2142
- }
2143
- #toggleMediaControls(trigger) {
2144
- if (this.isSubmenu) return;
2145
- if (this.#expanded()) this.#media.remote.pauseControls(trigger);
2146
- else this.#media.remote.resumeControls(trigger);
2147
- }
2148
- #addSubmenu(menu) {
2149
- this.#submenus.add(menu);
2150
- new EventsController(menu).add("open", this.#onSubmenuOpenBind).add("close", this.#onSubmenuCloseBind);
2151
- onDispose(this.#removeSubmenuBind);
2152
- }
2153
- #removeSubmenuBind = this.#removeSubmenu.bind(this);
2154
- #removeSubmenu(menu) {
2155
- this.#submenus.delete(menu);
2156
- }
2157
- #isSubmenuOpen = false;
2158
- #onSubmenuOpenBind = this.#onSubmenuOpen.bind(this);
2159
- #onSubmenuOpen(event) {
2160
- this.#isSubmenuOpen = true;
2161
- const content = this.#content();
2162
- if (this.isSubmenu) {
2163
- this.triggerElement?.setAttribute("aria-hidden", "true");
2164
- }
2165
- for (const target of this.#submenus) {
2166
- if (target !== event.target) {
2167
- for (const el of [target.el, target.triggerElement]) {
2168
- el?.setAttribute("aria-hidden", "true");
2169
- }
2170
- }
2171
- }
2172
- if (content) {
2173
- const el = event.target.el;
2174
- for (const child of content.children) {
2175
- if (child.contains(el)) {
2176
- child.setAttribute("data-open", "");
2177
- } else if (child !== el) {
2178
- child.setAttribute("data-hidden", "");
2179
- }
2180
- }
2181
- }
2182
- }
2183
- #onSubmenuCloseBind = this.#onSubmenuClose.bind(this);
2184
- #onSubmenuClose(event) {
2185
- this.#isSubmenuOpen = false;
2186
- const content = this.#content();
2187
- if (this.isSubmenu) {
2188
- this.triggerElement?.setAttribute("aria-hidden", "false");
2189
- }
2190
- for (const target of this.#submenus) {
2191
- for (const el of [target.el, target.triggerElement]) {
2192
- el?.setAttribute("aria-hidden", "false");
2193
- }
2194
- }
2195
- if (content) {
2196
- for (const child of content.children) {
2197
- child.removeAttribute("data-open");
2198
- child.removeAttribute("data-hidden");
2199
- }
2200
- }
2201
- }
2202
- #onResize = animationFrameThrottle(() => {
2203
- const content = peek(this.#content);
2204
- if (!content || false) return;
2205
- let height = 0, styles = getComputedStyle(content), children = [...content.children];
2206
- for (const prop2 of ["paddingTop", "paddingBottom", "borderTopWidth", "borderBottomWidth"]) {
2207
- height += parseFloat(styles[prop2]) || 0;
2208
- }
2209
- for (const child of children) {
2210
- if (isHTMLElement(child) && child.style.display === "contents") {
2211
- children.push(...child.children);
2212
- } else if (child.nodeType === 3) {
2213
- height += parseFloat(getComputedStyle(child).fontSize);
2214
- } else if (isHTMLElement(child)) {
2215
- if (!isElementVisible(child)) continue;
2216
- const style = getComputedStyle(child);
2217
- height += child.offsetHeight + (parseFloat(style.marginTop) || 0) + (parseFloat(style.marginBottom) || 0);
2218
- }
2219
- }
2220
- setStyle(content, "--menu-height", height + "px");
2221
- });
2222
- #isTransitionActive = false;
2223
- #onResizeTransition(event) {
2224
- const content = this.#content();
2225
- if (content && event.propertyName === "height") {
2226
- this.#isTransitionActive = event.type === "transitionstart";
2227
- setAttribute(content, "data-transition", this.#isTransitionActive ? "height" : null);
2228
- if (this.#expanded()) this.#updateFocus();
2229
- }
2230
- for (const callback of this.#transitionCallbacks) callback(event);
2231
- }
2232
- open(trigger) {
2233
- if (peek(this.#expanded)) return;
2234
- this.#popper.show(trigger);
2235
- tick();
2236
- }
2237
- close(trigger) {
2238
- if (!peek(this.#expanded)) return;
2239
- this.#popper.hide(trigger);
2240
- tick();
2241
- }
2242
- }
2243
- __decorateClass([
2244
- prop
2245
- ], Menu.prototype, "triggerElement");
2246
- __decorateClass([
2247
- prop
2248
- ], Menu.prototype, "contentElement");
2249
- __decorateClass([
2250
- prop
2251
- ], Menu.prototype, "isSubmenu");
2252
- __decorateClass([
2253
- method
2254
- ], Menu.prototype, "open");
2255
- __decorateClass([
2256
- method
2257
- ], Menu.prototype, "close");
2258
-
2259
- class MenuButton extends Component {
2260
- static props = {
2261
- disabled: false
2262
- };
2263
- #menu;
2264
- #hintEl = signal(null);
2265
- get expanded() {
2266
- return this.#menu?.expanded() ?? false;
2267
- }
2268
- constructor() {
2269
- super();
2270
- new FocusVisibleController();
2271
- }
2272
- onSetup() {
2273
- this.#menu = useContext(menuContext);
2274
- }
2275
- onAttach(el) {
2276
- this.#menu.attachMenuButton(this);
2277
- effect(this.#watchDisabled.bind(this));
2278
- setAttributeIfEmpty(el, "type", "button");
2279
- }
2280
- onConnect(el) {
2281
- effect(this.#watchHintEl.bind(this));
2282
- this.#onMutation();
2283
- const mutations = new MutationObserver(this.#onMutation.bind(this));
2284
- mutations.observe(el, { attributeFilter: ["data-part"], childList: true, subtree: true });
2285
- onDispose(() => mutations.disconnect());
2286
- onPress(el, (trigger) => {
2287
- this.dispatch("select", { trigger });
2288
- });
2289
- }
2290
- #watchDisabled() {
2291
- this.#menu.disableMenuButton(this.$props.disabled());
2292
- }
2293
- #watchHintEl() {
2294
- const el = this.#hintEl();
2295
- if (!el) return;
2296
- effect(() => {
2297
- const text = this.#menu.hint();
2298
- if (text) el.textContent = text;
2299
- });
2300
- }
2301
- #onMutation() {
2302
- const hintEl = this.el?.querySelector('[data-part="hint"]');
2303
- this.#hintEl.set(hintEl ?? null);
2304
- }
2305
- }
2306
- const menubutton__proto = MenuButton.prototype;
2307
- prop(menubutton__proto, "expanded");
2308
-
2309
- class MenuItem extends MenuButton {
2310
- }
2311
-
2312
- class MenuPortal extends Component {
2313
- static props = {
2314
- container: null,
2315
- disabled: false
2316
- };
2317
- #target = null;
2318
- #media;
2319
- onSetup() {
2320
- this.#media = useMediaContext();
2321
- provideContext(menuPortalContext, {
2322
- attach: this.#attachElement.bind(this)
2323
- });
2324
- }
2325
- onAttach(el) {
2326
- el.style.setProperty("display", "contents");
2327
- }
2328
- // Need this so connect scope is defined.
2329
- onConnect(el) {
2330
- }
2331
- onDestroy() {
2332
- this.#target?.remove();
2333
- this.#target = null;
2334
- }
2335
- #attachElement(el) {
2336
- this.#portal(false);
2337
- this.#target = el;
2338
- requestScopedAnimationFrame(() => {
2339
- requestScopedAnimationFrame(() => {
2340
- if (!this.connectScope) return;
2341
- effect(this.#watchDisabled.bind(this));
2342
- });
2343
- });
2344
- }
2345
- #watchDisabled() {
2346
- const { fullscreen } = this.#media.$state, { disabled } = this.$props;
2347
- this.#portal(disabled() === "fullscreen" ? !fullscreen() : !disabled());
2348
- }
2349
- #portal(shouldPortal) {
2350
- if (!this.#target) return;
2351
- let container = this.#getContainer(this.$props.container());
2352
- if (!container) return;
2353
- const isPortalled = this.#target.parentElement === container;
2354
- setAttribute(this.#target, "data-portal", shouldPortal);
2355
- if (shouldPortal) {
2356
- if (!isPortalled) {
2357
- this.#target.remove();
2358
- container.append(this.#target);
2359
- }
2360
- } else if (isPortalled && this.#target.parentElement === container) {
2361
- this.#target.remove();
2362
- this.el?.append(this.#target);
2363
- }
2364
- }
2365
- #getContainer(selector) {
2366
- if (isHTMLElement(selector)) return selector;
2367
- return selector ? document.querySelector(selector) : document.body;
2368
- }
2369
- }
2370
- const menuPortalContext = createContext();
2371
-
2372
- class MenuItems extends Component {
2373
- static props = {
2374
- placement: null,
2375
- offset: 0,
2376
- alignOffset: 0
2377
- };
2378
- #menu;
2379
- constructor() {
2380
- super();
2381
- new FocusVisibleController();
2382
- const { placement } = this.$props;
2383
- this.setAttributes({
2384
- "data-placement": placement
2385
- });
2386
- }
2387
- onAttach(el) {
2388
- this.#menu = useContext(menuContext);
2389
- this.#menu.attachMenuItems(this);
2390
- if (hasProvidedContext(menuPortalContext)) {
2391
- const portal = useContext(menuPortalContext);
2392
- if (portal) {
2393
- provideContext(menuPortalContext, null);
2394
- portal.attach(el);
2395
- onDispose(() => portal.attach(null));
2396
- }
2397
- }
2398
- }
2399
- onConnect(el) {
2400
- effect(this.#watchPlacement.bind(this));
2401
- }
2402
- #watchPlacement() {
2403
- const { expanded } = this.#menu;
2404
- if (!this.el || !expanded()) return;
2405
- const placement = this.$props.placement();
2406
- if (!placement) return;
2407
- Object.assign(this.el.style, {
2408
- position: "absolute",
2409
- top: 0,
2410
- left: 0,
2411
- width: "max-content"
2412
- });
2413
- const { offset: mainOffset, alignOffset } = this.$props;
2414
- onDispose(
2415
- autoPlacement(this.el, this.#getButton(), placement, {
2416
- offsetVarName: "media-menu",
2417
- xOffset: alignOffset(),
2418
- yOffset: mainOffset()
2419
- })
2420
- );
2421
- onDispose(this.#hide.bind(this));
2422
- }
2423
- #hide() {
2424
- if (!this.el) return;
2425
- this.el.removeAttribute("style");
2426
- this.el.style.display = "none";
2427
- }
2428
- #getButton() {
2429
- return this.#menu.button();
2430
- }
2431
- }
2432
-
2433
- const radioControllerContext = createContext();
2434
-
2435
- class RadioGroupController extends ViewController {
2436
- #group = /* @__PURE__ */ new Set();
2437
- #value = signal("");
2438
- #controller = null;
2439
- onValueChange;
2440
- get values() {
2441
- return Array.from(this.#group).map((radio) => radio.value());
2442
- }
2443
- get value() {
2444
- return this.#value();
2445
- }
2446
- set value(value) {
2447
- this.#onChange(value);
2448
- }
2449
- onSetup() {
2450
- provideContext(radioControllerContext, {
2451
- add: this.#addRadio.bind(this),
2452
- remove: this.#removeRadio.bind(this)
2453
- });
2454
- }
2455
- onAttach(el) {
2456
- const isMenuItem = hasProvidedContext(menuContext);
2457
- if (!isMenuItem) setAttributeIfEmpty(el, "role", "radiogroup");
2458
- this.setAttributes({ value: this.#value });
2459
- }
2460
- onDestroy() {
2461
- this.#group.clear();
2462
- }
2463
- #addRadio(radio) {
2464
- if (this.#group.has(radio)) return;
2465
- this.#group.add(radio);
2466
- radio.onCheck = this.#onChangeBind;
2467
- radio.check(radio.value() === this.#value());
2468
- }
2469
- #removeRadio(radio) {
2470
- radio.onCheck = null;
2471
- this.#group.delete(radio);
2472
- }
2473
- #onChangeBind = this.#onChange.bind(this);
2474
- #onChange(newValue, trigger) {
2475
- const currentValue = peek(this.#value);
2476
- if (!newValue || newValue === currentValue) return;
2477
- const currentRadio = this.#findRadio(currentValue), newRadio = this.#findRadio(newValue);
2478
- currentRadio?.check(false, trigger);
2479
- newRadio?.check(true, trigger);
2480
- this.#value.set(newValue);
2481
- this.onValueChange?.(newValue, trigger);
2482
- }
2483
- #findRadio(newValue) {
2484
- for (const radio of this.#group) {
2485
- if (newValue === peek(radio.value)) return radio;
2486
- }
2487
- return null;
2488
- }
2489
- }
2490
-
2491
- class Radio extends Component {
2492
- static props = {
2493
- value: ""
2494
- };
2495
- #checked = signal(false);
2496
- #controller = {
2497
- value: this.$props.value,
2498
- check: this.#check.bind(this),
2499
- onCheck: null
2500
- };
2501
- /**
2502
- * Whether this radio is currently checked.
2503
- */
2504
- get checked() {
2505
- return this.#checked();
2506
- }
2507
- constructor() {
2508
- super();
2509
- new FocusVisibleController();
2510
- }
2511
- onSetup() {
2512
- this.setAttributes({
2513
- value: this.$props.value,
2514
- "data-checked": this.#checked,
2515
- "aria-checked": $ariaBool(this.#checked)
2516
- });
2517
- }
2518
- onAttach(el) {
2519
- const isMenuItem = hasProvidedContext(menuContext);
2520
- setAttributeIfEmpty(el, "tabindex", isMenuItem ? "-1" : "0");
2521
- setAttributeIfEmpty(el, "role", isMenuItem ? "menuitemradio" : "radio");
2522
- effect(this.#watchValue.bind(this));
2523
- }
2524
- onConnect(el) {
2525
- this.#addToGroup();
2526
- onPress(el, this.#onPress.bind(this));
2527
- onDispose(this.#onDisconnect.bind(this));
2528
- }
2529
- #onDisconnect() {
2530
- scoped(() => {
2531
- const group = useContext(radioControllerContext);
2532
- group.remove(this.#controller);
2533
- }, this.connectScope);
2534
- }
2535
- #addToGroup() {
2536
- const group = useContext(radioControllerContext);
2537
- group.add(this.#controller);
2538
- }
2539
- #watchValue() {
2540
- const { value } = this.$props, newValue = value();
2541
- if (peek(this.#checked)) {
2542
- this.#controller.onCheck?.(newValue);
2543
- }
2544
- }
2545
- #onPress(event) {
2546
- if (peek(this.#checked)) return;
2547
- this.#onChange(true, event);
2548
- this.#onSelect(event);
2549
- this.#controller.onCheck?.(peek(this.$props.value), event);
2550
- }
2551
- #check(value, trigger) {
2552
- if (peek(this.#checked) === value) return;
2553
- this.#onChange(value, trigger);
2554
- }
2555
- #onChange(value, trigger) {
2556
- this.#checked.set(value);
2557
- this.dispatch("change", { detail: value, trigger });
2558
- }
2559
- #onSelect(trigger) {
2560
- this.dispatch("select", { trigger });
2561
- }
2562
- }
2563
- const radio__proto = Radio.prototype;
2564
- prop(radio__proto, "checked");
2565
-
2566
- class AudioRadioGroup extends Component {
2567
- static props = {
2568
- emptyLabel: "Default"
2569
- };
2570
- #menu;
2571
- #media;
2572
- #controller;
2573
- get value() {
2574
- return this.#controller.value;
2575
- }
2576
- get disabled() {
2577
- const { audioTracks } = this.#media.$state;
2578
- return audioTracks().length <= 1;
2579
- }
2580
- constructor() {
2581
- super();
2582
- this.#controller = new RadioGroupController();
2583
- this.#controller.onValueChange = this.#onValueChange.bind(this);
2584
- }
2585
- onSetup() {
2586
- this.#media = useMediaContext();
2587
- if (hasProvidedContext(menuContext)) {
2588
- this.#menu = useContext(menuContext);
2589
- }
2590
- }
2591
- onConnect(el) {
2592
- effect(this.#watchValue.bind(this));
2593
- effect(this.#watchControllerDisabled.bind(this));
2594
- effect(this.#watchHintText.bind(this));
2595
- }
2596
- getOptions() {
2597
- const { audioTracks } = this.#media.$state;
2598
- return audioTracks().map((track) => ({
2599
- track,
2600
- label: track.label,
2601
- value: track.label.toLowerCase()
2602
- }));
2603
- }
2604
- #watchValue() {
2605
- this.#controller.value = this.#getValue();
2606
- }
2607
- #watchHintText() {
2608
- const { emptyLabel } = this.$props, { audioTrack } = this.#media.$state, track = audioTrack();
2609
- this.#menu?.hint.set(track?.label ?? emptyLabel());
2610
- }
2611
- #watchControllerDisabled() {
2612
- this.#menu?.disable(this.disabled);
2613
- }
2614
- #getValue() {
2615
- const { audioTrack } = this.#media.$state;
2616
- const track = audioTrack();
2617
- return track ? track.label.toLowerCase() : "";
2618
- }
2619
- #onValueChange(value, trigger) {
2620
- if (this.disabled) return;
2621
- const index = this.#media.audioTracks.toArray().findIndex((track) => track.label.toLowerCase() === value);
2622
- if (index >= 0) {
2623
- const track = this.#media.audioTracks[index];
2624
- this.#media.remote.changeAudioTrack(index, trigger);
2625
- this.dispatch("change", { detail: track, trigger });
2626
- }
2627
- }
2628
- }
2629
- const audioradiogroup__proto = AudioRadioGroup.prototype;
2630
- prop(audioradiogroup__proto, "value");
2631
- prop(audioradiogroup__proto, "disabled");
2632
- method(audioradiogroup__proto, "getOptions");
2633
-
2634
- class CaptionsRadioGroup extends Component {
2635
- static props = {
2636
- offLabel: "Off"
2637
- };
2638
- #media;
2639
- #menu;
2640
- #controller;
2641
- get value() {
2642
- return this.#controller.value;
2643
- }
2644
- get disabled() {
2645
- const { hasCaptions } = this.#media.$state;
2646
- return !hasCaptions();
2647
- }
2648
- constructor() {
2649
- super();
2650
- this.#controller = new RadioGroupController();
2651
- this.#controller.onValueChange = this.#onValueChange.bind(this);
2652
- }
2653
- onSetup() {
2654
- this.#media = useMediaContext();
2655
- if (hasProvidedContext(menuContext)) {
2656
- this.#menu = useContext(menuContext);
2657
- }
2658
- }
2659
- onConnect(el) {
2660
- super.onConnect?.(el);
2661
- effect(this.#watchValue.bind(this));
2662
- effect(this.#watchControllerDisabled.bind(this));
2663
- effect(this.#watchHintText.bind(this));
2664
- }
2665
- getOptions() {
2666
- const { offLabel } = this.$props, { textTracks } = this.#media.$state;
2667
- return [
2668
- { value: "off", label: offLabel },
2669
- ...textTracks().filter(isTrackCaptionKind).map((track) => ({
2670
- track,
2671
- label: track.label,
2672
- value: this.#getTrackValue(track)
2673
- }))
2674
- ];
2675
- }
2676
- #watchValue() {
2677
- this.#controller.value = this.#getValue();
2678
- }
2679
- #watchHintText() {
2680
- const { offLabel } = this.$props, { textTrack } = this.#media.$state, track = textTrack();
2681
- this.#menu?.hint.set(
2682
- track && isTrackCaptionKind(track) && track.mode === "showing" ? track.label : offLabel()
2683
- );
2684
- }
2685
- #watchControllerDisabled() {
2686
- this.#menu?.disable(this.disabled);
2687
- }
2688
- #getValue() {
2689
- const { textTrack } = this.#media.$state, track = textTrack();
2690
- return track && isTrackCaptionKind(track) && track.mode === "showing" ? this.#getTrackValue(track) : "off";
2691
- }
2692
- #onValueChange(value, trigger) {
2693
- if (this.disabled) return;
2694
- if (value === "off") {
2695
- const track = this.#media.textTracks.selected;
2696
- if (track) {
2697
- const index2 = this.#media.textTracks.indexOf(track);
2698
- this.#media.remote.changeTextTrackMode(index2, "disabled", trigger);
2699
- this.dispatch("change", { detail: null, trigger });
2700
- }
2701
- return;
2702
- }
2703
- const index = this.#media.textTracks.toArray().findIndex((track) => this.#getTrackValue(track) === value);
2704
- if (index >= 0) {
2705
- const track = this.#media.textTracks[index];
2706
- this.#media.remote.changeTextTrackMode(index, "showing", trigger);
2707
- this.dispatch("change", { detail: track, trigger });
2708
- }
2709
- }
2710
- #getTrackValue(track) {
2711
- return track.id + ":" + track.kind + "-" + track.label.toLowerCase();
2712
- }
2713
- }
2714
- const captionsradiogroup__proto = CaptionsRadioGroup.prototype;
2715
- prop(captionsradiogroup__proto, "value");
2716
- prop(captionsradiogroup__proto, "disabled");
2717
- method(captionsradiogroup__proto, "getOptions");
2718
-
2719
- const DEFAULT_PLAYBACK_RATES = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
2720
- class SpeedRadioGroup extends Component {
2721
- static props = {
2722
- normalLabel: "Normal",
2723
- rates: DEFAULT_PLAYBACK_RATES
2724
- };
2725
- #media;
2726
- #menu;
2727
- #controller;
2728
- get value() {
2729
- return this.#controller.value;
2730
- }
2731
- get disabled() {
2732
- const { rates } = this.$props, { canSetPlaybackRate } = this.#media.$state;
2733
- return !canSetPlaybackRate() || rates().length === 0;
2734
- }
2735
- constructor() {
2736
- super();
2737
- this.#controller = new RadioGroupController();
2738
- this.#controller.onValueChange = this.#onValueChange.bind(this);
2739
- }
2740
- onSetup() {
2741
- this.#media = useMediaContext();
2742
- if (hasProvidedContext(menuContext)) {
2743
- this.#menu = useContext(menuContext);
2744
- }
2745
- }
2746
- onConnect(el) {
2747
- effect(this.#watchValue.bind(this));
2748
- effect(this.#watchHintText.bind(this));
2749
- effect(this.#watchControllerDisabled.bind(this));
2750
- }
2751
- getOptions() {
2752
- const { rates, normalLabel } = this.$props;
2753
- return rates().map((rate) => ({
2754
- label: rate === 1 ? normalLabel : rate + "\xD7",
2755
- value: rate.toString()
2756
- }));
2757
- }
2758
- #watchValue() {
2759
- this.#controller.value = this.#getValue();
2760
- }
2761
- #watchHintText() {
2762
- const { normalLabel } = this.$props, { playbackRate } = this.#media.$state, rate = playbackRate();
2763
- this.#menu?.hint.set(rate === 1 ? normalLabel() : rate + "\xD7");
2764
- }
2765
- #watchControllerDisabled() {
2766
- this.#menu?.disable(this.disabled);
2767
- }
2768
- #getValue() {
2769
- const { playbackRate } = this.#media.$state;
2770
- return playbackRate().toString();
2771
- }
2772
- #onValueChange(value, trigger) {
2773
- if (this.disabled) return;
2774
- const rate = +value;
2775
- this.#media.remote.changePlaybackRate(rate, trigger);
2776
- this.dispatch("change", { detail: rate, trigger });
2777
- }
2778
- }
2779
- const speedradiogroup__proto = SpeedRadioGroup.prototype;
2780
- prop(speedradiogroup__proto, "value");
2781
- prop(speedradiogroup__proto, "disabled");
2782
- method(speedradiogroup__proto, "getOptions");
2783
-
2784
- class QualityRadioGroup extends Component {
2785
- static props = {
2786
- autoLabel: "Auto",
2787
- hideBitrate: false,
2788
- sort: "descending"
2789
- };
2790
- #media;
2791
- #menu;
2792
- #controller;
2793
- get value() {
2794
- return this.#controller.value;
2795
- }
2796
- get disabled() {
2797
- const { canSetQuality, qualities } = this.#media.$state;
2798
- return !canSetQuality() || qualities().length <= 1;
2799
- }
2800
- #sortedQualities = computed(() => {
2801
- const { sort } = this.$props, { qualities } = this.#media.$state;
2802
- return sortVideoQualities(qualities(), sort() === "descending");
2803
- });
2804
- constructor() {
2805
- super();
2806
- this.#controller = new RadioGroupController();
2807
- this.#controller.onValueChange = this.#onValueChange.bind(this);
2808
- }
2809
- onSetup() {
2810
- this.#media = useMediaContext();
2811
- if (hasProvidedContext(menuContext)) {
2812
- this.#menu = useContext(menuContext);
2813
- }
2814
- }
2815
- onConnect(el) {
2816
- effect(this.#watchValue.bind(this));
2817
- effect(this.#watchControllerDisabled.bind(this));
2818
- effect(this.#watchHintText.bind(this));
2819
- }
2820
- getOptions() {
2821
- const { autoLabel, hideBitrate } = this.$props;
2822
- return [
2823
- { value: "auto", label: autoLabel },
2824
- ...this.#sortedQualities().map((quality) => {
2825
- const bitrate = quality.bitrate && quality.bitrate >= 0 ? `${round(quality.bitrate / 1e6, 2)} Mbps` : null;
2826
- return {
2827
- quality,
2828
- label: quality.height + "p",
2829
- value: this.#getQualityId(quality),
2830
- bitrate: () => !hideBitrate() ? bitrate : null
2831
- };
2832
- })
2833
- ];
2834
- }
2835
- #watchValue() {
2836
- this.#controller.value = this.#getValue();
2837
- }
2838
- #watchHintText() {
2839
- const { autoLabel } = this.$props, { autoQuality, quality } = this.#media.$state, qualityText = quality() ? quality().height + "p" : "";
2840
- this.#menu?.hint.set(
2841
- !autoQuality() ? qualityText : autoLabel() + (qualityText ? ` (${qualityText})` : "")
2842
- );
2843
- }
2844
- #watchControllerDisabled() {
2845
- this.#menu?.disable(this.disabled);
2846
- }
2847
- #onValueChange(value, trigger) {
2848
- if (this.disabled) return;
2849
- if (value === "auto") {
2850
- this.#media.remote.changeQuality(-1, trigger);
2851
- this.dispatch("change", { detail: "auto", trigger });
2852
- return;
2853
- }
2854
- const { qualities } = this.#media.$state, index = peek(qualities).findIndex((quality) => this.#getQualityId(quality) === value);
2855
- if (index >= 0) {
2856
- const quality = peek(qualities)[index];
2857
- this.#media.remote.changeQuality(index, trigger);
2858
- this.dispatch("change", { detail: quality, trigger });
2859
- }
2860
- }
2861
- #getValue() {
2862
- const { quality, autoQuality } = this.#media.$state;
2863
- if (autoQuality()) return "auto";
2864
- const currentQuality = quality();
2865
- return currentQuality ? this.#getQualityId(currentQuality) : "auto";
2866
- }
2867
- #getQualityId(quality) {
2868
- return quality.height + "_" + quality.bitrate;
2869
- }
2870
- }
2871
- const qualityradiogroup__proto = QualityRadioGroup.prototype;
2872
- prop(qualityradiogroup__proto, "value");
2873
- prop(qualityradiogroup__proto, "disabled");
2874
- method(qualityradiogroup__proto, "getOptions");
2875
-
2876
- class Time extends Component {
2877
- static props = {
2878
- type: "current",
2879
- showHours: false,
2880
- padHours: null,
2881
- padMinutes: null,
2882
- remainder: false,
2883
- toggle: false,
2884
- hidden: false
2885
- };
2886
- static state = new State({
2887
- timeText: "",
2888
- hidden: false
2889
- });
2890
- #media;
2891
- #invert = signal(null);
2892
- #isVisible = signal(true);
2893
- #isIntersecting = signal(true);
2894
- onSetup() {
2895
- this.#media = useMediaContext();
2896
- this.#watchTime();
2897
- const { type } = this.$props;
2898
- this.setAttributes({
2899
- "data-type": type,
2900
- "data-remainder": this.#shouldInvert.bind(this)
2901
- });
2902
- new IntersectionObserverController({
2903
- callback: this.#onIntersectionChange.bind(this)
2904
- }).attach(this);
2905
- }
2906
- onAttach(el) {
2907
- if (!el.hasAttribute("role")) effect(this.#watchRole.bind(this));
2908
- effect(this.#watchTime.bind(this));
2909
- }
2910
- onConnect(el) {
2911
- onDispose(observeVisibility(el, this.#isVisible.set));
2912
- effect(this.#watchHidden.bind(this));
2913
- effect(this.#watchToggle.bind(this));
2914
- }
2915
- #onIntersectionChange(entries) {
2916
- this.#isIntersecting.set(entries[0].isIntersecting);
2917
- }
2918
- #watchHidden() {
2919
- const { hidden } = this.$props;
2920
- this.$state.hidden.set(hidden() || !this.#isVisible() || !this.#isIntersecting());
2921
- }
2922
- #watchToggle() {
2923
- if (!this.$props.toggle()) {
2924
- this.#invert.set(null);
2925
- return;
2926
- }
2927
- if (this.el) {
2928
- onPress(this.el, this.#onToggle.bind(this));
2929
- }
2930
- }
2931
- #watchTime() {
2932
- const { hidden, timeText } = this.$state, { duration } = this.#media.$state;
2933
- if (hidden()) return;
2934
- const { type, padHours, padMinutes, showHours } = this.$props, seconds = this.#getSeconds(type()), $duration = duration(), shouldInvert = this.#shouldInvert();
2935
- if (!Number.isFinite(seconds + $duration)) {
2936
- timeText.set("LIVE");
2937
- return;
2938
- }
2939
- const time = shouldInvert ? Math.max(0, $duration - seconds) : seconds, formattedTime = formatTime(time, {
2940
- padHrs: padHours(),
2941
- padMins: padMinutes(),
2942
- showHrs: showHours()
2943
- });
2944
- timeText.set((shouldInvert ? "-" : "") + formattedTime);
2945
- }
2946
- #watchRole() {
2947
- if (!this.el) return;
2948
- const { toggle } = this.$props;
2949
- setAttribute(this.el, "role", toggle() ? "timer" : null);
2950
- setAttribute(this.el, "tabindex", toggle() ? 0 : null);
2951
- }
2952
- #getSeconds(type) {
2953
- const { bufferedEnd, duration, currentTime } = this.#media.$state;
2954
- switch (type) {
2955
- case "buffered":
2956
- return bufferedEnd();
2957
- case "duration":
2958
- return duration();
2959
- default:
2960
- return currentTime();
2961
- }
2962
- }
2963
- #shouldInvert() {
2964
- return this.$props.remainder() && this.#invert() !== false;
2965
- }
2966
- #onToggle(event) {
2967
- event.preventDefault();
2968
- if (this.#invert() === null) {
2969
- this.#invert.set(!this.$props.remainder());
2970
- return;
2971
- }
2972
- this.#invert.set((v) => !v);
2973
- }
2974
- }
2975
-
2976
- 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 };