@hanifhan1f/vidstack 1.12.34 → 1.12.35

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