@builder.io/sdk-svelte 3.0.3 → 3.0.6

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 (104) hide show
  1. package/lib/browser/blocks/columns/columns.svelte +3 -3
  2. package/lib/browser/blocks/form/form/form.svelte +2 -15
  3. package/lib/browser/blocks/image/image.svelte +1 -3
  4. package/lib/browser/blocks/personalization-container/component-info.d.ts +2 -0
  5. package/lib/browser/blocks/personalization-container/component-info.js +36 -0
  6. package/lib/browser/blocks/personalization-container/helpers/inlined-fns.d.ts +8 -0
  7. package/lib/browser/blocks/personalization-container/helpers/inlined-fns.js +87 -0
  8. package/lib/browser/blocks/personalization-container/helpers.d.ts +27 -0
  9. package/lib/browser/blocks/personalization-container/helpers.js +58 -0
  10. package/lib/browser/blocks/personalization-container/index.d.ts +1 -0
  11. package/lib/browser/blocks/personalization-container/index.js +1 -0
  12. package/lib/browser/blocks/personalization-container/personalization-container.svelte +118 -0
  13. package/lib/browser/blocks/personalization-container/personalization-container.svelte.d.ts +21 -0
  14. package/lib/browser/blocks/personalization-container/personalization-container.types.d.ts +14 -0
  15. package/lib/browser/blocks/personalization-container/personalization-container.types.js +1 -0
  16. package/lib/browser/blocks/video/video.svelte +0 -1
  17. package/lib/browser/components/blocks/blocks-wrapper.svelte +6 -1
  18. package/lib/browser/constants/builder-registered-components.js +6 -1
  19. package/lib/browser/constants/sdk-version.d.ts +1 -1
  20. package/lib/browser/constants/sdk-version.js +1 -1
  21. package/lib/browser/helpers/canTrack.js +6 -1
  22. package/lib/browser/helpers/user-attributes.d.ts +17 -0
  23. package/lib/browser/helpers/user-attributes.js +46 -0
  24. package/lib/browser/index-helpers/blocks-exports.d.ts +1 -0
  25. package/lib/browser/index-helpers/blocks-exports.js +1 -0
  26. package/lib/browser/scripts/init-editing.js +2 -1
  27. package/lib/browser/types/components.d.ts +2 -2
  28. package/lib/browser/types/input.d.ts +1 -0
  29. package/lib/edge/blocks/columns/columns.svelte +3 -3
  30. package/lib/edge/blocks/form/form/form.svelte +2 -15
  31. package/lib/edge/blocks/image/image.svelte +1 -3
  32. package/lib/edge/blocks/personalization-container/component-info.d.ts +2 -0
  33. package/lib/edge/blocks/personalization-container/component-info.js +36 -0
  34. package/lib/edge/blocks/personalization-container/helpers/inlined-fns.d.ts +8 -0
  35. package/lib/edge/blocks/personalization-container/helpers/inlined-fns.js +87 -0
  36. package/lib/edge/blocks/personalization-container/helpers.d.ts +27 -0
  37. package/lib/edge/blocks/personalization-container/helpers.js +58 -0
  38. package/lib/edge/blocks/personalization-container/index.d.ts +1 -0
  39. package/lib/edge/blocks/personalization-container/index.js +1 -0
  40. package/lib/edge/blocks/personalization-container/personalization-container.svelte +118 -0
  41. package/lib/edge/blocks/personalization-container/personalization-container.svelte.d.ts +21 -0
  42. package/lib/edge/blocks/personalization-container/personalization-container.types.d.ts +14 -0
  43. package/lib/edge/blocks/personalization-container/personalization-container.types.js +1 -0
  44. package/lib/edge/blocks/video/video.svelte +0 -1
  45. package/lib/edge/components/blocks/blocks-wrapper.svelte +6 -1
  46. package/lib/edge/constants/builder-registered-components.js +6 -1
  47. package/lib/edge/constants/sdk-version.d.ts +1 -1
  48. package/lib/edge/constants/sdk-version.js +1 -1
  49. package/lib/edge/helpers/canTrack.js +6 -1
  50. package/lib/edge/helpers/user-attributes.d.ts +17 -0
  51. package/lib/edge/helpers/user-attributes.js +46 -0
  52. package/lib/edge/index-helpers/blocks-exports.d.ts +1 -0
  53. package/lib/edge/index-helpers/blocks-exports.js +1 -0
  54. package/lib/edge/scripts/init-editing.js +2 -1
  55. package/lib/edge/types/components.d.ts +2 -2
  56. package/lib/edge/types/input.d.ts +1 -0
  57. package/lib/node/blocks/columns/columns.svelte +3 -3
  58. package/lib/node/blocks/form/form/form.svelte +2 -15
  59. package/lib/node/blocks/image/image.svelte +1 -3
  60. package/lib/node/blocks/personalization-container/component-info.d.ts +2 -0
  61. package/lib/node/blocks/personalization-container/component-info.js +36 -0
  62. package/lib/node/blocks/personalization-container/helpers/inlined-fns.d.ts +8 -0
  63. package/lib/node/blocks/personalization-container/helpers/inlined-fns.js +87 -0
  64. package/lib/node/blocks/personalization-container/helpers.d.ts +27 -0
  65. package/lib/node/blocks/personalization-container/helpers.js +58 -0
  66. package/lib/node/blocks/personalization-container/index.d.ts +1 -0
  67. package/lib/node/blocks/personalization-container/index.js +1 -0
  68. package/lib/node/blocks/personalization-container/personalization-container.svelte +118 -0
  69. package/lib/node/blocks/personalization-container/personalization-container.svelte.d.ts +21 -0
  70. package/lib/node/blocks/personalization-container/personalization-container.types.d.ts +14 -0
  71. package/lib/node/blocks/personalization-container/personalization-container.types.js +1 -0
  72. package/lib/node/blocks/video/video.svelte +0 -1
  73. package/lib/node/components/blocks/blocks-wrapper.svelte +6 -1
  74. package/lib/node/constants/builder-registered-components.js +6 -1
  75. package/lib/node/constants/sdk-version.d.ts +1 -1
  76. package/lib/node/constants/sdk-version.js +1 -1
  77. package/lib/node/helpers/canTrack.js +6 -1
  78. package/lib/node/helpers/user-attributes.d.ts +17 -0
  79. package/lib/node/helpers/user-attributes.js +46 -0
  80. package/lib/node/index-helpers/blocks-exports.d.ts +1 -0
  81. package/lib/node/index-helpers/blocks-exports.js +1 -0
  82. package/lib/node/scripts/init-editing.js +2 -1
  83. package/lib/node/types/components.d.ts +2 -2
  84. package/lib/node/types/input.d.ts +1 -0
  85. package/package.json +2 -2
  86. package/src/blocks/columns/columns.svelte +3 -3
  87. package/src/blocks/form/form/form.svelte +3 -15
  88. package/src/blocks/image/image.svelte +0 -4
  89. package/src/blocks/personalization-container/component-info.ts +37 -0
  90. package/src/blocks/personalization-container/helpers/inlined-fns.ts +88 -0
  91. package/src/blocks/personalization-container/helpers.ts +84 -0
  92. package/src/blocks/personalization-container/index.ts +1 -0
  93. package/src/blocks/personalization-container/personalization-container.svelte +155 -0
  94. package/src/blocks/personalization-container/personalization-container.types.ts +14 -0
  95. package/src/blocks/video/video.svelte +0 -1
  96. package/src/components/blocks/blocks-wrapper.svelte +6 -1
  97. package/src/constants/builder-registered-components.ts +6 -1
  98. package/src/constants/sdk-version.ts +1 -1
  99. package/src/helpers/canTrack.ts +6 -1
  100. package/src/helpers/user-attributes.ts +49 -0
  101. package/src/index-helpers/blocks-exports.ts +2 -1
  102. package/src/scripts/init-editing.ts +2 -1
  103. package/src/types/components.ts +2 -2
  104. package/src/types/input.ts +1 -0
@@ -42,7 +42,8 @@ export const setupBrowserForEditing = (options = {}) => {
42
42
  // Supports builder-model="..." attribute which is needed to
43
43
  // scope our '+ add block' button styling
44
44
  supportsAddBlockScoping: true,
45
- supportsCustomBreakpoints: true
45
+ supportsCustomBreakpoints: true,
46
+ blockLevelPersonalization: true
46
47
  }
47
48
  }, '*');
48
49
  window.parent?.postMessage({
@@ -122,7 +122,7 @@ export interface ComponentInfo {
122
122
  /**
123
123
  * Whether or not the component should receive the `builderBlock` prop, containing the current Builder block being rendered.
124
124
  *
125
- * Defaults to `true`.
125
+ * Defaults to `false`.
126
126
  */
127
127
  builderBlock?: boolean;
128
128
  /**
@@ -130,7 +130,7 @@ export interface ComponentInfo {
130
130
  * The Builder Context contains a lot of data relevant to the current `Content` render.
131
131
  * You can see more information [here](https://github.com/BuilderIO/builder/blob/main/packages/sdks/src/context/types.ts).
132
132
  *
133
- * Defaults to `true`.
133
+ * Defaults to `false`.
134
134
  */
135
135
  builderContext?: boolean;
136
136
  /**
@@ -124,6 +124,7 @@ export interface Input {
124
124
  * Use optionally with inputs of type `reference`. Restricts the content entry picker to a specific model by name.
125
125
  */
126
126
  model?: string;
127
+ behavior?: string;
127
128
  valueType?: {
128
129
  type?: string;
129
130
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@builder.io/sdk-svelte",
3
3
  "description": "Builder.io SDK for Svelte",
4
- "version": "3.0.3",
4
+ "version": "3.0.6",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "lib",
@@ -60,7 +60,7 @@
60
60
  "svelte-preprocess": "^5.0.4",
61
61
  "tslib": "^2.3.1",
62
62
  "typescript": "^5.1.6",
63
- "vite": "^4.4.8"
63
+ "vite": "^4.5.6"
64
64
  },
65
65
  "nx": {
66
66
  "targets": {
@@ -194,13 +194,13 @@
194
194
  ><Blocks
195
195
  path={`columns.${index}.blocks`}
196
196
  parent={builderBlock.id}
197
- styleProp={{
198
- flexGrow: "1",
199
- }}
200
197
  context={builderContext}
201
198
  registeredComponents={builderComponents}
202
199
  linkComponent={builderLinkComponent}
203
200
  blocks={column.blocks}
201
+ styleProp={{
202
+ flexGrow: "1",
203
+ }}
204
204
  /></DynamicRenderer
205
205
  >
206
206
  {/each}
@@ -25,6 +25,7 @@
25
25
  sendingMessage?: BuilderBlock[];
26
26
  resetFormOnSubmit?: boolean;
27
27
  errorMessagePath?: string;
28
+ children?: any;
28
29
  };
29
30
 
30
31
  /**
@@ -35,7 +36,6 @@
35
36
  </script>
36
37
 
37
38
  <script lang="ts">
38
- import Block from "../../../components/block/block.svelte";
39
39
  import Blocks from "../../../components/blocks/blocks.svelte";
40
40
  import { getEnv } from "../../../functions/get-env";
41
41
  import { get } from "../../../functions/get";
@@ -68,10 +68,7 @@
68
68
  export let successUrl: FormProps["successUrl"] = undefined;
69
69
  export let validate: FormProps["validate"] = undefined;
70
70
  export let attributes: FormProps["attributes"] = undefined;
71
- export let builderBlock: FormProps["builderBlock"];
72
- export let builderComponents: FormProps["builderComponents"];
73
- export let builderLinkComponent: FormProps["builderLinkComponent"] =
74
- undefined;
71
+
75
72
  export let errorMessage: FormProps["errorMessage"] = undefined;
76
73
  export let sendingMessage: FormProps["sendingMessage"] = undefined;
77
74
  export let successMessage: FormProps["successMessage"] = undefined;
@@ -307,16 +304,7 @@
307
304
  {...filterAttrs(attributes, "on:", false)}
308
305
  use:setAttrs={filterAttrs(attributes, "on:", true)}
309
306
  >
310
- {#if builderBlock && builderBlock.children}
311
- {#each builderBlock?.children as block, idx (`form-block-${idx}`)}
312
- <Block
313
- {block}
314
- context={builderContext}
315
- registeredComponents={builderComponents}
316
- linkComponent={builderLinkComponent}
317
- />
318
- {/each}
319
- {/if}
307
+ <slot />
320
308
  {#if submissionState() === "error"}
321
309
  <Blocks
322
310
  path="errorMessage"
@@ -1,6 +1,4 @@
1
1
  <script lang="ts">
2
- import { onMount } from "svelte";
3
-
4
2
  import { getSrcSet } from "./image.helpers";
5
3
  import type { ImageProps } from "./image.types";
6
4
 
@@ -74,8 +72,6 @@
74
72
  const out = aspectRatio ? aspectRatioStyles : undefined;
75
73
  return out;
76
74
  };
77
-
78
- onMount(() => {});
79
75
  </script>
80
76
 
81
77
  <picture>
@@ -0,0 +1,37 @@
1
+ import type { ComponentInfo } from '../../types/components';
2
+ export const componentInfo: ComponentInfo = {
3
+ name: 'PersonalizationContainer',
4
+ shouldReceiveBuilderProps: {
5
+ builderBlock: true,
6
+ builderContext: true
7
+ },
8
+ noWrap: true,
9
+ image: 'https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F37229ed30d8c41dfb10b8cca1992053a',
10
+ canHaveChildren: true,
11
+ inputs: [{
12
+ name: 'variants',
13
+ defaultValue: [],
14
+ behavior: 'personalizationVariantList',
15
+ type: 'list',
16
+ subFields: [{
17
+ name: 'name',
18
+ type: 'text'
19
+ }, {
20
+ name: 'query',
21
+ friendlyName: 'Targeting rules',
22
+ type: 'BuilderQuery',
23
+ defaultValue: []
24
+ }, {
25
+ name: 'startDate',
26
+ type: 'date'
27
+ }, {
28
+ name: 'endDate',
29
+ type: 'date'
30
+ }, {
31
+ name: 'blocks',
32
+ type: 'uiBlocks',
33
+ hideFromUI: true,
34
+ defaultValue: []
35
+ }]
36
+ }]
37
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * WARNING: This file contains functions that get stringified and inlined into the HTML at build-time.
3
+ * They cannot import anything.
4
+ */
5
+
6
+ import type { Query, UserAttributes } from '../helpers';
7
+ import { type PersonalizationContainerProps } from '../personalization-container.types';
8
+ export function filterWithCustomTargeting(userAttributes: UserAttributes, query: Query[], startDate?: string, endDate?: string) {
9
+ function isString(val: unknown): val is string {
10
+ return typeof val === 'string';
11
+ }
12
+ function isNumber(val: unknown): val is number {
13
+ return typeof val === 'number';
14
+ }
15
+ function objectMatchesQuery(userattr: UserAttributes, query: Query): boolean {
16
+ const result = (() => {
17
+ const property = query.property;
18
+ const operator = query.operator;
19
+ let testValue = query.value;
20
+ if (query && query.property === 'urlPath' && query.value && typeof query.value === 'string' && query.value !== '/' && query.value.endsWith('/')) {
21
+ testValue = query.value.slice(0, -1);
22
+ }
23
+ if (!(property && operator)) {
24
+ return true;
25
+ }
26
+ if (Array.isArray(testValue)) {
27
+ if (operator === 'isNot') {
28
+ return testValue.every(val => objectMatchesQuery(userattr, {
29
+ property,
30
+ operator,
31
+ value: val
32
+ }));
33
+ }
34
+ return !!testValue.find(val => objectMatchesQuery(userattr, {
35
+ property,
36
+ operator,
37
+ value: val
38
+ }));
39
+ }
40
+ const value = userattr[property];
41
+ if (Array.isArray(value)) {
42
+ return value.includes(testValue);
43
+ }
44
+ switch (operator) {
45
+ case 'is':
46
+ return value === testValue;
47
+ case 'isNot':
48
+ return value !== testValue;
49
+ case 'contains':
50
+ return (isString(value) || Array.isArray(value)) && value.includes(String(testValue));
51
+ case 'startsWith':
52
+ return isString(value) && value.startsWith(String(testValue));
53
+ case 'endsWith':
54
+ return isString(value) && value.endsWith(String(testValue));
55
+ case 'greaterThan':
56
+ return isNumber(value) && isNumber(testValue) && value > testValue;
57
+ case 'lessThan':
58
+ return isNumber(value) && isNumber(testValue) && value < testValue;
59
+ case 'greaterThanOrEqualTo':
60
+ return isNumber(value) && isNumber(testValue) && value >= testValue;
61
+ case 'lessThanOrEqualTo':
62
+ return isNumber(value) && isNumber(testValue) && value <= testValue;
63
+ default:
64
+ return false;
65
+ }
66
+ })();
67
+ return result;
68
+ }
69
+ const item = {
70
+ query,
71
+ startDate,
72
+ endDate
73
+ };
74
+ const now = userAttributes.date && new Date(userAttributes.date) || new Date();
75
+ if (item.startDate && new Date(item.startDate) > now) {
76
+ return false;
77
+ } else if (item.endDate && new Date(item.endDate) < now) {
78
+ return false;
79
+ }
80
+ if (!item.query || !item.query.length) {
81
+ return true;
82
+ }
83
+ return item.query.every((filter: Query) => {
84
+ return objectMatchesQuery(userAttributes, filter);
85
+ });
86
+ }
87
+ export const PERSONALIZATION_SCRIPT = "function getPersonalizedVariant(variants, blockId, locale) {\n if (!navigator.cookieEnabled) {\n return;\n }\n function getCookie(name) {\n const nameEQ = name + '=';\n const ca = document.cookie.split(';');\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n }\n return null;\n }\n function removeVariants() {\n variants?.forEach(function (_, index) {\n document.querySelector('template[data-variant-id=\"' + blockId + '-' + index + '\"]')?.remove();\n });\n document.querySelector('script[data-id=\"variants-script-' + blockId + '\"]')?.remove();\n document.querySelector('style[data-id=\"variants-styles-' + blockId + '\"]')?.remove();\n }\n const attributes = JSON.parse(getCookie('builder.userAttributes') || '{}');\n if (locale) {\n attributes.locale = locale;\n }\n const winningVariantIndex = variants?.findIndex(function (variant) {\n return filterWithCustomTargeting(attributes, variant.query, variant.startDate, variant.endDate);\n });\n const isDebug = location.href.includes('builder.debug=true');\n if (isDebug) {\n console.debug('PersonalizationContainer', {\n attributes,\n variants,\n winningVariantIndex\n });\n }\n if (winningVariantIndex !== -1) {\n const winningVariant = document.querySelector('template[data-variant-id=\"' + blockId + '-' + winningVariantIndex + '\"]');\n if (winningVariant) {\n const parentNode = winningVariant.parentNode;\n if (parentNode) {\n const newParent = parentNode.cloneNode(false);\n newParent.appendChild(winningVariant.content.firstChild);\n newParent.appendChild(winningVariant.content.lastChild);\n parentNode.parentNode?.replaceChild(newParent, parentNode);\n }\n if (isDebug) {\n console.debug('PersonalizationContainer', 'Winning variant Replaced:', winningVariant);\n }\n }\n } else if (variants && variants.length > 0) {\n removeVariants();\n }\n}";
88
+ export const FILTER_WITH_CUSTOM_TARGETING_SCRIPT = "function filterWithCustomTargeting(userAttributes, query, startDate, endDate) {\n function isString(val) {\n return typeof val === 'string';\n }\n function isNumber(val) {\n return typeof val === 'number';\n }\n function objectMatchesQuery(userattr, query) {\n const result = (() => {\n const property = query.property;\n const operator = query.operator;\n let testValue = query.value;\n if (query && query.property === 'urlPath' && query.value && typeof query.value === 'string' && query.value !== '/' && query.value.endsWith('/')) {\n testValue = query.value.slice(0, -1);\n }\n if (!(property && operator)) {\n return true;\n }\n if (Array.isArray(testValue)) {\n if (operator === 'isNot') {\n return testValue.every(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n return !!testValue.find(val => objectMatchesQuery(userattr, {\n property,\n operator,\n value: val\n }));\n }\n const value = userattr[property];\n if (Array.isArray(value)) {\n return value.includes(testValue);\n }\n switch (operator) {\n case 'is':\n return value === testValue;\n case 'isNot':\n return value !== testValue;\n case 'contains':\n return (isString(value) || Array.isArray(value)) && value.includes(String(testValue));\n case 'startsWith':\n return isString(value) && value.startsWith(String(testValue));\n case 'endsWith':\n return isString(value) && value.endsWith(String(testValue));\n case 'greaterThan':\n return isNumber(value) && isNumber(testValue) && value > testValue;\n case 'lessThan':\n return isNumber(value) && isNumber(testValue) && value < testValue;\n case 'greaterThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value >= testValue;\n case 'lessThanOrEqualTo':\n return isNumber(value) && isNumber(testValue) && value <= testValue;\n default:\n return false;\n }\n })();\n return result;\n }\n const item = {\n query,\n startDate,\n endDate\n };\n const now = userAttributes.date && new Date(userAttributes.date) || new Date();\n if (item.startDate && new Date(item.startDate) > now) {\n return false;\n } else if (item.endDate && new Date(item.endDate) < now) {\n return false;\n }\n if (!item.query || !item.query.length) {\n return true;\n }\n return item.query.every(filter => {\n return objectMatchesQuery(userAttributes, filter);\n });\n}";
@@ -0,0 +1,84 @@
1
+ import { TARGET } from '../../constants/target';
2
+ import { isBrowser } from '../../functions/is-browser';
3
+ import { isEditing } from '../../functions/is-editing';
4
+ import type { BuilderBlock } from '../../types/builder-block';
5
+ import { FILTER_WITH_CUSTOM_TARGETING_SCRIPT, PERSONALIZATION_SCRIPT } from './helpers/inlined-fns';
6
+ import type { PersonalizationContainerProps } from './personalization-container.types';
7
+ export type UserAttributes = {
8
+ date?: string | Date;
9
+ urlPath?: string;
10
+ [key: string]: any;
11
+ };
12
+ type QueryOperator = 'is' | 'isNot' | 'contains' | 'startsWith' | 'endsWith' | 'greaterThan' | 'lessThan' | 'greaterThanOrEqualTo' | 'lessThanOrEqualTo';
13
+ type QueryValue = string | number | boolean | Array<string | number | boolean>;
14
+ export type Query = {
15
+ property: string;
16
+ operator: QueryOperator;
17
+ value: QueryValue;
18
+ };
19
+ export function checkShouldRenderVariants(variants: PersonalizationContainerProps['variants'], canTrack: boolean) {
20
+ const hasVariants = variants && variants.length > 0;
21
+ if (TARGET === 'reactNative') return false;
22
+ if (!hasVariants) return false;
23
+ if (!canTrack) return false;
24
+ if (TARGET === 'vue' || TARGET === 'svelte') return true;
25
+ if (isBrowser()) return false;
26
+ return true;
27
+ }
28
+ export function getBlocksToRender({
29
+ variants,
30
+ previewingIndex,
31
+ isHydrated,
32
+ filteredVariants,
33
+ fallbackBlocks
34
+ }: {
35
+ variants: PersonalizationContainerProps['variants'];
36
+ previewingIndex?: number | null;
37
+ isHydrated: boolean;
38
+ filteredVariants: PersonalizationContainerProps['variants'];
39
+ fallbackBlocks?: BuilderBlock[];
40
+ }): {
41
+ blocks: BuilderBlock[];
42
+ path: string | undefined;
43
+ } {
44
+ const fallback = {
45
+ blocks: fallbackBlocks ?? [],
46
+ path: 'this.children'
47
+ };
48
+ if (isHydrated && isEditing()) {
49
+ // If editing a specific variant
50
+ if (typeof previewingIndex === 'number' && previewingIndex < (variants?.length ?? 0)) {
51
+ const variant = variants![previewingIndex];
52
+ return {
53
+ blocks: variant.blocks,
54
+ path: `component.options.variants.${previewingIndex}.blocks`
55
+ };
56
+ }
57
+ // Otherwise we're editing the default variant
58
+ return fallback;
59
+ }
60
+
61
+ // If we're on the browser, check if there's a winning variant
62
+ if (isBrowser()) {
63
+ const winningVariant = filteredVariants?.[0];
64
+ if (winningVariant) {
65
+ return {
66
+ blocks: winningVariant.blocks,
67
+ path: `component.options.variants.${variants?.indexOf(winningVariant)}.blocks`
68
+ };
69
+ }
70
+ }
71
+
72
+ // If no winning variant or we are on the server, return the default variant
73
+ return fallback;
74
+ }
75
+ export const getPersonalizationScript = (variants: PersonalizationContainerProps['variants'], blockId: string, locale?: string) => {
76
+ return `
77
+ (function() {
78
+ ${FILTER_WITH_CUSTOM_TARGETING_SCRIPT}
79
+ ${PERSONALIZATION_SCRIPT}
80
+ getPersonalizedVariant(${JSON.stringify(variants)}, "${blockId}"${locale ? `, "${locale}"` : ''})
81
+ })();
82
+ `;
83
+ };
84
+ export { filterWithCustomTargeting } from './helpers/inlined-fns'
@@ -0,0 +1 @@
1
+ export { default } from './personalization-container.svelte'
@@ -0,0 +1,155 @@
1
+ <script lang="ts">
2
+ import { onDestroy, onMount } from "svelte";
3
+
4
+ import Blocks from "../../components/blocks/blocks.svelte";
5
+ import InlinedScript from "../../components/inlined-script.svelte";
6
+ import InlinedStyles from "../../components/inlined-styles.svelte";
7
+ import { isEditing } from "../../functions/is-editing";
8
+ import { isPreviewing } from "../../functions/is-previewing";
9
+ import { getDefaultCanTrack } from "../../helpers/canTrack";
10
+ import { userAttributesService } from "../../helpers/user-attributes";
11
+ import {
12
+ checkShouldRenderVariants,
13
+ filterWithCustomTargeting,
14
+ getBlocksToRender,
15
+ getPersonalizationScript,
16
+ } from "./helpers";
17
+ import type { PersonalizationContainerProps } from "./personalization-container.types";
18
+
19
+ export let builderContext: PersonalizationContainerProps["builderContext"];
20
+ export let variants: PersonalizationContainerProps["variants"] = undefined;
21
+ export let builderBlock: PersonalizationContainerProps["builderBlock"];
22
+ export let previewingIndex: PersonalizationContainerProps["previewingIndex"] =
23
+ undefined;
24
+ export let attributes: PersonalizationContainerProps["attributes"] =
25
+ undefined;
26
+
27
+ $: filteredVariants = () => {
28
+ return (variants || []).filter((variant) => {
29
+ return filterWithCustomTargeting(
30
+ {
31
+ ...($builderContext?.rootState?.locale
32
+ ? {
33
+ locale: $builderContext?.rootState?.locale,
34
+ }
35
+ : {}),
36
+ ...(userAttributes as any),
37
+ },
38
+ variant.query,
39
+ variant.startDate,
40
+ variant.endDate
41
+ );
42
+ });
43
+ };
44
+ $: blocksToRender = () => {
45
+ return getBlocksToRender({
46
+ variants: variants,
47
+ fallbackBlocks: builderBlock?.children,
48
+ isHydrated: isHydrated,
49
+ filteredVariants: filteredVariants(),
50
+ previewingIndex: previewingIndex,
51
+ });
52
+ };
53
+ $: hideVariantsStyleString = () => {
54
+ return (variants || [])
55
+ .map(
56
+ (_, index) =>
57
+ `[data-variant-id="${builderBlock?.id}-${index}"] { display: none; } `
58
+ )
59
+ .join("");
60
+ };
61
+
62
+ let rootRef;
63
+
64
+ let userAttributes = userAttributesService.getUserAttributes();
65
+ let scriptStr = getPersonalizationScript(
66
+ variants,
67
+ builderBlock?.id || "none",
68
+ $builderContext?.rootState?.locale as string | undefined
69
+ );
70
+ let unsubscribers = [];
71
+ let shouldRenderVariants = checkShouldRenderVariants(
72
+ variants,
73
+ getDefaultCanTrack($builderContext?.canTrack)
74
+ );
75
+ let isHydrated = false;
76
+
77
+ onMount(() => {
78
+ isHydrated = true;
79
+ const unsub = userAttributesService.subscribeOnUserAttributesChange(
80
+ (attrs) => {
81
+ userAttributes = attrs;
82
+ }
83
+ );
84
+ if (!(isEditing() || isPreviewing())) {
85
+ const variant = filteredVariants()[0];
86
+ if (rootRef) {
87
+ rootRef.dispatchEvent(
88
+ new CustomEvent("builder.variantLoaded", {
89
+ detail: {
90
+ variant: variant || "default",
91
+ content: $builderContext?.content,
92
+ },
93
+ bubbles: true,
94
+ })
95
+ );
96
+ const observer = new IntersectionObserver((entries) => {
97
+ entries.forEach((entry) => {
98
+ if (entry.isIntersecting && rootRef) {
99
+ rootRef.dispatchEvent(
100
+ new CustomEvent("builder.variantDisplayed", {
101
+ detail: {
102
+ variant: variant || "default",
103
+ content: $builderContext?.content,
104
+ },
105
+ bubbles: true,
106
+ })
107
+ );
108
+ }
109
+ });
110
+ });
111
+ observer.observe(rootRef);
112
+ }
113
+ }
114
+ unsubscribers.push(unsub);
115
+ });
116
+
117
+ onDestroy(() => {
118
+ unsubscribers.forEach((unsub) => unsub());
119
+ });
120
+ </script>
121
+
122
+ <div
123
+ bind:this={rootRef}
124
+ {...attributes}
125
+ class={`builder-personalization-container ${attributes?.className || ""}`}
126
+ >
127
+ {#if shouldRenderVariants}
128
+ {#each variants as variant, index (index)}
129
+ <svelte:element
130
+ this={"template"}
131
+ data-variant-id={`${builderBlock?.id}-${index}`}
132
+ ><Blocks
133
+ blocks={variant.blocks}
134
+ parent={builderBlock?.id}
135
+ path={`component.options.variants.${index}.blocks`}
136
+ /></svelte:element
137
+ >
138
+ {/each}
139
+
140
+ <InlinedStyles
141
+ nonce={$builderContext?.nonce || ""}
142
+ styles={hideVariantsStyleString()}
143
+ id={`variants-styles-${builderBlock?.id}`}
144
+ />
145
+ <InlinedScript
146
+ nonce={$builderContext?.nonce || ""}
147
+ {scriptStr}
148
+ id={`variants-script-${builderBlock?.id}`}
149
+ />
150
+ {/if}<Blocks
151
+ blocks={blocksToRender().blocks}
152
+ parent={builderBlock?.id}
153
+ path={blocksToRender().path}
154
+ />
155
+ </div>
@@ -0,0 +1,14 @@
1
+ import type { BuilderBlock } from '../../types/builder-block';
2
+ import type { BuilderDataProps } from '../../types/builder-props';
3
+ import type { Query } from './helpers';
4
+ export type PersonalizationContainerProps = {
5
+ children?: any;
6
+ attributes?: any;
7
+ previewingIndex?: number | null;
8
+ variants?: Array<{
9
+ blocks: BuilderBlock[];
10
+ query: Query[];
11
+ startDate?: string;
12
+ endDate?: string;
13
+ }>;
14
+ } & BuilderDataProps
@@ -78,7 +78,6 @@
78
78
  objectPosition: position,
79
79
  // Hack to get object fit to work as expected and
80
80
  // not have the video overflow
81
- zIndex: 2,
82
81
  borderRadius: "1px",
83
82
  ...(aspectRatio
84
83
  ? {
@@ -79,8 +79,13 @@
79
79
  if (!path) {
80
80
  return undefined;
81
81
  }
82
+ const thisPrefix = "this.";
82
83
  const pathPrefix = "component.options.";
83
- return path.startsWith(pathPrefix) ? path : `${pathPrefix}${path || ""}`;
84
+ return path.startsWith(thisPrefix)
85
+ ? path.replace(thisPrefix, "")
86
+ : path.startsWith(pathPrefix)
87
+ ? path
88
+ : `${pathPrefix}${path || ""}`;
84
89
  };
85
90
 
86
91
  let blocksWrapperRef;
@@ -8,6 +8,8 @@ import { componentInfo as fragmentComponentInfo } from '../blocks/fragment/compo
8
8
  import { default as Fragment } from '../blocks/fragment/index';
9
9
  import { componentInfo as imageComponentInfo } from '../blocks/image/component-info';
10
10
  import { default as Image } from '../blocks/image/index';
11
+ import { componentInfo as personalizationContainerComponentInfo } from '../blocks/personalization-container/component-info';
12
+ import { default as PersonalizationContainer } from '../blocks/personalization-container/index';
11
13
  import { componentInfo as sectionComponentInfo } from '../blocks/section/component-info';
12
14
  import { default as Section } from '../blocks/section/index';
13
15
  import { componentInfo as slotComponentInfo } from '../blocks/slot/component-info';
@@ -50,7 +52,10 @@ export const getDefaultRegisteredComponents: () => RegisteredComponent[] = () =>
50
52
  }, {
51
53
  component: Text,
52
54
  ...textComponentInfo
53
- }, ...(TARGET === 'rsc' ? [] : [{
55
+ }, ...(TARGET === 'react' ? [{
56
+ component: PersonalizationContainer,
57
+ ...personalizationContainerComponentInfo
58
+ }] : []), ...(TARGET === 'rsc' ? [] : [{
54
59
  component: Tabs,
55
60
  ...tabsComponentInfo
56
61
  }, {
@@ -1 +1 @@
1
- export const SDK_VERSION = "3.0.3"
1
+ export const SDK_VERSION = "3.0.6"
@@ -1,2 +1,7 @@
1
1
  import { checkIsDefined } from './nullable';
2
- export const getDefaultCanTrack = (canTrack?: boolean) => checkIsDefined(canTrack) ? canTrack : true
2
+ import { userAttributesService } from './user-attributes';
3
+ export const getDefaultCanTrack = (canTrack?: boolean) => {
4
+ const result = checkIsDefined(canTrack) ? canTrack : true;
5
+ userAttributesService.setCanTrack(result);
6
+ return result;
7
+ }
@@ -0,0 +1,49 @@
1
+ import { isBrowser } from '../functions/is-browser';
2
+ import { getCookieSync, setCookie } from './cookie';
3
+ export interface UserAttributes {
4
+ [key: string]: any;
5
+ }
6
+ export const USER_ATTRIBUTES_COOKIE_NAME = 'builder.userAttributes';
7
+ export function createUserAttributesService() {
8
+ let canTrack = true;
9
+ const subscribers = new Set<(attrs: UserAttributes) => void>();
10
+ return {
11
+ setUserAttributes(newAttrs: UserAttributes) {
12
+ if (!isBrowser()) {
13
+ return;
14
+ }
15
+ const userAttributes: UserAttributes = {
16
+ ...this.getUserAttributes(),
17
+ ...newAttrs
18
+ };
19
+ setCookie({
20
+ name: USER_ATTRIBUTES_COOKIE_NAME,
21
+ value: JSON.stringify(userAttributes),
22
+ canTrack
23
+ });
24
+ subscribers.forEach(callback => callback(userAttributes));
25
+ },
26
+ getUserAttributes() {
27
+ if (!isBrowser()) {
28
+ return {};
29
+ }
30
+ return JSON.parse(getCookieSync({
31
+ name: USER_ATTRIBUTES_COOKIE_NAME,
32
+ canTrack
33
+ }) || '{}');
34
+ },
35
+ subscribeOnUserAttributesChange(callback: (attrs: UserAttributes) => void) {
36
+ subscribers.add(callback);
37
+ return () => {
38
+ subscribers.delete(callback);
39
+ };
40
+ },
41
+ setCanTrack(value: boolean) {
42
+ canTrack = value;
43
+ }
44
+ };
45
+ }
46
+ export const userAttributesService = createUserAttributesService();
47
+ export const setClientUserAttributes = (attributes: UserAttributes) => {
48
+ userAttributesService.setUserAttributes(attributes);
49
+ }
@@ -12,4 +12,5 @@ export { default as Content } from '../components/content-variants/index';
12
12
  /**
13
13
  * Builder Context
14
14
  */
15
- export { BuilderContext } from '../context/index'
15
+ export { BuilderContext } from '../context/index';
16
+ export { setClientUserAttributes } from '../helpers/user-attributes'
@@ -47,7 +47,8 @@ export const setupBrowserForEditing = (options: {
47
47
  // Supports builder-model="..." attribute which is needed to
48
48
  // scope our '+ add block' button styling
49
49
  supportsAddBlockScoping: true,
50
- supportsCustomBreakpoints: true
50
+ supportsCustomBreakpoints: true,
51
+ blockLevelPersonalization: true
51
52
  }
52
53
  }, '*');
53
54
  window.parent?.postMessage({