@streamscloud/embeddable 16.3.1 → 17.0.0-1775740482426

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 (35) hide show
  1. package/dist/ads/ad-card/cmp.ad-card.svelte +11 -9
  2. package/dist/feed-player/cmp.close-button.svelte +40 -0
  3. package/dist/feed-player/cmp.close-button.svelte.d.ts +19 -0
  4. package/dist/feed-player/cmp.feed-player.svelte +516 -0
  5. package/dist/feed-player/cmp.feed-player.svelte.d.ts +14 -0
  6. package/dist/feed-player/feed-player-localization.d.ts +16 -0
  7. package/dist/feed-player/feed-player-localization.js +70 -0
  8. package/dist/feed-player/index.d.ts +3 -0
  9. package/dist/feed-player/index.js +2 -0
  10. package/dist/feed-player/sidebar/article-tab.svelte +98 -0
  11. package/dist/feed-player/sidebar/article-tab.svelte.d.ts +19 -0
  12. package/dist/feed-player/sidebar/information-tab.svelte +102 -0
  13. package/dist/feed-player/sidebar/information-tab.svelte.d.ts +26 -0
  14. package/dist/feed-player/sidebar/playlist-tab.svelte +110 -0
  15. package/dist/feed-player/sidebar/playlist-tab.svelte.d.ts +19 -0
  16. package/dist/feed-player/sidebar/post-card.svelte +128 -0
  17. package/dist/feed-player/sidebar/post-card.svelte.d.ts +24 -0
  18. package/dist/feed-player/sidebar/recommended-tab.svelte +165 -0
  19. package/dist/feed-player/sidebar/recommended-tab.svelte.d.ts +24 -0
  20. package/dist/feed-player/sidebar/sidebar-panel.svelte +84 -0
  21. package/dist/feed-player/sidebar/sidebar-panel.svelte.d.ts +28 -0
  22. package/dist/feed-player/sidebar/sidebar-tab-bar.svelte +45 -0
  23. package/dist/feed-player/sidebar/sidebar-tab-bar.svelte.d.ts +20 -0
  24. package/dist/feed-player/sidebar/types.d.ts +4 -0
  25. package/dist/feed-player/sidebar/types.js +1 -0
  26. package/dist/feed-player/types.d.ts +62 -0
  27. package/dist/feed-player/types.js +1 -0
  28. package/dist/posts/attachments/cmp.attachments.svelte +7 -2
  29. package/dist/posts/post-viewer/cmp.post-viewer.svelte +10 -7
  30. package/dist/posts/post-viewer/media/post-media.svelte +1 -2
  31. package/dist/posts/posts-player/posts-player-view.svelte +2 -0
  32. package/dist/products/product-card/cmp.product-card.svelte +16 -12
  33. package/dist/streams/streams-player/streams-player-view.svelte +2 -0
  34. package/dist/ui/media-items/media-item-view/cmp.media-item-view.svelte +1 -1
  35. package/package.json +7 -3
@@ -35,8 +35,8 @@ const handleAdClick = () => {
35
35
 
36
36
  <div
37
37
  class="ad-card"
38
- style:--ad-card--cta-background={ad.ctaButton?.background}
39
- style:--ad-card--cta-text-color={ad.ctaButton?.textColor}
38
+ style:--_--ad-card--cta--background={ad.ctaButton?.background}
39
+ style:--_--ad-card--cta--text-color={ad.ctaButton?.textColor}
40
40
  inert={inert}
41
41
  use:trackImpression>
42
42
  <div class="ad-card__image">
@@ -81,6 +81,10 @@ const handleAdClick = () => {
81
81
  --_ad-card--background-color: var(--ad-card--background-color, rgb(from light-dark(#ffffff, #000000) r g b / 90%));
82
82
  --_ad-card--border-color: var(--ad-card--border-color, light-dark(#f2f2f2, #000000));
83
83
  --_ad-card--price-color: var(--ad-card--price-color, inherit);
84
+ --_ad-card--text--primary: var(--ad-card--text--primary, light-dark(#000000, #ffffff));
85
+ --_ad-card--text-secondary: var(--ad-card--text-secondary, light-dark(#6b7280, #d1d5db));
86
+ --_ad-card--cta--background: var(--_--ad-card--cta--background, light-dark(#ffffff, #111827));
87
+ --_ad-card--cta--text-color: var(--_--ad-card--cta--text-color, light-dark(#000000, #ffffff));
84
88
  width: 100%;
85
89
  height: max-content;
86
90
  display: flex;
@@ -174,6 +178,7 @@ const handleAdClick = () => {
174
178
  overflow: hidden;
175
179
  }
176
180
  .ad-card__title {
181
+ color: var(--_ad-card--text--primary);
177
182
  font-weight: 700;
178
183
  font-size: 1.125rem;
179
184
  line-height: 1.375rem;
@@ -189,7 +194,7 @@ const handleAdClick = () => {
189
194
  }
190
195
  .ad-card__description {
191
196
  font-weight: 400;
192
- color: var(--sc-mc-color--text-secondary);
197
+ color: var(--_ad-card--text-secondary);
193
198
  font-size: 0.9375rem;
194
199
  line-height: 1.375rem;
195
200
  min-height: 1.375rem;
@@ -227,7 +232,7 @@ const handleAdClick = () => {
227
232
  line-height: 1.0913rem;
228
233
  letter-spacing: 0;
229
234
  text-align: right;
230
- color: var(--sc-mc-color--text-secondary);
235
+ color: var(--_ad-card--text-secondary);
231
236
  white-space: nowrap;
232
237
  overflow: hidden;
233
238
  text-overflow: ellipsis;
@@ -243,11 +248,8 @@ const handleAdClick = () => {
243
248
  }
244
249
  .ad-card__button {
245
250
  width: 100%;
246
- --sc-kit--button--background: light-dark(
247
- var(--sc-player--light--card-button, var(--ad-card--cta-background)),
248
- var(--sc-player--dark--card-button, var(--ad-card--cta-background))
249
- );
250
- --sc-kit--button--font--color: var(--ad-card--cta-text-color);
251
+ --sc-kit--button--background: var(--_ad-card--cta--background);
252
+ --sc-kit--button--font--color: var(--_ad-card--cta--text-color);
251
253
  }
252
254
  .ad-card__button :global(*) {
253
255
  width: 100%;
@@ -0,0 +1,40 @@
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-fp--close-button--size` | Button size | `32px` |
18
+ | `--sc-fp--close-button--background` | Button background | `light-dark(#f1f6fd, #2a2a2a)` |
19
+ | `--sc-fp--close-button--color` | Icon color | `currentColor` |
20
+ | `--sc-fp--close-button--icon-size` | Icon size | `16px` |
21
+ -->
22
+
23
+ <style>.close-button {
24
+ --_close-button--size: var(--sc-fp--close-button--size, 2rem);
25
+ --_close-button--background: var(--sc-fp--close-button--background, light-dark(#f1f6fd, #2a2a2a));
26
+ --_close-button--color: var(--sc-fp--close-button--color, light-dark(#000000, #ffffff));
27
+ --_close-button--icon-size: var(--sc-fp--close-button--icon-size, 1rem);
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ width: var(--_close-button--size);
32
+ height: var(--_close-button--size);
33
+ padding: 0;
34
+ background: var(--_close-button--background);
35
+ border: none;
36
+ border-radius: 50%;
37
+ cursor: pointer;
38
+ --sc-kit--icon--size: var(--_close-button--icon-size);
39
+ --sc-kit--icon--color: var(--_close-button--color);
40
+ }</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-fp--close-button--size` | Button size | `32px` |
13
+ * | `--sc-fp--close-button--background` | Button background | `light-dark(#f1f6fd, #2a2a2a)` |
14
+ * | `--sc-fp--close-button--color` | Icon color | `currentColor` |
15
+ * | `--sc-fp--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,516 @@
1
+ <script lang="ts">import { default as CloseButton } from './cmp.close-button.svelte';
2
+ import { PostActionsGenerator } from '../posts/controls';
3
+ import { getPostCoverImage, PostModel } from '../posts/model';
4
+ import { PostViewer } from '../posts/post-viewer';
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 { Loading } from '@streamscloud/kit/ui/loading';
14
+ import { PlayerButtons } from '@streamscloud/kit/ui/player/buttons';
15
+ import { FeedSlider } from '@streamscloud/kit/ui/player/feed-slider';
16
+ import { initBufferFromProvider } from '@streamscloud/kit/ui/player/providers';
17
+ import { untrack } from 'svelte';
18
+ let { dataProvider, socialInteractionsHandler, sharingHandler, analyticsHandler, recommendedHandler, playlistHandler, trackingParams: externalTrackingParams, settings, header, on } = $props();
19
+ $effect(() => {
20
+ if (settings?.locale) {
21
+ AppLocale.change(settings.locale);
22
+ }
23
+ });
24
+ const localization = new FeedPlayerLocalization();
25
+ // ── Background mode ──
26
+ const isGlassBackground = $derived(settings?.background === 'glass');
27
+ const isTransparentBackground = $derived(settings?.background === 'transparent');
28
+ let backgroundImageUrl = $state(null);
29
+ // ── Buffer ──
30
+ let buffer = $state.raw(null);
31
+ $effect(() => {
32
+ void dataProvider;
33
+ untrack(() => {
34
+ buffer = null;
35
+ backgroundImageUrl = null;
36
+ mappedPostsCache = {};
37
+ initBuffer();
38
+ });
39
+ });
40
+ const initBuffer = async () => {
41
+ const newBuffer = initBufferFromProvider(dataProvider);
42
+ await newBuffer.ensureWarmedUp();
43
+ if (newBuffer.loaded.length) {
44
+ const coverUrl = getPostCoverImage(newBuffer.loaded[0]);
45
+ await preloadImage(coverUrl);
46
+ backgroundImageUrl = coverUrl;
47
+ }
48
+ buffer = newBuffer;
49
+ };
50
+ // ── PostModel cache ──
51
+ let mappedPostsCache = {};
52
+ const itemAsPostModel = (item) => {
53
+ if (mappedPostsCache[item.id]) {
54
+ return mappedPostsCache[item.id];
55
+ }
56
+ const postModel = new PostModel(item);
57
+ mappedPostsCache[item.id] = postModel;
58
+ return postModel;
59
+ };
60
+ // ── Current post ──
61
+ const currentPostModel = $derived.by(() => {
62
+ if (!buffer?.current) {
63
+ return null;
64
+ }
65
+ return itemAsPostModel(buffer.current);
66
+ });
67
+ const currentTrackingParams = $derived.by(() => {
68
+ if (externalTrackingParams === false) {
69
+ return false;
70
+ }
71
+ const shortVideoId = currentPostModel?.postType === 'SHORT_VIDEO' ? currentPostModel.id : undefined;
72
+ if (!externalTrackingParams && !shortVideoId) {
73
+ return false;
74
+ }
75
+ return { ...externalTrackingParams, shortVideoId };
76
+ });
77
+ const hasProducts = $derived(!!currentPostModel?.attachments && currentPostModel.attachments.products.length > 0);
78
+ const isArticle = $derived(currentPostModel?.postType === 'ARTICLE' && !!currentPostModel.articleId);
79
+ const hasAttachments = $derived(!!currentPostModel?.attachments);
80
+ // ── Sidebar tabs ──
81
+ let preferredTab = $state('information');
82
+ let activeSidebarTab = $state('information');
83
+ let selectedProductId = $state(null);
84
+ const visibleTabs = $derived.by(() => {
85
+ const tabs = [];
86
+ if (hasAttachments) {
87
+ tabs.push({ id: 'information', label: localization.tabInformation });
88
+ }
89
+ if (hasProducts) {
90
+ tabs.push({ id: 'product', label: localization.tabProduct });
91
+ }
92
+ if (isArticle) {
93
+ tabs.push({ id: 'article', label: localization.tabArticle });
94
+ }
95
+ if (recommendedHandler) {
96
+ tabs.push({ id: 'recommended', label: localization.tabRecommended });
97
+ }
98
+ if (playlistHandler) {
99
+ tabs.push({ id: 'playlist', label: localization.tabPlaylist });
100
+ }
101
+ return tabs;
102
+ });
103
+ const sidebarVisible = $derived(visibleTabs.length > 0);
104
+ const handleTabChange = (id) => {
105
+ preferredTab = id;
106
+ activeSidebarTab = id;
107
+ };
108
+ // Tab resolution: preferred tab if available, otherwise first visible
109
+ $effect(() => {
110
+ const tabs = visibleTabs;
111
+ if (tabs.length === 0) {
112
+ return;
113
+ }
114
+ if (tabs.some((t) => t.id === preferredTab)) {
115
+ activeSidebarTab = preferredTab;
116
+ }
117
+ else {
118
+ activeSidebarTab = tabs[0].id;
119
+ }
120
+ });
121
+ // Reset selectedProductId on post change
122
+ $effect(() => {
123
+ const post = currentPostModel;
124
+ untrack(() => {
125
+ if (post?.attachments && post.attachments.products.length > 0) {
126
+ selectedProductId = post.attachments.products[0].id;
127
+ }
128
+ else {
129
+ selectedProductId = null;
130
+ }
131
+ });
132
+ });
133
+ // ── Sidebar collapsed (user toggle) ──
134
+ let sidebarCollapsed = $state(false);
135
+ // ── Layout ──
136
+ const SAFE_AREA_SIZE = 80;
137
+ const SIDEBAR_WIDTH = 378;
138
+ const MOBILE_BREAKPOINT = 576;
139
+ // Measured from the root element
140
+ let totalWidth = $state(0);
141
+ let isMobileView = $derived(totalWidth <= MOBILE_BREAKPOINT);
142
+ const handleRootResize = (node) => {
143
+ const observer = new ResizeObserver(([entry]) => {
144
+ totalWidth = entry.contentRect.width;
145
+ });
146
+ observer.observe(node);
147
+ return { destroy: () => observer.disconnect() };
148
+ };
149
+ // Measured from video-area
150
+ let contentViewWidth = $state(0);
151
+ let videoAreaWidth = $state(0);
152
+ let videoAreaHeight = $state(0);
153
+ // Priority: sidebar hides first, then video shrinks, then controls go overlay
154
+ // Step 1: Would sidebar + video + safe areas fit?
155
+ const sidebarCanBeShown = $derived.by(() => {
156
+ if (!sidebarVisible || isMobileView || videoAreaHeight === 0) {
157
+ return false;
158
+ }
159
+ // Estimate video width from height at 9:16
160
+ const videoW = videoAreaHeight * (9 / 16);
161
+ const areaWithSidebar = totalWidth - SIDEBAR_WIDTH;
162
+ const sideSpace = (areaWithSidebar - videoW) / 2;
163
+ return sideSpace >= SAFE_AREA_SIZE;
164
+ });
165
+ const sidebarWidth = $derived(sidebarCanBeShown && !sidebarCollapsed ? SIDEBAR_WIDTH : 0);
166
+ // Step 2: Controls overlay — only when side space without sidebar is still too small
167
+ const sidePanelsMaxWidth = $derived((videoAreaWidth - contentViewWidth) / 2);
168
+ const showControlsOverlay = $derived(isMobileView);
169
+ // ResizeObserver on video-area — same logic as generic player's handleSliderMounted
170
+ const handleVideoAreaResize = (node) => {
171
+ const observer = new ResizeObserver(([entry]) => {
172
+ const { width: areaW, height: areaH } = entry.contentRect;
173
+ videoAreaWidth = areaW;
174
+ videoAreaHeight = areaH;
175
+ const ratio = 9 / 16;
176
+ let width;
177
+ let height;
178
+ let margin;
179
+ let contentWidthNumber = areaW;
180
+ if (isMobileView) {
181
+ width = '100%';
182
+ height = '100%';
183
+ margin = '0';
184
+ }
185
+ else {
186
+ // On desktop, reserve safe area on each side for controls
187
+ const maxContentWidth = areaW - 2 * SAFE_AREA_SIZE;
188
+ const heightBasedWidth = areaH * ratio;
189
+ contentWidthNumber = Math.min(maxContentWidth, heightBasedWidth);
190
+ width = `${contentWidthNumber}px`;
191
+ height = `${contentWidthNumber / ratio}px`;
192
+ margin = 'auto';
193
+ }
194
+ node.style.setProperty('--_feed-player--content--width', width);
195
+ node.style.setProperty('--_feed-player--content--height', height);
196
+ node.style.setProperty('--_feed-player--content--margin', margin);
197
+ contentViewWidth = contentWidthNumber;
198
+ });
199
+ observer.observe(node);
200
+ return { destroy: () => observer.disconnect() };
201
+ };
202
+ // ── Callbacks ──
203
+ const handleItemActivated = (id) => {
204
+ if (buffer) {
205
+ const item = buffer.loaded.find((i) => i.id === id);
206
+ if (item) {
207
+ backgroundImageUrl = getPostCoverImage(item);
208
+ if (item.postType === 'SHORT_VIDEO') {
209
+ analyticsHandler?.trackShortVideoView(id);
210
+ }
211
+ else if (item.postType) {
212
+ analyticsHandler?.trackPostOpened(item.postType, id);
213
+ }
214
+ }
215
+ }
216
+ on?.postActivated?.(id);
217
+ };
218
+ // ── Analytics callbacks ──
219
+ const onProductClick = (productId) => {
220
+ if (!currentPostModel) {
221
+ return;
222
+ }
223
+ if (currentPostModel.postType === 'SHORT_VIDEO') {
224
+ analyticsHandler?.trackShortVideoProductClick(productId, currentPostModel.id);
225
+ }
226
+ };
227
+ const onProductImpression = (productId) => {
228
+ if (!currentPostModel) {
229
+ return;
230
+ }
231
+ if (currentPostModel.postType === 'SHORT_VIDEO') {
232
+ analyticsHandler?.trackShortVideoProductImpression(productId, currentPostModel.id);
233
+ }
234
+ };
235
+ const onAdClick = (adId) => {
236
+ analyticsHandler?.trackAdClick(adId);
237
+ };
238
+ const onAdImpression = (adId) => {
239
+ analyticsHandler?.trackAdImpression(adId);
240
+ };
241
+ // ── Action buttons (like, share, attachments toggle) ──
242
+ const postActionsGenerator = untrack(() => new PostActionsGenerator({
243
+ socialInteractionsHandler,
244
+ sharingHandler,
245
+ on: { attachmentClicked: () => (sidebarCollapsed = !sidebarCollapsed) }
246
+ }));
247
+ const itemActions = $derived.by(() => {
248
+ if (!currentPostModel) {
249
+ return [];
250
+ }
251
+ const handler = postActionsGenerator.getPostActionsHandler(currentPostModel);
252
+ const actions = [...handler.actions];
253
+ if (sidebarCanBeShown) {
254
+ actions.push({ icon: IconInfo, callback: () => (sidebarCollapsed = !sidebarCollapsed) });
255
+ }
256
+ return actions;
257
+ });
258
+ </script>
259
+
260
+ <div
261
+ class="feed-player"
262
+ class:feed-player--glass={isGlassBackground}
263
+ class:feed-player--glass-active={isGlassBackground && backgroundImageUrl}
264
+ class:feed-player--transparent={isTransparentBackground}
265
+ style:--_feed-player--background-image-url={backgroundImageUrl ? `url(${backgroundImageUrl})` : undefined}
266
+ use:handleRootResize>
267
+
268
+
269
+ <div class="feed-player__main">
270
+ {#if header}
271
+ <div class="feed-player__header">
272
+ {@render header()}
273
+ </div>
274
+ {/if}
275
+ {#if buffer}
276
+ <div class="feed-player__video-area" class:feed-player__video-area--with-header={!!header} use:handleVideoAreaResize>
277
+ <FeedSlider buffer={buffer} on={{ itemActivated: handleItemActivated }}>
278
+ {#snippet children({ item })}
279
+ {@const postModel = itemAsPostModel(item)}
280
+ <div class="feed-player__content">
281
+ <PostViewer
282
+ model={postModel}
283
+ controlActions={itemActions}
284
+ trackingParams={externalTrackingParams ?? null}
285
+ enableAttachments={false}
286
+ enableControls={showControlsOverlay}
287
+ autoplay="on-appearance"
288
+ on={{
289
+ productClick: onProductClick,
290
+ productImpression: onProductImpression,
291
+ adClick: onAdClick,
292
+ adImpression: onAdImpression,
293
+ articleReadMore: (id) => on?.articleReadMore?.(id)
294
+ }} />
295
+ </div>
296
+ {/snippet}
297
+ </FeedSlider>
298
+
299
+ {#if !showControlsOverlay}
300
+ <div class="feed-player__controls" style:--_feed-player--controls-width="{sidePanelsMaxWidth}px">
301
+ <div class="feed-player__controls-spacer">&nbsp;</div>
302
+ <div class="feed-player__controls-left">
303
+ <PlayerButtons actions={itemActions} scaleEffect={true} />
304
+
305
+ {#if buffer && buffer.loaded.length > 1}
306
+ <div class="feed-player__navigation">
307
+ <PlayerButtons actions={[{ icon: IconChevronUp, callback: buffer.loadPrevious, disabled: !buffer.canLoadPrevious }]} scaleEffect={true} />
308
+ <PlayerButtons actions={[{ icon: IconChevronDown, callback: buffer.loadNext, disabled: !buffer.canLoadNext }]} scaleEffect={true} />
309
+ </div>
310
+ {/if}
311
+ </div>
312
+ <div class="feed-player__controls-spacer feed-player__controls-spacer--right">&nbsp;</div>
313
+ </div>
314
+ {/if}
315
+ </div>
316
+ {/if}
317
+
318
+
319
+ </div>
320
+ {#if buffer}
321
+ <div class="feed-player__sidebar-container">
322
+ {#if on?.closed}
323
+ <div class="feed-player__close">
324
+ <CloseButton on={{ click: on.closed }} />
325
+ </div>
326
+ {/if}
327
+
328
+ {#if sidebarWidth > 0 && currentPostModel}
329
+ <div class="feed-player__sidebar" class:feed-player__sidebar--with-close={on?.closed} style:--_--feed-player--sidebar--width={SIDEBAR_WIDTH+'px'}
330
+ transition:slideHorizontally|local>
331
+ <SidebarPanel
332
+ tabs={visibleTabs}
333
+ activeTabId={activeSidebarTab}
334
+ model={currentPostModel}
335
+ trackingParams={currentTrackingParams}
336
+ selectedProductId={selectedProductId}
337
+ {recommendedHandler}
338
+ {playlistHandler}
339
+ on={{
340
+ tabChange: handleTabChange,
341
+ productClick: onProductClick,
342
+ productImpression: onProductImpression,
343
+ productBuy: on?.productBuy,
344
+ productSelect: (id) => {
345
+ selectedProductId = id;
346
+ handleTabChange('product');
347
+ },
348
+ adClick: onAdClick,
349
+ adImpression: onAdImpression,
350
+ articleReadMore: on?.articleReadMore,
351
+ postActivate: (id) => buffer?.tryActivateItemById(id),
352
+ playlistSelect: (id) => on?.playlistSelect?.(id)
353
+ }} />
354
+ </div>
355
+ {/if}
356
+ </div>
357
+ {/if}
358
+ {#if !buffer}
359
+ <Loading positionAbsoluteCenter={true} timeout={500} />
360
+ {/if}
361
+ </div>
362
+
363
+ <!--
364
+ @component
365
+ Vertical post feed player with a tabbed sidebar for products, articles, and recommendations.
366
+
367
+ ### CSS Custom Properties
368
+ | Property | Description | Default |
369
+ |---|---|---|
370
+ | `--sc-fp--background` | Player background | `light-dark(white, black)` |
371
+ | `--sc-fp--button--color` | Control button color | inherited |
372
+ | `--sc-fp--button--color--inactive` | Inactive control button color | inherited |
373
+ -->
374
+
375
+ <style>.feed-player {
376
+ --_feed-player--background: var(--sc-fp--background, light-dark(#ffffff, #000000));
377
+ --_feed-player--button--color: var(--sc-fp--button--color);
378
+ --_feed-player--button--color--inactive: var(--sc-fp--button--color--inactive);
379
+ --post-viewer--button--color: var(--_feed-player--button--color);
380
+ --post-viewer--button--color--inactive: var(--_feed-player--button--color--inactive);
381
+ position: relative;
382
+ width: 100%;
383
+ height: 100%;
384
+ display: flex;
385
+ background: var(--_feed-player--background);
386
+ overflow: hidden;
387
+ container-type: inline-size;
388
+ }
389
+ .feed-player::before {
390
+ content: "";
391
+ position: absolute;
392
+ inset: 0;
393
+ backdrop-filter: blur(1.875rem);
394
+ background-color: rgb(from var(--_feed-player--background) r g b/50%);
395
+ display: none;
396
+ z-index: 0;
397
+ }
398
+ .feed-player--glass::before {
399
+ display: block;
400
+ }
401
+ .feed-player--glass-active {
402
+ background-image: var(--_feed-player--background-image-url);
403
+ background-size: cover;
404
+ background-position: center;
405
+ }
406
+ .feed-player--transparent {
407
+ background: transparent;
408
+ }
409
+ .feed-player__main {
410
+ display: flex;
411
+ flex-direction: column;
412
+ width: 100%;
413
+ flex: 1;
414
+ min-height: 0;
415
+ position: relative;
416
+ z-index: 1;
417
+ }
418
+ .feed-player__header {
419
+ position: relative;
420
+ flex-shrink: 0;
421
+ display: flex;
422
+ justify-content: center;
423
+ align-items: center;
424
+ z-index: 1;
425
+ pointer-events: none;
426
+ /* Set 'container-type: inline-size;' to reference container*/
427
+ }
428
+ @container (width < 576px) {
429
+ .feed-player__header {
430
+ position: absolute;
431
+ top: 0;
432
+ left: 0;
433
+ right: 0;
434
+ z-index: 2;
435
+ }
436
+ }
437
+ .feed-player__video-area {
438
+ flex: 1;
439
+ min-width: 0;
440
+ min-height: 0;
441
+ position: relative;
442
+ padding-bottom: 0.625rem;
443
+ overflow: hidden;
444
+ }
445
+ .feed-player__video-area--with-header {
446
+ padding-top: 0;
447
+ }
448
+ .feed-player__video-area {
449
+ /* Set 'container-type: inline-size;' to reference container*/
450
+ }
451
+ @container (width < 576px) {
452
+ .feed-player__video-area {
453
+ padding: 0;
454
+ }
455
+ }
456
+ .feed-player__content {
457
+ width: var(--_feed-player--content--width, 100%);
458
+ height: var(--_feed-player--content--height, 100%);
459
+ margin: var(--_feed-player--content--margin, 0);
460
+ position: relative;
461
+ display: flex;
462
+ justify-content: center;
463
+ align-items: center;
464
+ }
465
+ .feed-player__controls {
466
+ position: absolute;
467
+ top: 0;
468
+ right: 0;
469
+ height: 100%;
470
+ width: var(--_feed-player--controls-width);
471
+ display: flex;
472
+ padding: 0.625rem 0 1.875rem;
473
+ pointer-events: none;
474
+ }
475
+ .feed-player__controls-spacer {
476
+ flex: 0 0 1rem;
477
+ }
478
+ .feed-player__controls-spacer--right {
479
+ flex: 1;
480
+ }
481
+ .feed-player__controls-left {
482
+ display: flex;
483
+ flex-direction: column;
484
+ gap: 2.3125rem;
485
+ justify-content: flex-end;
486
+ align-items: center;
487
+ pointer-events: auto;
488
+ }
489
+ .feed-player__navigation {
490
+ display: flex;
491
+ flex-direction: column;
492
+ gap: 1rem;
493
+ }
494
+ .feed-player__sidebar-container {
495
+ flex-shrink: 0;
496
+ display: flex;
497
+ flex-direction: column;
498
+ align-items: flex-end;
499
+ position: relative;
500
+ z-index: 1;
501
+ }
502
+ .feed-player__close {
503
+ position: absolute;
504
+ top: 0.75rem;
505
+ right: 1rem;
506
+ z-index: 1;
507
+ }
508
+ .feed-player__sidebar {
509
+ flex: 1;
510
+ min-height: 0;
511
+ width: var(--_--feed-player--sidebar--width);
512
+ padding: 0.625rem 0.625rem 0.625rem 0;
513
+ }
514
+ .feed-player__sidebar--with-close {
515
+ padding-top: 3.75rem;
516
+ }</style>
@@ -0,0 +1,14 @@
1
+ import type { FeedPlayerProps } from './types';
2
+ /**
3
+ * Vertical post feed player with a tabbed sidebar for products, articles, and recommendations.
4
+ *
5
+ * ### CSS Custom Properties
6
+ * | Property | Description | Default |
7
+ * |---|---|---|
8
+ * | `--sc-fp--background` | Player background | `light-dark(white, black)` |
9
+ * | `--sc-fp--button--color` | Control button color | inherited |
10
+ * | `--sc-fp--button--color--inactive` | Inactive control button color | inherited |
11
+ */
12
+ declare const Cmp: import("svelte").Component<FeedPlayerProps, {}, "">;
13
+ type Cmp = ReturnType<typeof Cmp>;
14
+ 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 relatedPosts(): 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
+ }