@streamscloud/embeddable 6.0.0 → 6.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.
- package/dist/core/browser.d.ts +1 -0
- package/dist/core/browser.js +1 -0
- package/dist/media-center/media-center/cmp.media-center.svelte +127 -32
- package/dist/media-center/media-center/media-center-localization.d.ts +2 -0
- package/dist/media-center/media-center/media-center-localization.js +6 -0
- package/dist/media-center/media-center/overview.svelte +13 -4
- package/dist/short-videos/short-videos-player/controls.svelte +5 -1
- package/dist/ui/dropdown/cmp.dropdown.svelte +187 -0
- package/dist/ui/dropdown/cmp.dropdown.svelte.d.ts +23 -0
- package/dist/ui/dropdown/dropdown-ignore.d.ts +6 -0
- package/dist/ui/dropdown/dropdown-ignore.js +11 -0
- package/dist/ui/dropdown/index.d.ts +3 -0
- package/dist/ui/dropdown/index.js +2 -0
- package/dist/ui/player/cmp.player-slider.svelte +16 -86
- package/dist/ui/player/wheel-peak-detector.d.ts +22 -0
- package/dist/ui/player/wheel-peak-detector.js +157 -0
- package/package.json +4 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const runningInBrowser: () => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const runningInBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
@@ -10,14 +10,16 @@
|
|
|
10
10
|
import { Utils } from '../../core/utils';
|
|
11
11
|
import { default as ShortVideosPlayerView } from '../../short-videos/short-videos-player/short-videos-player-view.svelte';
|
|
12
12
|
import { default as StreamPlayer } from '../../streams/stream-player/stream-player.svelte';
|
|
13
|
-
import {
|
|
13
|
+
import { Dropdown } from '../../ui/dropdown';
|
|
14
|
+
import { Icon, IconColor } from '../../ui/icon';
|
|
14
15
|
import { Loading } from '../../ui/loading';
|
|
15
16
|
import { MediaCenterLocalization } from './media-center-localization';
|
|
16
17
|
import { default as Overview } from './overview.svelte';
|
|
17
18
|
import { makeShortVideosProvider } from './short-video-resources-generator';
|
|
18
19
|
import { MediaCenterMode } from './types';
|
|
20
|
+
import IconLineHorizontal3 from '@fluentui/svg-icons/icons/line_horizontal_3_20_regular.svg?raw';
|
|
19
21
|
import IconTextColumnThree from '@fluentui/svg-icons/icons/text_column_three_20_regular.svg?raw';
|
|
20
|
-
import { onMount } from 'svelte';
|
|
22
|
+
import { onDestroy, onMount } from 'svelte';
|
|
21
23
|
import { fade } from 'svelte/transition';
|
|
22
24
|
let { dataProvider, playerProps, localization: localizationInit = 'en' } = $props();
|
|
23
25
|
const localization = $derived(new MediaCenterLocalization(localizationInit));
|
|
@@ -30,6 +32,7 @@ let headerHeight = $state(0);
|
|
|
30
32
|
let shortVideoProps = $state.raw(playerProps.type === MediaCenterMode.ShortVideos ? playerProps.props : null);
|
|
31
33
|
let streamProps = $state.raw(playerProps.type === MediaCenterMode.Stream ? playerProps.props : null);
|
|
32
34
|
let overviewData = $state.raw(null);
|
|
35
|
+
let scrollResizeObserver = null;
|
|
33
36
|
const categories = $derived.by(() => {
|
|
34
37
|
if (!mediaCenterConfig) {
|
|
35
38
|
return [];
|
|
@@ -58,7 +61,13 @@ onMount(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
58
61
|
mediaDataLoading = false;
|
|
59
62
|
}
|
|
60
63
|
}
|
|
64
|
+
scrollResizeObserver = new ResizeObserver(() => {
|
|
65
|
+
updateScrollShadows();
|
|
66
|
+
});
|
|
61
67
|
}));
|
|
68
|
+
onDestroy(() => {
|
|
69
|
+
scrollResizeObserver === null || scrollResizeObserver === void 0 ? void 0 : scrollResizeObserver.disconnect();
|
|
70
|
+
});
|
|
62
71
|
const selectCategory = (categoryId) => {
|
|
63
72
|
if (!dataProvider) {
|
|
64
73
|
return;
|
|
@@ -139,21 +148,19 @@ const uniqueById = (arr) => {
|
|
|
139
148
|
}
|
|
140
149
|
return res;
|
|
141
150
|
};
|
|
142
|
-
let wrapperRef = null;
|
|
143
151
|
let scrollRef = null;
|
|
144
152
|
let scrollHasLeft = $state(false);
|
|
145
153
|
let scrollHasRight = $state(false);
|
|
146
154
|
const mounted = (node, callback) => {
|
|
147
|
-
|
|
148
|
-
|
|
155
|
+
scrollResizeObserver === null || scrollResizeObserver === void 0 ? void 0 : scrollResizeObserver.observe(node);
|
|
156
|
+
const heightResizeObserver = new ResizeObserver(() => {
|
|
149
157
|
headerHeight = node.clientHeight;
|
|
150
158
|
callback({ height: headerHeight });
|
|
151
159
|
});
|
|
152
|
-
|
|
153
|
-
ro.observe(wrapperRef);
|
|
160
|
+
heightResizeObserver.observe(node);
|
|
154
161
|
return {
|
|
155
162
|
destroy: () => {
|
|
156
|
-
|
|
163
|
+
heightResizeObserver.disconnect();
|
|
157
164
|
}
|
|
158
165
|
};
|
|
159
166
|
};
|
|
@@ -167,9 +174,7 @@ const updateScrollShadows = () => {
|
|
|
167
174
|
};
|
|
168
175
|
const onScrollMounted = (node) => {
|
|
169
176
|
scrollRef = node;
|
|
170
|
-
|
|
171
|
-
updateScrollShadows();
|
|
172
|
-
});
|
|
177
|
+
scrollResizeObserver === null || scrollResizeObserver === void 0 ? void 0 : scrollResizeObserver.observe(node);
|
|
173
178
|
};
|
|
174
179
|
</script>
|
|
175
180
|
|
|
@@ -179,10 +184,6 @@ const onScrollMounted = (node) => {
|
|
|
179
184
|
{#snippet categoriesSwitcher(data: { maxItemsWidth: Number; onMounted: (data: { height: Number }) => void })}
|
|
180
185
|
<div class="media-center" use:mounted={data.onMounted}>
|
|
181
186
|
<div class="media-center__row" style={`max-width: ${data.maxItemsWidth}px;`}>
|
|
182
|
-
<button type="button" class="media-center__overview-button" onclick={toggleOverview}>
|
|
183
|
-
<Icon src={IconTextColumnThree} />
|
|
184
|
-
</button>
|
|
185
|
-
|
|
186
187
|
<div
|
|
187
188
|
class="media-center__scroll"
|
|
188
189
|
class:media-center__scroll--has-left={scrollHasLeft}
|
|
@@ -190,17 +191,40 @@ const onScrollMounted = (node) => {
|
|
|
190
191
|
class:media-center__scroll--has-both={scrollHasRight && scrollHasLeft}
|
|
191
192
|
use:onScrollMounted
|
|
192
193
|
onscroll={updateScrollShadows}>
|
|
193
|
-
<
|
|
194
|
+
<button type="button" class="media-center__overview-button" onclick={toggleOverview}>
|
|
195
|
+
<Icon src={IconTextColumnThree} />
|
|
196
|
+
</button>
|
|
197
|
+
{#each categories as category (category.id)}
|
|
198
|
+
<button
|
|
199
|
+
type="button"
|
|
200
|
+
class="media-center__category-button"
|
|
201
|
+
class:media-center__category-button--active={selectedCategoryId === category.id}
|
|
202
|
+
title={category.name}
|
|
203
|
+
onclick={() => selectCategory(category.id)}>{category.name}</button>
|
|
204
|
+
{/each}
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="media-center__overview-dropdown">
|
|
208
|
+
<Dropdown>
|
|
209
|
+
{#snippet trigger()}
|
|
210
|
+
<div class="media-center__overview-dropdown-trigger">
|
|
211
|
+
<Icon src={IconLineHorizontal3} color={IconColor.White}></Icon>
|
|
212
|
+
</div>
|
|
213
|
+
{/snippet}
|
|
214
|
+
<div class="media-center__overview-dropdown-content">
|
|
215
|
+
<button type="button" class="media-center__category-button media-center__category-button--dropdown" onclick={toggleOverview}>
|
|
216
|
+
{localization.overviewLabel}
|
|
217
|
+
</button>
|
|
194
218
|
{#each categories as category (category.id)}
|
|
195
219
|
<button
|
|
196
220
|
type="button"
|
|
197
|
-
class="media-center__category-button"
|
|
221
|
+
class="media-center__category-button media-center__category-button--dropdown"
|
|
198
222
|
class:media-center__category-button--active={selectedCategoryId === category.id}
|
|
199
223
|
title={category.name}
|
|
200
224
|
onclick={() => selectCategory(category.id)}>{category.name}</button>
|
|
201
225
|
{/each}
|
|
202
226
|
</div>
|
|
203
|
-
</
|
|
227
|
+
</Dropdown>
|
|
204
228
|
</div>
|
|
205
229
|
</div>
|
|
206
230
|
{/snippet}
|
|
@@ -246,12 +270,6 @@ const onScrollMounted = (node) => {
|
|
|
246
270
|
right: 0;
|
|
247
271
|
z-index: 1;
|
|
248
272
|
pointer-events: none;
|
|
249
|
-
/* Set 'container-type: inline-size;' to reference container*/
|
|
250
|
-
}
|
|
251
|
-
@container (width < 576px) {
|
|
252
|
-
.media-center {
|
|
253
|
-
padding-left: 1.25rem;
|
|
254
|
-
}
|
|
255
273
|
}
|
|
256
274
|
.media-center__row {
|
|
257
275
|
pointer-events: auto;
|
|
@@ -259,18 +277,29 @@ const onScrollMounted = (node) => {
|
|
|
259
277
|
width: 100%;
|
|
260
278
|
display: flex;
|
|
261
279
|
align-items: center;
|
|
280
|
+
justify-content: center;
|
|
262
281
|
gap: 0.75rem;
|
|
282
|
+
/* Set 'container-type: inline-size;' to reference container*/
|
|
283
|
+
}
|
|
284
|
+
@container (width < 576px) {
|
|
285
|
+
.media-center__row {
|
|
286
|
+
display: none;
|
|
287
|
+
}
|
|
263
288
|
}
|
|
264
289
|
.media-center__scroll {
|
|
265
290
|
pointer-events: auto;
|
|
266
291
|
position: relative;
|
|
267
292
|
flex: 1 1 auto;
|
|
293
|
+
max-width: max-content;
|
|
268
294
|
min-width: 0;
|
|
269
295
|
overflow-x: auto;
|
|
270
296
|
overflow-y: hidden;
|
|
271
297
|
-webkit-overflow-scrolling: touch;
|
|
272
298
|
scrollbar-width: none;
|
|
273
299
|
display: flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
gap: 0.75rem;
|
|
302
|
+
flex-wrap: nowrap;
|
|
274
303
|
mask-image: none;
|
|
275
304
|
}
|
|
276
305
|
.media-center__scroll::-webkit-scrollbar {
|
|
@@ -285,14 +314,6 @@ const onScrollMounted = (node) => {
|
|
|
285
314
|
.media-center__scroll--has-both {
|
|
286
315
|
mask-image: linear-gradient(to right, rgba(0, 0, 0, 0) 0, rgb(0, 0, 0) 32px, rgb(0, 0, 0) calc(100% - 32px), rgba(0, 0, 0, 0) 100%);
|
|
287
316
|
}
|
|
288
|
-
.media-center__items {
|
|
289
|
-
display: inline-flex;
|
|
290
|
-
align-items: center;
|
|
291
|
-
gap: 0.75rem;
|
|
292
|
-
flex-wrap: nowrap;
|
|
293
|
-
pointer-events: none;
|
|
294
|
-
padding-inline: 0.25rem;
|
|
295
|
-
}
|
|
296
317
|
.media-center__overview-button {
|
|
297
318
|
pointer-events: auto;
|
|
298
319
|
padding: 0.375rem 0.75rem;
|
|
@@ -329,8 +350,82 @@ const onScrollMounted = (node) => {
|
|
|
329
350
|
background-color: rgba(255, 255, 255, 0.9);
|
|
330
351
|
color: #000000;
|
|
331
352
|
}
|
|
353
|
+
.media-center__category-button--dropdown {
|
|
354
|
+
width: max-content;
|
|
355
|
+
}
|
|
356
|
+
.media-center__overview-dropdown {
|
|
357
|
+
display: none;
|
|
358
|
+
pointer-events: auto;
|
|
359
|
+
/* Set 'container-type: inline-size;' to reference container*/
|
|
360
|
+
}
|
|
361
|
+
@container (width < 576px) {
|
|
362
|
+
.media-center__overview-dropdown {
|
|
363
|
+
display: block;
|
|
364
|
+
position: absolute;
|
|
365
|
+
top: 0.9375rem;
|
|
366
|
+
left: 0.625rem;
|
|
367
|
+
z-index: 1;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
.media-center__overview-dropdown-trigger {
|
|
371
|
+
width: 3rem;
|
|
372
|
+
min-width: 3rem;
|
|
373
|
+
max-width: 3rem;
|
|
374
|
+
height: 3rem;
|
|
375
|
+
min-height: 3rem;
|
|
376
|
+
max-height: 3rem;
|
|
377
|
+
display: flex;
|
|
378
|
+
justify-content: center;
|
|
379
|
+
align-items: center;
|
|
380
|
+
background-color: rgba(0, 0, 0, 0.6);
|
|
381
|
+
border: 1px solid #1c1c1c;
|
|
382
|
+
border-radius: 50%;
|
|
383
|
+
text-align: center;
|
|
384
|
+
--icon--color: #ffffff;
|
|
385
|
+
--icon--size: 1.75rem;
|
|
386
|
+
}
|
|
387
|
+
.media-center__overview-dropdown-trigger:hover {
|
|
388
|
+
background-color: rgba(0, 0, 0, 0.9);
|
|
389
|
+
transition: background-color 0.5s;
|
|
390
|
+
}
|
|
391
|
+
.media-center__overview-dropdown-content {
|
|
392
|
+
display: flex;
|
|
393
|
+
flex-direction: column;
|
|
394
|
+
gap: 0.625rem;
|
|
395
|
+
}
|
|
332
396
|
|
|
333
397
|
.media-center-overview {
|
|
334
398
|
position: absolute;
|
|
335
399
|
inset: 0;
|
|
400
|
+
container-type: inline-size;
|
|
401
|
+
overflow-y: auto;
|
|
402
|
+
scrollbar-color: transparent transparent;
|
|
403
|
+
scrollbar-width: thin;
|
|
404
|
+
}
|
|
405
|
+
.media-center-overview::-webkit-scrollbar {
|
|
406
|
+
width: 3px;
|
|
407
|
+
height: 3px;
|
|
408
|
+
background: var(--custom-scrollbar-background, transparent);
|
|
409
|
+
visibility: hidden;
|
|
410
|
+
}
|
|
411
|
+
.media-center-overview::-webkit-scrollbar-thumb {
|
|
412
|
+
background: transparent;
|
|
413
|
+
}
|
|
414
|
+
.media-center-overview:hover {
|
|
415
|
+
scrollbar-color: var(--custom-scrollbar-color, #7d7d7d) var(--custom-scrollbar-background, transparent);
|
|
416
|
+
scrollbar-width: thin;
|
|
417
|
+
}
|
|
418
|
+
.media-center-overview:hover::-webkit-scrollbar {
|
|
419
|
+
width: 3px;
|
|
420
|
+
height: 3px;
|
|
421
|
+
background: var(--custom-scrollbar-background, transparent);
|
|
422
|
+
visibility: hidden;
|
|
423
|
+
}
|
|
424
|
+
.media-center-overview:hover::-webkit-scrollbar-thumb {
|
|
425
|
+
background: var(--custom-scrollbar-color, #7d7d7d);
|
|
426
|
+
}
|
|
427
|
+
@media (max-width: 576px) {
|
|
428
|
+
.media-center-overview {
|
|
429
|
+
top: 5rem !important;
|
|
430
|
+
}
|
|
336
431
|
}</style>
|
|
@@ -2,10 +2,12 @@ import { type Locale } from '../../core/locale';
|
|
|
2
2
|
import type { IProductCardLocalization } from '../../products/product-card/product-card-localization';
|
|
3
3
|
export interface IMediaCenterLocalization {
|
|
4
4
|
shortVideosSectionTitle?: string;
|
|
5
|
+
overviewLabel?: string;
|
|
5
6
|
productLocalization?: IProductCardLocalization | Locale;
|
|
6
7
|
}
|
|
7
8
|
export declare class MediaCenterLocalization {
|
|
8
9
|
shortVideosSectionTitle: string;
|
|
10
|
+
overviewLabel: string;
|
|
9
11
|
productLocalization: IProductCardLocalization | Locale;
|
|
10
12
|
constructor(init: IMediaCenterLocalization | Locale);
|
|
11
13
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { isLocale } from '../../core/locale';
|
|
2
2
|
export class MediaCenterLocalization {
|
|
3
3
|
shortVideosSectionTitle;
|
|
4
|
+
overviewLabel;
|
|
4
5
|
productLocalization;
|
|
5
6
|
constructor(init) {
|
|
6
7
|
this.shortVideosSectionTitle = isLocale(init) ? loc.shortVideosSectionTitle[init] : init.shortVideosSectionTitle || loc.shortVideosSectionTitle.en;
|
|
8
|
+
this.overviewLabel = isLocale(init) ? loc.overviewLabel[init] : init.overviewLabel || loc.overviewLabel.en;
|
|
7
9
|
this.productLocalization = isLocale(init) ? init : init.productLocalization || 'en';
|
|
8
10
|
}
|
|
9
11
|
}
|
|
@@ -11,5 +13,9 @@ const loc = {
|
|
|
11
13
|
shortVideosSectionTitle: {
|
|
12
14
|
en: 'Popular Short Videos',
|
|
13
15
|
no: 'Populære korte videoer'
|
|
16
|
+
},
|
|
17
|
+
overviewLabel: {
|
|
18
|
+
en: 'Overview',
|
|
19
|
+
no: 'Oversikt'
|
|
14
20
|
}
|
|
15
21
|
};
|
|
@@ -81,6 +81,12 @@ const shortVideoSectionItems = $derived.by(() => {
|
|
|
81
81
|
display: flex;
|
|
82
82
|
justify-content: center;
|
|
83
83
|
padding: 1.25rem 1.875rem;
|
|
84
|
+
/* Set 'container-type: inline-size;' to reference container*/
|
|
85
|
+
}
|
|
86
|
+
@container (width < 576px) {
|
|
87
|
+
.media-center-overview {
|
|
88
|
+
padding: 0.625rem 0.9375rem;
|
|
89
|
+
}
|
|
84
90
|
}
|
|
85
91
|
.media-center-overview__content {
|
|
86
92
|
width: 100%;
|
|
@@ -121,20 +127,23 @@ const shortVideoSectionItems = $derived.by(() => {
|
|
|
121
127
|
display: grid;
|
|
122
128
|
gap: 2rem 1.25rem;
|
|
123
129
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
130
|
+
/* Set 'container-type: inline-size;' to reference container*/
|
|
131
|
+
/* Set 'container-type: inline-size;' to reference container*/
|
|
132
|
+
/* Set 'container-type: inline-size;' to reference container*/
|
|
124
133
|
}
|
|
125
|
-
@
|
|
134
|
+
@container (width < 992px) {
|
|
126
135
|
.media-center-overview__section-content {
|
|
127
136
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
|
-
@
|
|
139
|
+
@container (width < 768px) {
|
|
131
140
|
.media-center-overview__section-content {
|
|
132
141
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
133
142
|
}
|
|
134
143
|
}
|
|
135
|
-
@
|
|
144
|
+
@container (width < 576px) {
|
|
136
145
|
.media-center-overview__section-content {
|
|
137
|
-
grid-template-columns:
|
|
146
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
149
|
.media-center-overview__card-wrapper {
|
|
@@ -13,7 +13,7 @@ const changeShowAttachments = () => {
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
{#if uiManager.viewInitialized && !uiManager.showShortVideoOverlay}
|
|
16
|
-
<div class="short-videos-player-controls">
|
|
16
|
+
<div class="short-videos-player-controls" class:short-videos-player-controls--with-logo={!!playerLogo}>
|
|
17
17
|
<div class="short-videos-player-controls__left">
|
|
18
18
|
{#if shortVideo}
|
|
19
19
|
<div class="short-videos-player-controls__short-video-hub">
|
|
@@ -86,6 +86,9 @@ const changeShowAttachments = () => {
|
|
|
86
86
|
padding: var(--short-videos-player--controls--padding);
|
|
87
87
|
container-type: inline-size;
|
|
88
88
|
}
|
|
89
|
+
.short-videos-player-controls--with-logo {
|
|
90
|
+
padding-top: 0;
|
|
91
|
+
}
|
|
89
92
|
.short-videos-player-controls__left {
|
|
90
93
|
display: flex;
|
|
91
94
|
flex-direction: column;
|
|
@@ -145,6 +148,7 @@ const changeShowAttachments = () => {
|
|
|
145
148
|
height: var(--short-videos-player--media-center-header--height);
|
|
146
149
|
min-height: var(--short-videos-player--media-center-header--height);
|
|
147
150
|
max-height: var(--short-videos-player--media-center-header--height);
|
|
151
|
+
min-height: 4.375rem;
|
|
148
152
|
display: flex;
|
|
149
153
|
justify-content: center;
|
|
150
154
|
align-items: center;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<script lang="ts">import { runningInBrowser } from '../../core/browser';
|
|
2
|
+
import { Icon } from '../icon';
|
|
3
|
+
import { isIgnored } from './dropdown-ignore';
|
|
4
|
+
import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_20_regular.svg?raw';
|
|
5
|
+
import { createPopper } from '@popperjs/core';
|
|
6
|
+
import { onDestroy } from 'svelte';
|
|
7
|
+
let { position = 'bottom-start', disabled = false, keepDropdownOpen = false, fixedPosition = false, offset = 8, boundaryMargin = 8, on, children, trigger, isOpenRequested } = $props();
|
|
8
|
+
$effect(() => {
|
|
9
|
+
var _a;
|
|
10
|
+
(_a = on === null || on === void 0 ? void 0 : on.mounted) === null || _a === void 0 ? void 0 : _a.call(on, {
|
|
11
|
+
toggleOpen: (value) => {
|
|
12
|
+
if (value === undefined) {
|
|
13
|
+
if (opened) {
|
|
14
|
+
close();
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
open();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
if (!value) {
|
|
22
|
+
close();
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
open();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
const id = Math.random();
|
|
32
|
+
let opened = $state(false);
|
|
33
|
+
$effect(() => {
|
|
34
|
+
var _a, _b;
|
|
35
|
+
if (opened) {
|
|
36
|
+
(_a = on === null || on === void 0 ? void 0 : on.opened) === null || _a === void 0 ? void 0 : _a.call(on);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
(_b = on === null || on === void 0 ? void 0 : on.closed) === null || _b === void 0 ? void 0 : _b.call(on);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
$effect(() => {
|
|
43
|
+
popper === null || popper === void 0 ? void 0 : popper.setOptions({ placement: position });
|
|
44
|
+
});
|
|
45
|
+
$effect(() => {
|
|
46
|
+
if (isOpenRequested) {
|
|
47
|
+
open();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
let triggerRef = $state(null);
|
|
51
|
+
let dropdownRef;
|
|
52
|
+
let popper;
|
|
53
|
+
onDestroy(() => {
|
|
54
|
+
removeWindowClickListener();
|
|
55
|
+
});
|
|
56
|
+
const open = () => {
|
|
57
|
+
opened = true;
|
|
58
|
+
window.addEventListener('click', close);
|
|
59
|
+
};
|
|
60
|
+
const close = () => {
|
|
61
|
+
opened = false;
|
|
62
|
+
removeWindowClickListener();
|
|
63
|
+
};
|
|
64
|
+
const removeWindowClickListener = () => {
|
|
65
|
+
if (runningInBrowser()) {
|
|
66
|
+
window.removeEventListener('click', close);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const handleClick = (event) => {
|
|
70
|
+
event.stopPropagation();
|
|
71
|
+
if (disabled) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!opened) {
|
|
75
|
+
open();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const checkCanClose = (node) => {
|
|
79
|
+
if (keepDropdownOpen) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
while (node && node !== dropdownRef) {
|
|
83
|
+
if (isIgnored(node) || node.classList.contains('flatpickr-calendar')) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
node = node.parentElement;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
};
|
|
90
|
+
if (checkCanClose(event.target)) {
|
|
91
|
+
close();
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const initPopper = (node, _triggerEl) => {
|
|
95
|
+
popper = createPopper(_triggerEl, node, {
|
|
96
|
+
placement: position,
|
|
97
|
+
strategy: fixedPosition ? 'fixed' : 'absolute',
|
|
98
|
+
modifiers: [
|
|
99
|
+
{
|
|
100
|
+
name: 'offset',
|
|
101
|
+
options: {
|
|
102
|
+
offset: () => {
|
|
103
|
+
return [0, offset];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{ name: 'eventListeners', enabled: opened },
|
|
108
|
+
{ name: 'flip' },
|
|
109
|
+
{
|
|
110
|
+
name: 'preventOverflow',
|
|
111
|
+
options: {
|
|
112
|
+
boundary: document.body,
|
|
113
|
+
padding: boundaryMargin
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
update(_triggerEl) {
|
|
120
|
+
popper.state.elements.reference = _triggerEl;
|
|
121
|
+
popper.update();
|
|
122
|
+
},
|
|
123
|
+
destroy() {
|
|
124
|
+
popper.destroy();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
</script>
|
|
129
|
+
|
|
130
|
+
<div class="dropdown" class:dropdown--disabled={disabled} onclick={handleClick} onkeydown={() => ({})} bind:this={dropdownRef} role="none">
|
|
131
|
+
<button type="button" class="dropdown__trigger" bind:this={triggerRef}>
|
|
132
|
+
{#if trigger}
|
|
133
|
+
{@render trigger()}
|
|
134
|
+
{:else}
|
|
135
|
+
<Icon src={IconChevronDown} />
|
|
136
|
+
{/if}
|
|
137
|
+
</button>
|
|
138
|
+
{#if opened}
|
|
139
|
+
<div use:initPopper={triggerRef} class="dropdown__content" role="tooltip" tabindex="-1">
|
|
140
|
+
{@render children()}
|
|
141
|
+
</div>
|
|
142
|
+
{/if}
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<style>@keyframes fadeIn {
|
|
146
|
+
0% {
|
|
147
|
+
opacity: 1;
|
|
148
|
+
}
|
|
149
|
+
50% {
|
|
150
|
+
opacity: 0.4;
|
|
151
|
+
}
|
|
152
|
+
100% {
|
|
153
|
+
opacity: 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
.dropdown {
|
|
157
|
+
--_dropdown--width: var(--dropdown--width, auto);
|
|
158
|
+
--_dropdown--content--background-color: var(--dropdown--content--background-color, transparent);
|
|
159
|
+
--_dropdown--content--box-shadow: var(--dropdown--content--box-shadow, none);
|
|
160
|
+
height: 100%;
|
|
161
|
+
width: var(--_dropdown--width);
|
|
162
|
+
-webkit-user-drag: none;
|
|
163
|
+
user-select: none;
|
|
164
|
+
}
|
|
165
|
+
.dropdown :global([contenteditable]) {
|
|
166
|
+
user-select: text;
|
|
167
|
+
}
|
|
168
|
+
.dropdown__trigger {
|
|
169
|
+
height: 100%;
|
|
170
|
+
width: 100%;
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
line-height: 1;
|
|
174
|
+
}
|
|
175
|
+
.dropdown--disabled {
|
|
176
|
+
opacity: 0.5;
|
|
177
|
+
pointer-events: none;
|
|
178
|
+
}
|
|
179
|
+
.dropdown__content {
|
|
180
|
+
box-shadow: var(--_dropdown--content--box-shadow);
|
|
181
|
+
background: var(--_dropdown--content--background-color);
|
|
182
|
+
width: max-content;
|
|
183
|
+
z-index: 999;
|
|
184
|
+
}
|
|
185
|
+
.dropdown :global([data-popper-escaped]) {
|
|
186
|
+
visibility: hidden !important;
|
|
187
|
+
}</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DropdownPosition } from './index';
|
|
2
|
+
import { type Snippet } from 'svelte';
|
|
3
|
+
type Props = {
|
|
4
|
+
position?: DropdownPosition;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
keepDropdownOpen?: boolean;
|
|
7
|
+
fixedPosition?: boolean;
|
|
8
|
+
offset?: number;
|
|
9
|
+
boundaryMargin?: number;
|
|
10
|
+
on?: {
|
|
11
|
+
opened?: () => void;
|
|
12
|
+
closed?: () => void;
|
|
13
|
+
mounted?: (callbacks: {
|
|
14
|
+
toggleOpen: (value?: boolean) => void;
|
|
15
|
+
}) => void;
|
|
16
|
+
};
|
|
17
|
+
children: Snippet;
|
|
18
|
+
trigger?: Snippet;
|
|
19
|
+
isOpenRequested?: boolean;
|
|
20
|
+
};
|
|
21
|
+
declare const Cmp: import("svelte").Component<Props, {}, "">;
|
|
22
|
+
type Cmp = ReturnType<typeof Cmp>;
|
|
23
|
+
export default Cmp;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** @type {import('svelte/action').Action} */
|
|
2
|
+
export declare const dropdownIgnore: (node: HTMLElement, value?: boolean) => {
|
|
3
|
+
destroy(): void;
|
|
4
|
+
};
|
|
5
|
+
export declare const ignoreAttribute = "dropdownIgnore";
|
|
6
|
+
export declare const isIgnored: (node: HTMLElement) => boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** @type {import('svelte/action').Action} */
|
|
2
|
+
export const dropdownIgnore = (node, value = true) => {
|
|
3
|
+
node.dataset[ignoreAttribute] = value.toString();
|
|
4
|
+
return {
|
|
5
|
+
destroy() {
|
|
6
|
+
// the node has been removed from the DOM
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export const ignoreAttribute = 'dropdownIgnore';
|
|
11
|
+
export const isIgnored = (node) => node.dataset[ignoreAttribute] === 'true';
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
<script lang="ts">import {
|
|
1
|
+
<script lang="ts">import { createWheelPeakDetector } from './wheel-peak-detector';
|
|
2
2
|
import { onDestroy, onMount, untrack } from 'svelte';
|
|
3
|
-
const MOUSE_DETECTION_THRESHOLD_MS = 100;
|
|
4
3
|
let { buffer, on, children } = $props();
|
|
5
4
|
let slidesRef;
|
|
6
5
|
let sliderHeight = $state(0);
|
|
@@ -90,89 +89,6 @@ onMount(() => {
|
|
|
90
89
|
}
|
|
91
90
|
reset();
|
|
92
91
|
});
|
|
93
|
-
let waveDetector = {
|
|
94
|
-
events: [],
|
|
95
|
-
direction: 0,
|
|
96
|
-
peakReached: false,
|
|
97
|
-
lastPeak: 0,
|
|
98
|
-
waveStartTime: 0
|
|
99
|
-
};
|
|
100
|
-
let isAnimatingWheel = false;
|
|
101
|
-
const triggerAnimation = (direction) => {
|
|
102
|
-
isAnimatingWheel = true;
|
|
103
|
-
if (direction > 0 && buffer.canLoadNext) {
|
|
104
|
-
buffer.loadNext();
|
|
105
|
-
}
|
|
106
|
-
else if (direction < 0 && buffer.canLoadPrevious) {
|
|
107
|
-
buffer.loadPrevious();
|
|
108
|
-
}
|
|
109
|
-
setTimeout(() => {
|
|
110
|
-
isAnimatingWheel = false;
|
|
111
|
-
}, buffer.animationDuration + 100);
|
|
112
|
-
};
|
|
113
|
-
slidesRef.addEventListener('wheel', (e) => {
|
|
114
|
-
e.preventDefault();
|
|
115
|
-
const checkCanHandleWheel = (node) => {
|
|
116
|
-
while (node && node !== slidesRef) {
|
|
117
|
-
if (isScrollingPrevented(node)) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
node = node.parentElement;
|
|
121
|
-
}
|
|
122
|
-
return true;
|
|
123
|
-
};
|
|
124
|
-
if (!checkCanHandleWheel(e.target)) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
const now = Date.now();
|
|
128
|
-
const absDelta = Math.abs(e.deltaY);
|
|
129
|
-
const direction = Math.sign(e.deltaY);
|
|
130
|
-
// Mouse - large stable values, trigger immediately
|
|
131
|
-
if (absDelta >= 10 && !isAnimatingWheel) {
|
|
132
|
-
const lastEvent = waveDetector.events[waveDetector.events.length - 1];
|
|
133
|
-
const timeSinceLastEvent = lastEvent ? now - lastEvent.time : 1000;
|
|
134
|
-
// If enough time has passed since the last event - it's a mouse
|
|
135
|
-
if (timeSinceLastEvent > MOUSE_DETECTION_THRESHOLD_MS) {
|
|
136
|
-
triggerAnimation(direction);
|
|
137
|
-
waveDetector = { events: [], direction: 0, peakReached: false, lastPeak: 0, waveStartTime: 0 };
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
// Touchpad - small, variable values, need to analyze the wave
|
|
142
|
-
// New wave if: direction changed or a lot of time has passed
|
|
143
|
-
if (direction !== waveDetector.direction || (waveDetector.waveStartTime && now - waveDetector.waveStartTime > 1500)) {
|
|
144
|
-
// Finalize the previous wave if it existed
|
|
145
|
-
if (waveDetector.peakReached && !isAnimatingWheel) {
|
|
146
|
-
triggerAnimation(waveDetector.direction);
|
|
147
|
-
}
|
|
148
|
-
// Start a new wave
|
|
149
|
-
waveDetector = {
|
|
150
|
-
events: [{ delta: absDelta, time: now }],
|
|
151
|
-
direction: direction,
|
|
152
|
-
peakReached: false,
|
|
153
|
-
lastPeak: absDelta,
|
|
154
|
-
waveStartTime: now
|
|
155
|
-
};
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
// Continue the current wave
|
|
159
|
-
waveDetector.events.push({ delta: absDelta, time: now });
|
|
160
|
-
// Determine the phase of the wave
|
|
161
|
-
if (absDelta > waveDetector.lastPeak) {
|
|
162
|
-
// The wave is growing
|
|
163
|
-
waveDetector.lastPeak = absDelta;
|
|
164
|
-
waveDetector.peakReached = absDelta >= 5; // The minimum peak to consider a valid wave
|
|
165
|
-
}
|
|
166
|
-
else if (absDelta < waveDetector.lastPeak * 0.5) {
|
|
167
|
-
// The wave has dropped significantly - consider the gesture complete
|
|
168
|
-
if (waveDetector.peakReached && !isAnimatingWheel) {
|
|
169
|
-
triggerAnimation(waveDetector.direction);
|
|
170
|
-
waveDetector = { events: [], direction: 0, peakReached: false, lastPeak: 0, waveStartTime: 0 };
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// Cleanup old events
|
|
174
|
-
waveDetector.events = waveDetector.events.filter((evt) => now - evt.time < 2000);
|
|
175
|
-
});
|
|
176
92
|
slidesRef.addEventListener('transitionend', (e) => {
|
|
177
93
|
if (e.target !== slidesRef) {
|
|
178
94
|
return;
|
|
@@ -199,10 +115,24 @@ const styles = $derived.by(() => {
|
|
|
199
115
|
];
|
|
200
116
|
return values.join(';');
|
|
201
117
|
});
|
|
118
|
+
const peakDetectorCallbacks = {
|
|
119
|
+
canLoadNext: () => buffer.canLoadNext,
|
|
120
|
+
canLoadPrevious: () => buffer.canLoadPrevious,
|
|
121
|
+
onTrigger: (direction) => {
|
|
122
|
+
// direction: 1 -> next, -1 -> previous
|
|
123
|
+
if (direction > 0) {
|
|
124
|
+
buffer.loadNext();
|
|
125
|
+
}
|
|
126
|
+
else if (direction < 0) {
|
|
127
|
+
buffer.loadPrevious();
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
getAnimationDurationMs: () => buffer.animationDuration
|
|
131
|
+
};
|
|
202
132
|
</script>
|
|
203
133
|
|
|
204
134
|
<div class="player-slider">
|
|
205
|
-
<div class="player-slider__slides" bind:this={slidesRef} style={styles}>
|
|
135
|
+
<div class="player-slider__slides" bind:this={slidesRef} use:createWheelPeakDetector={{ cbs: peakDetectorCallbacks }} style={styles}>
|
|
206
136
|
{#each buffer.loaded as item, index (item)}
|
|
207
137
|
<div class="player-slider__slide">
|
|
208
138
|
{#if index >= activeIndex - 1 && index <= activeIndex + 1}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type PeakDetectorConfig = {
|
|
2
|
+
mouseGapMs?: number;
|
|
3
|
+
mouseDeltaThreshold?: number;
|
|
4
|
+
interEventTimeout?: number;
|
|
5
|
+
minPeakToTrigger?: number;
|
|
6
|
+
waveMaxAgeMs?: number;
|
|
7
|
+
directionFlipEndsWave?: boolean;
|
|
8
|
+
animationCooldownMs?: number;
|
|
9
|
+
directionChangeMinAbsDelta?: number;
|
|
10
|
+
};
|
|
11
|
+
export type PeakDetectorCallbacks = {
|
|
12
|
+
canLoadNext: () => boolean;
|
|
13
|
+
canLoadPrevious: () => boolean;
|
|
14
|
+
onTrigger: (direction: number) => void;
|
|
15
|
+
getAnimationDurationMs: () => number;
|
|
16
|
+
};
|
|
17
|
+
export declare const createWheelPeakDetector: (target: HTMLElement, params: {
|
|
18
|
+
cbs: PeakDetectorCallbacks;
|
|
19
|
+
cfg?: PeakDetectorConfig;
|
|
20
|
+
}) => {
|
|
21
|
+
destroy(): void;
|
|
22
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { isScrollingPrevented } from './prevent-slider-scroll';
|
|
2
|
+
export const createWheelPeakDetector = (target, params) => {
|
|
3
|
+
const { cbs, cfg } = params;
|
|
4
|
+
// Defaults tuned for smooth touchpads; adjust in caller if needed.
|
|
5
|
+
const config = {
|
|
6
|
+
mouseGapMs: cfg?.mouseGapMs ?? 120,
|
|
7
|
+
mouseDeltaThreshold: cfg?.mouseDeltaThreshold ?? 12,
|
|
8
|
+
interEventTimeout: cfg?.interEventTimeout ?? 180,
|
|
9
|
+
minPeakToTrigger: cfg?.minPeakToTrigger ?? 6,
|
|
10
|
+
waveMaxAgeMs: cfg?.waveMaxAgeMs ?? 1800,
|
|
11
|
+
directionFlipEndsWave: cfg?.directionFlipEndsWave ?? true,
|
|
12
|
+
animationCooldownMs: cfg?.animationCooldownMs ?? 100, // extra after CSS transition
|
|
13
|
+
directionChangeMinAbsDelta: cfg?.directionChangeMinAbsDelta ?? 2 // ignore tiny opposite spikes
|
|
14
|
+
};
|
|
15
|
+
let wave = null;
|
|
16
|
+
let lastWheelTime = 0;
|
|
17
|
+
let lastDelta = 0;
|
|
18
|
+
let isAnimating = false;
|
|
19
|
+
let cooldownTimer = null;
|
|
20
|
+
const clearWave = () => {
|
|
21
|
+
wave = null;
|
|
22
|
+
};
|
|
23
|
+
const setAnimatingWithCooldown = () => {
|
|
24
|
+
isAnimating = true;
|
|
25
|
+
const total = cbs.getAnimationDurationMs() + config.animationCooldownMs;
|
|
26
|
+
if (cooldownTimer) {
|
|
27
|
+
clearTimeout(cooldownTimer);
|
|
28
|
+
}
|
|
29
|
+
cooldownTimer = window.setTimeout(() => {
|
|
30
|
+
isAnimating = false;
|
|
31
|
+
cooldownTimer = null;
|
|
32
|
+
}, total);
|
|
33
|
+
};
|
|
34
|
+
const trigger = (direction) => {
|
|
35
|
+
if (direction > 0 && !cbs.canLoadNext()) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (direction < 0 && !cbs.canLoadPrevious()) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
setAnimatingWithCooldown();
|
|
42
|
+
cbs.onTrigger(direction);
|
|
43
|
+
};
|
|
44
|
+
const canHandle = (node) => {
|
|
45
|
+
while (node && node !== target) {
|
|
46
|
+
if (isScrollingPrevented(node)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
node = node.parentElement;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
};
|
|
53
|
+
const finalizeWaveWithTrigger = () => {
|
|
54
|
+
if (wave && !isAnimating) {
|
|
55
|
+
if (wave.maxAbsDelta >= config.minPeakToTrigger) {
|
|
56
|
+
trigger(wave.direction);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
clearWave();
|
|
60
|
+
};
|
|
61
|
+
const finalizeWaveSilently = () => {
|
|
62
|
+
// Finish the wave without triggering (used on direction flip to avoid ghost slide)
|
|
63
|
+
clearWave();
|
|
64
|
+
};
|
|
65
|
+
const onWheel = (e) => {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
if (!canHandle(e.target)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const dy = e.deltaY;
|
|
72
|
+
const absDelta = Math.abs(dy);
|
|
73
|
+
console.warn(absDelta);
|
|
74
|
+
const dir = Math.sign(dy);
|
|
75
|
+
// Mouse branch: big, sparse deltas
|
|
76
|
+
const timeSinceLast = now - lastWheelTime;
|
|
77
|
+
if (absDelta >= config.mouseDeltaThreshold && timeSinceLast > config.mouseGapMs && !isAnimating) {
|
|
78
|
+
trigger(dir);
|
|
79
|
+
clearWave();
|
|
80
|
+
lastWheelTime = now;
|
|
81
|
+
lastDelta = dy;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Determine direction change with hysteresis to avoid micro flips
|
|
85
|
+
const directionChanged = !!wave && dir !== 0 && dir !== wave.direction && absDelta >= config.directionChangeMinAbsDelta;
|
|
86
|
+
const inactiveTooLong = wave && now - wave?.lastEventTime > config.interEventTimeout;
|
|
87
|
+
const waveTooOld = wave && now - wave?.startTime > config.waveMaxAgeMs;
|
|
88
|
+
// If direction flip should end the wave, do it silently (no trigger of the old direction)
|
|
89
|
+
if (wave && config.directionFlipEndsWave && directionChanged) {
|
|
90
|
+
finalizeWaveSilently();
|
|
91
|
+
// Start a new wave immediately in the new direction
|
|
92
|
+
wave = {
|
|
93
|
+
startTime: now,
|
|
94
|
+
lastEventTime: now,
|
|
95
|
+
direction: dir,
|
|
96
|
+
maxAbsDelta: absDelta,
|
|
97
|
+
sumAbsDelta: absDelta,
|
|
98
|
+
events: 1
|
|
99
|
+
};
|
|
100
|
+
lastWheelTime = now;
|
|
101
|
+
lastDelta = dy;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Finalize by inactivity or age (these DO trigger)
|
|
105
|
+
if (inactiveTooLong || waveTooOld) {
|
|
106
|
+
finalizeWaveWithTrigger();
|
|
107
|
+
}
|
|
108
|
+
// Start condition for new wave:
|
|
109
|
+
// - no wave
|
|
110
|
+
// - current amplitude grows vs previous
|
|
111
|
+
// - long gap since last wheel
|
|
112
|
+
const growing = absDelta > Math.abs(lastDelta);
|
|
113
|
+
const longGap = timeSinceLast > config.interEventTimeout;
|
|
114
|
+
if (!wave || growing || longGap) {
|
|
115
|
+
// Opportunistic finalize only if not animating and not caused by a flip
|
|
116
|
+
// (flip branch handled earlier and is silent)
|
|
117
|
+
if (wave && !isAnimating) {
|
|
118
|
+
if (wave.maxAbsDelta >= config.minPeakToTrigger) {
|
|
119
|
+
trigger(wave.direction);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
wave = {
|
|
123
|
+
startTime: now,
|
|
124
|
+
lastEventTime: now,
|
|
125
|
+
direction: dir || (wave?.direction ?? 0),
|
|
126
|
+
maxAbsDelta: absDelta,
|
|
127
|
+
sumAbsDelta: absDelta,
|
|
128
|
+
events: 1
|
|
129
|
+
};
|
|
130
|
+
lastWheelTime = now;
|
|
131
|
+
lastDelta = dy;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Continue current wave
|
|
135
|
+
if (wave) {
|
|
136
|
+
wave.lastEventTime = now;
|
|
137
|
+
wave.events += 1;
|
|
138
|
+
wave.sumAbsDelta += absDelta;
|
|
139
|
+
if (absDelta > wave.maxAbsDelta) {
|
|
140
|
+
wave.maxAbsDelta = absDelta; // peak update
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
lastWheelTime = now;
|
|
144
|
+
lastDelta = dy;
|
|
145
|
+
};
|
|
146
|
+
target.addEventListener('wheel', onWheel, { passive: false });
|
|
147
|
+
return {
|
|
148
|
+
destroy() {
|
|
149
|
+
target.removeEventListener('wheel', onWheel);
|
|
150
|
+
if (cooldownTimer) {
|
|
151
|
+
clearTimeout(cooldownTimer);
|
|
152
|
+
cooldownTimer = null;
|
|
153
|
+
}
|
|
154
|
+
wave = null;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamscloud/embeddable",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.2",
|
|
4
4
|
"author": "StreamsCloud",
|
|
5
5
|
"repository": "https://github.com/StreamsCloud/streamscloud-frontend-packages.git",
|
|
6
6
|
"type": "module",
|
|
@@ -152,5 +152,8 @@
|
|
|
152
152
|
"typescript-eslint": "^8.32.1",
|
|
153
153
|
"vite": "^6.3.5",
|
|
154
154
|
"vite-tsconfig-paths": "^5.1.4"
|
|
155
|
+
},
|
|
156
|
+
"dependencies": {
|
|
157
|
+
"@popperjs/core": "^2.11.8"
|
|
155
158
|
}
|
|
156
159
|
}
|