@hh.ru/magritte-ui-video 0.0.0-stage → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/Video.d.ts +13 -0
  2. package/Video.js +102 -0
  3. package/Video.js.map +1 -0
  4. package/VideoControls.d.ts +8 -0
  5. package/VideoControls.js +214 -0
  6. package/VideoControls.js.map +1 -0
  7. package/VideoPlayer.d.ts +4 -0
  8. package/VideoPlayer.js +200 -0
  9. package/VideoPlayer.js.map +1 -0
  10. package/index.css +355 -0
  11. package/index.d.ts +2 -0
  12. package/index.js +34 -0
  13. package/index.js.map +1 -0
  14. package/index.mock.d.ts +2 -0
  15. package/index.mock.js +16 -0
  16. package/index.mock.js.map +1 -0
  17. package/internal/ControlButton.d.ts +28 -0
  18. package/internal/ControlButton.js +63 -0
  19. package/internal/ControlButton.js.map +1 -0
  20. package/internal/Scrubber.d.ts +11 -0
  21. package/internal/Scrubber.js +125 -0
  22. package/internal/Scrubber.js.map +1 -0
  23. package/internal/SeekIndicator.d.ts +7 -0
  24. package/internal/SeekIndicator.js +33 -0
  25. package/internal/SeekIndicator.js.map +1 -0
  26. package/internal/TimeLabel.d.ts +10 -0
  27. package/internal/TimeLabel.js +22 -0
  28. package/internal/TimeLabel.js.map +1 -0
  29. package/internal/VolumeSlider.d.ts +6 -0
  30. package/internal/VolumeSlider.js +60 -0
  31. package/internal/VolumeSlider.js.map +1 -0
  32. package/internal/context.d.ts +26 -0
  33. package/internal/context.js +20 -0
  34. package/internal/context.js.map +1 -0
  35. package/internal/controlKey.d.ts +7 -0
  36. package/internal/controlKey.js +12 -0
  37. package/internal/controlKey.js.map +1 -0
  38. package/internal/formatTime.d.ts +10 -0
  39. package/internal/formatTime.js +19 -0
  40. package/internal/formatTime.js.map +1 -0
  41. package/internal/store.d.ts +154 -0
  42. package/internal/store.js +622 -0
  43. package/internal/store.js.map +1 -0
  44. package/internal/useCanvasGestures.d.ts +38 -0
  45. package/internal/useCanvasGestures.js +162 -0
  46. package/internal/useCanvasGestures.js.map +1 -0
  47. package/internal/useInert.d.ts +6 -0
  48. package/internal/useInert.js +11 -0
  49. package/internal/useInert.js.map +1 -0
  50. package/internal/useSliderPointer.d.ts +15 -0
  51. package/internal/useSliderPointer.js +46 -0
  52. package/internal/useSliderPointer.js.map +1 -0
  53. package/internal/useVideoHotkeys.d.ts +10 -0
  54. package/internal/useVideoHotkeys.js +47 -0
  55. package/internal/useVideoHotkeys.js.map +1 -0
  56. package/internal/useViewportVisibility.d.ts +6 -0
  57. package/internal/useViewportVisibility.js +31 -0
  58. package/internal/useViewportVisibility.js.map +1 -0
  59. package/package.json +42 -3
  60. package/types.d.ts +131 -0
  61. package/types.js +3 -0
  62. package/types.js.map +1 -0
  63. package/video-BnfN4Emx.js +5 -0
  64. package/video-BnfN4Emx.js.map +1 -0
  65. package/README.md +0 -3
package/index.css ADDED
@@ -0,0 +1,355 @@
1
+ :root{
2
+ --magritte-color-background-accent-v26-0-2:#0070ff;
3
+ --magritte-color-background-overlay-v26-0-2:#20262bad;
4
+ --magritte-color-background-overlay-toolbar-v26-0-2:#000000a3;
5
+ --magritte-color-background-constant-v26-0-2:#000000;
6
+ --magritte-color-stroke-neutral-v26-0-2:#DCE3EB;
7
+ --magritte-color-stroke-state-focused-accessible-v26-0-2:#0070ff7a;
8
+ --magritte-color-icon-constant-v26-0-2:#ffffff;
9
+ }
10
+
11
+ :root{
12
+ --magritte-semantic-animation-time-m-duration-v26-0-2:400ms;
13
+ --magritte-semantic-animation-ease-base-timing-function-v26-0-2:cubic-bezier(0.4, 0.24, 0, 1);
14
+ }
15
+
16
+ .magritte-night-theme{
17
+ --magritte-color-background-accent-v26-0-2:#2b7fff;
18
+ --magritte-color-stroke-neutral-v26-0-2:#303030;
19
+ }
20
+
21
+ .magritte-zp-day-theme{
22
+ --magritte-color-background-accent-v26-0-2:#7195AF;
23
+ }
24
+ .magritte-player___IHHy5_1-0-2{
25
+ position:relative;
26
+ width:100%;
27
+ overflow:hidden;
28
+ background-color:var(--magritte-color-background-constant-v26-0-2);
29
+ outline:none;
30
+ isolation:isolate;
31
+ user-select:none;
32
+ -webkit-user-select:none;
33
+ -webkit-touch-callout:none;
34
+ }
35
+ .magritte-player-fit___TX-6X_1-0-2{
36
+ height:100%;
37
+ }
38
+ .magritte-player-embedded___GNvRb_1-0-2{
39
+ overflow:visible;
40
+ background-color:transparent;
41
+ }
42
+ .magritte-video___4cS-u_1-0-2{
43
+ display:block;
44
+ width:100%;
45
+ height:100%;
46
+ object-fit:contain;
47
+ }
48
+ .magritte-video-hidden___agqVq_1-0-2{
49
+ visibility:hidden;
50
+ }
51
+ .magritte-overlay-button___MIRAP_1-0-2{
52
+ position:absolute;
53
+ top:50%;
54
+ left:50%;
55
+ transform:translate(-50%, -50%);
56
+ display:flex;
57
+ border-radius:50%;
58
+ backdrop-filter:blur(24px);
59
+ transition:opacity var(--magritte-semantic-animation-time-m-duration-v26-0-2) var(--magritte-semantic-animation-ease-base-timing-function-v26-0-2);
60
+ }
61
+ .magritte-duration___ND1ei_1-0-2{
62
+ position:absolute;
63
+ right:16px;
64
+ bottom:16px;
65
+ display:flex;
66
+ padding:2px 6px 1px;
67
+ border-radius:4px;
68
+ background-color:var(--magritte-color-background-overlay-toolbar-v26-0-2);
69
+ backdrop-filter:blur(24px);
70
+ pointer-events:none;
71
+ }
72
+ .magritte-seek-indicator___VJQBC_1-0-2{
73
+ position:absolute;
74
+ top:50%;
75
+ display:flex;
76
+ align-items:center;
77
+ gap:12px;
78
+ transform:translateY(-50%);
79
+ pointer-events:none;
80
+ }
81
+ .magritte-seek-indicator-forward___9C5gD_1-0-2{
82
+ right:40px;
83
+ }
84
+ .magritte-seek-indicator-backward___gIZ0E_1-0-2{
85
+ left:40px;
86
+ flex-direction:row-reverse;
87
+ --magritte-ui-video-seek-arrow-shift:16px;
88
+ }
89
+ .magritte-seek-arrows___Ql1eP_1-0-2{
90
+ position:relative;
91
+ display:flex;
92
+ }
93
+ .magritte-seek-arrow-incoming___oZxww_1-0-2{
94
+ position:absolute;
95
+ inset:0;
96
+ display:flex;
97
+ animation:magritte-seek-arrow-in___CB4-s_1-0-2 var(--magritte-semantic-animation-time-m-duration-v26-0-2) var(--magritte-semantic-animation-ease-base-timing-function-v26-0-2) both;
98
+ }
99
+ @keyframes magritte-seek-arrow-in___CB4-s_1-0-2{
100
+ from{
101
+ transform:translateX(var(--magritte-ui-video-seek-arrow-shift, -16px));
102
+ }
103
+ to{
104
+ transform:translateX(0);
105
+ }
106
+ }
107
+ .magritte-boost-indicator___ujllx_1-0-2{
108
+ position:absolute;
109
+ top:24px;
110
+ left:50%;
111
+ display:flex;
112
+ align-items:center;
113
+ gap:4px;
114
+ padding:6px 12px;
115
+ border-radius:999px;
116
+ background-color:var(--magritte-color-background-overlay-v26-0-2);
117
+ backdrop-filter:blur(24px);
118
+ transform:translateX(-50%);
119
+ pointer-events:none;
120
+ }
121
+ .magritte-loader___qVu0P_1-0-2{
122
+ position:absolute;
123
+ top:50%;
124
+ left:50%;
125
+ transform:translate(-50%, -50%);
126
+ display:flex;
127
+ pointer-events:none;
128
+ }
129
+ .magritte-error___Rm7uG_1-0-2{
130
+ position:absolute;
131
+ inset:0;
132
+ display:flex;
133
+ align-items:center;
134
+ justify-content:center;
135
+ padding:16px;
136
+ overflow:auto;
137
+ }
138
+ .magritte-overlay-button-hidden___AoGFc_1-0-2,
139
+ .magritte-controls___pKoSN_1-0-2.magritte-controls-hidden___bePTD_1-0-2{
140
+ opacity:0;
141
+ pointer-events:none;
142
+ }
143
+ .magritte-controls___pKoSN_1-0-2{
144
+ display:flex;
145
+ flex-direction:column;
146
+ gap:4px;
147
+ padding:4px 12px 12px;
148
+ pointer-events:auto;
149
+ transition:opacity var(--magritte-semantic-animation-time-m-duration-v26-0-2) var(--magritte-semantic-animation-ease-base-timing-function-v26-0-2);
150
+ }
151
+ .magritte-controls-mobile___tdKGT_1-0-2{
152
+ gap:6px;
153
+ padding:4px 16px 8px;
154
+ }
155
+ .magritte-controls-overlay___mRsJL_1-0-2{
156
+ position:absolute;
157
+ right:0;
158
+ bottom:0;
159
+ left:0;
160
+ }
161
+ .magritte-controls-row___2sdJg_1-0-2{
162
+ display:flex;
163
+ align-items:center;
164
+ justify-content:space-between;
165
+ gap:8px;
166
+ }
167
+ .magritte-controls-group-start___yY9ZA_1-0-2,
168
+ .magritte-controls-group-end___ziIm8_1-0-2{
169
+ display:flex;
170
+ align-items:center;
171
+ min-width:0;
172
+ }
173
+ .magritte-controls-group-start___yY9ZA_1-0-2{
174
+ gap:12px;
175
+ }
176
+ .magritte-controls-group-end___ziIm8_1-0-2{
177
+ gap:8px;
178
+ }
179
+ .magritte-controls-mobile___tdKGT_1-0-2 .magritte-controls-group-end___ziIm8_1-0-2{
180
+ gap:4px;
181
+ }
182
+ .magritte-control-pill___hK8Mv_1-0-2,
183
+ .magritte-controls-time___e9zoz_1-0-2,
184
+ .magritte-controls-volume___o4ajR_1-0-2{
185
+ display:flex;
186
+ align-items:center;
187
+ justify-content:center;
188
+ border-radius:999px;
189
+ backdrop-filter:blur(24px);
190
+ }
191
+ .magritte-control-pill-bare___lU9ev_1-0-2{
192
+ backdrop-filter:none;
193
+ }
194
+ .magritte-control-button___VJ2Qr_1-0-2,
195
+ .magritte-controls-time___e9zoz_1-0-2,
196
+ .magritte-controls-volume___o4ajR_1-0-2{
197
+ box-sizing:border-box;
198
+ min-width:48px;
199
+ min-height:48px;
200
+ padding:12px;
201
+ border-radius:999px;
202
+ background-color:var(--magritte-color-background-overlay-v26-0-2);
203
+ }
204
+ .magritte-control-button___VJ2Qr_1-0-2{
205
+ display:flex;
206
+ align-items:center;
207
+ justify-content:center;
208
+ border-width:0;
209
+ cursor:pointer;
210
+ }
211
+ .magritte-control-button___VJ2Qr_1-0-2.focus-visible{
212
+ outline:4px solid var(--magritte-color-stroke-state-focused-accessible-v26-0-2);
213
+ }
214
+ .magritte-controls-time___e9zoz_1-0-2{
215
+ padding:12px 16px;
216
+ white-space:nowrap;
217
+ }
218
+ .magritte-controls-mobile___tdKGT_1-0-2 .magritte-controls-time___e9zoz_1-0-2{
219
+ padding:12px;
220
+ }
221
+ .magritte-controls-volume___o4ajR_1-0-2{
222
+ gap:6px;
223
+ }
224
+ .magritte-scrubber___-K3ik_1-0-2{
225
+ position:relative;
226
+ display:flex;
227
+ align-items:center;
228
+ height:12px;
229
+ cursor:pointer;
230
+ touch-action:none;
231
+ outline:none;
232
+ --magritte-ui-video-scrubber-track-height:4px;
233
+ --magritte-ui-video-scrubber-thumb-size:8px;
234
+ }
235
+ .magritte-controls-mobile___tdKGT_1-0-2 .magritte-scrubber___-K3ik_1-0-2::before{
236
+ content:'';
237
+ position:absolute;
238
+ inset:calc(-1 * 20px) 0 calc(-1 * 6px);
239
+ }
240
+ .magritte-scrubber___-K3ik_1-0-2.focus-visible{
241
+ outline:4px solid var(--magritte-color-stroke-state-focused-accessible-v26-0-2);
242
+ border-radius:4px;
243
+ }
244
+ .magritte-scrubber___-K3ik_1-0-2[data-scrubbing]{
245
+ --magritte-ui-video-scrubber-track-height:6px;
246
+ --magritte-ui-video-scrubber-thumb-size:12px;
247
+ }
248
+ @media (min-width: 1020px){
249
+ body.magritte-old-layout .magritte-scrubber___-K3ik_1-0-2:hover{
250
+ --magritte-ui-video-scrubber-track-height:6px;
251
+ --magritte-ui-video-scrubber-thumb-size:12px;
252
+ }
253
+ }
254
+ @media (min-width: 1024px){
255
+ body:not(.magritte-old-layout) .magritte-scrubber___-K3ik_1-0-2:hover{
256
+ --magritte-ui-video-scrubber-track-height:6px;
257
+ --magritte-ui-video-scrubber-thumb-size:12px;
258
+ }
259
+ }
260
+ .magritte-scrubber-track___ki1zV_1-0-2{
261
+ position:relative;
262
+ flex-grow:1;
263
+ height:var(--magritte-ui-video-scrubber-track-height);
264
+ overflow:hidden;
265
+ border-radius:999px;
266
+ background-color:var(--magritte-color-background-overlay-v26-0-2);
267
+ }
268
+ .magritte-scrubber-buffered___ufvLe_1-0-2,
269
+ .magritte-scrubber-progress___ZIyew_1-0-2{
270
+ position:absolute;
271
+ inset:0;
272
+ transform-origin:left;
273
+ }
274
+ .magritte-scrubber-buffered___ufvLe_1-0-2{
275
+ background-color:var(--magritte-color-stroke-neutral-v26-0-2);
276
+ transform:scaleX(var(--magritte-ui-video-scrubber-buffered, 0));
277
+ }
278
+ .magritte-scrubber-progress___ZIyew_1-0-2{
279
+ background-color:var(--magritte-color-background-accent-v26-0-2);
280
+ transform:scaleX(var(--magritte-ui-video-scrubber-progress, 0));
281
+ }
282
+ .magritte-scrubber-thumb___VXyPP_1-0-2{
283
+ position:absolute;
284
+ top:50%;
285
+ left:calc(var(--magritte-ui-video-scrubber-progress, 0) * 100%);
286
+ width:var(--magritte-ui-video-scrubber-thumb-size);
287
+ height:var(--magritte-ui-video-scrubber-thumb-size);
288
+ border-radius:50%;
289
+ background-color:var(--magritte-color-icon-constant-v26-0-2);
290
+ transform:translate(-50%, -50%);
291
+ pointer-events:none;
292
+ }
293
+ .magritte-scrubber-timecode___M-syA_1-0-2{
294
+ position:absolute;
295
+ bottom:calc(100% + 8px);
296
+ left:calc(var(--magritte-ui-video-scrubber-timecode, 0) * 100%);
297
+ display:none;
298
+ padding:1px 6px 0;
299
+ border-radius:999px;
300
+ background-color:var(--magritte-color-background-overlay-v26-0-2);
301
+ transform:translateX(-50%);
302
+ white-space:nowrap;
303
+ pointer-events:none;
304
+ }
305
+ .magritte-scrubber___-K3ik_1-0-2[data-scrubbing] .magritte-scrubber-timecode___M-syA_1-0-2{
306
+ display:flex;
307
+ }
308
+ @media (min-width: 1020px){
309
+ body.magritte-old-layout .magritte-scrubber___-K3ik_1-0-2:hover .magritte-scrubber-timecode___M-syA_1-0-2{
310
+ display:flex;
311
+ }
312
+ }
313
+ @media (min-width: 1024px){
314
+ body:not(.magritte-old-layout) .magritte-scrubber___-K3ik_1-0-2:hover .magritte-scrubber-timecode___M-syA_1-0-2{
315
+ display:flex;
316
+ }
317
+ }
318
+ .magritte-volume-slider___qE2P7_1-0-2{
319
+ display:flex;
320
+ align-items:center;
321
+ width:64px;
322
+ height:12px;
323
+ cursor:pointer;
324
+ touch-action:none;
325
+ outline:none;
326
+ }
327
+ .magritte-volume-slider___qE2P7_1-0-2.focus-visible{
328
+ outline:4px solid var(--magritte-color-stroke-state-focused-accessible-v26-0-2);
329
+ border-radius:4px;
330
+ }
331
+ .magritte-volume-track___up8nd_1-0-2{
332
+ position:relative;
333
+ flex-grow:1;
334
+ height:4px;
335
+ border-radius:999px;
336
+ background-color:var(--magritte-color-background-overlay-v26-0-2);
337
+ }
338
+ .magritte-volume-value___aUSuU_1-0-2{
339
+ position:relative;
340
+ width:calc(var(--magritte-ui-video-volume, 1) * 100%);
341
+ height:100%;
342
+ border-radius:999px;
343
+ background-color:var(--magritte-color-stroke-neutral-v26-0-2);
344
+ }
345
+ .magritte-volume-value___aUSuU_1-0-2::after{
346
+ content:'';
347
+ position:absolute;
348
+ top:50%;
349
+ right:-4px;
350
+ width:8px;
351
+ height:8px;
352
+ border-radius:50%;
353
+ background-color:var(--magritte-color-icon-constant-v26-0-2);
354
+ transform:translateY(-50%);
355
+ }
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { Video } from './Video';
2
+ export { type VideoAttachMedia, type VideoAttachMediaApi, type VideoControlsProps, type VideoHandle, type VideoPlayerProps, type VideoProps, type VideoSource, type VideoStatus, } from './types';
package/index.js ADDED
@@ -0,0 +1,34 @@
1
+ import './index.css';
2
+ export { Video } from './Video.js';
3
+ import 'react/jsx-runtime';
4
+ import 'react';
5
+ import './VideoControls.js';
6
+ import 'react-dom';
7
+ import 'classnames';
8
+ import '@hh.ru/magritte-common-use-translation';
9
+ import '@hh.ru/magritte-ui-breakpoint';
10
+ import '@hh.ru/magritte-ui-icon/icon';
11
+ import '@hh.ru/magritte-ui-typography';
12
+ import './internal/ControlButton.js';
13
+ import '@hh.ru/magritte-ui-tooltip';
14
+ import './video-BnfN4Emx.js';
15
+ import './internal/Scrubber.js';
16
+ import './internal/formatTime.js';
17
+ import './internal/useSliderPointer.js';
18
+ import './internal/useVideoHotkeys.js';
19
+ import '@hh.ru/magritte-common-keyboard';
20
+ import './internal/TimeLabel.js';
21
+ import './internal/VolumeSlider.js';
22
+ import './internal/context.js';
23
+ import './internal/controlKey.js';
24
+ import './internal/useInert.js';
25
+ import './VideoPlayer.js';
26
+ import '@hh.ru/magritte-ui-button';
27
+ import '@hh.ru/magritte-ui-button-stack';
28
+ import '@hh.ru/magritte-ui-loader';
29
+ import '@hh.ru/magritte-ui-placeholder';
30
+ import './internal/SeekIndicator.js';
31
+ import './internal/store.js';
32
+ import './internal/useCanvasGestures.js';
33
+ import './internal/useViewportVisibility.js';
34
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,2 @@
1
+ export { type VideoAttachMedia, type VideoAttachMediaApi, type VideoControlsProps, type VideoHandle, type VideoPlayerProps, type VideoProps, type VideoSource, type VideoStatus, } from './types';
2
+ export declare const Video: import("react").ForwardRefExoticComponent<Record<string, unknown>>;
package/index.mock.js ADDED
@@ -0,0 +1,16 @@
1
+ import './index.css';
2
+ import { mockComponent } from '@hh.ru/magritte-ui-mock-component';
3
+
4
+ const Video = mockComponent(
5
+ "Video",
6
+ {
7
+ Player: mockComponent("Video.Player", {}, { withChildren: true }),
8
+ Controls: mockComponent("Video.Controls")
9
+ },
10
+ {
11
+ withChildren: true
12
+ }
13
+ );
14
+
15
+ export { Video };
16
+ //# sourceMappingURL=index.mock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mock.js","sources":["src/index.mock.ts"],"sourcesContent":["import { mockComponent } from '@hh.ru/magritte-ui-mock-component';\n\nexport {\n type VideoAttachMedia,\n type VideoAttachMediaApi,\n type VideoControlsProps,\n type VideoHandle,\n type VideoPlayerProps,\n type VideoProps,\n type VideoSource,\n type VideoStatus,\n} from '@hh.ru/magritte-ui-video/types';\n\nexport const Video = mockComponent(\n 'Video',\n {\n Player: mockComponent('Video.Player', {}, { withChildren: true }),\n Controls: mockComponent('Video.Controls'),\n },\n {\n withChildren: true,\n }\n);\n"],"names":[],"mappings":";;AAaO,MAAM,KAAQ,GAAA,aAAA;AAAA,EACjB,OAAA;AAAA,EACA;AAAA,IACI,MAAA,EAAQ,cAAc,cAAgB,EAAA,IAAI,EAAE,YAAA,EAAc,MAAM,CAAA;AAAA,IAChE,QAAA,EAAU,cAAc,gBAAgB,CAAA;AAAA,GAC5C;AAAA,EACA;AAAA,IACI,YAAc,EAAA,IAAA;AAAA,GAClB;AACJ;;;;"}
@@ -0,0 +1,28 @@
1
+ import { type FC, type ReactNode } from 'react';
2
+ import { type IconWrapperComponentSize24 } from '@hh.ru/magritte-ui-icon';
3
+ interface ControlButtonBaseProps {
4
+ /** Имя контрола: по нему контролы находят кнопку (вернуть фокус после подмены). Не путать с `data-qa` */
5
+ control: string;
6
+ /** aria-label и текст подсказки */
7
+ label?: string;
8
+ onClick: VoidFunction;
9
+ /** Подсказка при наведении — только на desktop и пока контролы видны */
10
+ withTooltip: boolean;
11
+ 'data-qa'?: string;
12
+ }
13
+ interface IconControlButtonProps extends ControlButtonBaseProps {
14
+ /** Иконка-кнопка: подложку, отступы, скругление и состояния даёт сама иконка */
15
+ icon: IconWrapperComponentSize24;
16
+ /** Без собственной подложки — внутри общей «таблетки» (громкость) */
17
+ bare?: boolean;
18
+ children?: never;
19
+ }
20
+ interface TextControlButtonProps extends ControlButtonBaseProps {
21
+ icon?: never;
22
+ bare?: never;
23
+ /** Текстовое содержимое кнопки (скорость воспроизведения) */
24
+ children: ReactNode;
25
+ }
26
+ type ControlButtonProps = IconControlButtonProps | TextControlButtonProps;
27
+ export declare const ControlButton: FC<ControlButtonProps>;
28
+ export {};
@@ -0,0 +1,63 @@
1
+ import './../index.css';
2
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
+ import { useRef } from 'react';
4
+ import classnames from 'classnames';
5
+ import { TooltipHover } from '@hh.ru/magritte-ui-tooltip';
6
+ import { s as styles } from '../video-BnfN4Emx.js';
7
+
8
+ const ControlButton = ({
9
+ control,
10
+ label,
11
+ onClick,
12
+ withTooltip,
13
+ icon: Icon,
14
+ bare = false,
15
+ children,
16
+ "data-qa": dataQa
17
+ }) => {
18
+ const ref = useRef(null);
19
+ let button;
20
+ if (!Icon) {
21
+ button = /* @__PURE__ */ jsx(
22
+ "button",
23
+ {
24
+ type: "button",
25
+ className: styles.controlButton,
26
+ "aria-label": label,
27
+ onClick,
28
+ "data-qa": dataQa,
29
+ children
30
+ }
31
+ );
32
+ } else if (bare) {
33
+ button = /* @__PURE__ */ jsx(Icon, { initialColor: "constant", "aria-label": label, onClick, "data-qa": dataQa });
34
+ } else {
35
+ button = /* @__PURE__ */ jsx(
36
+ Icon,
37
+ {
38
+ initialColor: "constant",
39
+ padding: 12,
40
+ borderRadius: "halfHeight",
41
+ backgroundStyle: "overlay",
42
+ "aria-label": label,
43
+ onClick,
44
+ "data-qa": dataQa
45
+ }
46
+ );
47
+ }
48
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
49
+ /* @__PURE__ */ jsx(
50
+ "span",
51
+ {
52
+ ref,
53
+ className: classnames(styles.controlPill, { [styles.controlPillBare]: bare }),
54
+ "data-video-control": control,
55
+ children: button
56
+ }
57
+ ),
58
+ withTooltip && label && /* @__PURE__ */ jsx(TooltipHover, { activatorRef: ref, placement: "top-center", size: "small", children: label })
59
+ ] });
60
+ };
61
+
62
+ export { ControlButton };
63
+ //# sourceMappingURL=ControlButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ControlButton.js","sources":["src/internal/ControlButton.tsx"],"sourcesContent":["import { type FC, type ReactNode, useRef } from 'react';\nimport classnames from 'classnames';\n\nimport { type IconWrapperComponentSize24 } from '@hh.ru/magritte-ui-icon';\nimport { TooltipHover } from '@hh.ru/magritte-ui-tooltip';\n\nimport styles from '../video.less';\n\ninterface ControlButtonBaseProps {\n /** Имя контрола: по нему контролы находят кнопку (вернуть фокус после подмены). Не путать с `data-qa` */\n control: string;\n /** aria-label и текст подсказки */\n label?: string;\n onClick: VoidFunction;\n /** Подсказка при наведении — только на desktop и пока контролы видны */\n withTooltip: boolean;\n 'data-qa'?: string;\n}\n\ninterface IconControlButtonProps extends ControlButtonBaseProps {\n /** Иконка-кнопка: подложку, отступы, скругление и состояния даёт сама иконка */\n icon: IconWrapperComponentSize24;\n /** Без собственной подложки — внутри общей «таблетки» (громкость) */\n bare?: boolean;\n children?: never;\n}\n\ninterface TextControlButtonProps extends ControlButtonBaseProps {\n icon?: never;\n bare?: never;\n /** Текстовое содержимое кнопки (скорость воспроизведения) */\n children: ReactNode;\n}\n\ntype ControlButtonProps = IconControlButtonProps | TextControlButtonProps;\n\nexport const ControlButton: FC<ControlButtonProps> = ({\n control,\n label,\n onClick,\n withTooltip,\n icon: Icon,\n bare = false,\n children,\n 'data-qa': dataQa,\n}) => {\n // активатор подсказки — обёртка: наведение на кнопку внутри неё тоже считается\n const ref = useRef<HTMLSpanElement>(null);\n\n let button: ReactNode;\n if (!Icon) {\n button = (\n <button\n type=\"button\"\n className={styles.controlButton}\n aria-label={label}\n onClick={onClick}\n data-qa={dataQa}\n >\n {children}\n </button>\n );\n } else if (bare) {\n button = <Icon initialColor=\"constant\" aria-label={label} onClick={onClick} data-qa={dataQa} />;\n } else {\n button = (\n <Icon\n initialColor=\"constant\"\n padding={12}\n borderRadius=\"halfHeight\"\n backgroundStyle=\"overlay\"\n aria-label={label}\n onClick={onClick}\n data-qa={dataQa}\n />\n );\n }\n\n return (\n <>\n {/* иконка-кнопка отдаёт свои атрибуты svg, а не кнопке — имя контрола на обёртке */}\n <span\n ref={ref}\n className={classnames(styles.controlPill, { [styles.controlPillBare]: bare })}\n data-video-control={control}\n >\n {button}\n </span>\n {withTooltip && label && (\n <TooltipHover activatorRef={ref} placement=\"top-center\" size=\"small\">\n {label}\n </TooltipHover>\n )}\n </>\n );\n};\n"],"names":[],"mappings":";;;;;;AAoCO,MAAM,gBAAwC,CAAC;AAAA,EAClD,OAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA,WAAA;AAAA,EACA,IAAM,EAAA,IAAA;AAAA,EACN,IAAO,GAAA,KAAA;AAAA,EACP,QAAA;AAAA,EACA,SAAW,EAAA,MAAA;AACf,CAAM,KAAA;AAEF,EAAM,MAAA,GAAA,GAAM,OAAwB,IAAI,CAAA,CAAA;AAExC,EAAI,IAAA,MAAA,CAAA;AACJ,EAAA,IAAI,CAAC,IAAM,EAAA;AACP,IACI,MAAA,mBAAA,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACG,IAAK,EAAA,QAAA;AAAA,QACL,WAAW,MAAO,CAAA,aAAA;AAAA,QAClB,YAAY,EAAA,KAAA;AAAA,QACZ,OAAA;AAAA,QACA,SAAS,EAAA,MAAA;AAAA,QAER,QAAA;AAAA,OAAA;AAAA,KACL,CAAA;AAAA,aAEG,IAAM,EAAA;AACb,IAAS,MAAA,mBAAA,GAAA,CAAC,QAAK,YAAa,EAAA,UAAA,EAAW,cAAY,KAAO,EAAA,OAAA,EAAkB,WAAS,MAAQ,EAAA,CAAA,CAAA;AAAA,GAC1F,MAAA;AACH,IACI,MAAA,mBAAA,GAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACG,YAAa,EAAA,UAAA;AAAA,QACb,OAAS,EAAA,EAAA;AAAA,QACT,YAAa,EAAA,YAAA;AAAA,QACb,eAAgB,EAAA,SAAA;AAAA,QAChB,YAAY,EAAA,KAAA;AAAA,QACZ,OAAA;AAAA,QACA,SAAS,EAAA,MAAA;AAAA,OAAA;AAAA,KACb,CAAA;AAAA,GAER;AAEA,EAAA,uBAGQ,IAAA,CAAA,QAAA,EAAA,EAAA,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACG,GAAA;AAAA,QACA,SAAA,EAAW,UAAW,CAAA,MAAA,CAAO,WAAa,EAAA,EAAE,CAAC,MAAO,CAAA,eAAe,GAAG,IAAA,EAAM,CAAA;AAAA,QAC5E,oBAAoB,EAAA,OAAA;AAAA,QAEnB,QAAA,EAAA,MAAA;AAAA,OAAA;AAAA,KACL;AAAA,IACC,WAAA,IAAe,KACZ,oBAAA,GAAA,CAAC,YAAa,EAAA,EAAA,YAAA,EAAc,KAAK,SAAU,EAAA,YAAA,EAAa,IAAK,EAAA,OAAA,EACxD,QACL,EAAA,KAAA,EAAA,CAAA;AAAA,GAER,EAAA,CAAA,CAAA;AAER;;;;"}
@@ -0,0 +1,11 @@
1
+ import { type FC } from 'react';
2
+ import { type VideoStore } from '../internal/store';
3
+ interface ScrubberProps {
4
+ store: VideoStore;
5
+ }
6
+ /**
7
+ * Ползунок перемотки. Позиция, загруженная часть и таймкод пишутся прямо в DOM из `subscribeTime` —
8
+ * без React-рендеров на каждом кадре. Перемотка применяется при отпускании бегунка
9
+ */
10
+ export declare const Scrubber: FC<ScrubberProps>;
11
+ export {};
@@ -0,0 +1,125 @@
1
+ import './../index.css';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useRef, useEffect } from 'react';
4
+ import { useTranslation } from '@hh.ru/magritte-common-use-translation';
5
+ import { Text } from '@hh.ru/magritte-ui-typography';
6
+ import { formatTime, formatDuration } from './formatTime.js';
7
+ import { useSliderPointer } from './useSliderPointer.js';
8
+ import { SEEK_STEP_SECONDS } from './useVideoHotkeys.js';
9
+ import { s as styles } from '../video-BnfN4Emx.js';
10
+ import '@hh.ru/magritte-common-keyboard';
11
+
12
+ const KEYBOARD_SEEK_STEP = SEEK_STEP_SECONDS;
13
+ const Scrubber = ({ store }) => {
14
+ const rootRef = useRef(null);
15
+ const progressRef = useRef(null);
16
+ const bufferedRef = useRef(null);
17
+ const thumbRef = useRef(null);
18
+ const timecodeRef = useRef(null);
19
+ const timecodeTextRef = useRef(null);
20
+ const draggingRef = useRef(false);
21
+ const seekTrl = useTranslation("Video", "seek");
22
+ const ofTrl = useTranslation("Video", "of");
23
+ const paintPosition = (ratio) => {
24
+ progressRef.current?.style.setProperty("--magritte-ui-video-scrubber-progress", String(ratio));
25
+ thumbRef.current?.style.setProperty("--magritte-ui-video-scrubber-progress", String(ratio));
26
+ };
27
+ const paintTimecode = (ratio) => {
28
+ const duration = store.getTime().duration;
29
+ timecodeRef.current?.style.setProperty("--magritte-ui-video-scrubber-timecode", String(ratio));
30
+ if (timecodeTextRef.current) {
31
+ timecodeTextRef.current.textContent = formatDuration(ratio * duration);
32
+ }
33
+ };
34
+ useEffect(
35
+ () => store.subscribeTime(({ current, duration, buffered }) => {
36
+ const root = rootRef.current;
37
+ if (!root || duration <= 0) {
38
+ return;
39
+ }
40
+ bufferedRef.current?.style.setProperty(
41
+ "--magritte-ui-video-scrubber-buffered",
42
+ String(Math.min(buffered / duration, 1))
43
+ );
44
+ if (!draggingRef.current) {
45
+ paintPosition(Math.min(current / duration, 1));
46
+ }
47
+ root.setAttribute("aria-valuenow", String(Math.round(current)));
48
+ root.setAttribute("aria-valuemax", String(Math.round(duration)));
49
+ root.setAttribute("aria-valuetext", `${formatTime(current)} ${ofTrl ?? ""} ${formatTime(duration)}`);
50
+ }),
51
+ [store, ofTrl]
52
+ );
53
+ useEffect(
54
+ () => () => {
55
+ if (draggingRef.current) {
56
+ draggingRef.current = false;
57
+ store.holdControls(false);
58
+ }
59
+ },
60
+ [store]
61
+ );
62
+ const onChange = (ratio, phase) => {
63
+ paintPosition(ratio);
64
+ paintTimecode(ratio);
65
+ if (phase === "start") {
66
+ if (!draggingRef.current) {
67
+ draggingRef.current = true;
68
+ rootRef.current?.setAttribute("data-scrubbing", "");
69
+ store.holdControls(true);
70
+ }
71
+ return;
72
+ }
73
+ if (phase === "end") {
74
+ draggingRef.current = false;
75
+ rootRef.current?.removeAttribute("data-scrubbing");
76
+ store.seek(ratio * store.getTime().duration);
77
+ store.holdControls(false);
78
+ }
79
+ };
80
+ const pointerHandlers = useSliderPointer(onChange, paintTimecode);
81
+ const onKeyDown = (event) => {
82
+ const { current, duration } = store.getTime();
83
+ const targets = {
84
+ ArrowLeft: current - KEYBOARD_SEEK_STEP,
85
+ ArrowDown: current - KEYBOARD_SEEK_STEP,
86
+ ArrowRight: current + KEYBOARD_SEEK_STEP,
87
+ ArrowUp: current + KEYBOARD_SEEK_STEP,
88
+ Home: 0,
89
+ End: duration
90
+ };
91
+ if (!(event.key in targets)) {
92
+ return;
93
+ }
94
+ event.preventDefault();
95
+ store.seek(targets[event.key]);
96
+ store.notifyActivity();
97
+ };
98
+ return /* @__PURE__ */ jsxs(
99
+ "div",
100
+ {
101
+ ref: rootRef,
102
+ role: "slider",
103
+ tabIndex: 0,
104
+ "aria-label": seekTrl,
105
+ "aria-valuemin": 0,
106
+ "aria-orientation": "horizontal",
107
+ className: styles.scrubber,
108
+ onKeyDown,
109
+ "data-video-control": "scrubber",
110
+ "data-qa": "video-scrubber",
111
+ ...pointerHandlers,
112
+ children: [
113
+ /* @__PURE__ */ jsxs("div", { className: styles.scrubberTrack, children: [
114
+ /* @__PURE__ */ jsx("div", { ref: bufferedRef, className: styles.scrubberBuffered }),
115
+ /* @__PURE__ */ jsx("div", { ref: progressRef, className: styles.scrubberProgress })
116
+ ] }),
117
+ /* @__PURE__ */ jsx("div", { ref: thumbRef, className: styles.scrubberThumb }),
118
+ /* @__PURE__ */ jsx("div", { ref: timecodeRef, className: styles.scrubberTimecode, "aria-hidden": true, children: /* @__PURE__ */ jsx(Text, { typography: "paragraph-3-regular", style: "constant", children: /* @__PURE__ */ jsx("span", { ref: timecodeTextRef }) }) })
119
+ ]
120
+ }
121
+ );
122
+ };
123
+
124
+ export { Scrubber };
125
+ //# sourceMappingURL=Scrubber.js.map