@streamscloud/blocks 0.1.0

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 (91) hide show
  1. package/dist/content/analytics/index.d.ts +1 -0
  2. package/dist/content/analytics/index.js +1 -0
  3. package/dist/content/analytics/types.d.ts +9 -0
  4. package/dist/content/analytics/types.js +1 -0
  5. package/dist/content/content-viewer/attachments-horizontal.svelte +210 -0
  6. package/dist/content/content-viewer/attachments-horizontal.svelte.d.ts +15 -0
  7. package/dist/content/content-viewer/cmp.content-viewer.svelte +143 -0
  8. package/dist/content/content-viewer/cmp.content-viewer.svelte.d.ts +23 -0
  9. package/dist/content/content-viewer/content-texts.svelte +96 -0
  10. package/dist/content/content-viewer/content-texts.svelte.d.ts +14 -0
  11. package/dist/content/content-viewer/content-viewer-localization.d.ts +4 -0
  12. package/dist/content/content-viewer/content-viewer-localization.js +16 -0
  13. package/dist/content/content-viewer/heading.svelte +66 -0
  14. package/dist/content/content-viewer/heading.svelte.d.ts +11 -0
  15. package/dist/content/content-viewer/index.d.ts +1 -0
  16. package/dist/content/content-viewer/index.js +1 -0
  17. package/dist/content/content-viewer/media/content-media.svelte +53 -0
  18. package/dist/content/content-viewer/media/content-media.svelte.d.ts +12 -0
  19. package/dist/content/content-viewer/ui-manager.svelte.d.ts +10 -0
  20. package/dist/content/content-viewer/ui-manager.svelte.js +18 -0
  21. package/dist/content/controls/content-actions-generator.svelte.d.ts +18 -0
  22. package/dist/content/controls/content-actions-generator.svelte.js +27 -0
  23. package/dist/content/controls/content-actions-handler.svelte.d.ts +23 -0
  24. package/dist/content/controls/content-actions-handler.svelte.js +55 -0
  25. package/dist/content/controls/index.d.ts +1 -0
  26. package/dist/content/controls/index.js +1 -0
  27. package/dist/content/model/content-media-model.svelte.d.ts +20 -0
  28. package/dist/content/model/content-media-model.svelte.js +16 -0
  29. package/dist/content/model/content-model.d.ts +24 -0
  30. package/dist/content/model/content-model.js +32 -0
  31. package/dist/content/model/index.d.ts +3 -0
  32. package/dist/content/model/index.js +2 -0
  33. package/dist/content/model/types.d.ts +61 -0
  34. package/dist/content/model/types.js +1 -0
  35. package/dist/content/model/utils.d.ts +4 -0
  36. package/dist/content/model/utils.js +7 -0
  37. package/dist/content/sharing/index.d.ts +1 -0
  38. package/dist/content/sharing/index.js +1 -0
  39. package/dist/content/sharing/types.d.ts +5 -0
  40. package/dist/content/sharing/types.js +1 -0
  41. package/dist/content/social-interactions/index.d.ts +1 -0
  42. package/dist/content/social-interactions/index.js +1 -0
  43. package/dist/content/social-interactions/types.d.ts +8 -0
  44. package/dist/content/social-interactions/types.js +1 -0
  45. package/dist/core/enums.d.ts +4 -0
  46. package/dist/core/enums.js +1 -0
  47. package/dist/cta/cta-card/cmp.cta-card.svelte +259 -0
  48. package/dist/cta/cta-card/cmp.cta-card.svelte.d.ts +24 -0
  49. package/dist/cta/cta-card/index.d.ts +2 -0
  50. package/dist/cta/cta-card/index.js +1 -0
  51. package/dist/cta/cta-card/types.d.ts +18 -0
  52. package/dist/cta/cta-card/types.js +1 -0
  53. package/dist/feed-player/cmp.close-button.svelte +43 -0
  54. package/dist/feed-player/cmp.close-button.svelte.d.ts +19 -0
  55. package/dist/feed-player/cmp.feed-player.svelte +510 -0
  56. package/dist/feed-player/cmp.feed-player.svelte.d.ts +13 -0
  57. package/dist/feed-player/feed-player-localization.d.ts +16 -0
  58. package/dist/feed-player/feed-player-localization.js +70 -0
  59. package/dist/feed-player/index.d.ts +7 -0
  60. package/dist/feed-player/index.js +2 -0
  61. package/dist/feed-player/sidebar/article-tab.svelte +90 -0
  62. package/dist/feed-player/sidebar/article-tab.svelte.d.ts +10 -0
  63. package/dist/feed-player/sidebar/information-tab.svelte +85 -0
  64. package/dist/feed-player/sidebar/information-tab.svelte.d.ts +15 -0
  65. package/dist/feed-player/sidebar/panel-surface.svelte +13 -0
  66. package/dist/feed-player/sidebar/panel-surface.svelte.d.ts +7 -0
  67. package/dist/feed-player/sidebar/playlist-tab.svelte +90 -0
  68. package/dist/feed-player/sidebar/playlist-tab.svelte.d.ts +11 -0
  69. package/dist/feed-player/sidebar/post-card.svelte +92 -0
  70. package/dist/feed-player/sidebar/post-card.svelte.d.ts +14 -0
  71. package/dist/feed-player/sidebar/recommended-tab.svelte +161 -0
  72. package/dist/feed-player/sidebar/recommended-tab.svelte.d.ts +8 -0
  73. package/dist/feed-player/sidebar/sidebar-panel.svelte +69 -0
  74. package/dist/feed-player/sidebar/sidebar-panel.svelte.d.ts +26 -0
  75. package/dist/feed-player/sidebar/sidebar-tab-bar.svelte +44 -0
  76. package/dist/feed-player/sidebar/sidebar-tab-bar.svelte.d.ts +12 -0
  77. package/dist/feed-player/sidebar/types.d.ts +4 -0
  78. package/dist/feed-player/sidebar/types.js +1 -0
  79. package/dist/feed-player/types.d.ts +65 -0
  80. package/dist/feed-player/types.js +1 -0
  81. package/dist/index.d.ts +1 -0
  82. package/dist/index.js +1 -0
  83. package/dist/products/product-card/cmp.product-card.svelte +282 -0
  84. package/dist/products/product-card/cmp.product-card.svelte.d.ts +30 -0
  85. package/dist/products/product-card/index.d.ts +2 -0
  86. package/dist/products/product-card/index.js +1 -0
  87. package/dist/products/product-card/product-card-localization.d.ts +4 -0
  88. package/dist/products/product-card/product-card-localization.js +19 -0
  89. package/dist/products/product-card/types.d.ts +12 -0
  90. package/dist/products/product-card/types.js +1 -0
  91. package/package.json +92 -0
@@ -0,0 +1,24 @@
1
+ import type { CtaCardModel } from './types';
2
+ type Props = {
3
+ cta: CtaCardModel;
4
+ inert?: boolean;
5
+ on?: {
6
+ click?: (id: string) => void;
7
+ impression?: (id: string) => void;
8
+ };
9
+ };
10
+ /**
11
+ * Call-to-action card with image, primary text, secondary text, and CTA button.
12
+ *
13
+ * ### CSS Custom Properties
14
+ * | Property | Description | Default |
15
+ * |---|---|---|
16
+ * | `--sc-blocks--cta-card--background-color` | Card background | `rgb(from light-dark(white, black) r g b / 90%)` |
17
+ * | `--sc-blocks--cta-card--border-color` | Card border color | `--sc-kit--color--border` |
18
+ * | `--sc-blocks--cta-card--price-color` | Price color | `inherit` |
19
+ * | `--sc-blocks--cta-card--text--primary` | Primary text color | `--sc-kit--color--text--primary` |
20
+ * | `--sc-blocks--cta-card--text-secondary` | Secondary text color | `--sc-kit--color--text--secondary` |
21
+ */
22
+ declare const Cmp: import("svelte").Component<Props, {}, "">;
23
+ type Cmp = ReturnType<typeof Cmp>;
24
+ export default Cmp;
@@ -0,0 +1,2 @@
1
+ export { default as CtaCard } from './cmp.cta-card.svelte';
2
+ export type { CtaCardModel } from './types';
@@ -0,0 +1 @@
1
+ export { default as CtaCard } from './cmp.cta-card.svelte';
@@ -0,0 +1,18 @@
1
+ import type { CtaType, Currency } from '../../core/enums';
2
+ export type CtaCardModel = {
3
+ id: string;
4
+ type: CtaType;
5
+ image: string | null;
6
+ title: string;
7
+ description: string | null;
8
+ price: number | null;
9
+ currency: Currency | null;
10
+ priceInfoLabel: string | null;
11
+ ctaButton: {
12
+ background: string;
13
+ textColor: string;
14
+ text: string;
15
+ url: string;
16
+ border: string;
17
+ } | null;
18
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ <script lang="ts">import IconDismiss from '@fluentui/svg-icons/icons/dismiss_20_regular.svg?raw';
2
+ import { Icon } from '@streamscloud/kit/ui/icon';
3
+ const { on } = $props();
4
+ </script>
5
+
6
+ <button type="button" class="close-button" onclick={() => on?.click?.()}>
7
+ <Icon src={IconDismiss} />
8
+ </button>
9
+
10
+ <!--
11
+ @component
12
+ Round close button — 32px circle with dismiss icon.
13
+
14
+ ### CSS Custom Properties
15
+ | Property | Description | Default |
16
+ |---|---|---|
17
+ | `--sc-blocks--player--close-button--size` | Button size | `32px` |
18
+ | `--sc-blocks--player--close-button--background` | Button background | `light-dark(bg--element, bg--panel)` |
19
+ | `--sc-blocks--player--close-button--color` | Icon color | `--sc-kit--color--text--primary` |
20
+ | `--sc-blocks--player--close-button--icon-size` | Icon size | `16px` |
21
+ -->
22
+
23
+ <style>.close-button {
24
+ --_close-button--size: var(--sc-blocks--player--close-button--size, 2rem);
25
+ --_close-button--background: var(
26
+ --sc-blocks--player--close-button--background,
27
+ light-dark(var(--sc-kit--color--bg--element), var(--sc-kit--color--bg--panel))
28
+ );
29
+ --_close-button--color: var(--sc-blocks--player--close-button--color, var(--sc-kit--color--text--primary));
30
+ --_close-button--icon-size: var(--sc-blocks--player--close-button--icon-size, 1rem);
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ width: var(--_close-button--size);
35
+ height: var(--_close-button--size);
36
+ padding: 0;
37
+ background: var(--_close-button--background);
38
+ border: none;
39
+ border-radius: var(--sc-kit--radius--circle);
40
+ cursor: pointer;
41
+ --sc-kit--icon--size: var(--_close-button--icon-size);
42
+ --sc-kit--icon--color: var(--_close-button--color);
43
+ }</style>
@@ -0,0 +1,19 @@
1
+ type Props = {
2
+ on?: {
3
+ click?: () => void;
4
+ };
5
+ };
6
+ /**
7
+ * Round close button — 32px circle with dismiss icon.
8
+ *
9
+ * ### CSS Custom Properties
10
+ * | Property | Description | Default |
11
+ * |---|---|---|
12
+ * | `--sc-blocks--player--close-button--size` | Button size | `32px` |
13
+ * | `--sc-blocks--player--close-button--background` | Button background | `light-dark(bg--element, bg--panel)` |
14
+ * | `--sc-blocks--player--close-button--color` | Icon color | `--sc-kit--color--text--primary` |
15
+ * | `--sc-blocks--player--close-button--icon-size` | Icon size | `16px` |
16
+ */
17
+ declare const Cmp: import("svelte").Component<Props, {}, "">;
18
+ type Cmp = ReturnType<typeof Cmp>;
19
+ export default Cmp;
@@ -0,0 +1,510 @@
1
+ <script lang="ts">import { ContentViewer } from '../content/content-viewer';
2
+ import { ContentActionsGenerator } from '../content/controls';
3
+ import { ContentModel, getContentCoverImage } from '../content/model';
4
+ import { default as CloseButton } from './cmp.close-button.svelte';
5
+ import { FeedPlayerLocalization } from './feed-player-localization';
6
+ import { default as SidebarPanel } from './sidebar/sidebar-panel.svelte';
7
+ import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_28_regular.svg?raw';
8
+ import IconChevronUp from '@fluentui/svg-icons/icons/chevron_up_28_regular.svg?raw';
9
+ import IconInfo from '@fluentui/svg-icons/icons/info_24_regular.svg?raw';
10
+ import { AppLocale } from '@streamscloud/kit/core/locale';
11
+ import { slideHorizontally } from '@streamscloud/kit/core/transitions';
12
+ import { preloadImage } from '@streamscloud/kit/core/utils';
13
+ import { PlayerButtons } from '@streamscloud/kit/ui/player/buttons';
14
+ import { FeedSlider } from '@streamscloud/kit/ui/player/feed-slider';
15
+ import { initBufferFromProvider } from '@streamscloud/kit/ui/player/providers';
16
+ import { Spinner } from '@streamscloud/kit/ui/spinner';
17
+ import { untrack } from 'svelte';
18
+ let { dataProvider, socialInteractionsHandler, sharingHandler, analyticsHandler, recommendedHandler, playlistHandler, settings, header, on } = $props();
19
+ $effect(() => {
20
+ if (settings?.locale) {
21
+ AppLocale.change(settings.locale);
22
+ }
23
+ });
24
+ const localization = new FeedPlayerLocalization();
25
+ const isPreview = $derived(settings?.mode === 'preview');
26
+ const isGlassBackground = $derived(settings?.background === 'glass');
27
+ const isTransparentBackground = $derived(settings?.background === 'transparent');
28
+ let backgroundImageUrl = $state(null);
29
+ let buffer = $state.raw(null);
30
+ $effect(() => {
31
+ void dataProvider;
32
+ untrack(() => {
33
+ buffer = null;
34
+ backgroundImageUrl = null;
35
+ mappedContentCache = {};
36
+ void initBuffer();
37
+ });
38
+ });
39
+ const initBuffer = async () => {
40
+ const newBuffer = initBufferFromProvider(dataProvider);
41
+ await newBuffer.ensureWarmedUp();
42
+ if (newBuffer.loaded.length) {
43
+ const coverUrl = getContentCoverImage(newBuffer.loaded[0]);
44
+ await preloadImage(coverUrl);
45
+ backgroundImageUrl = coverUrl;
46
+ }
47
+ buffer = newBuffer;
48
+ };
49
+ let mappedContentCache = {};
50
+ const itemAsContentModel = (item) => {
51
+ if (mappedContentCache[item.id]) {
52
+ return mappedContentCache[item.id];
53
+ }
54
+ const contentModel = new ContentModel(item);
55
+ mappedContentCache[item.id] = contentModel;
56
+ return contentModel;
57
+ };
58
+ const currentContentModel = $derived.by(() => {
59
+ if (!buffer?.current) {
60
+ return null;
61
+ }
62
+ return itemAsContentModel(buffer.current);
63
+ });
64
+ const hasProducts = $derived(!!currentContentModel?.attachments && currentContentModel.attachments.products.length > 0);
65
+ const isArticle = $derived(currentContentModel?.contentType === 'ARTICLE' && !!currentContentModel.articleId);
66
+ const hasAttachments = $derived(!!currentContentModel?.attachments);
67
+ let preferredTab = $state('information');
68
+ let activeSidebarTab = $state('information');
69
+ let selectedProductId = $state(null);
70
+ const visibleTabs = $derived.by(() => {
71
+ const tabs = [];
72
+ if (hasAttachments) {
73
+ tabs.push({ id: 'information', label: localization.tabInformation });
74
+ }
75
+ if (hasProducts) {
76
+ tabs.push({ id: 'product', label: localization.tabProduct });
77
+ }
78
+ if (isArticle) {
79
+ tabs.push({ id: 'article', label: localization.tabArticle });
80
+ }
81
+ if (recommendedHandler) {
82
+ tabs.push({ id: 'recommended', label: localization.tabRecommended });
83
+ }
84
+ if (playlistHandler) {
85
+ tabs.push({ id: 'playlist', label: localization.tabPlaylist });
86
+ }
87
+ return tabs;
88
+ });
89
+ const sidebarVisible = $derived(visibleTabs.some((t) => t.id !== 'recommended'));
90
+ const handleTabChange = (id) => {
91
+ preferredTab = id;
92
+ activeSidebarTab = id;
93
+ };
94
+ $effect(() => {
95
+ const tabs = visibleTabs;
96
+ if (tabs.length === 0) {
97
+ return;
98
+ }
99
+ if (tabs.some((t) => t.id === preferredTab)) {
100
+ activeSidebarTab = preferredTab;
101
+ }
102
+ else {
103
+ activeSidebarTab = tabs[0].id;
104
+ }
105
+ });
106
+ $effect(() => {
107
+ const content = currentContentModel;
108
+ untrack(() => {
109
+ if (content?.attachments && content.attachments.products.length > 0) {
110
+ selectedProductId = content.attachments.products[0].id;
111
+ }
112
+ else {
113
+ selectedProductId = null;
114
+ }
115
+ });
116
+ });
117
+ let sidebarCollapsed = $state(false);
118
+ const LAYOUT = {
119
+ ratio: 9 / 16,
120
+ safeArea: 80,
121
+ sidebarWidth: 378,
122
+ gap: 60,
123
+ mobileBreakpoint: 576
124
+ };
125
+ let totalWidth = $state(0);
126
+ let isMobileView = $derived(totalWidth < LAYOUT.mobileBreakpoint);
127
+ const handleRootResize = (node) => {
128
+ const observer = new ResizeObserver(([entry]) => {
129
+ totalWidth = entry.contentRect.width;
130
+ });
131
+ observer.observe(node);
132
+ return { destroy: () => observer.disconnect() };
133
+ };
134
+ let headerHeight = $state(0);
135
+ const handleHeaderResize = (node) => {
136
+ const observer = new ResizeObserver(([entry]) => {
137
+ headerHeight = entry.contentRect.height;
138
+ });
139
+ observer.observe(node);
140
+ return { destroy: () => observer.disconnect() };
141
+ };
142
+ let videoAreaHeight = $state(0);
143
+ const sidebarSlotFits = $derived.by(() => {
144
+ if (isMobileView || videoAreaHeight === 0) {
145
+ return false;
146
+ }
147
+ const videoW = videoAreaHeight * LAYOUT.ratio;
148
+ const rowWidth = videoW + 2 * LAYOUT.safeArea + LAYOUT.gap + LAYOUT.sidebarWidth;
149
+ return totalWidth >= rowWidth;
150
+ });
151
+ const sideControlsFit = $derived.by(() => {
152
+ if (isMobileView || videoAreaHeight === 0) {
153
+ return false;
154
+ }
155
+ const videoW = videoAreaHeight * LAYOUT.ratio;
156
+ const needed = videoW + 2 * LAYOUT.safeArea + (sidebarSlotFits ? LAYOUT.gap + LAYOUT.sidebarWidth : 0);
157
+ return totalWidth >= needed;
158
+ });
159
+ const videoWidth = $derived.by(() => {
160
+ if (isMobileView || videoAreaHeight === 0) {
161
+ return 0;
162
+ }
163
+ const videoW = videoAreaHeight * LAYOUT.ratio;
164
+ return sideControlsFit ? videoW : Math.min(videoW, totalWidth);
165
+ });
166
+ const mainWidth = $derived.by(() => {
167
+ if (videoWidth === 0) {
168
+ return null;
169
+ }
170
+ return sideControlsFit ? videoWidth + 2 * LAYOUT.safeArea : videoWidth;
171
+ });
172
+ const contentWidth = $derived(isMobileView || videoWidth === 0 ? '100%' : `${videoWidth}px`);
173
+ const contentHeight = $derived(isMobileView || videoWidth === 0 ? '100%' : `${videoWidth / LAYOUT.ratio}px`);
174
+ const showControlsOverlay = $derived(isMobileView);
175
+ const handleVideoAreaResize = (node) => {
176
+ const observer = new ResizeObserver(([entry]) => {
177
+ videoAreaHeight = entry.contentRect.height;
178
+ });
179
+ observer.observe(node);
180
+ return { destroy: () => observer.disconnect() };
181
+ };
182
+ const handleItemActivated = (id) => {
183
+ if (buffer) {
184
+ const item = buffer.loaded.find((i) => i.id === id);
185
+ if (item) {
186
+ backgroundImageUrl = getContentCoverImage(item);
187
+ if (item.contentType === 'SHORT_VIDEO') {
188
+ analyticsHandler?.trackShortVideoView(id);
189
+ }
190
+ else if (item.contentType) {
191
+ analyticsHandler?.trackContentOpened(item.contentType, id);
192
+ }
193
+ }
194
+ }
195
+ on?.contentActivated?.(id);
196
+ };
197
+ const onProductClick = (productId) => {
198
+ if (!currentContentModel) {
199
+ return;
200
+ }
201
+ analyticsHandler?.trackProductClick(productId, currentContentModel.id);
202
+ };
203
+ const onProductImpression = (productId) => {
204
+ if (!currentContentModel) {
205
+ return;
206
+ }
207
+ analyticsHandler?.trackProductImpression(productId, currentContentModel.id);
208
+ };
209
+ const onCtaClick = (ctaId) => {
210
+ analyticsHandler?.trackCtaClick(ctaId);
211
+ };
212
+ const onCtaImpression = (ctaId) => {
213
+ analyticsHandler?.trackCtaImpression(ctaId);
214
+ };
215
+ const contentActionsGenerator = untrack(() => new ContentActionsGenerator({
216
+ get socialInteractionsHandler() {
217
+ return socialInteractionsHandler;
218
+ },
219
+ get sharingHandler() {
220
+ return sharingHandler;
221
+ },
222
+ on: { attachmentClicked: () => (sidebarCollapsed = !sidebarCollapsed) }
223
+ }));
224
+ const itemActions = $derived.by(() => {
225
+ if (!currentContentModel) {
226
+ return [];
227
+ }
228
+ const handler = contentActionsGenerator.getContentActionsHandler(currentContentModel);
229
+ const actions = handler.actions.map((a) => ({
230
+ icon: a.iconColor ? { src: a.icon, color: a.iconColor } : a.icon,
231
+ callback: a.callback
232
+ }));
233
+ if (sidebarSlotFits && (!sidebarCollapsed || sidebarVisible)) {
234
+ actions.push({ icon: IconInfo, callback: () => (sidebarCollapsed = !sidebarCollapsed) });
235
+ }
236
+ return actions;
237
+ });
238
+ </script>
239
+
240
+ <div
241
+ class="feed-player"
242
+ class:feed-player--glass={isGlassBackground}
243
+ class:feed-player--glass-active={isGlassBackground && backgroundImageUrl}
244
+ class:feed-player--transparent={isTransparentBackground}
245
+ style:--_feed-player--background-image-url={backgroundImageUrl ? `url(${backgroundImageUrl})` : undefined}
246
+ style:--_--fp--gap="{LAYOUT.gap}px"
247
+ style:--_--fp--safe-area="{LAYOUT.safeArea}px"
248
+ style:--_--fp--sidebar-width="{LAYOUT.sidebarWidth}px"
249
+ use:handleRootResize>
250
+ {#if buffer && on?.closed}
251
+ <div class="feed-player__close">
252
+ <CloseButton on={{ click: on.closed }} />
253
+ </div>
254
+ {/if}
255
+ <div class="feed-player__main" style:width={mainWidth !== null ? mainWidth + 'px' : null}>
256
+ {#if header}
257
+ <div class="feed-player__header" use:handleHeaderResize>
258
+ {@render header()}
259
+ </div>
260
+ {/if}
261
+ {#if buffer}
262
+ <div
263
+ class="feed-player__video-area"
264
+ class:feed-player__video-area--with-header={!!header}
265
+ style:--_--fp--content-width={contentWidth}
266
+ style:--_--fp--content-height={contentHeight}
267
+ use:handleVideoAreaResize>
268
+ <FeedSlider buffer={buffer} on={{ itemActivated: handleItemActivated }}>
269
+ {#snippet children({ item })}
270
+ {@const contentModel = itemAsContentModel(item)}
271
+ <div class="feed-player__content">
272
+ <ContentViewer
273
+ model={contentModel}
274
+ controlActions={itemActions}
275
+ enableAttachments={false}
276
+ enableControls={showControlsOverlay}
277
+ autoplay="on-appearance"
278
+ on={isPreview
279
+ ? {}
280
+ : {
281
+ productClick: onProductClick,
282
+ productImpression: onProductImpression,
283
+ ctaClick: onCtaClick,
284
+ ctaImpression: onCtaImpression,
285
+ articleReadMore: (id) => on?.articleReadMore?.(id)
286
+ }} />
287
+ </div>
288
+ {/snippet}
289
+ </FeedSlider>
290
+
291
+ {#if !showControlsOverlay}
292
+ <div class="feed-player__controls">
293
+ <div class="feed-player__controls-spacer">&nbsp;</div>
294
+ <div class="feed-player__controls-left">
295
+ <PlayerButtons actions={itemActions} scaleEffect={true} />
296
+
297
+ {#if buffer && buffer.loaded.length > 1}
298
+ <div class="feed-player__navigation">
299
+ <PlayerButtons actions={[{ icon: IconChevronUp, callback: buffer.loadPrevious, disabled: !buffer.canLoadPrevious }]} scaleEffect={true} />
300
+ <PlayerButtons actions={[{ icon: IconChevronDown, callback: buffer.loadNext, disabled: !buffer.canLoadNext }]} scaleEffect={true} />
301
+ </div>
302
+ {/if}
303
+ </div>
304
+ <div class="feed-player__controls-spacer feed-player__controls-spacer--right">&nbsp;</div>
305
+ </div>
306
+ {/if}
307
+ </div>
308
+ {/if}
309
+ </div>
310
+ {#if buffer && sidebarSlotFits}
311
+ <div class="feed-player__sidebar-container" style:width="{!sidebarCollapsed ? LAYOUT.sidebarWidth : 0}px">
312
+ {#if !sidebarCollapsed && sidebarVisible && currentContentModel}
313
+ <div
314
+ class="feed-player__sidebar"
315
+ class:feed-player__sidebar--with-close={on?.closed}
316
+ class:feed-player__sidebar--with-header={!on?.closed && !!header}
317
+ style:--_--feed-player--header-height={headerHeight + 'px'}
318
+ transition:slideHorizontally|local>
319
+ <SidebarPanel
320
+ tabs={visibleTabs}
321
+ activeTabId={activeSidebarTab}
322
+ model={currentContentModel}
323
+ selectedProductId={selectedProductId}
324
+ interactionsDisabled={isPreview}
325
+ recommendedHandler={recommendedHandler}
326
+ playlistHandler={playlistHandler}
327
+ on={isPreview
328
+ ? { tabChange: () => {} }
329
+ : {
330
+ tabChange: handleTabChange,
331
+ productClick: onProductClick,
332
+ productImpression: onProductImpression,
333
+ productBuy: on?.productBuy,
334
+ productSelect: (id) => {
335
+ selectedProductId = id;
336
+ handleTabChange('product');
337
+ },
338
+ ctaClick: onCtaClick,
339
+ ctaImpression: onCtaImpression,
340
+ articleReadMore: on?.articleReadMore,
341
+ contentActivate: (id) => buffer?.tryActivateItemById(id)
342
+ }} />
343
+ </div>
344
+ {/if}
345
+ </div>
346
+ {/if}
347
+ {#if !buffer}
348
+ <Spinner position="absolute-center" timeout={500} />
349
+ {/if}
350
+ </div>
351
+
352
+ <!--
353
+ @component
354
+ Vertical content feed player with a tabbed sidebar for products, articles, and recommendations.
355
+
356
+ ### CSS Custom Properties
357
+ | Property | Description | Default |
358
+ |---|---|---|
359
+ | `--sc-blocks--player--background` | Player background | `light-dark(white, black)` |
360
+ | `--sc-blocks--player--padding-left` | Left padding for video area offset | `0px` |
361
+ -->
362
+
363
+ <style>.feed-player {
364
+ --_feed-player--background: var(--sc-blocks--player--background, light-dark(#ffffff, #000000));
365
+ --_feed-player--padding-left: var(--sc-blocks--player--padding-left, 0px);
366
+ position: relative;
367
+ width: 100%;
368
+ height: 100%;
369
+ display: flex;
370
+ justify-content: center;
371
+ gap: var(--_--fp--gap);
372
+ padding-left: var(--_feed-player--padding-left);
373
+ background: var(--_feed-player--background);
374
+ overflow: hidden;
375
+ container-type: inline-size;
376
+ }
377
+ .feed-player::before {
378
+ content: "";
379
+ position: absolute;
380
+ inset: 0;
381
+ backdrop-filter: blur(1.875rem);
382
+ background-color: rgb(from var(--_feed-player--background) r g b/50%);
383
+ display: none;
384
+ z-index: 0;
385
+ }
386
+ .feed-player--glass::before {
387
+ display: block;
388
+ }
389
+ .feed-player--glass-active {
390
+ background-image: var(--_feed-player--background-image-url);
391
+ background-size: cover;
392
+ background-position: center;
393
+ }
394
+ .feed-player--transparent {
395
+ background: transparent;
396
+ }
397
+ .feed-player__main {
398
+ display: flex;
399
+ flex-direction: column;
400
+ flex-shrink: 0;
401
+ min-height: 0;
402
+ width: 100%;
403
+ position: relative;
404
+ z-index: 1;
405
+ }
406
+ .feed-player__header {
407
+ position: relative;
408
+ flex-shrink: 0;
409
+ display: flex;
410
+ justify-content: center;
411
+ align-items: center;
412
+ z-index: 1;
413
+ pointer-events: none;
414
+ /* Set 'container-type: inline-size;' to reference container*/
415
+ }
416
+ @container (width < 576px) {
417
+ .feed-player__header {
418
+ position: absolute;
419
+ top: 0;
420
+ left: 0;
421
+ right: 0;
422
+ z-index: 2;
423
+ }
424
+ }
425
+ .feed-player__video-area {
426
+ flex: 1;
427
+ min-width: 0;
428
+ min-height: 0;
429
+ position: relative;
430
+ padding-top: 0.625rem;
431
+ padding-bottom: 0.625rem;
432
+ overflow: hidden;
433
+ }
434
+ .feed-player__video-area--with-header {
435
+ padding-top: 0;
436
+ }
437
+ .feed-player__video-area {
438
+ /* Set 'container-type: inline-size;' to reference container*/
439
+ }
440
+ @container (width < 576px) {
441
+ .feed-player__video-area {
442
+ padding: 0;
443
+ }
444
+ }
445
+ .feed-player__content {
446
+ width: var(--_--fp--content-width, 100%);
447
+ height: var(--_--fp--content-height, 100%);
448
+ margin: auto;
449
+ position: relative;
450
+ display: flex;
451
+ justify-content: center;
452
+ align-items: center;
453
+ }
454
+ .feed-player__controls {
455
+ position: absolute;
456
+ top: 0;
457
+ right: 0;
458
+ height: 100%;
459
+ width: var(--_--fp--safe-area);
460
+ display: flex;
461
+ padding: 0.625rem 0 1.875rem;
462
+ pointer-events: none;
463
+ }
464
+ .feed-player__controls-spacer {
465
+ flex: 0 0 1rem;
466
+ }
467
+ .feed-player__controls-spacer--right {
468
+ flex: 1;
469
+ }
470
+ .feed-player__controls-left {
471
+ display: flex;
472
+ flex-direction: column;
473
+ gap: 2.3125rem;
474
+ justify-content: flex-end;
475
+ align-items: center;
476
+ pointer-events: auto;
477
+ }
478
+ .feed-player__navigation {
479
+ display: flex;
480
+ flex-direction: column;
481
+ gap: var(--sc-kit--space--4);
482
+ }
483
+ .feed-player__sidebar-container {
484
+ flex-shrink: 0;
485
+ display: flex;
486
+ flex-direction: column;
487
+ align-items: flex-end;
488
+ overflow: hidden;
489
+ position: relative;
490
+ z-index: 1;
491
+ transition: width 250ms ease-in-out;
492
+ }
493
+ .feed-player__close {
494
+ position: absolute;
495
+ top: var(--sc-kit--space--3);
496
+ right: var(--sc-kit--space--4);
497
+ z-index: 2;
498
+ }
499
+ .feed-player__sidebar {
500
+ flex: 1;
501
+ min-height: 0;
502
+ width: var(--_--fp--sidebar-width);
503
+ padding: 0.625rem 0.625rem 0.625rem 0;
504
+ }
505
+ .feed-player__sidebar--with-close {
506
+ padding-top: 3.75rem;
507
+ }
508
+ .feed-player__sidebar--with-header {
509
+ padding-top: var(--_--feed-player--header-height, 0px);
510
+ }</style>
@@ -0,0 +1,13 @@
1
+ import type { FeedPlayerProps } from './types';
2
+ /**
3
+ * Vertical content feed player with a tabbed sidebar for products, articles, and recommendations.
4
+ *
5
+ * ### CSS Custom Properties
6
+ * | Property | Description | Default |
7
+ * |---|---|---|
8
+ * | `--sc-blocks--player--background` | Player background | `light-dark(white, black)` |
9
+ * | `--sc-blocks--player--padding-left` | Left padding for video area offset | `0px` |
10
+ */
11
+ declare const Cmp: import("svelte").Component<FeedPlayerProps, {}, "">;
12
+ type Cmp = ReturnType<typeof Cmp>;
13
+ export default Cmp;
@@ -0,0 +1,16 @@
1
+ export declare class FeedPlayerLocalization {
2
+ get tabInformation(): string;
3
+ get tabProduct(): string;
4
+ get tabArticle(): string;
5
+ get tabRecommended(): string;
6
+ get tabPlaylist(): string;
7
+ get show(): string;
8
+ get relatedContent(): string;
9
+ get suggestedPlaylist(): string;
10
+ get suggestedProducts(): string;
11
+ get showList(): string;
12
+ get updatedLabel(): string;
13
+ postsCount(count: number): string;
14
+ postOf(current: number, total: number): string;
15
+ viewsLabel(count: number): string;
16
+ }