@streamscloud/embeddable 6.0.0 → 6.0.1

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.
@@ -0,0 +1 @@
1
+ export declare const runningInBrowser: () => boolean;
@@ -0,0 +1 @@
1
+ export const runningInBrowser = () => !import.meta.env.SSR;
@@ -10,13 +10,15 @@
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 { Icon } from '../../ui/icon';
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';
19
20
  import IconTextColumnThree from '@fluentui/svg-icons/icons/text_column_three_20_regular.svg?raw';
21
+ import IconLineHorizontal3 from '@fluentui/svg-icons/icons/line_horizontal_3_20_regular.svg?raw';
20
22
  import { onMount } from 'svelte';
21
23
  import { fade } from 'svelte/transition';
22
24
  let { dataProvider, playerProps, localization: localizationInit = 'en' } = $props();
@@ -139,21 +141,21 @@ const uniqueById = (arr) => {
139
141
  }
140
142
  return res;
141
143
  };
142
- let wrapperRef = null;
143
144
  let scrollRef = null;
144
145
  let scrollHasLeft = $state(false);
145
146
  let scrollHasRight = $state(false);
146
147
  const mounted = (node, callback) => {
147
- wrapperRef = node;
148
- requestAnimationFrame(() => {
148
+ const scrollResizeObserver = new ResizeObserver(updateScrollShadows);
149
+ scrollResizeObserver.observe(node);
150
+ const heightResizeObserver = new ResizeObserver(() => {
149
151
  headerHeight = node.clientHeight;
150
152
  callback({ height: headerHeight });
151
153
  });
152
- const ro = new ResizeObserver(updateScrollShadows);
153
- ro.observe(wrapperRef);
154
+ heightResizeObserver.observe(node);
154
155
  return {
155
156
  destroy: () => {
156
- ro.disconnect();
157
+ scrollResizeObserver.disconnect();
158
+ heightResizeObserver.disconnect();
157
159
  }
158
160
  };
159
161
  };
@@ -182,7 +184,6 @@ const onScrollMounted = (node) => {
182
184
  <button type="button" class="media-center__overview-button" onclick={toggleOverview}>
183
185
  <Icon src={IconTextColumnThree} />
184
186
  </button>
185
-
186
187
  <div
187
188
  class="media-center__scroll"
188
189
  class:media-center__scroll--has-left={scrollHasLeft}
@@ -202,6 +203,28 @@ const onScrollMounted = (node) => {
202
203
  </div>
203
204
  </div>
204
205
  </div>
206
+ <div class="media-center__overview-dropdown">
207
+ <Dropdown>
208
+ {#snippet trigger()}
209
+ <div class="media-center__overview-dropdown-trigger">
210
+ <Icon src={IconLineHorizontal3} color={IconColor.White}></Icon>
211
+ </div>
212
+ {/snippet}
213
+ <div class="media-center__overview-dropdown-content">
214
+ <button type="button" class="media-center__category-button media-center__category-button--dropdown" onclick={toggleOverview}>
215
+ {localization.overviewLabel}
216
+ </button>
217
+ {#each categories as category (category.id)}
218
+ <button
219
+ type="button"
220
+ class="media-center__category-button media-center__category-button--dropdown"
221
+ class:media-center__category-button--active={selectedCategoryId === category.id}
222
+ title={category.name}
223
+ onclick={() => selectCategory(category.id)}>{category.name}</button>
224
+ {/each}
225
+ </div>
226
+ </Dropdown>
227
+ </div>
205
228
  </div>
206
229
  {/snippet}
207
230
  {#if shortVideoProps}
@@ -246,12 +269,6 @@ const onScrollMounted = (node) => {
246
269
  right: 0;
247
270
  z-index: 1;
248
271
  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
272
  }
256
273
  .media-center__row {
257
274
  pointer-events: auto;
@@ -260,6 +277,12 @@ const onScrollMounted = (node) => {
260
277
  display: flex;
261
278
  align-items: center;
262
279
  gap: 0.75rem;
280
+ /* Set 'container-type: inline-size;' to reference container*/
281
+ }
282
+ @container (width < 576px) {
283
+ .media-center__row {
284
+ display: none;
285
+ }
263
286
  }
264
287
  .media-center__scroll {
265
288
  pointer-events: auto;
@@ -329,8 +352,82 @@ const onScrollMounted = (node) => {
329
352
  background-color: rgba(255, 255, 255, 0.9);
330
353
  color: #000000;
331
354
  }
355
+ .media-center__category-button--dropdown {
356
+ width: max-content;
357
+ }
358
+ .media-center__overview-dropdown {
359
+ display: none;
360
+ pointer-events: auto;
361
+ /* Set 'container-type: inline-size;' to reference container*/
362
+ }
363
+ @container (width < 576px) {
364
+ .media-center__overview-dropdown {
365
+ display: block;
366
+ position: absolute;
367
+ top: 0.9375rem;
368
+ left: 0.625rem;
369
+ z-index: 1;
370
+ }
371
+ }
372
+ .media-center__overview-dropdown-trigger {
373
+ width: 3rem;
374
+ min-width: 3rem;
375
+ max-width: 3rem;
376
+ height: 3rem;
377
+ min-height: 3rem;
378
+ max-height: 3rem;
379
+ display: flex;
380
+ justify-content: center;
381
+ align-items: center;
382
+ background-color: rgba(0, 0, 0, 0.6);
383
+ border: 1px solid #1c1c1c;
384
+ border-radius: 50%;
385
+ text-align: center;
386
+ --icon--color: #ffffff;
387
+ --icon--size: 1.75rem;
388
+ }
389
+ .media-center__overview-dropdown-trigger:hover {
390
+ background-color: rgba(0, 0, 0, 0.9);
391
+ transition: background-color 0.5s;
392
+ }
393
+ .media-center__overview-dropdown-content {
394
+ display: flex;
395
+ flex-direction: column;
396
+ gap: 0.625rem;
397
+ }
332
398
 
333
399
  .media-center-overview {
334
400
  position: absolute;
335
401
  inset: 0;
402
+ container-type: inline-size;
403
+ overflow-y: auto;
404
+ scrollbar-color: transparent transparent;
405
+ scrollbar-width: thin;
406
+ }
407
+ .media-center-overview::-webkit-scrollbar {
408
+ width: 3px;
409
+ height: 3px;
410
+ background: var(--custom-scrollbar-background, transparent);
411
+ visibility: hidden;
412
+ }
413
+ .media-center-overview::-webkit-scrollbar-thumb {
414
+ background: transparent;
415
+ }
416
+ .media-center-overview:hover {
417
+ scrollbar-color: var(--custom-scrollbar-color, #7d7d7d) var(--custom-scrollbar-background, transparent);
418
+ scrollbar-width: thin;
419
+ }
420
+ .media-center-overview:hover::-webkit-scrollbar {
421
+ width: 3px;
422
+ height: 3px;
423
+ background: var(--custom-scrollbar-background, transparent);
424
+ visibility: hidden;
425
+ }
426
+ .media-center-overview:hover::-webkit-scrollbar-thumb {
427
+ background: var(--custom-scrollbar-color, #7d7d7d);
428
+ }
429
+ @media (max-width: 576px) {
430
+ .media-center-overview {
431
+ top: 5rem !important;
432
+ }
336
433
  }</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
- @media (max-width: 768px) {
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
- @media (max-width: 576px) {
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
- @media (max-width: 480px) {
144
+ @container (width < 576px) {
136
145
  .media-center-overview__section-content {
137
- grid-template-columns: 2fr;
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 { isIgnored } from './dropdown-ignore';
3
+ import { Icon } from '../icon';
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';
@@ -0,0 +1,3 @@
1
+ export { default as Dropdown } from './cmp.dropdown.svelte';
2
+ export type DropdownPosition = import('@popperjs/core').Placement;
3
+ export { dropdownIgnore } from './dropdown-ignore';
@@ -0,0 +1,2 @@
1
+ export { default as Dropdown } from './cmp.dropdown.svelte';
2
+ export { dropdownIgnore } from './dropdown-ignore';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/embeddable",
3
- "version": "6.0.0",
3
+ "version": "6.0.1",
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
  }