@streamscloud/embeddable 16.1.1 → 16.2.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.
@@ -21,7 +21,8 @@ const context = untrack(() => new MediaCenterContext({
21
21
  case 'posts':
22
22
  instance.postsFeedHandler.activateWithDataProvider({
23
23
  dataProvider: modeProps.props.dataProvider,
24
- onPostActivated: modeProps.props.onPostActivated
24
+ onPostActivated: modeProps.props.onPostActivated,
25
+ onProductBuy: modeProps.props.onProductBuy
25
26
  });
26
27
  break;
27
28
  case 'streams':
@@ -14,6 +14,7 @@ export declare class PostsFeedHandler {
14
14
  private _mediaCenterContentHandler;
15
15
  private _closeOrchestrator;
16
16
  private _callbacks;
17
+ private _onProductBuy;
17
18
  constructor(data: {
18
19
  dataProvider: IMediaCenterDataProvider;
19
20
  mediaCenterSettingsHandler: MediaCenterSettingsHandler;
@@ -32,6 +33,7 @@ export declare class PostsFeedHandler {
32
33
  activateWithDataProvider: (data: {
33
34
  dataProvider: IPlayerDataProvider<PostPlayerModel>;
34
35
  onPostActivated?: (id: string) => void;
36
+ onProductBuy?: (productId: string) => void;
35
37
  }) => Promise<void>;
36
38
  activate: (options: Partial<{
37
39
  filter: PostsFeedFilter;
@@ -11,6 +11,7 @@ export class PostsFeedHandler {
11
11
  _mediaCenterContentHandler;
12
12
  _closeOrchestrator;
13
13
  _callbacks;
14
+ _onProductBuy;
14
15
  constructor(data) {
15
16
  const { dataProvider, mediaCenterSettingsHandler, mediaCenterContentHandler, closeOrchestrator, on } = data;
16
17
  this._providersGenerator = new FeedProvidersGenerator(dataProvider);
@@ -70,12 +71,14 @@ export class PostsFeedHandler {
70
71
  this._feedPlayerData = null;
71
72
  };
72
73
  activateWithDataProvider = async (data) => {
73
- const { dataProvider, onPostActivated } = data;
74
+ const { dataProvider, onPostActivated, onProductBuy } = data;
75
+ this._onProductBuy = onProductBuy;
74
76
  const on = {
75
77
  postActivated: (id) => {
76
78
  onPostActivated?.(id);
77
79
  this._callbacks.navigationStateChanged({ mode: 'posts-feed', postId: id, categoryId: null });
78
- }
80
+ },
81
+ productBuy: this._onProductBuy
79
82
  };
80
83
  try {
81
84
  this._state = 'loading';
@@ -100,7 +103,8 @@ export class PostsFeedHandler {
100
103
  const on = {
101
104
  postActivated: (id) => {
102
105
  this._callbacks.navigationStateChanged({ mode: 'posts-feed', postId: id, categoryId: filter?.categoryId ?? null });
103
- }
106
+ },
107
+ productBuy: this._onProductBuy
104
108
  };
105
109
  try {
106
110
  this._state = 'loading';
@@ -180,6 +184,7 @@ export class PostsFeedHandler {
180
184
  closeOrchestrator: this._closeOrchestrator,
181
185
  on: {
182
186
  postActivated: on.postActivated,
187
+ productBuy: on.productBuy,
183
188
  backgroundImageLoaded: this._mediaCenterSettingsHandler.backgroundImageLoadedHandler
184
189
  }
185
190
  };
@@ -15,6 +15,7 @@ export type MediaCenterModeProps = {
15
15
  props: {
16
16
  dataProvider: IPlayerDataProvider<PostPlayerModel>;
17
17
  onPostActivated?: (id: string) => void;
18
+ onProductBuy?: (productId: string) => void;
18
19
  };
19
20
  } | {
20
21
  mode: 'streams';
@@ -84,6 +84,7 @@ export declare function openMediaPageModalWithInitialPostsProvider(init: {
84
84
  on?: {
85
85
  closed?: () => void;
86
86
  postActivated?: (id: string) => void;
87
+ productBuy?: (productId: string) => void;
87
88
  };
88
89
  }): void;
89
90
  export declare function openMediaPageModalWithInitialStreamsProvider(init: {
@@ -115,7 +115,8 @@ export function openMediaPageModalWithInitialPostsProvider(init) {
115
115
  mode: 'posts',
116
116
  props: {
117
117
  dataProvider,
118
- onPostActivated: on?.postActivated
118
+ onPostActivated: on?.postActivated,
119
+ onProductBuy: on?.productBuy
119
120
  }
120
121
  },
121
122
  closeOrchestrator: makeCloseOrchestrator()
@@ -34,7 +34,8 @@ const trackingParams = $derived.by(() => {
34
34
  trackingParams={trackingParams}
35
35
  on={{
36
36
  click: on?.productClick,
37
- impression: on?.productImpression
37
+ impression: on?.productImpression,
38
+ buy: on?.productBuy
38
39
  }} />
39
40
  {/each}
40
41
  {/if}
@@ -6,6 +6,7 @@ type Props = {
6
6
  on?: {
7
7
  productClick?: (productId: string) => void;
8
8
  productImpression?: (productId: string) => void;
9
+ productBuy?: (productId: string) => void;
9
10
  adClick?: (adId: string) => void;
10
11
  adImpression?: (adId: string) => void;
11
12
  };
@@ -35,7 +35,7 @@ let { id, media, autoplay = 'on-appearance', on } = $props();
35
35
  </div>
36
36
 
37
37
  <style>.post-media {
38
- --_post-media--background-color: light-dark(#ffffff, #000000);
38
+ --_post-media--background-color: light-dark(#000000, #ffffff);
39
39
  width: 100%;
40
40
  min-width: 100%;
41
41
  max-width: 100%;
@@ -29,11 +29,12 @@ const initHost = (node) => {
29
29
  closeOrchestrator: new CloseOrchestrator({
30
30
  closeFn: async () => {
31
31
  await unmount(mounted);
32
+ on?.playerClosed?.();
32
33
  },
33
- canClose: false
34
+ canClose: !!on?.playerClosed
34
35
  }),
35
36
  get on() {
36
- return on;
37
+ return { postActivated: on?.postActivated, productBuy: on?.productBuy };
37
38
  }
38
39
  }
39
40
  });
@@ -18,6 +18,8 @@ type Props = {
18
18
  };
19
19
  on?: {
20
20
  postActivated?: (id: string) => void;
21
+ playerClosed?: () => void;
22
+ productBuy?: (productId: string) => void;
21
23
  };
22
24
  };
23
25
  declare const Cmp: import("svelte").Component<Props, {}, "">;
@@ -46,12 +46,14 @@ export type { ArticleDataProvider, IMediaCenterDataProvider, IPostAnalyticsHandl
46
46
  * Optional theme for the player UI.
47
47
  *
48
48
  * Events (optional)
49
- * @param {{ playerClosed?: () => void; postActivated?: (id: string) => void }} [init.on]
49
+ * @param {{ playerClosed?: () => void; postActivated?: (id: string) => void; productBuy?: (productId: string) => void }} [init.on]
50
50
  * Optional event handlers.
51
51
  * @param {() => void} [init.on.playerClosed]
52
52
  * Called after the player is fully closed (after unmount and removal from the DOM).
53
53
  * @param {(id: string) => void} [init.on.postActivated]
54
54
  * Called when a post becomes active (receives the post's id).
55
+ * @param {(productId: string) => void} [init.on.productBuy]
56
+ * Called when the user clicks the "Buy" button on a product card. When provided, a "Buy" button is shown on product cards in the attachments panel.
55
57
  *
56
58
  * @returns {void}
57
59
  *
@@ -104,6 +106,7 @@ export declare function openPostsPlayer(init: {
104
106
  on?: {
105
107
  playerClosed?: () => void;
106
108
  postActivated?: (id: string) => void;
109
+ productBuy?: (productId: string) => void;
107
110
  };
108
111
  }): void;
109
112
  export type IPostsPlayerDataProvider<TChunk extends WithId = WithId> = {
@@ -44,12 +44,14 @@ export { PostsPlayer };
44
44
  * Optional theme for the player UI.
45
45
  *
46
46
  * Events (optional)
47
- * @param {{ playerClosed?: () => void; postActivated?: (id: string) => void }} [init.on]
47
+ * @param {{ playerClosed?: () => void; postActivated?: (id: string) => void; productBuy?: (productId: string) => void }} [init.on]
48
48
  * Optional event handlers.
49
49
  * @param {() => void} [init.on.playerClosed]
50
50
  * Called after the player is fully closed (after unmount and removal from the DOM).
51
51
  * @param {(id: string) => void} [init.on.postActivated]
52
52
  * Called when a post becomes active (receives the post's id).
53
+ * @param {(productId: string) => void} [init.on.productBuy]
54
+ * Called when the user clicks the "Buy" button on a product card. When provided, a "Buy" button is shown on product cards in the attachments panel.
53
55
  *
54
56
  * @returns {void}
55
57
  *
@@ -111,7 +113,8 @@ export function openPostsPlayer(init) {
111
113
  playerSettings,
112
114
  closeOrchestrator: makeCloseOrchestrator(),
113
115
  on: {
114
- postActivated: on?.postActivated
116
+ postActivated: on?.postActivated,
117
+ productBuy: on?.productBuy
115
118
  }
116
119
  }
117
120
  });
@@ -21,5 +21,9 @@ const handleBackgroundImagedLoaded = (url) => {
21
21
  analyticsHandler={analyticsHandler}
22
22
  articleDataProvider={articleDataProvider}
23
23
  closeOrchestrator={closeOrchestrator}
24
- on={{ postActivated: on?.postActivated, backgroundImageLoaded: playerSettings?.disableBackground === true ? undefined : handleBackgroundImagedLoaded }} />
24
+ on={{
25
+ postActivated: on?.postActivated,
26
+ productBuy: on?.productBuy,
27
+ backgroundImageLoaded: playerSettings?.disableBackground === true ? undefined : handleBackgroundImagedLoaded
28
+ }} />
25
29
  </ShadowRoot>
@@ -20,6 +20,7 @@ type Props = {
20
20
  };
21
21
  on?: {
22
22
  postActivated?: (id: string) => void;
23
+ productBuy?: (productId: string) => void;
23
24
  };
24
25
  };
25
26
  declare const PostsPlayerProxy: import("svelte").Component<Props, {}, "">;
@@ -153,6 +153,7 @@ const currentItemActions = $derived.by(() => {
153
153
  on={{
154
154
  productClick: (id) => onProductClick(id, postModel.id),
155
155
  productImpression: (id) => onProductImpression(id, postModel.id),
156
+ productBuy: on?.productBuy,
156
157
  adClick: (id) => onAdClick(id),
157
158
  adImpression: (id) => onAdImpression(id)
158
159
  }} />
@@ -34,6 +34,7 @@ export type PostsPlayerProps = {
34
34
  on?: {
35
35
  postActivated?: (id: string) => void;
36
36
  backgroundImageLoaded?: (imageUrl: string | null) => void;
37
+ productBuy?: (productId: string) => void;
37
38
  };
38
39
  };
39
40
  export type PostPlayerSettings = {
@@ -1,6 +1,9 @@
1
1
  <script lang="ts">import { enrichProductLinkWithTracking } from '../../marketing-tracking';
2
2
  import { toPriceRepresentation } from '../price-helper';
3
3
  import { ProductCardLocalization } from './product-card-localization';
4
+ import IconCart from '@fluentui/svg-icons/icons/cart_20_regular.svg?raw';
5
+ import { Button } from '@streamscloud/kit/ui/button';
6
+ import { IconText } from '@streamscloud/kit/ui/icon-text';
4
7
  import { Image } from '@streamscloud/kit/ui/image';
5
8
  import { LineClamp } from '@streamscloud/kit/ui/line-clamp';
6
9
  import { ProportionalContainer } from '@streamscloud/kit/ui/proportional-container';
@@ -36,6 +39,11 @@ const onProductClicked = (event) => {
36
39
  window.open(enrichedLink, '_blank', 'noopener noreferrer');
37
40
  }
38
41
  };
42
+ const onBuyClicked = (event) => {
43
+ event.preventDefault();
44
+ event.stopPropagation();
45
+ on?.buy?.(product.id);
46
+ };
39
47
  </script>
40
48
 
41
49
  <div class="product-card" inert={inert} use:trackImpression>
@@ -66,6 +74,14 @@ const onProductClicked = (event) => {
66
74
  {toPriceRepresentation({ amount: product.salePrice ?? product.price, currency: product.currency })}
67
75
  </div>
68
76
  </div>
77
+
78
+ {#if on?.buy}
79
+ <div class="product-card__buy" onclick={onBuyClicked} onkeydown={() => {}} role="none">
80
+ <Button size="standard" on={{ click: onBuyClicked }} --sc-kit--button--min-width="100%">
81
+ <IconText icon={IconCart}>{localization.buy}</IconText>
82
+ </Button>
83
+ </div>
84
+ {/if}
69
85
  </div>
70
86
 
71
87
  {#if enrichedLink}
@@ -172,6 +188,22 @@ const onProductClicked = (event) => {
172
188
  height: 1.75rem;
173
189
  }
174
190
  }
191
+ .product-card__buy {
192
+ position: relative;
193
+ z-index: 1;
194
+ margin-top: 0.375rem;
195
+ --sc-kit--button--background: light-dark(
196
+ var(--sc-player--light--card-button, #f2f2f2),
197
+ var(--sc-player--dark--card-button, #2e2e2e)
198
+ );
199
+ --sc-kit--button--font--color: var(--sc-mc-color--text-primary);
200
+ /* Set 'container-type: inline-size;' to reference container*/
201
+ }
202
+ @container (width < 230px) {
203
+ .product-card__buy {
204
+ margin-top: 0.25rem;
205
+ }
206
+ }
175
207
  .product-card__link {
176
208
  position: absolute;
177
209
  inset: 0;
@@ -8,6 +8,7 @@ type Props = {
8
8
  on?: {
9
9
  click?: (id: string) => void;
10
10
  impression?: (id: string) => void;
11
+ buy?: (id: string) => void;
11
12
  };
12
13
  };
13
14
  declare const Cmp: import("svelte").Component<Props, {}, "">;
@@ -1,3 +1,4 @@
1
1
  export declare class ProductCardLocalization {
2
2
  get beforeNowPrefix(): string | null;
3
+ get buy(): string;
3
4
  }
@@ -3,10 +3,17 @@ export class ProductCardLocalization {
3
3
  get beforeNowPrefix() {
4
4
  return loc.beforeNowPrefix[AppLocale.current];
5
5
  }
6
+ get buy() {
7
+ return loc.buy[AppLocale.current];
8
+ }
6
9
  }
7
10
  const loc = {
8
11
  beforeNowPrefix: {
9
12
  en: 'Before',
10
13
  no: 'Før'
14
+ },
15
+ buy: {
16
+ en: 'Buy',
17
+ no: 'Kjøp'
11
18
  }
12
19
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/embeddable",
3
- "version": "16.1.1",
3
+ "version": "16.2.0",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,8 +17,8 @@
17
17
  "preview": "vite preview",
18
18
  "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json",
19
19
  "check:strict": "svelte-check --tsconfig ./tsconfig.app.json --fail-on-warnings --output machine && eslint . --format codeframe --max-warnings 0 && prettier --check --plugin prettier-plugin-svelte --cache .",
20
- "uad": "graphql-codegen --config codegen.yml && prettier --write src/**/*.generated.ts src/gql/*",
21
- "uad-types-only": "graphql-codegen --config codegen.types-only.yml && prettier --write src/gql/*",
20
+ "uad": "graphql-codegen --config codegen.yml",
21
+ "uad-types-only": "graphql-codegen --config codegen.types-only.yml",
22
22
  "lint": "prettier --check --plugin prettier-plugin-svelte . && eslint .",
23
23
  "lint-format": "prettier --write --plugin prettier-plugin-svelte . && eslint --fix .",
24
24
  "format": "prettier --write --plugin prettier-plugin-svelte ."